mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 13:23:22 +00:00
Panels: a floating palette keeps out of the way of dialogs and menus
Floating panels are OS windows of their own, and craft-ui keeps a palette above the app's other windows while it is active. A modal dialog or an open menu is drawn in the main window, so a floating panel covered it: the Colour Picker's OK button and fields, Recolor Artwork, and the menu bar's drop-downs (#5238, defect 1). While a modal dialog or a menu of the in-window menu bar is open the docking renderer hides each palette window, and when neither is open each palette shows again at its palette level: topmost while the app is active, behind other applications' windows while it is not, as craft-ui leaves a palette it creates. Hiding rather than lowering covers both: a lowered window can be clicked back to the front, and craft-ui varies the level through the viewport builder, which egui applies when the window opens and not to one already on screen, so the commands go explicitly to the windows there are. The menu bar tells the docking renderer whether one of its top-level menus is open, the same state its hover switching uses; egui's own "any popup open" misses the in-window menu bar. Measured on Windows with the app focused and panels floating: before, a palette was at the front of the z-order and topmost over the main window with the Colour Picker open; after, the main window is in front and the palette is hidden until the dialog or menu closes. Tests: a_modal_lowers_the_native_palette_windows and a_menu_of_the_menu_bar_lowers_the_palette_windows check the commands sent, and a_palette_window_keeps_out_of_the_way_of_the_main_windows_dialog_and_menus the level and visibility table. The wheel test `wheel_over_a_floating_character_font_field_changes_the_font` fails on this machine without this change too (font-order dependent), so it is not part of it.
This commit is contained in:
@@ -58,6 +58,7 @@ mod tree;
|
||||
mod ui_commands;
|
||||
|
||||
pub use bar::menu_bar;
|
||||
pub(crate) use bar::menu_open;
|
||||
pub(crate) use bar::{home_key, home_showing};
|
||||
#[cfg(test)]
|
||||
use bar::{menu_body, pointer_reaches_title};
|
||||
|
||||
@@ -42,6 +42,10 @@ pub fn menu_bar(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> f32 {
|
||||
(ui.cursor().min.x, switch_on_hover(ui.ctx(), &titles))
|
||||
})
|
||||
.inner;
|
||||
// Whether a top-level menu is open: the docking renderer lowers the floating panels' OS windows
|
||||
// while one is, or the menu would open under them.
|
||||
ctx.data_mut(|d| d.insert_temp(menu_open_id(), open));
|
||||
|
||||
let field = ctx.data(|d| d.get_temp::<Field>(field_key));
|
||||
if !open && focused_text_field(&ctx).is_none() {
|
||||
ctx.data_mut(|d| d.remove::<Field>(field_key));
|
||||
@@ -58,6 +62,16 @@ pub fn menu_bar(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> f32 {
|
||||
end
|
||||
}
|
||||
|
||||
/// Where the menu bar keeps whether one of its top-level menus is open.
|
||||
fn menu_open_id() -> egui::Id {
|
||||
egui::Id::unique("menu-bar-open")
|
||||
}
|
||||
|
||||
/// Whether a menu of the in-window menu bar is open (`false` when no menu bar was drawn yet).
|
||||
pub(crate) fn menu_open(ctx: &egui::Context) -> bool {
|
||||
ctx.data(|d| d.get_temp::<bool>(menu_open_id())).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether the pointer at `p` is really over the menu title `title`: inside it, and with no
|
||||
/// popup above it (a tall menu that egui moves up can cover the bar; hovering that menu must
|
||||
/// not switch to the title under it).
|
||||
|
||||
@@ -1113,6 +1113,10 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
}
|
||||
let modal = modal_open(app, &ctx);
|
||||
// Palette windows are OS windows of their own, held above the app's window while it is active,
|
||||
// so a dialog or a menu drawn in the main window would be covered by them.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
palette_window_levels(&ctx, &layout, modal, egui::Popup::is_any_open(&ctx) || crate::menus::menu_open(&ctx));
|
||||
let mut blocked = Vec::new();
|
||||
if modal || egui::Popup::is_any_open(&ctx) {
|
||||
blocked.push(ctx.content_rect());
|
||||
@@ -1174,6 +1178,54 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
|
||||
/// Whether a modal is open: a dialog, About, the command palette, a native file dialog, or a
|
||||
/// window behind the modal backdrop last frame (export progress, plug-in runs). Floating panels
|
||||
/// in OS windows of their own take no input meanwhile, as the main window behind it takes none.
|
||||
/// The window level and visibility a palette window takes: hidden under a modal dialog (a click
|
||||
/// must not bring it back over the dialog), at the normal level under a menu or another popup drawn
|
||||
/// in the main window (so the menu is seen), and otherwise its palette level, which craft-ui leaves
|
||||
/// a palette at when it creates one: topmost while the app is active, behind other applications'
|
||||
/// windows while it is not.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn palette_window_state(modal: bool, popup: bool, active: bool) -> (egui::WindowLevel, bool) {
|
||||
if modal {
|
||||
return (egui::WindowLevel::Normal, false);
|
||||
}
|
||||
if popup {
|
||||
// Hidden as under a dialog: a menu is drawn in the main window, and lowering a palette
|
||||
// window is not always enough for it to go behind the window (the OS may keep it above
|
||||
// while the app is activated through it).
|
||||
return (egui::WindowLevel::Normal, false);
|
||||
}
|
||||
let level = if active {
|
||||
egui::WindowLevel::AlwaysOnTop
|
||||
} else if cfg!(target_os = "windows") {
|
||||
egui::WindowLevel::AlwaysOnBottom
|
||||
} else {
|
||||
egui::WindowLevel::Normal
|
||||
};
|
||||
(level, true)
|
||||
}
|
||||
|
||||
/// Sends the palette windows of `layout` the level and visibility [`palette_window_state`] gives
|
||||
/// when they change. craft-ui varies a palette's level through the viewport builder, which egui
|
||||
/// applies when the window opens and not to one already on screen, so the commands go explicitly to
|
||||
/// the windows there are.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn palette_window_levels(ctx: &egui::Context, layout: &Layout<String>, modal: bool, popup: bool) {
|
||||
let active = ctx.input(|i| i.focused || i.raw.viewports.values().any(|v| v.focused == Some(true)));
|
||||
let key = area_id().with("palette-levels-state");
|
||||
if ctx.data(|d| d.get_temp::<(bool, bool, bool)>(key)) == Some((modal, popup, active)) {
|
||||
return;
|
||||
}
|
||||
ctx.data_mut(|d| d.insert_temp(key, (modal, popup, active)));
|
||||
|
||||
let (level, visible) = palette_window_state(modal, popup, active);
|
||||
for window in &layout.floating {
|
||||
let viewport = craft_ui::docking::DockArea::<String>::window_viewport(area_id(), window.id);
|
||||
ctx.send_viewport_cmd_to(viewport, egui::ViewportCommand::WindowLevel(level));
|
||||
ctx.send_viewport_cmd_to(viewport, egui::ViewportCommand::Visible(visible));
|
||||
}
|
||||
}
|
||||
|
||||
/// Does a modal dialog block the app's other windows ([`modal_open`] for the docking renderer)?
|
||||
pub(crate) fn modal_open(app: &VectorcraftApp, ctx: &egui::Context) -> bool {
|
||||
app.ui.dialog.is_some()
|
||||
|| app.ui.about
|
||||
|
||||
@@ -396,6 +396,84 @@ fn a_modal_blocks_native_windows_and_takes_the_keyboard_from_them() {
|
||||
assert_eq!(h.app.session.tool_id(), "pen", "the window takes keys again once the modal closes");
|
||||
}
|
||||
|
||||
/// The level and visibility a palette window takes under a dialog or a menu, and otherwise: a
|
||||
/// palette stays out of the way of anything drawn in the main window.
|
||||
#[test]
|
||||
fn a_palette_window_keeps_out_of_the_way_of_the_main_windows_dialog_and_menus() {
|
||||
use egui::WindowLevel;
|
||||
assert_eq!(palette_window_state(true, true, true), (WindowLevel::Normal, false), "hidden under a dialog, even in a menu");
|
||||
assert_eq!(palette_window_state(true, false, true), (WindowLevel::Normal, false), "hidden under a dialog");
|
||||
assert_eq!(palette_window_state(false, true, true), (WindowLevel::Normal, false), "hidden under a menu: it is drawn in the main window");
|
||||
assert_eq!(palette_window_state(false, false, true), (WindowLevel::AlwaysOnTop, true), "its palette level while the app is active");
|
||||
let inactive = palette_window_state(false, false, false);
|
||||
assert!(inactive.1, "shown while the app is inactive");
|
||||
assert_ne!(inactive.0, WindowLevel::AlwaysOnTop, "and not above other applications' windows: {:?}", inactive.0);
|
||||
}
|
||||
|
||||
/// With a menu of the in-window menu bar open, the palettes take the normal level (still shown):
|
||||
/// the menu is drawn in the main window, or it would open under them.
|
||||
#[test]
|
||||
fn a_menu_of_the_menu_bar_lowers_the_palette_windows() {
|
||||
let mut h = Harness::new();
|
||||
let viewport = h.float("navigator");
|
||||
h.host.borrow_mut().commands.clear();
|
||||
let layout = layout_of(&h.app.ui);
|
||||
h.ctx.data_mut(|d| d.insert_temp(egui::Id::unique("menu-bar-open"), true));
|
||||
assert!(crate::menus::menu_open(&h.ctx), "the menu bar's state says a menu is open");
|
||||
palette_window_levels(&h.ctx, &layout, false, crate::menus::menu_open(&h.ctx));
|
||||
h.frames(1);
|
||||
let commands: Vec<(egui::ViewportId, ViewportCommand)> = h.host.borrow().commands.clone();
|
||||
let levels: Vec<egui::WindowLevel> = commands
|
||||
.iter()
|
||||
.filter_map(|(id, command)| match command {
|
||||
ViewportCommand::WindowLevel(level) if *id == viewport => Some(*level),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert!(levels.contains(&egui::WindowLevel::Normal), "the palette drops its topmost level under the menu: {levels:?}");
|
||||
assert!(
|
||||
commands.iter().any(|(id, command)| *id == viewport && matches!(command, ViewportCommand::Visible(false))),
|
||||
"and is hidden until the menu closes"
|
||||
);
|
||||
}
|
||||
/// A modal dialog is drawn in the main window, so the palettes' OS windows leave their topmost
|
||||
/// level while it is open and take it back after, or the dialog would be covered by them.
|
||||
#[test]
|
||||
fn a_modal_lowers_the_native_palette_windows() {
|
||||
let mut h = Harness::new();
|
||||
let viewport = h.float("swatches");
|
||||
let levels = |h: &Harness| -> Vec<egui::WindowLevel> {
|
||||
h.host
|
||||
.borrow()
|
||||
.commands
|
||||
.iter()
|
||||
.filter_map(|(id, command)| match command {
|
||||
ViewportCommand::WindowLevel(level) if *id == viewport => Some(*level),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
let hidden = |h: &Harness| -> Vec<bool> {
|
||||
h.host
|
||||
.borrow()
|
||||
.commands
|
||||
.iter()
|
||||
.filter_map(|(id, command)| match command {
|
||||
ViewportCommand::Visible(visible) if *id == viewport => Some(*visible),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
h.app.ui.about = true;
|
||||
h.frames(4);
|
||||
assert!(levels(&h).contains(&egui::WindowLevel::Normal), "the palette leaves AlwaysOnTop under the modal: {:?}", levels(&h));
|
||||
assert_eq!(hidden(&h), [false], "and is hidden, so a click cannot bring it up over the dialog: {:?}", hidden(&h));
|
||||
h.app.ui.about = false;
|
||||
h.frames(4);
|
||||
assert!(levels(&h).contains(&egui::WindowLevel::AlwaysOnTop), "and takes its palette level back: {:?}", levels(&h));
|
||||
assert_eq!(hidden(&h), [false, true], "shown again with the dialog gone: {:?}", hidden(&h));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_dropped_on_a_native_window_opens() {
|
||||
let mut h = Harness::new();
|
||||
|
||||
@@ -31,6 +31,7 @@ M7 type: Touch Type moves a right-to-left character along its line and up from i
|
||||
| 2026-11-05 | minor | Selection Tools add-on: Select Linked, Embedded, Missing and Modified select the images and placed documents with that link status (Object › Plug-ins › Selection Tools and the Links panel menu; Shift adds), and Select by Link Status… lists them by name, status and format to pick from, optionally zooming to them; what is hidden, locked, on a template layer or outside the isolated group is left out and the status bar says how many (#5526, a VectorCraft addition); scores and estimates unchanged (estimated). |
|
||||
| 2026-11-04 | minor | M7: Find Font's Replace With Font lists family and style rows with a search, and the list and the document's font list scroll on their own (#4972); scores and estimates unchanged. |
|
||||
| 2026-11-04 | minor | Selection Tools add-on: Select by Artboards selects the objects overlapping, fully inside or fully outside several chosen artboards, from a dialog that lists, searches and multi-selects the artboards and fits one in the window on double-click (#5525, #5548, #5549, a VectorCraft addition); scores and estimates unchanged. |
|
||||
| 2026-11-05 | minor | Panels: a floating panel window hides under a modal dialog and drops out of the top level under an open menu, so neither the Colour Picker or Recolor Artwork nor the menu bar is covered, and it comes back at its palette level after (#5238); scores and estimates unchanged. |
|
||||
| 2026-11-04 | minor | New Document: the units chosen in Preset Details belong to the chosen preset; Recent and Saved cards keep the units their document or preset was made in, and the built-in tabs still follow the chosen units (#2078). Scores and estimates unchanged. |
|
||||
| 2026-11-03 | minor | M1: Enclosing Lasso provides whole-object enclosure selection as an add-on with no default shortcut or Basic toolbar slot; estimates unchanged. |
|
||||
| 2026-11-03 | minor | New gradients use CMYK default stops in CMYK documents, including Gradient tool creation. Scores and estimates unchanged. |
|
||||
|
||||
@@ -34,6 +34,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
| 2026-11-05 | minor | Gradient panel: Delete Stop sits at the right end of the ramp, so it shows whenever the ramp does instead of under the stop fields, where a short panel hid it (#5403); scores and estimates unchanged |
|
||||
| 2026-11-04 | minor | Type core: a Type-range Copy or Cut publishes styled art alongside plain text, so canvas Paste keeps font family, size and color after editing ends (#5636); scores and estimates unchanged. |
|
||||
| 2026-11-04 | minor | Selection Tools add-on: Select by Artboards selects the objects overlapping, fully inside or fully outside several chosen artboards, from a dialog that lists, searches and multi-selects the artboards and fits one in the window on double-click (#5525, #5548, #5549, a VectorCraft addition); scores and estimates unchanged. |
|
||||
| 2026-11-05 | minor | Floating panels keep out of the main window's way: hidden under a modal dialog, and out of the top level while a menu is open (#5238); scores and estimates unchanged. |
|
||||
| 2026-11-04 | minor | New Document: the units chosen in Preset Details belong to the chosen preset, as the public page lists the pane's options for the selected preset; the chosen card shows them, every other Recent and Saved card keeps the units its document or preset was made in, and the built-in tabs still follow the chosen units (#2078). Scores and estimates unchanged. |
|
||||
| 2026-11-04 | minor | Document Tools add-on (a VectorCraft addition): Batch Trace Folder under Object › Plug-ins (`addon.document.batchTraceFolder`, #4921); evidence added; scores unchanged. |
|
||||
| 2026-11-04 | minor | Object › Expand turns selected symbol instances into their art as Break Link places it, one level deep, with the Expand dialog's Fill and Stroke reaching that art. Scores and estimates unchanged. |
|
||||
|
||||
Reference in New Issue
Block a user