Merge PR #1076: Tools: a tool group's flyout closes on Esc and on a click outside it, as InDesign's

This commit is contained in:
aiongg
2026-10-16 04:07:44 +00:00
2 changed files with 49 additions and 2 deletions
+1
View File
@@ -465,6 +465,7 @@ Newest first.
- **2026-10-15** · A panel tab's context menu (and the end of its panel menu) has InDesign's Close, Close Tab Group, Minimize and Collapse to Icons, before Float Panel and Group with; `window.panel.closeGroup` and `window.collapseToIcons` are commands. No readiness percentage change.
- **2026-10-15** · Window › Styles › Paragraph Styles (or Character Styles) opens InDesign's floating Character Styles | Paragraph Styles group, which stays open while other panels open; it was a flyout that the next panel replaced. No readiness percentage change.
- 2026-10-15: Double-clicking the status bar's preflight count or its light shows the Preflight panel, as in InDesign.
- 2026-10-15: A tool group's flyout closes on Esc and on a click anywhere outside it (a panel, the Fill swatch), as InDesign's; it only closed on a click on the canvas.
- **2026-10-14** · The Type tool draws a new text frame over a locked empty frame, as InDesign does, instead of turning the locked frame into a text frame. No readiness percentage change.
- **2026-10-14** · 100% black shows as pure black on screen by default, as InDesign's Appearance of Black does out of the box (Display All Blacks as Rich Black); Preferences › Appearance of Black › Display All Blacks Accurately still shows it as the dark grey it prints, and a saved choice is kept. No readiness percentage change.
- **2026-10-14** · Pathfinder keeps the frontmost object, as InDesign does: a photo placed over shapes and intersected with them shows through the shapes (it was dropped, and the shapes kept their black fill); Add, Exclude Overlap and Minus Back take the front object's fill too, Subtract still keeps the back one. No readiness percentage change.
+48 -2
View File
@@ -45,6 +45,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
ui.add_space(2.0);
let current = app.session.tool_id();
let mut chosen: Option<&'static str> = None;
let mut opened_now = false;
// A slot is a group (its current tool shown, the others in its flyout) or, in two
// columns, one tool of a group InDesign lays out tool by tool.
let mut row_items: Vec<Option<(usize, Option<usize>)>> = Vec::new();
@@ -118,6 +119,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|| (resp.is_pointer_button_down_on()
&& ui.input(|i| i.pointer.press_start_time().is_some_and(|s| i.time - s > 0.35))))
{
opened_now |= app.ui.flyout != Some(gi);
app.ui.flyout = Some(gi);
}
if app.ui.flyout == Some(gi) {
@@ -157,6 +159,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
t.text_dim,
);
}
row.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, tl.label));
if row.clicked() {
chosen = Some(tl.id);
}
@@ -178,8 +181,18 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
if let Some(id) = chosen {
app.select_tool(id);
}
if app.ui.flyout.is_some() && ui.input(|i| i.pointer.any_click()) && chosen.is_none() && !ui.ctx().is_pointer_over_egui() {
app.ui.flyout = None;
// As InDesign's, an open flyout closes on Esc and on a click anywhere outside it (a
// panel, a swatch, the canvas), not only on the canvas.
if let Some(gi) = app.ui.flyout
&& chosen.is_none()
&& !opened_now
{
let area = ui.ctx().memory(|m| m.area_rect(egui::Id::new(("flyout", gi))));
let outside = ui.input(|i| i.pointer.any_click() && i.pointer.interact_pos().is_some_and(|p| area.is_none_or(|a| !a.contains(p))));
let esc = ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Escape));
if outside || esc {
app.ui.flyout = None;
}
}
ui.add_space(8.0);
fill_stroke_proxy(app, ui, width, cols);
@@ -618,6 +631,39 @@ mod tests {
h.get_by_label(label).rect()
}
/// As InDesign's: a tool group's flyout closes on Esc and on a click outside it (the fill
/// swatch, a panel), not only on the canvas.
#[test]
fn a_tool_flyout_closes_on_escape_and_on_a_click_elsewhere() {
use egui::{Event, Modifiers, PointerButton};
use egui_kittest::kittest::Queryable;
let app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
let mut h = crate::test_window::open(app, vec2(1400.0, 900.0));
let open = |h: &mut egui_kittest::Harness<'static, crate::test_window::Window>| {
let at = h.get_by_label("Rectangle Frame Tool (F)").rect().center();
h.input_mut().events.push(Event::PointerMoved(at));
for pressed in [true, false] {
h.input_mut().events.push(Event::PointerButton { pos: at, button: PointerButton::Secondary, pressed, modifiers: Modifiers::NONE });
}
h.run_steps(3);
assert!(h.state().app.ui.flyout.is_some(), "a right-click opens the flyout");
let _ = h.get_by_label("Ellipse Frame Tool");
};
open(&mut h);
h.key_press(egui::Key::Escape);
h.run_steps(2);
assert!(h.state().app.ui.flyout.is_none(), "Esc closes it");
// A click on a panel closes it.
open(&mut h);
crate::test_window::click_at(&mut h, egui::pos2(1300.0, 700.0));
assert!(h.state().app.ui.flyout.is_none(), "a click elsewhere closes it");
// Choosing a tool in it closes it and selects the tool.
open(&mut h);
h.get_by_label("Ellipse Frame Tool").click();
h.run_steps(3);
assert_eq!((h.state().app.session.tool_id(), h.state().app.ui.flyout), ("ellipseFrame", None));
}
#[test]
fn a_short_window_reaches_every_tool() {
for double in [false, true] {