From 99609f0437940b1367ac27b7fb16e0b95ed8bcba Mon Sep 17 00:00:00 2001 From: Daneel Date: Sat, 28 Feb 2026 15:33:45 +0100 Subject: [PATCH] patches: v4 0007 - Zoom follows overlay candidate Zoom now tracks the selected candidate row via overlayZoomRect instead of always pointing at the text cursor. Returns to cursor on typing (chars_modiff change). --- ...lay-completion-candidates-for-VoiceO.patch | 99 +++++++++++-------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/patches/0007-ns-announce-overlay-completion-candidates-for-VoiceO.patch b/patches/0007-ns-announce-overlay-completion-candidates-for-VoiceO.patch index f39d180..230c859 100644 --- a/patches/0007-ns-announce-overlay-completion-candidates-for-VoiceO.patch +++ b/patches/0007-ns-announce-overlay-completion-candidates-for-VoiceO.patch @@ -1,4 +1,4 @@ -From bfba1d81a0b70651fb626da57c0f3cc68e77998c Mon Sep 17 00:00:00 2001 +From 403a1c4664e7491c20eac86c143898bc366a57bc Mon Sep 17 00:00:00 2001 From: Martin Sukany Date: Sat, 28 Feb 2026 14:46:25 +0100 Subject: [PATCH] ns: announce overlay completion candidates for VoiceOver @@ -31,24 +31,28 @@ Key implementation details: Do not post SelectedTextChanged (that reads the AX text at cursor position, which is the minibuffer input, not the candidate). -- Add Zoom tracking (UAZoomChangeFocus) for the selected candidate - glyph row in the minibuffer window matrix. +- Zoom tracking: store the selected candidate's glyph row rect in + overlayZoomRect. draw_window_cursor checks overlayZoomActive and + uses the stored rect instead of the text cursor rect, keeping + Zoom focused on the candidate. The flag is cleared when the user + types (BUF_CHARS_MODIFF changes) to return Zoom to the cursor. -* src/nsterm.h (EmacsAccessibilityBuffer): Add cachedCharsModiff. +* src/nsterm.h (EmacsView): Add overlayZoomActive, overlayZoomRect. +(EmacsAccessibilityBuffer): Add cachedCharsModiff. * src/nsterm.m (ns_ax_face_is_selected): New predicate. (ns_ax_selected_overlay_text): New function. -(EmacsAccessibilityBuffer ensureTextCache): Remove overlay_modiff -from cache validity check. +(ns_draw_window_cursor): Use overlayZoomRect when active. +(EmacsAccessibilityBuffer ensureTextCache): Remove overlay_modiff. (EmacsAccessibilityBuffer postAccessibilityNotificationsForFrame:): -Use BUF_CHARS_MODIFF for ValueChanged gating; make overlay branch -independent; announce candidate with Zoom tracking. +Independent overlay branch, BUF_CHARS_MODIFF gating, candidate +announcement with overlay Zoom rect storage. --- - src/nsterm.h | 1 + - src/nsterm.m | 259 +++++++++++++++++++++++++++++++++++++++++++++++++-- - 2 files changed, 250 insertions(+), 10 deletions(-) + src/nsterm.h | 3 + + src/nsterm.m | 252 ++++++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 244 insertions(+), 11 deletions(-) diff --git a/src/nsterm.h b/src/nsterm.h -index 51c30ca..dd0e226 100644 +index 51c30ca..5c15639 100644 --- a/src/nsterm.h +++ b/src/nsterm.h @@ -507,6 +507,7 @@ typedef struct ns_ax_visible_run @@ -59,11 +63,34 @@ index 51c30ca..dd0e226 100644 @property (nonatomic, assign) ptrdiff_t cachedPoint; @property (nonatomic, assign) BOOL cachedMarkActive; @property (nonatomic, copy) NSString *cachedCompletionAnnouncement; +@@ -591,6 +592,8 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType) + BOOL accessibilityUpdating; + @public /* Accessed by ns_draw_phys_cursor (C function). */ + NSRect lastAccessibilityCursorRect; ++ BOOL overlayZoomActive; ++ NSRect overlayZoomRect; + #endif + BOOL font_panel_active; + NSFont *font_panel_result; diff --git a/src/nsterm.m b/src/nsterm.m -index 1780194..203d3a8 100644 +index 1780194..5c3758a 100644 --- a/src/nsterm.m +++ b/src/nsterm.m -@@ -6915,11 +6915,145 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) +@@ -3258,7 +3258,12 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, + && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 + if (UAZoomEnabled ()) + { +- NSRect windowRect = [view convertRect:r toView:nil]; ++ /* When overlay completion is active (e.g. Vertico), ++ focus Zoom on the selected candidate row instead ++ of the text cursor. */ ++ NSRect zoomSrc = view->overlayZoomActive ++ ? view->overlayZoomRect : r; ++ NSRect windowRect = [view convertRect:zoomSrc toView:nil]; + NSRect screenRect = [[view window] convertRectToScreen:windowRect]; + CGRect cgRect = NSRectToCGRect (screenRect); + +@@ -6915,11 +6920,145 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) are truncated for accessibility purposes. */ #define NS_AX_TEXT_CAP 100000 @@ -210,7 +237,7 @@ index 1780194..203d3a8 100644 static NSString * ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start, ns_ax_visible_run **out_runs, NSUInteger *out_nruns) -@@ -7556,6 +7690,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, +@@ -7556,6 +7695,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, @synthesize cachedOverlayModiff; @synthesize cachedTextStart; @synthesize cachedModiff; @@ -218,7 +245,7 @@ index 1780194..203d3a8 100644 @synthesize cachedPoint; @synthesize cachedMarkActive; @synthesize cachedCompletionAnnouncement; -@@ -7609,16 +7744,15 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, +@@ -7609,16 +7749,15 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, return; ptrdiff_t modiff = BUF_MODIFF (b); @@ -241,7 +268,7 @@ index 1780194..203d3a8 100644 && cachedTextStart == BUF_BEGV (b) && pt >= cachedTextStart && (textLen == 0 -@@ -7635,7 +7769,6 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, +@@ -7635,7 +7774,6 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, [cachedText release]; cachedText = [text retain]; cachedTextModiff = modiff; @@ -249,7 +276,7 @@ index 1780194..203d3a8 100644 cachedTextStart = start; if (visibleRuns) -@@ -8789,10 +8922,116 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, +@@ -8789,10 +8927,102 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, BOOL markActive = !NILP (BVAR (b, mark_active)); /* --- Text changed (edit) --- */ @@ -267,6 +294,7 @@ index 1780194..203d3a8 100644 + if (chars_modiff != self.cachedCharsModiff) + { + self.cachedCharsModiff = chars_modiff; ++ self.emacsView->overlayZoomActive = NO; + [self postTextChangedNotification:point]; + } + } @@ -315,13 +343,10 @@ index 1780194..203d3a8 100644 + annInfo); + + /* --- Zoom tracking for overlay candidates --- -+ The overlay candidates appear as visual lines in the -+ minibuffer window. Row 0 is the input line; overlay -+ candidates start from row 1 onward. Find the glyph -+ row for the selected candidate and focus Zoom there. */ -+#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \ -+ && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 -+ if (selected_line >= 0 && UAZoomEnabled ()) ++ Store the candidate row rect so draw_window_cursor ++ focuses Zoom there instead of on the text cursor. ++ Cleared when the user types (chars_modiff change). */ ++ if (selected_line >= 0) + { + struct window *w2 = [self validWindow]; + if (w2 && w2->current_matrix) @@ -336,33 +361,21 @@ index 1780194..203d3a8 100644 + if (row->enabled_p + && row->visible_height > 0) + { -+ NSRect r = NSMakeRect ( ++ view->overlayZoomRect = NSMakeRect ( + w2->pixel_left, + WINDOW_TOP_EDGE_Y (w2) + row->y, + w2->pixel_width, + row->visible_height); -+ NSRect winRect -+ = [view convertRect:r toView:nil]; -+ NSRect screenRect -+ = [[view window] -+ convertRectToScreen:winRect]; -+ CGRect cgRect -+ = NSRectToCGRect (screenRect); -+ CGFloat primaryH -+ = [[[NSScreen screens] firstObject] -+ frame].size.height; -+ cgRect.origin.y -+ = (primaryH - cgRect.origin.y -+ - cgRect.size.height); -+ UAZoomChangeFocus ( -+ &cgRect, &cgRect, -+ kUAZoomFocusTypeInsertionPoint); ++ view->overlayZoomActive = YES; + } + } + } + } -+#endif + } ++ } ++ else ++ { ++ self.emacsView->overlayZoomActive = NO; + } }