mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-18 18:38:05 +08:00
Add support for persistent storage of panel layouts, sizes, and active tabs (#4017)
* Add persistence to panel layouts * Fix and persist the Window > Focus Document mode * Add a Window > Reset Workspace action * workspace_layout.json -> workspace_layout.ron * Fix native app hole punch * Cleanup review pass
This commit is contained in:
@@ -234,7 +234,7 @@ impl Dispatcher {
|
||||
Message::MenuBar(message) => {
|
||||
let menu_bar_message_handler = &mut self.message_handlers.menu_bar_message_handler;
|
||||
|
||||
menu_bar_message_handler.focus_document = self.message_handlers.portfolio_message_handler.focus_document;
|
||||
menu_bar_message_handler.focus_document = self.message_handlers.portfolio_message_handler.workspace_panel_layout.focus_document;
|
||||
let layout = &self.message_handlers.portfolio_message_handler.workspace_panel_layout;
|
||||
menu_bar_message_handler.data_panel_open = layout.is_panel_present(PanelType::Data);
|
||||
menu_bar_message_handler.layers_panel_open = layout.is_panel_present(PanelType::Layers);
|
||||
|
||||
@@ -127,12 +127,17 @@ pub enum FrontendMessage {
|
||||
TriggerLoadRestAutoSaveDocuments,
|
||||
TriggerOpenLaunchDocuments,
|
||||
TriggerLoadPreferences,
|
||||
TriggerLoadWorkspaceLayout,
|
||||
TriggerOpen,
|
||||
TriggerImport,
|
||||
TriggerSavePreferences {
|
||||
#[tsify(type = "unknown")]
|
||||
preferences: PreferencesMessageHandler,
|
||||
},
|
||||
TriggerSaveWorkspaceLayout {
|
||||
#[serde(rename = "workspaceLayout")]
|
||||
workspace_layout: WorkspacePanelLayout,
|
||||
},
|
||||
TriggerSaveActiveDocument {
|
||||
#[serde(rename = "documentId")]
|
||||
document_id: DocumentId,
|
||||
|
||||
@@ -647,6 +647,12 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleFocusDocument))
|
||||
.on_commit(|_| PortfolioMessage::ToggleFocusDocument.into()),
|
||||
],
|
||||
vec![
|
||||
MenuListEntry::new("Reset Workspace")
|
||||
.label("Reset Workspace")
|
||||
.icon("Reset")
|
||||
.on_commit(|_| PortfolioMessage::ResetWorkspaceLayout.into()),
|
||||
],
|
||||
vec![
|
||||
MenuListEntry::new("Properties")
|
||||
.label("Properties")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use super::utility_types::{DockingSplitDirection, PanelGroupId, PanelType};
|
||||
use super::utility_types::{DockingSplitDirection, PanelGroupId, PanelType, WorkspacePanelLayout};
|
||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::portfolio::utility_types::FontCatalog;
|
||||
@@ -61,6 +61,9 @@ pub enum PortfolioMessage {
|
||||
LoadDocumentResources {
|
||||
document_id: DocumentId,
|
||||
},
|
||||
LoadWorkspaceLayout {
|
||||
layout: WorkspacePanelLayout,
|
||||
},
|
||||
MoveAllPanelTabs {
|
||||
source_group: PanelGroupId,
|
||||
target_group: PanelGroupId,
|
||||
@@ -184,4 +187,16 @@ pub enum PortfolioMessage {
|
||||
UpdateDocumentWidgets,
|
||||
UpdateOpenDocumentsList,
|
||||
UpdateWorkspacePanelLayout,
|
||||
SaveWorkspaceLayout,
|
||||
ResetWorkspaceLayout,
|
||||
ResetPanelGroupSizes {
|
||||
/// Path of child indices from the root to the split node whose children's sizes should be reset to defaults.
|
||||
split_path: Vec<usize>,
|
||||
},
|
||||
SetPanelGroupSizes {
|
||||
/// Path of child indices from the root to the split node whose children's sizes are being set.
|
||||
split_path: Vec<usize>,
|
||||
/// New sizes for the children at that split node.
|
||||
sizes: Vec<f64>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use super::document::utility_types::network_interface;
|
||||
use super::utility_types::{PanelType, PersistentData, WorkspacePanelLayout};
|
||||
use super::utility_types::{PanelLayoutSubdivision, PanelType, PersistentData, WorkspacePanelLayout};
|
||||
use crate::application::{Editor, generate_uuid};
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION};
|
||||
use crate::messages::animation::TimingInformation;
|
||||
@@ -57,7 +57,6 @@ pub struct PortfolioMessageHandler {
|
||||
pub executor: NodeGraphExecutor,
|
||||
pub selection_mode: SelectionMode,
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
pub focus_document: bool,
|
||||
pub workspace_panel_layout: WorkspacePanelLayout,
|
||||
}
|
||||
|
||||
@@ -88,9 +87,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
current_tool,
|
||||
preferences,
|
||||
viewport,
|
||||
data_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Data) && !self.focus_document,
|
||||
layers_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Layers) && !self.focus_document,
|
||||
properties_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Properties) && !self.focus_document,
|
||||
data_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Data) && !self.workspace_panel_layout.focus_document,
|
||||
layers_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Layers) && !self.workspace_panel_layout.focus_document,
|
||||
properties_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Properties) && !self.workspace_panel_layout.focus_document,
|
||||
};
|
||||
document.process_message(message, responses, document_inputs)
|
||||
}
|
||||
@@ -105,6 +104,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
// Tell frontend to load persistent preferences
|
||||
responses.add(FrontendMessage::TriggerLoadPreferences);
|
||||
responses.add(FrontendMessage::TriggerLoadWorkspaceLayout);
|
||||
|
||||
// Before loading any documents, initially prepare the welcome screen buttons layout
|
||||
responses.add(PortfolioMessage::RequestWelcomeScreenButtonsLayout);
|
||||
@@ -155,9 +155,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
current_tool,
|
||||
preferences,
|
||||
viewport,
|
||||
data_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Data) && !self.focus_document,
|
||||
layers_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Layers) && !self.focus_document,
|
||||
properties_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Properties) && !self.focus_document,
|
||||
data_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Data) && !self.workspace_panel_layout.focus_document,
|
||||
layers_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Layers) && !self.workspace_panel_layout.focus_document,
|
||||
properties_panel_open: self.workspace_panel_layout.is_panel_visible(PanelType::Properties) && !self.workspace_panel_layout.focus_document,
|
||||
};
|
||||
document.process_message(message, responses, document_inputs)
|
||||
}
|
||||
@@ -445,6 +445,17 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
document.load_layer_resources(responses);
|
||||
}
|
||||
}
|
||||
PortfolioMessage::LoadWorkspaceLayout { layout } => {
|
||||
self.workspace_panel_layout = layout;
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
|
||||
// Refresh all visible panels since the layout may have changed
|
||||
for group_id in self.workspace_panel_layout.root.all_group_ids() {
|
||||
if let Some(panel_type) = self.workspace_panel_layout.panel_group(group_id).and_then(|g| g.active_panel_type()) {
|
||||
self.refresh_panel_content(panel_type, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
PortfolioMessage::NewDocumentWithName { name } => {
|
||||
let mut new_document = DocumentMessageHandler::default();
|
||||
new_document.name = name;
|
||||
@@ -507,6 +518,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
|
||||
// Refresh the new active tab
|
||||
if let Some(panel_type) = self.workspace_panel_layout.panel_group(target_group).and_then(|g| g.active_panel_type()) {
|
||||
@@ -556,6 +568,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
|
||||
// Refresh the moved panel's content in its new location
|
||||
self.refresh_panel_content(panel_type, responses);
|
||||
@@ -1190,6 +1203,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
}
|
||||
}
|
||||
PortfolioMessage::RequestWelcomeScreenButtonsLayout => {
|
||||
@@ -1269,6 +1283,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
// Send the layout update first so the frontend mounts the new panel component before it receives content
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
|
||||
if let Some(panel_type) = self.workspace_panel_layout.panel_group(group).and_then(|g| g.active_panel_type()) {
|
||||
self.refresh_panel_content(panel_type, responses);
|
||||
@@ -1303,6 +1318,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
|
||||
// Refresh the new panel group's active tab
|
||||
if let Some(panel_type) = self.workspace_panel_layout.panel_group(new_id).and_then(|g| g.active_panel_type()) {
|
||||
@@ -1465,43 +1481,25 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
}
|
||||
PortfolioMessage::ToggleFocusDocument => {
|
||||
self.focus_document = !self.focus_document;
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
self.workspace_panel_layout.focus_document = !self.workspace_panel_layout.focus_document;
|
||||
|
||||
let properties_present = self.workspace_panel_layout.is_panel_present(PanelType::Properties);
|
||||
let layers_present = self.workspace_panel_layout.is_panel_present(PanelType::Layers);
|
||||
let data_present = self.workspace_panel_layout.is_panel_present(PanelType::Data);
|
||||
|
||||
if self.focus_document {
|
||||
if properties_present {
|
||||
Self::destroy_panel_layouts(PanelType::Properties, responses);
|
||||
}
|
||||
if layers_present {
|
||||
Self::destroy_panel_layouts(PanelType::Layers, responses);
|
||||
}
|
||||
if data_present {
|
||||
Self::destroy_panel_layouts(PanelType::Data, responses);
|
||||
}
|
||||
} else {
|
||||
// Run the graph to grab the data
|
||||
if properties_present || layers_present || data_present {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
if properties_present {
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
}
|
||||
if layers_present && self.active_document_id.is_some() {
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()],
|
||||
});
|
||||
// Destroy or refresh non-document panel layouts based on focus mode
|
||||
for &panel_type in PanelType::non_document_panels() {
|
||||
if self.workspace_panel_layout.is_panel_present(panel_type) {
|
||||
if self.workspace_panel_layout.focus_document {
|
||||
Self::destroy_panel_layouts(panel_type, responses);
|
||||
} else {
|
||||
self.refresh_panel_content(panel_type, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
}
|
||||
PortfolioMessage::TogglePropertiesPanelOpen => {
|
||||
if self.focus_document {
|
||||
if self.workspace_panel_layout.focus_document {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1509,7 +1507,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
self.toggle_dockable_panel(panel_type, responses);
|
||||
}
|
||||
PortfolioMessage::ToggleLayersPanelOpen => {
|
||||
if self.focus_document {
|
||||
if self.workspace_panel_layout.focus_document {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1517,7 +1515,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
self.toggle_dockable_panel(panel_type, responses);
|
||||
}
|
||||
PortfolioMessage::ToggleDataPanelOpen => {
|
||||
if self.focus_document {
|
||||
if self.workspace_panel_layout.focus_document {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1538,11 +1536,72 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
}
|
||||
PortfolioMessage::UpdateWorkspacePanelLayout => {
|
||||
let panel_layout = match self.workspace_panel_layout.focus_document {
|
||||
true => self.workspace_panel_layout.document_only_layout(),
|
||||
false => self.workspace_panel_layout.clone(),
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateWorkspacePanelLayout { panel_layout });
|
||||
}
|
||||
PortfolioMessage::SaveWorkspaceLayout => {
|
||||
responses.add(FrontendMessage::TriggerSaveWorkspaceLayout {
|
||||
workspace_layout: self.workspace_panel_layout.clone(),
|
||||
});
|
||||
}
|
||||
PortfolioMessage::ResetWorkspaceLayout => {
|
||||
// Destroy layouts for all currently visible non-document panels
|
||||
for &panel_type in PanelType::non_document_panels() {
|
||||
if self.workspace_panel_layout.is_panel_present(panel_type) {
|
||||
Self::destroy_panel_layouts(panel_type, responses);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace layout with the default and recalculate sizes
|
||||
self.workspace_panel_layout = WorkspacePanelLayout::default();
|
||||
self.workspace_panel_layout.recalculate_default_sizes();
|
||||
|
||||
responses.add(FrontendMessage::UpdateWorkspacePanelLayout {
|
||||
panel_layout: self.workspace_panel_layout.clone(),
|
||||
});
|
||||
// Refresh all visible panels since the layout has been completely replaced
|
||||
for group_id in self.workspace_panel_layout.root.all_group_ids() {
|
||||
if let Some(panel_type) = self.workspace_panel_layout.panel_group(group_id).and_then(|g| g.active_panel_type()) {
|
||||
self.refresh_panel_content(panel_type, responses);
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
}
|
||||
PortfolioMessage::ResetPanelGroupSizes { split_path } => {
|
||||
// Walk the tree to the target split node using the path
|
||||
let mut node = &mut self.workspace_panel_layout.root;
|
||||
for &index in &split_path {
|
||||
let PanelLayoutSubdivision::Split { children } = node else { return };
|
||||
let Some(child) = children.get_mut(index) else { return };
|
||||
node = &mut child.subdivision;
|
||||
}
|
||||
|
||||
// Recalculate default sizes for this split node
|
||||
node.recalculate_default_sizes();
|
||||
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
}
|
||||
PortfolioMessage::SetPanelGroupSizes { split_path, sizes } => {
|
||||
// Walk the tree to the target split node using the path
|
||||
let mut node = &mut self.workspace_panel_layout.root;
|
||||
for &index in &split_path {
|
||||
let PanelLayoutSubdivision::Split { children } = node else { return };
|
||||
let Some(child) = children.get_mut(index) else { return };
|
||||
node = &mut child.subdivision;
|
||||
}
|
||||
|
||||
// Apply the new sizes to the split's children
|
||||
if let PanelLayoutSubdivision::Split { children } = node {
|
||||
for (child, &size) in children.iter_mut().zip(sizes.iter()) {
|
||||
child.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
}
|
||||
PortfolioMessage::UpdateOpenDocumentsList => {
|
||||
// Send the list of document tab names
|
||||
@@ -1602,7 +1661,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
|
||||
// Extend with actions that are disabled when focusing the document
|
||||
if !self.focus_document {
|
||||
if !self.workspace_panel_layout.focus_document {
|
||||
common.extend(actions!(PortfolioMessageDiscriminant;
|
||||
TogglePropertiesPanelOpen,
|
||||
ToggleLayersPanelOpen,
|
||||
@@ -1696,8 +1755,14 @@ impl PortfolioMessageHandler {
|
||||
} else {
|
||||
self.document_ids.push_back(document_id);
|
||||
}
|
||||
new_document.update_layers_panel_control_bar_widgets(self.workspace_panel_layout.is_panel_visible(PanelType::Layers) && !self.focus_document, responses);
|
||||
new_document.update_layers_panel_bottom_bar_widgets(self.workspace_panel_layout.is_panel_visible(PanelType::Layers) && !self.focus_document, responses);
|
||||
new_document.update_layers_panel_control_bar_widgets(
|
||||
self.workspace_panel_layout.is_panel_visible(PanelType::Layers) && !self.workspace_panel_layout.focus_document,
|
||||
responses,
|
||||
);
|
||||
new_document.update_layers_panel_bottom_bar_widgets(
|
||||
self.workspace_panel_layout.is_panel_visible(PanelType::Layers) && !self.workspace_panel_layout.focus_document,
|
||||
responses,
|
||||
);
|
||||
|
||||
self.documents.insert(document_id, new_document);
|
||||
|
||||
@@ -1744,7 +1809,7 @@ impl PortfolioMessageHandler {
|
||||
/// Get the ID of the selected node that should be used as the current source for the Data panel.
|
||||
pub fn node_to_inspect(&self) -> Option<NodeId> {
|
||||
// Skip if the Data panel is not open
|
||||
if !self.workspace_panel_layout.is_panel_visible(PanelType::Data) || self.focus_document {
|
||||
if !self.workspace_panel_layout.is_panel_visible(PanelType::Data) || self.workspace_panel_layout.focus_document {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1794,6 +1859,7 @@ impl PortfolioMessageHandler {
|
||||
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(PortfolioMessage::SaveWorkspaceLayout);
|
||||
}
|
||||
|
||||
/// Destroy the stored layout for a panel that is no longer the active tab.
|
||||
|
||||
@@ -106,6 +106,12 @@ impl From<String> for PanelType {
|
||||
}
|
||||
}
|
||||
|
||||
impl PanelType {
|
||||
pub fn non_document_panels() -> &'static [PanelType] {
|
||||
&[PanelType::Layers, PanelType::Properties, PanelType::Data]
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique identifier for a panel group (a leaf subdivision in the layout tree that holds tabs).
|
||||
#[repr(transparent)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||
@@ -127,7 +133,6 @@ pub enum DockingSplitDirection {
|
||||
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PanelGroupState {
|
||||
pub tabs: Vec<PanelType>,
|
||||
#[serde(rename = "activeTabIndex")]
|
||||
pub active_tab_index: usize,
|
||||
}
|
||||
|
||||
@@ -156,6 +161,12 @@ pub enum PanelLayoutSubdivision {
|
||||
Split { children: Vec<SplitChild> },
|
||||
}
|
||||
|
||||
impl Default for PanelLayoutSubdivision {
|
||||
fn default() -> Self {
|
||||
PanelLayoutSubdivision::Split { children: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
/// A child within a split container, with a proportional size weight.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
@@ -170,13 +181,17 @@ pub struct SplitChild {
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct WorkspacePanelLayout {
|
||||
#[serde(default)]
|
||||
pub root: PanelLayoutSubdivision,
|
||||
/// Counter for generating unique panel group IDs.
|
||||
#[serde(rename = "nextGroupId")]
|
||||
#[serde(default)]
|
||||
next_group_id: PanelGroupId,
|
||||
/// Remembers where a panel was before being removed (panel type, group ID, and tab index), so it can be restored there.
|
||||
#[serde(default, rename = "savedPositions")]
|
||||
#[serde(default)]
|
||||
saved_positions: Vec<(PanelType, PanelGroupId, usize)>,
|
||||
/// Whether Focus Document mode is active, hiding all non-document panels.
|
||||
#[serde(default)]
|
||||
pub focus_document: bool,
|
||||
}
|
||||
|
||||
impl WorkspacePanelLayout {
|
||||
@@ -217,6 +232,14 @@ impl WorkspacePanelLayout {
|
||||
self.root.prune();
|
||||
}
|
||||
|
||||
/// Produce a filtered copy of this layout containing only the document panel, for use in Focus Document mode.
|
||||
pub fn document_only_layout(&self) -> WorkspacePanelLayout {
|
||||
let mut layout = self.clone();
|
||||
layout.root.retain_only_document_panels();
|
||||
layout.root.prune();
|
||||
layout
|
||||
}
|
||||
|
||||
/// Split a panel group by inserting a new panel group adjacent to it.
|
||||
/// The direction determines where the new group goes relative to the target.
|
||||
/// Left/Right creates a horizontal (row) split, Top/Bottom creates a vertical (column) split.
|
||||
@@ -399,6 +422,7 @@ impl Default for WorkspacePanelLayout {
|
||||
},
|
||||
next_group_id: PanelGroupId(3),
|
||||
saved_positions: Vec::new(),
|
||||
focus_document: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,6 +479,19 @@ impl PanelLayoutSubdivision {
|
||||
children.retain(|child| !matches!(&child.subdivision, PanelLayoutSubdivision::Split { children } if children.is_empty()));
|
||||
}
|
||||
|
||||
/// Remove all non-document/non-welcome tabs from panel groups, leaving only document-related panels.
|
||||
pub fn retain_only_document_panels(&mut self) {
|
||||
match self {
|
||||
PanelLayoutSubdivision::PanelGroup { state, .. } => {
|
||||
state.tabs.retain(|t| matches!(t, PanelType::Document | PanelType::Welcome));
|
||||
state.active_tab_index = state.active_tab_index.min(state.tabs.len().saturating_sub(1));
|
||||
}
|
||||
PanelLayoutSubdivision::Split { children } => {
|
||||
children.iter_mut().for_each(|child| child.subdivision.retain_only_document_panels());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this subtree contains a panel group with the given ID.
|
||||
pub fn contains_group(&self, target_id: PanelGroupId) -> bool {
|
||||
match self {
|
||||
|
||||
Reference in New Issue
Block a user