Skip to content

Commit 66b004a

Browse files
committed
refactor: only hide those with overlaps
1 parent da4f5bd commit 66b004a

4 files changed

Lines changed: 57 additions & 37 deletions

File tree

src/app_engine/drawing.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,6 @@ impl AppEngine {
4646
})
4747
}
4848

49-
pub(super) fn finalize_hints(&self) {
50-
for hb in self.hint_boxes.iter() {
51-
hb.refresh(0, &self.overlay_frame, &self.config.theme);
52-
}
53-
}
54-
5549
/// Show/Hide hint_boxes/colored_frames, update hint text and positions.
5650
/// Returns indices of visible hint boxes
5751
pub(super) fn update_hints(&mut self) -> Vec<usize> {
@@ -73,7 +67,8 @@ impl AppEngine {
7367
let matches_search_prefix = search_pattern
7468
.as_ref()
7569
.is_none_or(|p| self.search_targets.get(idx).is_some_and(|h| p.is_match(h)));
76-
let visible = hb.label.starts_with(&self.hint_prefix)
70+
let visible = !hb.disabled
71+
&& hb.label.starts_with(&self.hint_prefix)
7772
&& matches_search_prefix
7873
&& !is_selected_side;
7974

src/app_engine/lifecycle.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use crate::{
55
config::{GlyphlowConfig, RoleOfInterest, VisibilityCheckingLevel},
66
os_util::{AppWindowInfo, get_focused_window},
77
user_interface::{HintBox, hint_label_from_index, resolve_collisions},
8-
util::digits_by_length,
8+
util::{Frame, digits_by_length},
99
};
1010
use accessibility::AXUIElementAttributes;
1111
use log::Level;
1212
use objc2::rc::autoreleasepool;
1313
use objc2_quartz_core::CATransaction;
14-
use std::{path::PathBuf, sync::mpsc::Receiver, time::Duration};
14+
use std::{ops::Range, path::PathBuf, sync::mpsc::Receiver, time::Duration};
1515
use tokio::sync::mpsc::Sender;
1616

1717
const SHORT_TIMEOUT: u64 = 1;
@@ -157,30 +157,51 @@ impl AppEngine {
157157
let result_rx = self.ui_element_traverse_on_activation(target);
158158

159159
let mut color_idx = 0;
160+
let mut popup_ranges: Vec<Range<usize>> = vec![];
161+
let mut popup_frames: Vec<Frame> = vec![];
162+
let mut popup_start_idx = 0;
160163
let mut popup_level = 0;
161-
let mut no_popup = true;
162164

163165
autoreleasepool(|_| {
164166
for (idx, signal) in result_rx.iter().enumerate() {
165167
match signal {
166-
ElementSignal::ElementFound(Some(ele)) if no_popup || popup_level > 0 => {
168+
ElementSignal::ElementFound(Some(ele)) => {
167169
let need_flush = (idx + 1) % Self::HINTBOX_FLUSH_BATCH_SIZE == 0;
168170
self.handle_element_found(ele, &mut color_idx, need_flush);
169171
}
170172
ElementSignal::TraversalFinished(target) => {
173+
// Disable hints that are covered by popups
174+
if !popup_frames.is_empty()
175+
&& matches!(target, Target::Clickable | Target::Text)
176+
{
177+
for (idx, hb) in self.hint_boxes.iter_mut().enumerate() {
178+
if popup_ranges.iter().any(|r| r.contains(&idx)) {
179+
continue;
180+
}
181+
let frame = self.element_cache.cache[idx].frame;
182+
if popup_frames
183+
.iter()
184+
.any(|p_f| p_f.intersect(&frame).is_some())
185+
{
186+
hb.disabled = true;
187+
hb.set_visible(false);
188+
}
189+
}
190+
}
171191
self.handle_traversal_finished(target);
172192
}
173-
ElementSignal::StartPopup => {
174-
if no_popup {
175-
self.hint_boxes.iter().for_each(|hb| hb.free());
176-
self.hint_boxes.clear();
177-
self.element_cache.clear();
178-
no_popup = false;
193+
ElementSignal::StartPopup(frame) => {
194+
if popup_level == 0 {
195+
popup_frames.push(frame);
196+
popup_start_idx = self.hint_boxes.len();
179197
}
180198
popup_level += 1;
181199
}
182200
ElementSignal::EndPopup => {
183201
popup_level -= 1;
202+
if popup_level == 0 {
203+
popup_ranges.push(popup_start_idx..self.hint_boxes.len());
204+
}
184205
}
185206
_ => (),
186207
}
@@ -254,7 +275,7 @@ impl AppEngine {
254275
if !self.hint_boxes.is_empty() {
255276
resolve_collisions(&mut self.hint_boxes, self.hint_width, &self.config.theme);
256277
// Update layers to match final positions and labels without clearing (avoid flicker)
257-
self.finalize_hints();
278+
self.update_hints();
258279

259280
if need_help_msg {
260281
self.notify("Press Enter to act.", Level::Trace);

src/ax_element.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ use accessibility::{AXAttribute, AXUIElement, AXUIElementAttributes};
66
use accessibility_sys::{
77
AXUIElementCopyMultipleAttributeValues, AXValueCreate, AXValueGetValue, AXValueRef,
88
kAXButtonRole, kAXCellRole, kAXCheckBoxRole, kAXComboBoxRole, kAXContentListSubrole,
9-
kAXErrorSuccess, kAXGroupRole, kAXHiddenAttribute, kAXImageRole, kAXListRole,
10-
kAXMenuButtonRole, kAXMenuItemRole, kAXMenuRole, kAXPopUpButtonRole, kAXPositionAttribute,
11-
kAXPressAction, kAXRoleAttribute, kAXRowRole, kAXScrollAreaRole, kAXScrollBarRole,
12-
kAXSelectedTextRangeAttribute, kAXSizeAttribute, kAXStaticTextRole, kAXTextAreaRole,
13-
kAXTextFieldRole, kAXTitleAttribute, kAXValueTypeCFRange, kAXValueTypeCGPoint,
14-
kAXValueTypeCGSize, kAXWindowRole,
9+
kAXErrorSuccess, kAXGroupRole, kAXHiddenAttribute, kAXImageRole, kAXListRole, kAXMenuItemRole,
10+
kAXMenuRole, kAXPopUpButtonRole, kAXPositionAttribute, kAXPressAction, kAXRoleAttribute,
11+
kAXRowRole, kAXScrollAreaRole, kAXScrollBarRole, kAXSelectedTextRangeAttribute,
12+
kAXSizeAttribute, kAXStaticTextRole, kAXTextAreaRole, kAXTextFieldRole, kAXTitleAttribute,
13+
kAXValueTypeCFRange, kAXValueTypeCGPoint, kAXValueTypeCGSize, kAXWindowRole,
1514
};
1615
use core_foundation::{
1716
array::{CFArray, CFArrayRef},
@@ -35,7 +34,7 @@ pub enum ElementSignal {
3534
// Traversal
3635
ElementFound(Option<ElementOfInterest>),
3736
TraversalFinished(Target),
38-
StartPopup,
37+
StartPopup(Frame),
3938
EndPopup,
4039
}
4140

@@ -1028,8 +1027,10 @@ fn traverse_elements(
10281027
}
10291028
}
10301029
kAXGroupRole if element.subrole().is_ok_and(|r| r == "AXApplicationDialog") => {
1031-
let _ = result_tx.send(ElementSignal::StartPopup);
1032-
is_popup = true;
1030+
if let Some(frame) = ele_fp.frame {
1031+
let _ = result_tx.send(ElementSignal::StartPopup(frame));
1032+
is_popup = true;
1033+
}
10331034
}
10341035
kAXGroupRole => match target {
10351036
Target::Clickable if element.is_clickable() => {
@@ -1051,13 +1052,11 @@ fn traverse_elements(
10511052
}
10521053
_ => (),
10531054
},
1054-
kAXMenuRole
1055-
if element.parent().and_then(|p| p.role()).is_ok_and(|r| {
1056-
r == kAXMenuButtonRole || r == kAXButtonRole || r == kAXCellRole
1057-
}) =>
1058-
{
1059-
let _ = result_tx.send(ElementSignal::StartPopup);
1060-
is_popup = true;
1055+
kAXMenuRole => {
1056+
if let Some(frame) = ele_fp.frame {
1057+
let _ = result_tx.send(ElementSignal::StartPopup(frame));
1058+
is_popup = true;
1059+
}
10611060
}
10621061
kAXMenuItemRole => match target {
10631062
Target::Text => {

src/user_interface/hint_box.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub struct HintBox {
3939
x: f64,
4040
y: f64,
4141
pub idx: usize,
42+
pub disabled: bool,
4243
/// Moved distance to avoid collision
4344
delta: (f64, f64),
4445
frame: Option<Frame>,
@@ -82,6 +83,7 @@ impl HintBox {
8283
x,
8384
y,
8485
idx,
86+
disabled: false,
8587
delta: (0.0, 0.0),
8688
frame,
8789
color,
@@ -369,10 +371,13 @@ fn resolve_collisions_reactive(boxes: &mut [HintBox], x_thres: f64, y_thres: f64
369371
let mut in_queue = vec![true; boxes.len()];
370372

371373
// Initial setup
372-
for i in 0..boxes.len() {
374+
for (i, hb) in boxes.iter().enumerate() {
375+
if hb.disabled {
376+
continue;
377+
}
373378
let coords = (
374-
(boxes[i].x / x_thres).floor() as i32,
375-
(boxes[i].y / y_thres).floor() as i32,
379+
(hb.x / x_thres).floor() as i32,
380+
(hb.y / y_thres).floor() as i32,
376381
);
377382
cell_coords[i] = coords;
378383
grid.entry(coords).or_default().push(i);

0 commit comments

Comments
 (0)