mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-14 14:22:39 +00:00
Fixes #2742. Make UiState::hidden_panels session-only (#[serde(skip)]) and clear hidden_panels when opening or revealing panels, toggling the Control bar while hidden, or loading/resetting workspaces. Treat hidden panels and flyouts as not visible in checked/toggle checks so Window commands unhide them instead of toggling them off invisibly.
This commit is contained in:
1 parent
dbdc5b697a
commit
618c191166
6 files changed
+123
-5
No files matched your search
@@ -9,8 +9,14 @@ use crate::app::file_dialogs::{Bound, with_path};
|
||||
/// Type › Tabs: float the Tabs panel (placed above the selected text frame) or close it.
|
||||
fn toggle_tabs_panel(app: &mut DesignApp) {
|
||||
if app.ui.floating.iter().any(|(p, _)| p == "tabs") {
|
||||
app.ui.floating.retain(|(p, _)| p != "tabs");
|
||||
if app.ui.hidden_panels != 0 {
|
||||
app.ui.hidden_panels = 0;
|
||||
app.ui.tabs_panel.snap_request = 2;
|
||||
} else {
|
||||
app.ui.floating.retain(|(p, _)| p != "tabs");
|
||||
}
|
||||
} else {
|
||||
app.ui.hidden_panels = 0;
|
||||
let at = app.canvas_rect.map_or(egui::pos2(300.0, 140.0), |r| r.min + egui::vec2(80.0, 60.0));
|
||||
app.ui.floating.push(("tabs".into(), [at.x, at.y]));
|
||||
app.ui.tabs_panel.snap_request = 2;
|
||||
@@ -984,7 +990,15 @@ fn run_ui_part5(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value
|
||||
app.ui.ui_scale = v.clamp(0.5, 3.0);
|
||||
Ok(json!({"scale": app.ui.ui_scale}))
|
||||
}
|
||||
"window.controlBar" => flag(&mut app.ui.control_bar),
|
||||
"window.controlBar" => {
|
||||
app.ui.control_bar = if app.ui.hidden_panels != 0 {
|
||||
app.ui.hidden_panels = 0;
|
||||
true
|
||||
} else {
|
||||
!app.ui.control_bar
|
||||
};
|
||||
Ok(json!(app.ui.control_bar))
|
||||
}
|
||||
"view.flattenerPreview" => flag(&mut app.ui.flattener_preview),
|
||||
"view.tagMarkers" => {
|
||||
app.canvas.shown = None;
|
||||
@@ -1115,6 +1129,7 @@ fn run_ui_part5(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value
|
||||
"window.workspace" => {
|
||||
app.ui.docking_generation = app.ui.docking_generation.wrapping_add(1);
|
||||
crate::panel_docking::forget_panels(app);
|
||||
app.ui.hidden_panels = 0;
|
||||
let name = p.get("name").and_then(Value::as_str).unwrap_or("Essentials");
|
||||
if let Some(w) = app.ui.custom_workspaces.iter().find(|w| w.name == name).cloned() {
|
||||
if let Some(hidden) = w.hidden_menu_items {
|
||||
|
||||
@@ -110,7 +110,7 @@ pub fn checked(app: &DesignApp, id: &str, params: &Value) -> Option<bool> {
|
||||
"list.bullets" | "list.numbers" => {
|
||||
app.session.selection_has_list(if id == "list.bullets" { designcraft_doc::ListType::Bullets } else { designcraft_doc::ListType::Numbers })
|
||||
}
|
||||
"window.controlBar" => app.ui.control_bar,
|
||||
"window.controlBar" => app.ui.control_bar && app.ui.hidden_panels == 0,
|
||||
"window.split" => app.split,
|
||||
"window.newWindow" => app.second_window,
|
||||
"view.proofColors" => app.ui.proof_colors,
|
||||
|
||||
@@ -3519,3 +3519,91 @@ fn zoom_commands_zoom_around_the_pointer_when_it_is_over_the_canvas() {
|
||||
let kept = under(&app, rect.center());
|
||||
assert!((kept.x - centre.x).abs() < 1e-3 && (kept.y - centre.y).abs() < 1e-3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_menu_commands_restore_hidden_panels() {
|
||||
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.session.execute("file.new", &json!({})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
let frame = |app: &mut crate::DesignApp| {
|
||||
for _ in 0..2 {
|
||||
let input =
|
||||
egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 900.0))), ..Default::default() };
|
||||
let mut out = ctx.run_ui(input, |ui| {
|
||||
app.logic(&ui.ctx().clone());
|
||||
app.ui(ui);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
};
|
||||
let props_tab = crate::panel_docking::tab_id("properties");
|
||||
frame(&mut app);
|
||||
assert!(ctx.read_response(props_tab).is_some(), "properties panel starts visible");
|
||||
|
||||
// Window › Properties unhides panels hidden by Tab.
|
||||
run_ui(&mut app, "window.hidePanels", &json!({})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 1);
|
||||
frame(&mut app);
|
||||
assert!(ctx.read_response(props_tab).is_none());
|
||||
run_ui(&mut app, "window.panel", &json!({"panel": "properties"})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 0);
|
||||
frame(&mut app);
|
||||
assert!(ctx.read_response(props_tab).is_some(), "Window > Properties restores hidden panels");
|
||||
|
||||
// Shift+Tab + Type/Window panel commands unhide and reveal the requested panel.
|
||||
run_ui(&mut app, "window.hidePanelsExceptTools", &json!({})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 2);
|
||||
run_ui(&mut app, "app.characterPanel", &json!({})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 0);
|
||||
assert!(crate::panel_docking::showing(&app, "character"));
|
||||
|
||||
run_ui(&mut app, "window.hidePanels", &json!({})).unwrap().unwrap();
|
||||
run_ui(&mut app, "app.paragraphStylesPanel", &json!({})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 0);
|
||||
assert!(crate::panel_docking::showing(&app, "paragraphStyles"));
|
||||
|
||||
// Float-only Tabs: opening when hidden reveals it; choosing it while already open but hidden
|
||||
// reveals it instead of closing it invisibly.
|
||||
run_ui(&mut app, "window.hidePanels", &json!({})).unwrap().unwrap();
|
||||
run_ui(&mut app, "window.panel", &json!({"panel": "tabs"})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 0);
|
||||
assert!(app.ui.floating.iter().any(|(p, _)| p == "tabs"));
|
||||
|
||||
run_ui(&mut app, "window.hidePanels", &json!({})).unwrap().unwrap();
|
||||
run_ui(&mut app, "window.panel", &json!({"panel": "tabs"})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 0);
|
||||
assert!(app.ui.floating.iter().any(|(p, _)| p == "tabs"), "Tabs stays open when unhidden");
|
||||
|
||||
// Table toggle activates instead of closing while hidden.
|
||||
run_ui(&mut app, "window.panel", &json!({"panel": "table"})).unwrap().unwrap();
|
||||
assert!(crate::panel_docking::showing(&app, "table"));
|
||||
run_ui(&mut app, "window.hidePanels", &json!({})).unwrap().unwrap();
|
||||
run_ui(&mut app, "app.tablePanel", &json!({})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 0);
|
||||
assert!(crate::panel_docking::showing(&app, "table"));
|
||||
|
||||
// An open flyout in an iconic column isn't closed by Window › <panel> while hidden.
|
||||
run_ui(&mut app, "window.workspace", &json!({"name": "Typography"})).unwrap().unwrap();
|
||||
run_ui(&mut app, "window.panel", &json!({"panel": "character"})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.docking_flyout.as_deref(), Some("character"));
|
||||
run_ui(&mut app, "window.hidePanels", &json!({})).unwrap().unwrap();
|
||||
run_ui(&mut app, "window.panel", &json!({"panel": "character"})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 0);
|
||||
assert!(crate::panel_docking::showing(&app, "character"));
|
||||
|
||||
// Window › Workspace › Reset and Control restore hidden panels.
|
||||
run_ui(&mut app, "window.workspace", &json!({"name": "Essentials"})).unwrap().unwrap();
|
||||
run_ui(&mut app, "window.hidePanels", &json!({})).unwrap().unwrap();
|
||||
run_ui(&mut app, "window.resetWorkspace", &json!({})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 0);
|
||||
frame(&mut app);
|
||||
assert!(ctx.read_response(props_tab).is_some(), "Reset Workspace restores hidden panels");
|
||||
|
||||
app.ui.control_bar = true;
|
||||
run_ui(&mut app, "window.hidePanels", &json!({})).unwrap().unwrap();
|
||||
assert_eq!(checked(&app, "window.controlBar", &Value::Null), Some(false));
|
||||
run_ui(&mut app, "window.controlBar", &json!({})).unwrap().unwrap();
|
||||
assert_eq!(app.ui.hidden_panels, 0);
|
||||
assert!(app.ui.control_bar);
|
||||
assert_eq!(checked(&app, "window.controlBar", &Value::Null), Some(true));
|
||||
}
|
||||
@@ -517,7 +517,7 @@ pub(crate) fn command(app: &mut DesignApp, id: &str, params: &Value) -> Option<R
|
||||
if flyout_open(app, "table") {
|
||||
return Some(Ok(close_flyout(app)));
|
||||
}
|
||||
let id = if showing(app, "table") { "window.panel.close" } else { "window.panel.activate" };
|
||||
let id = if app.ui.hidden_panels == 0 && showing(app, "table") { "window.panel.close" } else { "window.panel.activate" };
|
||||
return Some(shared_command(app, id, &json!({"panel": "table"})));
|
||||
}
|
||||
if id == "window.collapseToIcons" {
|
||||
@@ -546,7 +546,7 @@ pub(crate) fn command(app: &mut DesignApp, id: &str, params: &Value) -> Option<R
|
||||
|
||||
/// Is `panel` showing in the open flyout of an iconic column?
|
||||
fn flyout_open(app: &DesignApp, panel: &str) -> bool {
|
||||
app.ui.docking_flyout.as_deref() == Some(panel) && in_iconic_column(&layout_of(app), panel)
|
||||
app.ui.hidden_panels == 0 && app.ui.docking_flyout.as_deref() == Some(panel) && in_iconic_column(&layout_of(app), panel)
|
||||
}
|
||||
|
||||
/// Close the open flyout; the layout is unchanged.
|
||||
@@ -826,6 +826,7 @@ impl Transaction {
|
||||
release_closed(app, &closed);
|
||||
keep_float_only(app);
|
||||
if let Some(panel) = reveal {
|
||||
app.ui.hidden_panels = 0;
|
||||
app.ui.docking_opened = Some(panel.clone());
|
||||
app.ui.docking_raise = Some(panel);
|
||||
app.ui.docking_flyout = flyout;
|
||||
@@ -905,6 +906,7 @@ fn float_only_panel(app: &mut DesignApp, id: &str, panel: &str, params: &Value)
|
||||
};
|
||||
app.ui.floating.retain(|(p, _)| p != panel);
|
||||
if let Some(at) = at {
|
||||
app.ui.hidden_panels = 0;
|
||||
app.ui.floating.push((panel.to_string(), at));
|
||||
}
|
||||
Ok(json!({"panel": panel, "floating": floating_panels(app)}))
|
||||
|
||||
@@ -310,3 +310,14 @@ fn unrelated_malformed_preferences_are_still_rejected() {
|
||||
assert!(UiState::from_preferences_json(br#"{"uiScale":"invalid","docking":false}"#).is_err());
|
||||
assert!(UiState::from_preferences_json(br#"{"customWorkspaces":false}"#).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hidden_panels_is_not_persisted_across_restarts() {
|
||||
let read = UiState::read_preferences(br#"{"hiddenPanels":1}"#).unwrap();
|
||||
assert!(read.intact);
|
||||
assert_eq!(read.ui.hidden_panels, 0);
|
||||
let mut ui = read.ui;
|
||||
ui.hidden_panels = 2;
|
||||
let saved = serde_json::to_value(&ui).unwrap();
|
||||
assert!(saved.get("hiddenPanels").is_none());
|
||||
}
|
||||
@@ -204,6 +204,8 @@ pub struct UiState {
|
||||
#[serde(skip)]
|
||||
pub flattener_preview: bool,
|
||||
/// Tab / Shift+Tab: 1 = every panel hidden, 2 = all but the Tools panel (0 = shown).
|
||||
/// Session-only so panels never stay hidden across restarts.
|
||||
#[serde(skip)]
|
||||
pub hidden_panels: u8,
|
||||
/// "Don't show again" in the warning before deleting pages that contain objects.
|
||||
pub skip_delete_pages_warning: bool,
|
||||
|
||||
Reference in new issue
Block a user