diff --git a/Formula/emacs-plus@31.rb b/Formula/emacs-plus@31.rb index eb25d877..f357aa9b 100644 --- a/Formula/emacs-plus@31.rb +++ b/Formula/emacs-plus@31.rb @@ -20,6 +20,7 @@ class EmacsPlusAT31 < EmacsBase option "with-xwidgets", "Experimental: build with xwidgets support" option "with-no-frame-refocus", "Disables frame re-focus (ie. closing one frame does not refocus another one)" option "with-compress-install", "Build with compressed install optimization" + option "with-smooth-cursor", "Build with animated smooth cursor" # # Dependencies @@ -92,6 +93,7 @@ class EmacsPlusAT31 < EmacsBase opoo "The option --with-no-frame-refocus is not required anymore in emacs-plus@31." if build.with? "no-frame-refocus" local_patch "system-appearance", sha: "53283503db5ed2887e9d733baaaf80f2c810e668e782e988bda5855a0b1ebeb4" local_patch "round-undecorated-frame", sha: "26947b6724fc29fadd44889808c5cf0b4ce6278cf04f46086a21df50c8c4151d" + local_patch "ksqsf-smooth-cursor", sha: "3bc5283437cc918718f6a92380313c1e468302b1028b98c17ef652c1cfbf18e2" if build.with? "smooth-cursor" # # Install diff --git a/README.org b/README.org index f23e1dc0..4cd20603 100644 --- a/README.org +++ b/README.org @@ -144,6 +144,7 @@ By default =emacs-plus@31= uses the following features. | =--with-x11= | build with x11 support | | =--with-xwidgets= | build [[#xwidgets-webkit][→ with xwidgets]] support | | =--without-cocoa= | build a non-Cocoa version of Emacs (terminal only) | +| =--with-smooth-cursor= | build with smooth cursor support | *** No title bar Please note, that ~--with-no-titlebar~ is no longer needed in Emacs 30+, since the same can be achieved natively using [[https://github.com/d12frosted/homebrew-emacs-plus#emacs-29-1][this method]]. @@ -302,6 +303,10 @@ Knows issues: - =ld: library not found for -lSystem=. This only happens on older versions of =gcc= installed by Homebrew. Please execute =$ brew reinstall gcc libgccjit= to resolve this issue. - Errors during compilation of your =init.el=. Try running Emacs with =-Q= option and give it some time to compile everything (maybe run =M-x= to force compilation) - you shall see buffer =*Async-native-compile-log*= in the list of buffers. +*** Smooth Cursor + +The smooth cursor feature adds native cursor movement animation for macOS. The feature extends [[https://github.com/ksqsf/emacsmoe/pull/7][ksqsf's implementation]] to support more (if not all) kinds of cursors and cursor blinks. + ** Icons | Option | Author | Image | URL | diff --git a/patches/emacs-31/ksqsf-smooth-cursor.patch b/patches/emacs-31/ksqsf-smooth-cursor.patch new file mode 100644 index 00000000..631e7376 --- /dev/null +++ b/patches/emacs-31/ksqsf-smooth-cursor.patch @@ -0,0 +1,147 @@ +From ebfa4855d7a323ebc3424936d6a3df812f74967a Mon Sep 17 00:00:00 2001 +From: Mingkai Dong +Date: Sat, 5 Apr 2025 00:27:15 +0800 +Subject: [PATCH] Add smooth cursor animation + +This adds and extends the patch from ksqsf. +https://github.com/ksqsf/emacsmoe/pull/7 + +Co-Authored-By: Mingkai Dong +Co-Authored-By: ksqsf +--- + lisp/term/ns-win.el | 7 ++++++ + src/nsterm.h | 1 + + src/nsterm.m | 59 +++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 67 insertions(+) + +diff --git a/lisp/term/ns-win.el b/lisp/term/ns-win.el +index 46639f2422b..8c606c441af 100644 +--- a/lisp/term/ns-win.el ++++ b/lisp/term/ns-win.el +@@ -65,6 +65,13 @@ x-command-line-resources + ;; nsterm.m. + (defvar ns-input-file) + ++(defcustom ns-cursor-animation-duration 0.1 ++ "Duration in seconds for cursor animation on macOS. ++This controls how long the cursor animation takes when changing position or style. ++A value of 0 disables animation." ++ :type 'number ++ :group 'ns) ++ + (defun ns-handle-nxopen (_switch &optional temp) + (setq unread-command-events (append unread-command-events + (if temp '(ns-open-temp-file) +diff --git a/src/nsterm.h b/src/nsterm.h +index 2abf402f8bc..33338370537 100644 +--- a/src/nsterm.h ++++ b/src/nsterm.h +@@ -485,6 +485,7 @@ #define NSTRACE_UNSILENCE() + struct frame *emacsframe; + int scrollbarsNeedingUpdate; + NSRect ns_userRect; ++ CALayer *cursor_layer; + } + + /* AppKit-side interface. */ +diff --git a/src/nsterm.m b/src/nsterm.m +index 5514a693c86..95238209356 100644 +--- a/src/nsterm.m ++++ b/src/nsterm.m +@@ -44,6 +44,7 @@ Updated by Christian Limpach (chris@nice.ch) + + #include "lisp.h" + #include "blockinput.h" ++#include "dispextern.h" + #include "sysselect.h" + #include "nsterm.h" + #include "systime.h" +@@ -71,6 +72,7 @@ Updated by Christian Limpach (chris@nice.ch) + #include "macfont.h" + #include + #include ++#include + #endif + + static EmacsMenu *dockMenu; +@@ -3016,6 +3018,12 @@ Hide the window (X11 semantics) + ns_unfocus (f); + } + ++static double ++ns_get_cursor_animation_duration (void) ++{ ++ Lisp_Object duration = Fsymbol_value (intern_c_string ("ns-cursor-animation-duration")); ++ return NUMBERP (duration) ? XFLOATINT (duration) : 0.1; ++} + + static void + ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, +@@ -3101,6 +3109,45 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors. + /* Prevent the cursor from being drawn outside the text area. */ + r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA)); + ++ /* We use the CA layer as a temporary animation layer. */ ++ EmacsView *view = FRAME_NS_VIEW (f); ++ CALayer *cursor_layer = view->cursor_layer; ++ ++ if (active_p && cursor_layer) { ++ NSRect r2 = r; ++ r2.origin.y = [view bounds].size.height - r2.size.height - r2.origin.y; ++ cursor_glyph = get_phys_cursor_glyph (w); ++ if ((cursor_glyph->resolved_level & 1) != 0) ++ r2.origin.x += cursor_glyph->pixel_width - r2.size.width; ++ ++ switch (cursor_type) ++ { ++ case DEFAULT_CURSOR: ++ case NO_CURSOR: ++ /* Make the layer invisible. */ ++ cursor_layer.opacity = 0.0; ++ cursor_layer.backgroundColor = nil; ++ cursor_layer.frame = r2; ++ break; ++ case FILLED_BOX_CURSOR: ++ case HOLLOW_BOX_CURSOR: ++ case HBAR_CURSOR: ++ case BAR_CURSOR: ++ [CATransaction begin]; ++ [CATransaction setAnimationDuration:ns_get_cursor_animation_duration ()]; ++ [CATransaction setCompletionBlock:^{ ++ cursor_layer.backgroundColor = nil; /* hide after animation */ ++ cursor_layer.opacity = 0.0; ++ }]; ++ cursor_layer.backgroundColor = FRAME_CURSOR_COLOR (f).CGColor; ++ cursor_layer.opacity = 1.0; ++ cursor_layer.frame = r2; ++ [CATransaction commit]; ++ break; ++ } ++ } ++ /* Below is the original Emacs drawing code for the cursor. */ ++ + ns_focus (f, NULL, 0); + + NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; +@@ -9381,6 +9428,18 @@ - (instancetype) initWithEmacsFrame: (struct frame *) f + [[self contentView] addSubview:view]; + [self makeFirstResponder:view]; + ++ /* Overlay a canvas view on top of EmacsView. */ ++ NSView *canvasView = [[NSView alloc] initWithFrame:view.bounds]; ++ canvasView.wantsLayer = YES; ++ canvasView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; ++ [view addSubview:canvasView positioned:NSWindowAbove relativeTo:nil]; ++ ++ /* Create a cursor layer on the canvas. */ ++ view->cursor_layer = [CALayer layer]; ++ [canvasView.layer addSublayer: view->cursor_layer]; ++ view->cursor_layer.frame = CGRectMake(0, 0, 0, 0); ++ ++ + #if !defined (NS_IMPL_COCOA) || MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 + #if MAC_OS_X_VERSION_MAX_ALLOWED > 1090 + if ([self respondsToSelector: @selector(useOptimizedDrawing:)]) +-- +2.49.0 +