From 0781c48870bc6777c37dfa6fb5e0cc55f12bb9bf Mon Sep 17 00:00:00 2001 From: aiongg Date: Tue, 20 Oct 2026 15:21:46 +0000 Subject: [PATCH] docking: screen-reader resize values follow the column width on either edge On a dock at the right edge the column handle passed accessibility values through the physical axis, so SetValue 300 gave width 180 and Increment shrank the column. ResizeHandle::reversed applies accessibility actions in value space; the pointer and arrow keys stay physical. --- crates/ui/src/docking/dock_tests.rs | 24 ++++++++++++++++++++++++ crates/ui/src/docking/render/dock.rs | 1 + crates/ui/src/layout/resize.rs | 20 ++++++++++++++++---- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/crates/ui/src/docking/dock_tests.rs b/crates/ui/src/docking/dock_tests.rs index 17c673d5..0e3ea1f0 100644 --- a/crates/ui/src/docking/dock_tests.rs +++ b/crates/ui/src/docking/dock_tests.rs @@ -539,3 +539,27 @@ fn a_dock_wider_than_the_window_shrinks_to_fit_and_resizes() { assert!(total < 800.0, "{total}"); assert!((d.hit(&Hit::Divider(Divider::Column(1))).unwrap().center().x - (divider.x - 30.0)).abs() < 1.0); } + +#[test] +fn screen_reader_values_follow_the_column_width_on_either_edge() { + use egui::accesskit::{Action as A, ActionData, ActionRequest, TreeId}; + for (side, right_to_left) in [(Side::End, false), (Side::Start, true), (Side::Start, false)] { + let mut d = testing::DockDriver::new(docked(side, &[&[&[2]]]), TestContent::default()).with_style(move |s| s.right_to_left = right_to_left); + d.ctx.enable_accesskit(); + d.settle(); + let id = DockArea::::hit_id(d.id, &Hit::Divider(Divider::Column(2))); + let request = |action, data| Event::AccessKitActionRequest(ActionRequest { action, target_tree: TreeId::ROOT, target_node: id.accesskit_id(), data }); + let case = format!("{side:?}, right to left {right_to_left}"); + let out = d.frame(vec![request(A::SetValue, Some(ActionData::NumericValue(300.0)))]); + assert_eq!(out.actions, vec![Action::ResizeColumn { panel: 2, width: 300.0 }], "{case}"); + let out = d.frame(vec![request(A::Increment, None)]); + assert_eq!(out.actions, vec![Action::ResizeColumn { panel: 2, width: 248.0 }], "{case}"); + // Arrow keys stay physical: right moves the divider right. + d.ctx.memory_mut(|m| m.request_focus(id)); + d.frame(vec![]); + let out = d.frame(vec![key(egui::Key::ArrowRight)]); + let wider = d.hit(&Hit::Divider(Divider::Column(2))).unwrap().center().x < 400.0; + let width = if wider { 248.0 } else { 232.0 }; + assert_eq!(out.actions, vec![Action::ResizeColumn { panel: 2, width }], "{case}"); + } +} diff --git a/crates/ui/src/docking/render/dock.rs b/crates/ui/src/docking/render/dock.rs index 140cc3d7..0544bc4f 100644 --- a/crates/ui/src/docking/render/dock.rs +++ b/crates/ui/src/docking/render/dock.rs @@ -209,6 +209,7 @@ impl Renderer<'_, P> { self.handle_style(), ) .value(width, lo..=hi) + .reversed(inward < 0.0) .show(ui); self.register_interaction(ui.ctx(), response.response.id); let next = (width + inward * response.delta).clamp(lo, hi); diff --git a/crates/ui/src/layout/resize.rs b/crates/ui/src/layout/resize.rs index 54005881..eed86e93 100644 --- a/crates/ui/src/layout/resize.rs +++ b/crates/ui/src/layout/resize.rs @@ -37,11 +37,12 @@ pub struct ResizeHandle<'a> { hit_margin: f32, step: f32, value: Option<(f32, RangeInclusive)>, + reversed: bool, } impl<'a> ResizeHandle<'a> { pub fn new(id: Id, axis: SplitAxis, rect: Rect, label: &'a str, style: ResizeHandleStyle) -> Self { - Self { id, axis, rect, label, style, hit_margin: 3.0, step: 8.0, value: None } + Self { id, axis, rect, label, style, hit_margin: 3.0, step: 8.0, value: None, reversed: false } } /// Expand the hit rectangle along the movement axis, in logical points. Default: 3. @@ -63,6 +64,14 @@ impl<'a> ResizeHandle<'a> { self } + /// The value grows against the axis, as for a column docked on the right edge that widens + /// as its divider moves left. Accessibility actions then work on the value; the pointer + /// and arrow keys stay physical, and `delta` stays along the axis. + pub fn reversed(mut self, reversed: bool) -> Self { + self.reversed = reversed; + self + } + pub fn show(self, ui: &mut Ui) -> ResizeResponse { let hit = self.rect.expand2(match self.axis { SplitAxis::Horizontal => Vec2::new(self.hit_margin, 0.0), @@ -116,27 +125,30 @@ impl<'a> ResizeHandle<'a> { } }); } + let mut change = 0.0; ui.input_mut(|input| { input.consume_accesskit_action_requests(response.id, |request| match request.action { accesskit::Action::Increment => { - delta += self.step; + change += self.step; true } accesskit::Action::Decrement => { - delta -= self.step; + change -= self.step; true } accesskit::Action::SetValue => { if let (Some((value, range)), Some(accesskit::ActionData::NumericValue(new_value))) = (&self.value, &request.data) && new_value.is_finite() { - delta = (new_value.clamp(f64::from(*range.start()), f64::from(*range.end())) - f64::from(*value)) as f32; + change = (new_value.clamp(f64::from(*range.start()), f64::from(*range.end())) - f64::from(*value)) as f32; + delta = 0.0; } true } _ => false, }); }); + delta += if self.reversed { -change } else { change }; } if !delta.is_finite() { delta = 0.0;