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).
This commit is contained in:
2026-02-28 15:33:45 +01:00
parent 9359277143
commit 99609f0437

View File

@@ -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 <martin@sukany.cz>
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;
+ }
}