mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 05:37:23 -06:00
Panels: a panel's function key restores only the tabs its group hid
Hiding a group with a panel's function key records the tabs it closed, and the next press reopens only that set with the panel in front. A tab closed on its own before the group was hidden, or reopened and closed again while the group was hidden, stays closed. The record is saved with the workspace.
This commit is contained in:
@@ -353,38 +353,30 @@ pub(crate) fn hide_group(app: &mut VectorcraftApp, panel: &str) -> Result<Value,
|
||||
let mut txn = Transaction::begin(app)?;
|
||||
let panel = panel.to_string();
|
||||
let closed = txn.apply(Action::CloseGroup { panel })?;
|
||||
Ok(txn.commit(app, closed, None))
|
||||
let group = closed.clone();
|
||||
let result = txn.commit(app, closed, None);
|
||||
if !group.is_empty() {
|
||||
app.ui.docking_hidden_groups.push(group);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Reopens the hidden tabs connected to `panel` and brings it to the front.
|
||||
/// Reopens the tabs hidden together with `panel` and brings it to the front.
|
||||
fn restore_hidden_group(app: &mut VectorcraftApp, panel: &str) -> Result<Value, String> {
|
||||
let mut txn = Transaction::begin(app)?;
|
||||
let mut root = panel.to_string();
|
||||
for _ in 0..craft_ui::docking::MAX_PANELS {
|
||||
let Some((_, location)) = txn.workspace.hidden.iter().find(|(id, _)| id == &root) else { break };
|
||||
let Some(anchor) = location.anchor.as_ref().filter(|_| matches!(location.placement, Placement::Tab { .. })) else { break };
|
||||
if !txn.workspace.hidden.iter().any(|(id, _)| id == anchor) || anchor == &root {
|
||||
break;
|
||||
}
|
||||
root = anchor.clone();
|
||||
}
|
||||
let mut members = vec![root];
|
||||
for _ in 0..craft_ui::docking::MAX_PANELS {
|
||||
let next = txn.workspace.hidden.iter().find_map(|(id, location)| {
|
||||
(matches!(location.placement, Placement::Tab { .. })
|
||||
&& location.anchor.as_ref().is_some_and(|anchor| members.contains(anchor))
|
||||
&& !members.contains(id))
|
||||
.then(|| id.clone())
|
||||
});
|
||||
let Some(next) = next else { break };
|
||||
members.push(next);
|
||||
}
|
||||
let members =
|
||||
app.ui.docking_hidden_groups.iter().find(|group| group.iter().any(|id| id == panel)).cloned().unwrap_or_else(|| vec![panel.to_string()]);
|
||||
for member in members {
|
||||
if !txn.workspace.hidden.iter().any(|(id, _)| id == &member) {
|
||||
continue;
|
||||
}
|
||||
let fallback = first_dock_panel(&txn.workspace.layout, &member);
|
||||
txn.workspace.set_visible(member, true, fallback, model_permissions).map_err(|e| e.to_string())?;
|
||||
}
|
||||
txn.apply(Action::Activate { panel: panel.to_string() })?;
|
||||
Ok(txn.commit(app, Vec::new(), Some(panel.to_string())))
|
||||
let result = txn.commit(app, Vec::new(), Some(panel.to_string()));
|
||||
app.ui.docking_hidden_groups.retain(|group| !group.iter().any(|id| id == panel));
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Show `panel` as `window.panel` does, but leave it be when it already shows ([`showing`]): a
|
||||
@@ -969,6 +961,10 @@ impl Transaction {
|
||||
}
|
||||
}
|
||||
let reveal = reveal.filter(|panel| layout.contains(panel));
|
||||
for group in &mut app.ui.docking_hidden_groups {
|
||||
group.retain(|panel| hidden.contains_key(panel) && !layout.contains(panel));
|
||||
}
|
||||
app.ui.docking_hidden_groups.retain(|group| !group.is_empty());
|
||||
let flyout = reveal.as_ref().filter(|panel| in_iconic_column(&layout, panel)).cloned();
|
||||
match (app.ui.docking.is_none() && hidden.is_empty()).then(|| default_flags(&app.ui, &layout)).flatten() {
|
||||
Some((tab, collapsed, width)) => {
|
||||
|
||||
@@ -532,6 +532,7 @@ fn f7_activates_a_background_tab_before_hiding_it() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("window.panel.move", json!({"panel": "layers", "anchor": "properties"})).unwrap();
|
||||
h.app.run("window.panel.activate", json!({"panel": "properties"})).unwrap();
|
||||
let before = group_of(&h.layout(), &"layers".into());
|
||||
assert!(!showing(&h.app.ui, "layers"));
|
||||
h.press(egui::Key::F7, egui::Modifiers::NONE);
|
||||
assert!(showing(&h.app.ui, "layers"));
|
||||
@@ -539,7 +540,7 @@ fn f7_activates_a_background_tab_before_hiding_it() {
|
||||
assert!(["properties", "layers", "libraries"].iter().all(|panel| !h.layout().contains(&panel.to_string())));
|
||||
h.press(egui::Key::F7, egui::Modifiers::NONE);
|
||||
assert!(showing(&h.app.ui, "layers"));
|
||||
assert_eq!(group_of(&h.layout(), &"layers".into()), vec!["properties".to_string(), "layers".into(), "libraries".into()]);
|
||||
assert_eq!(group_of(&h.layout(), &"layers".into()), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -557,6 +558,41 @@ fn f7_hides_and_restores_the_whole_expanded_group() {
|
||||
assert!(showing(&h.app.ui, "properties"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f7_restores_only_tabs_hidden_with_the_group() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("window.panel.close", json!({"panel": "libraries"})).unwrap();
|
||||
assert!(h.app.ui.docking_hidden.contains_key("libraries"));
|
||||
h.press(egui::Key::F7, egui::Modifiers::NONE);
|
||||
h.press(egui::Key::F7, egui::Modifiers::NONE);
|
||||
assert!(["properties", "layers"].iter().all(|panel| h.app.ui.docking_hidden.contains_key(*panel)));
|
||||
h.press(egui::Key::F7, egui::Modifiers::NONE);
|
||||
assert_eq!(group_of(&h.layout(), &"layers".into()), ["properties", "layers"]);
|
||||
assert!(!h.layout().contains(&"libraries".to_string()));
|
||||
assert!(showing(&h.app.ui, "layers"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independently_reopened_tab_is_not_restored_by_the_group_key() {
|
||||
let mut h = Harness::new();
|
||||
h.press(egui::Key::F7, egui::Modifiers::NONE);
|
||||
assert!(showing(&h.app.ui, "layers"));
|
||||
h.press(egui::Key::F7, egui::Modifiers::NONE);
|
||||
assert!(["properties", "layers", "libraries"].iter().all(|panel| h.app.ui.docking_hidden.contains_key(*panel)));
|
||||
assert!(["properties", "layers", "libraries"].iter().all(|panel| !h.layout().contains(&panel.to_string())));
|
||||
h.app.run("window.panel.activate", json!({"panel": "libraries"})).unwrap();
|
||||
assert!(h.layout().contains(&"libraries".to_string()));
|
||||
assert!(!h.app.ui.docking_hidden.contains_key("libraries"));
|
||||
assert!(["properties", "layers"].iter().all(|panel| h.app.ui.docking_hidden.contains_key(*panel)));
|
||||
h.app.run("window.panel.close", json!({"panel": "libraries"})).unwrap();
|
||||
assert!(h.app.ui.docking_hidden.contains_key("libraries"));
|
||||
assert!(!h.layout().contains(&"libraries".to_string()));
|
||||
h.press(egui::Key::F7, egui::Modifiers::NONE);
|
||||
assert_eq!(group_of(&h.layout(), &"layers".into()), ["properties", "layers"]);
|
||||
assert!(!h.layout().contains(&"libraries".to_string()));
|
||||
assert!(showing(&h.app.ui, "layers"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f7_hides_and_restores_a_floating_tab_group() {
|
||||
let mut h = Harness::new();
|
||||
|
||||
@@ -381,6 +381,13 @@ fn rename(ui: &mut UiState, from: &str, to: &str) -> Result<(), String> {
|
||||
}
|
||||
ui.docking = docking;
|
||||
ui.docking_hidden = hidden;
|
||||
for group in &mut ui.docking_hidden_groups {
|
||||
for panel in group {
|
||||
if panel == from {
|
||||
*panel = to.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ui.open_panel.as_deref() == Some(from) {
|
||||
ui.open_panel = Some(to.into());
|
||||
}
|
||||
|
||||
@@ -382,6 +382,9 @@ pub struct UiState {
|
||||
pub docking: Option<craft_ui::docking::Layout<String>>,
|
||||
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
|
||||
pub docking_hidden: std::collections::BTreeMap<String, craft_ui::docking::Location<String>>,
|
||||
/// Tabs closed together by panel shortcuts, kept with their workspace return locations.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub docking_hidden_groups: Vec<Vec<String>>,
|
||||
/// Panels dropped on the icon column, in older files (read only, like [`Self::floating_panels`]).
|
||||
#[serde(default, skip_serializing)]
|
||||
pub docking_icons: std::collections::BTreeSet<String>,
|
||||
@@ -838,6 +841,7 @@ impl Default for UiState {
|
||||
floating_panels: vec![],
|
||||
docking: None,
|
||||
docking_hidden: Default::default(),
|
||||
docking_hidden_groups: Vec::new(),
|
||||
docking_icons: Default::default(),
|
||||
docking_collapsed: Default::default(),
|
||||
docking_generation: 0,
|
||||
|
||||
@@ -43,6 +43,9 @@ pub struct Workspace {
|
||||
pub docking: Option<craft_ui::docking::Layout<String>>,
|
||||
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
|
||||
pub docking_hidden: std::collections::BTreeMap<String, craft_ui::docking::Location<String>>,
|
||||
/// Tabs closed together by a panel shortcut.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub docking_hidden_groups: Vec<Vec<String>>,
|
||||
/// Panels dropped on the icon column, in older workspaces (read only).
|
||||
#[serde(default, skip_serializing)]
|
||||
pub docking_icons: std::collections::BTreeSet<String>,
|
||||
@@ -184,6 +187,7 @@ pub fn capture(ui: &UiState, name: &str) -> Workspace {
|
||||
floating_panels: Vec::new(),
|
||||
docking: ui.docking.clone(),
|
||||
docking_hidden: ui.docking_hidden.clone(),
|
||||
docking_hidden_groups: ui.docking_hidden_groups.clone(),
|
||||
docking_icons: Default::default(),
|
||||
docking_collapsed: Default::default(),
|
||||
toolbar_pos: ui.toolbar_pos,
|
||||
@@ -209,6 +213,7 @@ pub fn apply(ui: &mut UiState, w: &Workspace) {
|
||||
ui.docking_generation = ui.docking_generation.wrapping_add(1);
|
||||
ui.docking = w.docking.clone().filter(crate::panel_docking::valid);
|
||||
ui.docking_hidden = w.docking_hidden.clone();
|
||||
ui.docking_hidden_groups = w.docking_hidden_groups.clone();
|
||||
ui.toolbar_pos = w.toolbar_pos;
|
||||
ui.dock_width = w.dock_width;
|
||||
crate::state::sanitize_toolbar_pos(&mut ui.toolbar_pos);
|
||||
|
||||
Reference in New Issue
Block a user