mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-11 17:37:26 +00:00
UI.8: shape dock action labels with logical accessibility
This commit is contained in:
1 parent
b1406e3e6a
commit
5c874d51ee
3 files changed
+85
-9
No files matched your search
@@ -158,6 +158,11 @@ pub trait DockContent<P> {
|
||||
fn label(&mut self, _ui: &Ui, _panel: &P, logical: &str, _font: &FontId, _color: Color32) -> egui::WidgetText {
|
||||
logical.into()
|
||||
}
|
||||
/// Shape stock Float/Move/Close action text independently of panel identity.
|
||||
/// The renderer preserves `logical` for accessibility, even when the visual text differs.
|
||||
fn action_label(&mut self, _ui: &Ui, logical: &str, _font: &FontId, _color: Color32) -> egui::WidgetText {
|
||||
logical.into()
|
||||
}
|
||||
fn panel_menu(&mut self, _ui: &mut Ui, _panel: &P) {}
|
||||
fn group_header_width(&self, _panels: &[P], _active: &P, _floating: bool) -> f32 {
|
||||
0.0
|
||||
@@ -690,13 +695,19 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
response.context_menu(|ui| {
|
||||
self.content.panel_menu(ui, panel);
|
||||
if policy.movable && policy.floatable && ui.button(&self.style.float_label).clicked() {
|
||||
let rect = clamp_float(group.translate(vec2(24.0, 24.0)), ui.ctx().content_rect());
|
||||
self.output.actions.push(Action::Float { panel: panel.clone(), rect: [rect.left(), rect.top(), rect.width(), rect.height()] });
|
||||
ui.close();
|
||||
if policy.movable && policy.floatable {
|
||||
let text = self.content.action_label(ui, &self.style.float_label, &self.style.font, self.style.text);
|
||||
let response = ui.button(text);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &self.style.float_label));
|
||||
if response.clicked() {
|
||||
let rect = clamp_float(group.translate(vec2(24.0, 24.0)), ui.ctx().content_rect());
|
||||
self.output.actions.push(Action::Float { panel: panel.clone(), rect: [rect.left(), rect.top(), rect.width(), rect.height()] });
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
if policy.movable {
|
||||
ui.menu_button(&self.style.move_label, |ui| {
|
||||
let text = self.content.action_label(ui, &self.style.move_label, &self.style.font, self.style.text);
|
||||
let response = ui.menu_button(text, |ui| {
|
||||
egui::ScrollArea::vertical().max_height(360.0).show(ui, |ui| {
|
||||
for target in self.layout.panels() {
|
||||
if target != panel && (self.permissions)(target).accepts_tabs && self.menu_label(ui, target).clicked() {
|
||||
@@ -710,10 +721,16 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
}
|
||||
});
|
||||
});
|
||||
response.response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &self.style.move_label));
|
||||
}
|
||||
if policy.closable && ui.button(&self.style.close_label).clicked() {
|
||||
self.output.actions.push(Action::Close { panel: panel.clone() });
|
||||
ui.close();
|
||||
if policy.closable {
|
||||
let text = self.content.action_label(ui, &self.style.close_label, &self.style.font, self.style.text);
|
||||
let response = ui.button(text);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &self.style.close_label));
|
||||
if response.clicked() {
|
||||
self.output.actions.push(Action::Close { panel: panel.clone() });
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -683,6 +683,9 @@ impl DockContent<u32> for CustomContent {
|
||||
self.labels.push(logical.to_owned());
|
||||
ui.painter().layout_no_wrap("Custom visual label".into(), font.clone(), color).into()
|
||||
}
|
||||
fn action_label(&mut self, ui: &egui::Ui, logical: &str, font: &egui::FontId, color: egui::Color32) -> egui::WidgetText {
|
||||
ui.painter().layout_no_wrap(format!("Action visual: {logical}"), font.clone(), color).into()
|
||||
}
|
||||
fn panel_menu(&mut self, ui: &mut egui::Ui, _: &u32) {
|
||||
self.menus += 1;
|
||||
ui.button("App menu").clicked();
|
||||
@@ -1035,3 +1038,55 @@ fn workspace_cancel_preserves_other_area_and_body_drag_ownership() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn popup_action_labels_use_custom_visual_text_and_logical_accessibility() {
|
||||
use egui_kittest::{Harness, kittest::Queryable};
|
||||
fn paints(shape: &egui::Shape, text: &str) -> bool {
|
||||
match shape {
|
||||
egui::Shape::Text(shape) => shape.galley.text() == text,
|
||||
egui::Shape::Vec(shapes) => shapes.iter().any(|shape| paints(shape, text)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
let layout = sample();
|
||||
let mut h = Harness::builder().with_size(vec2(800.0, 600.0)).build_ui_state(
|
||||
move |ui, content: &mut CustomContent| {
|
||||
DockArea::new(Id::new("action-label-test")).show_customized(
|
||||
ui,
|
||||
&layout,
|
||||
&DockStyle::from_ui(ui),
|
||||
|p| format!("Logical {p}"),
|
||||
|_| Permissions::default(),
|
||||
|_| PanelLimits::default(),
|
||||
content,
|
||||
);
|
||||
},
|
||||
CustomContent { labels: vec![], headers: 0, menus: 0, bodies: 0 },
|
||||
);
|
||||
h.run_steps(2);
|
||||
let tab = Id::new("action-label-test").with(("tab", &5_u32));
|
||||
let at = h.ctx.read_response(tab).unwrap().rect.center();
|
||||
h.input_mut().events.extend([
|
||||
Event::PointerMoved(at),
|
||||
Event::PointerButton { pos: at, button: PointerButton::Secondary, pressed: true, modifiers: Modifiers::NONE },
|
||||
]);
|
||||
h.run_steps(1);
|
||||
h.input_mut().events.push(Event::PointerButton { pos: at, button: PointerButton::Secondary, pressed: false, modifiers: Modifiers::NONE });
|
||||
h.run_steps(2);
|
||||
assert!(egui::Popup::is_any_open(&h.ctx));
|
||||
for logical in ["Float panel", "Move to group", "Close panel"] {
|
||||
assert!(
|
||||
h.output().shapes.iter().any(|shape| paints(&shape.shape, &format!("Action visual: {logical}"))),
|
||||
"custom action must paint: {logical}"
|
||||
);
|
||||
// This sample also has a floating-group grip labeled Move to group.
|
||||
let expected = if logical == "Move to group" { 2 } else { 1 };
|
||||
assert_eq!(h.query_all_by_label(logical).count(), expected, "popup action must retain its own logical accessibility: {logical}");
|
||||
assert_eq!(
|
||||
h.query_all_by_label(&format!("Action visual: {logical}")).count(),
|
||||
0,
|
||||
"visual substitution must not replace logical accessibility"
|
||||
);
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -120,7 +120,11 @@ Collapsed accordion entries still have panel menus; collapsing tab groups is hos
|
||||
|
||||
`label` returns `WidgetText`, including an application-shaped galley, used for tab width,
|
||||
painting and menu labels. The separate logical label callback supplies tab accessibility
|
||||
text. Original `show` and `show_with_limits` call sites remain supported.
|
||||
text. `action_label` independently shapes the localized stock Float/Move/Close menu
|
||||
text without receiving a panel identity. Its default returns unchanged logical `WidgetText`;
|
||||
custom visual text never replaces the action's logical accessibility label. Panel targets
|
||||
and overflow entries continue to use `label`. Original `show` and `show_with_limits` call
|
||||
sites remain supported.
|
||||
|
||||
Apply `Action::Activate` in the host, then use `DockArea::raise_panel(panel)` on the next
|
||||
frame to raise the owning in-viewport floating layer and focus the panel tab. This request
|
||||
|
||||
Reference in new issue
Block a user