From 4e5596d9de39257ef89b11fb4c7b01c8c4d4379b Mon Sep 17 00:00:00 2001 From: Daneel Date: Sat, 28 Feb 2026 15:39:44 +0100 Subject: [PATCH] patches: v6 0007 - fix Zoom Y offset (line_height arithmetic) --- ...lay-completion-candidates-for-VoiceO.patch | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 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 91436ef..eaf2689 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 3616ffd4289b0a73da93d83fa8dfc7be9d6554b9 Mon Sep 17 00:00:00 2001 +From b37888bd77b77009e40b564b05164c584c9305ae 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,7 +31,8 @@ Key implementation details: Do not post SelectedTextChanged (that reads the AX text at cursor position, which is the minibuffer input, not the candidate). -- Zoom tracking: store the selected candidate's glyph row rect in +- Zoom tracking: store the selected candidate's rect (computed from + FRAME_LINE_HEIGHT and the candidate's visual line index) in overlayZoomRect. ns_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 @@ -49,8 +50,8 @@ Independent overlay branch, BUF_CHARS_MODIFF gating, candidate announcement with overlay Zoom rect storage. --- src/nsterm.h | 3 + - src/nsterm.m | 314 +++++++++++++++++++++++++++++++++++++++++++++------ - 2 files changed, 281 insertions(+), 36 deletions(-) + src/nsterm.m | 316 +++++++++++++++++++++++++++++++++++++++++++++------ + 2 files changed, 283 insertions(+), 36 deletions(-) diff --git a/src/nsterm.h b/src/nsterm.h index 51c30ca..5c15639 100644 @@ -74,7 +75,7 @@ index 51c30ca..5c15639 100644 BOOL font_panel_active; NSFont *font_panel_result; diff --git a/src/nsterm.m b/src/nsterm.m -index 1780194..31b2c33 100644 +index 1780194..2fdea91 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -3258,7 +3258,12 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, @@ -397,7 +398,7 @@ index 1780194..31b2c33 100644 self.cachedPoint = point; NSDictionary *change = @{ -@@ -8789,14 +8935,110 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, +@@ -8789,14 +8935,112 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, BOOL markActive = !NILP (BVAR (b, mark_active)); /* --- Text changed (edit) --- */ @@ -466,29 +467,31 @@ index 1780194..31b2c33 100644 + /* --- Zoom tracking for overlay candidates --- + 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). */ ++ Cleared when the user types (chars_modiff change). ++ ++ Use default line height to compute the Y offset: ++ row 0 is the input line, overlay candidates start ++ from row 1. This avoids fragile glyph matrix row ++ index mapping which can be off when group titles ++ or wrapped lines shift row numbering. */ + if (selected_line >= 0) + { + struct window *w2 = [self validWindow]; -+ if (w2 && w2->current_matrix) ++ if (w2) + { + EmacsView *view = self.emacsView; -+ int target_vrow = selected_line + 1; -+ int nrows = w2->current_matrix->nrows; -+ if (target_vrow < nrows) ++ struct frame *f2 = XFRAME (w2->frame); ++ int line_h = FRAME_LINE_HEIGHT (f2); ++ int y_off = (selected_line + 1) * line_h; ++ ++ if (y_off < w2->pixel_height) + { -+ struct glyph_row *row -+ = w2->current_matrix->rows + target_vrow; -+ if (row->enabled_p -+ && row->visible_height > 0) -+ { -+ view->overlayZoomRect = NSMakeRect ( -+ w2->pixel_left, -+ WINDOW_TOP_EDGE_Y (w2) + row->y, -+ w2->pixel_width, -+ row->visible_height); -+ view->overlayZoomActive = YES; -+ } ++ view->overlayZoomRect = NSMakeRect ( ++ w2->pixel_left, ++ WINDOW_TOP_EDGE_Y (w2) + y_off, ++ w2->pixel_width, ++ line_h); ++ view->overlayZoomActive = YES; + } + } + } @@ -510,7 +513,7 @@ index 1780194..31b2c33 100644 per the WebKit/Chromium pattern. */ else if (point != self.cachedPoint || markActive != self.cachedMarkActive) { -@@ -8966,7 +9208,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, +@@ -8966,7 +9210,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, /* =================================================================== @@ -519,7 +522,7 @@ index 1780194..31b2c33 100644 =================================================================== */ /* Scan visible range of window W for interactive spans. -@@ -9157,7 +9399,7 @@ ns_ax_scan_interactive_spans (struct window *w, +@@ -9157,7 +9401,7 @@ ns_ax_scan_interactive_spans (struct window *w, - (BOOL) isAccessibilityFocused { /* Read the cached point stored by EmacsAccessibilityBuffer on the main @@ -528,7 +531,7 @@ index 1780194..31b2c33 100644 EmacsAccessibilityBuffer *pb = self.parentBuffer; if (!pb) return NO; -@@ -9174,7 +9416,7 @@ ns_ax_scan_interactive_spans (struct window *w, +@@ -9174,7 +9418,7 @@ ns_ax_scan_interactive_spans (struct window *w, dispatch_async (dispatch_get_main_queue (), ^{ /* lwin is a Lisp_Object captured by value. This is GC-safe because Lisp_Objects are tagged integers/pointers that @@ -537,7 +540,7 @@ index 1780194..31b2c33 100644 Emacs. The WINDOW_LIVE_P check below guards against the window being deleted between capture and execution. */ if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin))) -@@ -9200,7 +9442,7 @@ ns_ax_scan_interactive_spans (struct window *w, +@@ -9200,7 +9444,7 @@ ns_ax_scan_interactive_spans (struct window *w, @end @@ -546,7 +549,7 @@ index 1780194..31b2c33 100644 Methods are kept here (same .m file) so they access the ivars declared in the @interface ivar block. */ @implementation EmacsAccessibilityBuffer (InteractiveSpans) -@@ -10520,13 +10762,13 @@ ns_in_echo_area (void) +@@ -10520,13 +10764,13 @@ ns_in_echo_area (void) if (old_title == 0) { char *t = strdup ([[[self window] title] UTF8String]); @@ -562,7 +565,7 @@ index 1780194..31b2c33 100644 [window setTitle: [NSString stringWithUTF8String: size_title]]; [window display]; xfree (size_title); -@@ -11922,7 +12164,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, +@@ -11922,7 +12166,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, if (WINDOW_LEAF_P (w)) { @@ -571,7 +574,7 @@ index 1780194..31b2c33 100644 EmacsAccessibilityBuffer *elem = [existing objectForKey:[NSValue valueWithPointer:w]]; if (!elem) -@@ -11956,7 +12198,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, +@@ -11956,7 +12200,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, } else { @@ -580,7 +583,7 @@ index 1780194..31b2c33 100644 Lisp_Object child = w->contents; while (!NILP (child)) { -@@ -12068,7 +12310,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, +@@ -12068,7 +12312,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, accessibilityUpdating = YES; /* Detect window tree change (split, delete, new buffer). Compare @@ -589,7 +592,7 @@ index 1780194..31b2c33 100644 Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe); if (!EQ (curRoot, lastRootWindow)) { -@@ -12077,12 +12319,12 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, +@@ -12077,12 +12321,12 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, } /* If tree is stale, rebuild FIRST so we don't iterate freed