native_mouse_cursor 1.0.2 copy "native_mouse_cursor: ^1.0.2" to clipboard
native_mouse_cursor: ^1.0.2 copied to clipboard

Turn any image, SVG, or painted glyph into a real OS mouse cursor on Flutter desktop, web & Android.

native_mouse_cursor logo

native_mouse_cursor #

Turn any image, SVG, or painted glyph into a real OS mouse cursor โ€” on Flutter desktop, web & Android.

pub package live demo license platforms

๐Ÿš€ Try the live web demo โ†’ (web preview โ€” full native cursors shine on macOS / Windows / Linux / Android)

native_mouse_cursor demo โ€” a custom arrow cursor rotates to aim, mirrors across quadrants, and casts a baked drop shadow

Unlike a cursor "painted" inside Flutter (a widget that chases the pointer), a NativeMouseCursor is handed to the operating system, so the OS compositor draws it for you. ๐Ÿช„

โœจ Why use it #

  • โšก Zero lag โ€” tracks the hardware pointer exactly, with no one-frame trail.
  • ๐Ÿซง No jitter โ€” a shadow or glow baked into the bitmap never shimmers, even while the cursor rotates.
  • ๐Ÿ”Œ Drop-in โ€” it's a real MouseCursor, so it works anywhere a SystemMouseCursors value does (MouseRegion, InkWell, scrollbars, โ€ฆ).
  • ๐Ÿ” Rotation & mirroring โ€” spin a glyph by angle or flip it on demand; each variant is baked and cached automatically.
  • ๐ŸŒ‘ Baked drop shadows โ€” CSS-style shadows rendered into the bitmap, so they stay rock-steady at every angle.
  • ๐Ÿ–ฅ๏ธ HiDPI-crisp โ€” bakes at your device pixel ratio and re-bakes on change.
  • ๐Ÿ–Œ๏ธ Optional painted overlay โ€” on web/desktop, opt into an in-app overlay that hides the system cursor and paints a perfectly seamless per-region one.
  • ๐Ÿ“ฆ SPM-first on macOS โ€” no CocoaPods required.

๐Ÿงฉ Platform support #

Platform Backend Status
macOS NSCursor (Swift, SPM) โœ… Supported
Windows HCURSOR (Win32) โœ… Supported
Linux GdkCursor (GTK) โœ… Supported
Android PointerIcon (API 24+) โœ… Supported ยฒ
Web CSS url(...) cursor โœ… Supported ยน
iOS / iPadOS system pointer โŒ Not possible ยณ

ยน Each cursor is applied as a CSS cursor: url(...) value, sized in logical px and capped at 128 px (browsers draw a cursor image at its intrinsic pixels and Chrome ignores larger ones). For HiDPI crispness it also emits a device-resolution image via image-set(โ€ฆ 2x), with the plain url() as a fallback. For a perfectly seamless per-region cursor, wrap your app in NativeMouseCursorOverlay(force: true) to paint the glyph and hide the CSS cursor instead.

ยฒ Native PointerIcon for tablets/Chromebooks with a connected mouse, trackpad or stylus on API 24+. On older devices the system pointer is used. For a rotating cursor, prefer the painted overlay โ€” rapid PointerIcon swaps flicker on Android.

ยณ iPadOS draws and manages the pointer itself โ€” there's no API to install an arbitrary bitmap cursor, nor to hide the system pointer, so the system pointer is used. (A painted overlay would just show through it as a double cursor.) iPhone is touch-only โ€” no pointer to replace.

๐Ÿ“ฆ Install #

dependencies:
  native_mouse_cursor: ^1.0.1
flutter pub add native_mouse_cursor

๐Ÿš€ Quick start #

The whole API is: register a source under an id, then get it. ๐ŸŽฏ

Everything hard โ€” loading the glyph, rotation, the baked drop shadow, automatic bitmap sizing, the angle-keyed cache, background warming and DPR re-baking โ€” lives in the package.

Mix NativeMouseCursorMixin into your State and the rest is automatic: it points the cache at the context's devicePixelRatio (re-baking on a DPR change) and rebuilds when a cursor finishes baking โ€” so you can call svg / get straight from build():

import 'package:native_mouse_cursor/native_mouse_cursor.dart';

class _MyState extends State<MyWidget> with NativeMouseCursorMixin {
  @override
  void initState() {
    super.initState();
    // ๐Ÿ“ Register here, NOT in build() โ€” svg() kicks off an async load + bake,
    // so it's a one-time side effect. For an SVG asset that's the whole call;
    // size, shadow and the hotspot all default.
    NativeMouseCursor.svg('rotate', 'assets/icons/rotate.svg');
    //   size:   defaults to the SVG's own (viewBox) size
    //   shadow: defaults to x:0 y:1 blur:1.5 black 50% (ฯƒ=blur/2); null = none
  }

  @override
  Widget build(BuildContext context) {
    // ๐Ÿ” build() only fetches โ€” the bitmap is baked + cached per angle on
    // demand, and the mixin rebuilds when a fresh one lands.
    return MouseRegion(
      // get() never returns null: until the bitmap is baked it returns
      // SystemMouseCursors.basic, so no `??` is needed.
      cursor: NativeMouseCursor.get('rotate', angle: handleAngleRadians),
      child: handle,
    );
  }
}

๐Ÿ’ก NativeMouseCursor.has(id) lets you guard a one-off lazy registration if you can't register up front. Prefer not to use the mixin? Call NativeMouseCursor.configure(devicePixelRatio:, onReady:) yourself once (and again whenever the DPR changes) instead.

๐ŸŽจ Cursor sources #

Pick the register call that matches your glyph โ€” all take the same id, size, shadow and hotspot options:

Call Glyph source
๐Ÿ–ผ๏ธ NativeMouseCursor.svg an SVG asset path (re-rasterised from vector)
๐ŸŒ… NativeMouseCursor.image a decoded ui.Image
โœ๏ธ NativeMouseCursor.draw a CursorPainter you paint into a box yourself
๐Ÿ› ๏ธ NativeMouseCursor.builder produce the bitmap yourself per angle + DPR
NativeMouseCursor.image('pointer', myUiImage, size: const Size(24, 24));

๐Ÿ” Rotation #

There's no rotation flag โ€” just the angle you pass to get. A fixed cursor is simply one you always fetch at the default angle (0), so a single bitmap is baked and reused:

NativeMouseCursor.svg('resize-h', 'assets/resize-h.svg');   // โ†”
// ...
cursor: NativeMouseCursor.get('resize-h'),

For a glyph that turns with a handle, vary the angle โ€” each rotation bucket is baked and cached the first time it's requested (the at-rest angle is warmed in the background; the nearest already-baked angle is shown meanwhile). The bitmap box is always sized for the glyph's diagonal, so it never clips as it turns. ๐ŸŒ€

โ†”๏ธ Mirroring #

flipX / flipY are resolved at get time, so one registered glyph yields a mirrored pair on demand โ€” no second asset:

NativeMouseCursor.svg('hand', 'assets/hand-right.svg');
// the same glyph, flipped โ€” a left hand from the right-hand asset:
cursor: NativeMouseCursor.get('hand', flipX: pointingLeft),

Every (angle, flip) combination is baked and cached the first time it's asked for; the unflipped variant is warmed in the background.

๐ŸŽฏ Hotspot #

By default the click point is the glyph's centre. To anchor it elsewhere (e.g. a tip-anchored pointer), pass hotspot in the glyph's own coords (its size / SVG viewBox, origin top-left) โ€” the package centres the glyph in the auto-sized bitmap and maps the hotspot in for you, so you never deal with box coordinates:

// A 32ร—32 arrow whose tip is at (9, 3):
NativeMouseCursor.svg('pointer', 'assets/icons/pointer.svg',
    hotspot: const Offset(9, 3));

๐Ÿ–ฅ๏ธ High-DPI & disposing #

Cursors bake at the DPR passed to configure and re-bake automatically when you call configure again with a new one, so they stay crisp on Retina/HiDPI. Release them when you're done:

NativeMouseCursor.dispose('rotate');  // ๐Ÿงน one cursor
NativeMouseCursor.disposeAll();       // ๐Ÿงผ everything

๐Ÿ–Œ๏ธ Painted overlay (web / desktop) #

Want the cursor painted inside Flutter instead of as a real OS cursor? Wrap your app in NativeMouseCursorOverlay(force: true): it hides the system cursor and paints the same baked bitmap at the live pointer position.

MaterialApp(
  builder: (context, child) =>
      NativeMouseCursorOverlay(force: kIsWeb, child: child!),
  home: const MyHomePage(),
);

This is useful where the system cursor can actually be hidden:

  • Web โ€” a perfectly seamless per-region cursor (the engine's CSS handling is best-effort across regions); the CSS cursor is hidden.
  • Android โ€” recommended for a rotating cursor: the native PointerIcon flickers when swapped rapidly (an OS quirk), so the painted overlay (system pointer hidden) gives smooth rotation.
  • macOS / Windows / Linux โ€” preview the painted cursor (the native cursor is already pixel-perfect, so you rarely need this).

Off by default; the widget is a transparent pass-through unless force is set.

โš ๏ธ The overlay is a Flutter widget chasing the pointer, so it has a one-frame lag a real OS cursor doesn't. It only works where the system cursor can be hidden โ€” not on iOS/iPadOS (the system pointer can't be hidden, so a painted one would just double it).

๐Ÿงช Example #

The example/ app is an interactive showcase โ€” rotation (an arrow that aims at a dot), mirroring (flipX/flipY), the hotspot (a red dot marking the true pointer position), the baked shadow, and all four cursor sources โ€” plus a switch to toggle the painted overlay.

cd example && flutter run -d macos   # or -d chrome / windows / linux

โš™๏ธ How it works #

NativeMouseCursor extends Flutter's MouseCursor. When the framework activates the cursor for a pointer, the plugin asks the host to make the matching OS cursor current (NSCursor.set() / SetCursor / gdk_window_set_cursor). Because activation flows through Flutter's own cursor machinery, the OS cursor isn't fought over by the engine's system-cursor handling. ๐Ÿค

With NativeMouseCursorOverlay(force: true), activation is intercepted instead: it keeps the baked bitmaps, hides the system cursor, and paints the active cursor at the live pointer position.

๐Ÿ‘ค Author #

Rami Al-Dhafiri.

๐Ÿ“„ License #

MIT ยฉ Rami Al-Dhafiri.

0
likes
160
points
175
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Turn any image, SVG, or painted glyph into a real OS mouse cursor on Flutter desktop, web & Android.

Repository (GitHub)
View/report issues

Topics

#cursor #mouse #pointer #ui #desktop

License

MIT (license)

Dependencies

flutter, flutter_svg, flutter_web_plugins, plugin_platform_interface, web

More

Packages that depend on native_mouse_cursor

Packages that implement native_mouse_cursor