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:
@@ -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>
|
From: Martin Sukany <martin@sukany.cz>
|
||||||
Date: Sat, 28 Feb 2026 14:46:25 +0100
|
Date: Sat, 28 Feb 2026 14:46:25 +0100
|
||||||
Subject: [PATCH] ns: announce overlay completion candidates for VoiceOver
|
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
|
Do not post SelectedTextChanged (that reads the AX text at cursor
|
||||||
position, which is the minibuffer input, not the candidate).
|
position, which is the minibuffer input, not the candidate).
|
||||||
|
|
||||||
- Add Zoom tracking (UAZoomChangeFocus) for the selected candidate
|
- Zoom tracking: store the selected candidate's glyph row rect in
|
||||||
glyph row in the minibuffer window matrix.
|
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.
|
* src/nsterm.m (ns_ax_face_is_selected): New predicate.
|
||||||
(ns_ax_selected_overlay_text): New function.
|
(ns_ax_selected_overlay_text): New function.
|
||||||
(EmacsAccessibilityBuffer ensureTextCache): Remove overlay_modiff
|
(ns_draw_window_cursor): Use overlayZoomRect when active.
|
||||||
from cache validity check.
|
(EmacsAccessibilityBuffer ensureTextCache): Remove overlay_modiff.
|
||||||
(EmacsAccessibilityBuffer postAccessibilityNotificationsForFrame:):
|
(EmacsAccessibilityBuffer postAccessibilityNotificationsForFrame:):
|
||||||
Use BUF_CHARS_MODIFF for ValueChanged gating; make overlay branch
|
Independent overlay branch, BUF_CHARS_MODIFF gating, candidate
|
||||||
independent; announce candidate with Zoom tracking.
|
announcement with overlay Zoom rect storage.
|
||||||
---
|
---
|
||||||
src/nsterm.h | 1 +
|
src/nsterm.h | 3 +
|
||||||
src/nsterm.m | 259 +++++++++++++++++++++++++++++++++++++++++++++++++--
|
src/nsterm.m | 252 ++++++++++++++++++++++++++++++++++++++++++++++++---
|
||||||
2 files changed, 250 insertions(+), 10 deletions(-)
|
2 files changed, 244 insertions(+), 11 deletions(-)
|
||||||
|
|
||||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||||
index 51c30ca..dd0e226 100644
|
index 51c30ca..5c15639 100644
|
||||||
--- a/src/nsterm.h
|
--- a/src/nsterm.h
|
||||||
+++ b/src/nsterm.h
|
+++ b/src/nsterm.h
|
||||||
@@ -507,6 +507,7 @@ typedef struct ns_ax_visible_run
|
@@ -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) ptrdiff_t cachedPoint;
|
||||||
@property (nonatomic, assign) BOOL cachedMarkActive;
|
@property (nonatomic, assign) BOOL cachedMarkActive;
|
||||||
@property (nonatomic, copy) NSString *cachedCompletionAnnouncement;
|
@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
|
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||||
index 1780194..203d3a8 100644
|
index 1780194..5c3758a 100644
|
||||||
--- a/src/nsterm.m
|
--- a/src/nsterm.m
|
||||||
+++ b/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. */
|
are truncated for accessibility purposes. */
|
||||||
#define NS_AX_TEXT_CAP 100000
|
#define NS_AX_TEXT_CAP 100000
|
||||||
|
|
||||||
@@ -210,7 +237,7 @@ index 1780194..203d3a8 100644
|
|||||||
static NSString *
|
static NSString *
|
||||||
ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start,
|
ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start,
|
||||||
ns_ax_visible_run **out_runs, NSUInteger *out_nruns)
|
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 cachedOverlayModiff;
|
||||||
@synthesize cachedTextStart;
|
@synthesize cachedTextStart;
|
||||||
@synthesize cachedModiff;
|
@synthesize cachedModiff;
|
||||||
@@ -218,7 +245,7 @@ index 1780194..203d3a8 100644
|
|||||||
@synthesize cachedPoint;
|
@synthesize cachedPoint;
|
||||||
@synthesize cachedMarkActive;
|
@synthesize cachedMarkActive;
|
||||||
@synthesize cachedCompletionAnnouncement;
|
@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;
|
return;
|
||||||
|
|
||||||
ptrdiff_t modiff = BUF_MODIFF (b);
|
ptrdiff_t modiff = BUF_MODIFF (b);
|
||||||
@@ -241,7 +268,7 @@ index 1780194..203d3a8 100644
|
|||||||
&& cachedTextStart == BUF_BEGV (b)
|
&& cachedTextStart == BUF_BEGV (b)
|
||||||
&& pt >= cachedTextStart
|
&& pt >= cachedTextStart
|
||||||
&& (textLen == 0
|
&& (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 release];
|
||||||
cachedText = [text retain];
|
cachedText = [text retain];
|
||||||
cachedTextModiff = modiff;
|
cachedTextModiff = modiff;
|
||||||
@@ -249,7 +276,7 @@ index 1780194..203d3a8 100644
|
|||||||
cachedTextStart = start;
|
cachedTextStart = start;
|
||||||
|
|
||||||
if (visibleRuns)
|
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));
|
BOOL markActive = !NILP (BVAR (b, mark_active));
|
||||||
|
|
||||||
/* --- Text changed (edit) --- */
|
/* --- Text changed (edit) --- */
|
||||||
@@ -267,6 +294,7 @@ index 1780194..203d3a8 100644
|
|||||||
+ if (chars_modiff != self.cachedCharsModiff)
|
+ if (chars_modiff != self.cachedCharsModiff)
|
||||||
+ {
|
+ {
|
||||||
+ self.cachedCharsModiff = chars_modiff;
|
+ self.cachedCharsModiff = chars_modiff;
|
||||||
|
+ self.emacsView->overlayZoomActive = NO;
|
||||||
+ [self postTextChangedNotification:point];
|
+ [self postTextChangedNotification:point];
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
@@ -315,13 +343,10 @@ index 1780194..203d3a8 100644
|
|||||||
+ annInfo);
|
+ annInfo);
|
||||||
+
|
+
|
||||||
+ /* --- Zoom tracking for overlay candidates ---
|
+ /* --- Zoom tracking for overlay candidates ---
|
||||||
+ The overlay candidates appear as visual lines in the
|
+ Store the candidate row rect so draw_window_cursor
|
||||||
+ minibuffer window. Row 0 is the input line; overlay
|
+ focuses Zoom there instead of on the text cursor.
|
||||||
+ candidates start from row 1 onward. Find the glyph
|
+ Cleared when the user types (chars_modiff change). */
|
||||||
+ row for the selected candidate and focus Zoom there. */
|
+ if (selected_line >= 0)
|
||||||
+#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
|
|
||||||
+ && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
|
|
||||||
+ if (selected_line >= 0 && UAZoomEnabled ())
|
|
||||||
+ {
|
+ {
|
||||||
+ struct window *w2 = [self validWindow];
|
+ struct window *w2 = [self validWindow];
|
||||||
+ if (w2 && w2->current_matrix)
|
+ if (w2 && w2->current_matrix)
|
||||||
@@ -336,33 +361,21 @@ index 1780194..203d3a8 100644
|
|||||||
+ if (row->enabled_p
|
+ if (row->enabled_p
|
||||||
+ && row->visible_height > 0)
|
+ && row->visible_height > 0)
|
||||||
+ {
|
+ {
|
||||||
+ NSRect r = NSMakeRect (
|
+ view->overlayZoomRect = NSMakeRect (
|
||||||
+ w2->pixel_left,
|
+ w2->pixel_left,
|
||||||
+ WINDOW_TOP_EDGE_Y (w2) + row->y,
|
+ WINDOW_TOP_EDGE_Y (w2) + row->y,
|
||||||
+ w2->pixel_width,
|
+ w2->pixel_width,
|
||||||
+ row->visible_height);
|
+ row->visible_height);
|
||||||
+ NSRect winRect
|
+ view->overlayZoomActive = YES;
|
||||||
+ = [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);
|
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+#endif
|
|
||||||
+ }
|
+ }
|
||||||
|
+ }
|
||||||
|
+ else
|
||||||
|
+ {
|
||||||
|
+ self.emacsView->overlayZoomActive = NO;
|
||||||
+ }
|
+ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user