11use super :: document:: utility_types:: document_metadata:: LayerNodeIdentifier ;
22use super :: document:: utility_types:: network_interface;
3- use super :: utility_types:: { PanelGroupId , PanelType , PersistentData , WorkspacePanelLayout } ;
3+ use super :: utility_types:: { PanelType , PersistentData , WorkspacePanelLayout } ;
44use crate :: application:: { Editor , generate_uuid} ;
55use crate :: consts:: { DEFAULT_DOCUMENT_NAME , DEFAULT_STROKE_WIDTH , FILE_EXTENSION } ;
66use crate :: messages:: animation:: TimingInformation ;
@@ -469,26 +469,31 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
469469 return ;
470470 }
471471
472- let source_state = self . workspace_panel_layout . panel_group ( source_group) ;
472+ let Some ( source_state) = self . workspace_panel_layout . panel_group ( source_group) else { return } ;
473473 let Some ( panel_type) = source_state. active_panel_type ( ) else { return } ;
474474
475475 // Destroy layouts for the moved panel (so backend and frontend start in sync when it remounts)
476476 // and for the panel that was previously active in the target panel group (it will be displaced by the incoming tab)
477477 Self :: destroy_panel_layouts ( panel_type, responses) ;
478- if let Some ( old_target_panel) = self . workspace_panel_layout . panel_group ( target_group) . active_panel_type ( ) {
478+ if let Some ( old_target_panel) = self . workspace_panel_layout . panel_group ( target_group) . and_then ( |g| g . active_panel_type ( ) ) {
479479 Self :: destroy_panel_layouts ( old_target_panel, responses) ;
480480 }
481481
482482 // Remove from source panel group
483- let source = self . workspace_panel_layout . panel_group_mut ( source_group) ;
484- source. tabs . retain ( |& t| t != panel_type) ;
485- source. active_tab_index = source. active_tab_index . min ( source. tabs . len ( ) . saturating_sub ( 1 ) ) ;
483+ if let Some ( source) = self . workspace_panel_layout . panel_group_mut ( source_group) {
484+ source. tabs . retain ( |& t| t != panel_type) ;
485+ source. active_tab_index = source. active_tab_index . min ( source. tabs . len ( ) . saturating_sub ( 1 ) ) ;
486+ }
486487
487488 // Insert into target panel group
488- let target = self . workspace_panel_layout . panel_group_mut ( target_group) ;
489- let index = insert_index. min ( target. tabs . len ( ) ) ;
490- target. tabs . insert ( index, panel_type) ;
491- target. active_tab_index = index;
489+ if let Some ( target) = self . workspace_panel_layout . panel_group_mut ( target_group) {
490+ let index = insert_index. min ( target. tabs . len ( ) ) ;
491+ target. tabs . insert ( index, panel_type) ;
492+ target. active_tab_index = index;
493+ }
494+
495+ // Remove empty panel groups from the tree
496+ self . workspace_panel_layout . prune ( ) ;
492497
493498 responses. add ( MenuBarMessage :: SendLayout ) ;
494499 responses. add ( PortfolioMessage :: UpdateWorkspacePanelLayout ) ;
@@ -497,7 +502,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
497502 self . refresh_panel_content ( panel_type, responses) ;
498503
499504 // Refresh the source panel group's newly active tab (if any remain) so it's not left stale
500- if let Some ( new_source_active) = self . workspace_panel_layout . panel_group ( source_group) . active_panel_type ( ) {
505+ if let Some ( new_source_active) = self . workspace_panel_layout . panel_group ( source_group) . and_then ( |g| g . active_panel_type ( ) ) {
501506 Self :: destroy_panel_layouts ( new_source_active, responses) ;
502507 self . refresh_panel_content ( new_source_active, responses) ;
503508 }
@@ -1110,7 +1115,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
11101115 }
11111116 }
11121117 PortfolioMessage :: ReorderPanelGroupTab { group, old_index, new_index } => {
1113- let group_state = self . workspace_panel_layout . panel_group_mut ( group) ;
1118+ let Some ( group_state) = self . workspace_panel_layout . panel_group_mut ( group) else { return } ;
11141119
11151120 if old_index < group_state. tabs . len ( ) && new_index < group_state. tabs . len ( ) && old_index != new_index {
11161121 let tab = group_state. tabs . remove ( old_index) ;
@@ -1189,7 +1194,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
11891194 } ) ;
11901195 }
11911196 PortfolioMessage :: SetPanelGroupActiveTab { group, tab_index } => {
1192- let group_state = self . workspace_panel_layout . panel_group ( group) ;
1197+ let Some ( group_state) = self . workspace_panel_layout . panel_group ( group) else { return } ;
11931198 if tab_index < group_state. tabs . len ( ) && tab_index != group_state. active_tab_index {
11941199 // Destroy layouts for the old and new panels so the backend's diffing state is in sync with the frontend's fresh mount
11951200 if let Some ( old_panel_type) = group_state. active_panel_type ( ) {
@@ -1199,12 +1204,14 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
11991204 Self :: destroy_panel_layouts ( new_panel_type, responses) ;
12001205
12011206 // Update the active tab index for the panel
1202- self . workspace_panel_layout . panel_group_mut ( group) . active_tab_index = tab_index;
1207+ if let Some ( group_state) = self . workspace_panel_layout . panel_group_mut ( group) {
1208+ group_state. active_tab_index = tab_index;
1209+ }
12031210
12041211 // Send the layout update first so the frontend mounts the new panel component before it receives content
12051212 responses. add ( PortfolioMessage :: UpdateWorkspacePanelLayout ) ;
12061213
1207- if let Some ( panel_type) = self . workspace_panel_layout . panel_group ( group) . active_panel_type ( ) {
1214+ if let Some ( panel_type) = self . workspace_panel_layout . panel_group ( group) . and_then ( |g| g . active_panel_type ( ) ) {
12081215 self . refresh_panel_content ( panel_type, responses) ;
12091216 }
12101217 }
@@ -1433,6 +1440,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
14331440 }
14341441 }
14351442 PortfolioMessage :: UpdateWorkspacePanelLayout => {
1443+ self . workspace_panel_layout . recalculate_default_sizes ( ) ;
1444+
14361445 responses. add ( FrontendMessage :: UpdateWorkspacePanelLayout {
14371446 panel_layout : self . workspace_panel_layout . clone ( ) ,
14381447 } ) ;
@@ -1652,50 +1661,44 @@ impl PortfolioMessageHandler {
16521661 selected_nodes. first ( ) . copied ( )
16531662 }
16541663
1655- /// Remove a dockable panel type from whichever panel group currently contains it.
1664+ /// Remove a dockable panel type from whichever panel group currently contains it, then prune empty groups .
16561665 fn remove_panel_from_layout ( & mut self , panel_type : PanelType ) {
1657- for group_id in [ PanelGroupId :: PropertiesGroup , PanelGroupId :: LayersGroup , PanelGroupId :: DataGroup ] {
1658- let group = self . workspace_panel_layout . panel_group_mut ( group_id) ;
1659- if let Some ( index) = group. tabs . iter ( ) . position ( |& t| t == panel_type) {
1660- group. tabs . remove ( index) ;
1661- group. active_tab_index = group. active_tab_index . min ( group. tabs . len ( ) . saturating_sub ( 1 ) ) ;
1662- break ;
1663- }
1666+ // Save the panel's current position so it can be restored there later
1667+ self . workspace_panel_layout . save_panel_position ( panel_type) ;
1668+
1669+ if let Some ( group_id) = self . workspace_panel_layout . find_panel ( panel_type)
1670+ && let Some ( group) = self . workspace_panel_layout . panel_group_mut ( group_id)
1671+ {
1672+ group. tabs . retain ( |& t| t != panel_type) ;
1673+ group. active_tab_index = group. active_tab_index . min ( group. tabs . len ( ) . saturating_sub ( 1 ) ) ;
16641674 }
1675+
1676+ self . workspace_panel_layout . prune ( ) ;
16651677 }
16661678
16671679 /// Toggle a dockable panel on or off. When toggling off, refresh the newly active tab in its panel group (if any).
16681680 fn toggle_dockable_panel ( & mut self , panel_type : PanelType , responses : & mut VecDeque < Message > ) {
16691681 if let Some ( group_id) = self . workspace_panel_layout . find_panel ( panel_type) {
16701682 // Panel is present, remove it
1671- let was_visible = self . workspace_panel_layout . panel_group ( group_id) . is_visible ( panel_type) ;
1683+ let was_visible = self . workspace_panel_layout . panel_group ( group_id) . is_some_and ( |g| g . is_visible ( panel_type) ) ;
16721684 Self :: destroy_panel_layouts ( panel_type, responses) ;
16731685 self . remove_panel_from_layout ( panel_type) ;
16741686
16751687 // If the removed panel was the active tab, refresh whichever panel is now active in that panel group
1676- if was_visible && let Some ( new_active) = self . workspace_panel_layout . panel_group ( group_id) . active_panel_type ( ) {
1688+ if was_visible && let Some ( new_active) = self . workspace_panel_layout . panel_group ( group_id) . and_then ( |g| g . active_panel_type ( ) ) {
16771689 Self :: destroy_panel_layouts ( new_active, responses) ;
16781690 self . refresh_panel_content ( new_active, responses) ;
16791691 }
16801692 } else {
1681- // Panel is not present, add it to its default panel group
1682- self . add_panel_to_its_default_group ( panel_type) ;
1693+ // Panel is not present, restore it to its default position in the layout tree
1694+ self . workspace_panel_layout . restore_panel ( panel_type) ;
16831695 self . refresh_panel_content ( panel_type, responses) ;
16841696 }
16851697
16861698 responses. add ( MenuBarMessage :: SendLayout ) ;
16871699 responses. add ( PortfolioMessage :: UpdateWorkspacePanelLayout ) ;
16881700 }
16891701
1690- /// Add a dockable panel type to its default panel group.
1691- fn add_panel_to_its_default_group ( & mut self , panel_type : PanelType ) {
1692- let group = self . workspace_panel_layout . panel_group_mut ( panel_type. default_panel_group ( ) ) ;
1693- if !group. tabs . contains ( & panel_type) {
1694- group. tabs . push ( panel_type) ;
1695- group. active_tab_index = group. tabs . len ( ) - 1 ;
1696- }
1697- }
1698-
16991702 /// Destroy the stored layout for a panel that is no longer the active tab.
17001703 /// This resets the backend's diffing state so it won't try to send updates to a frontend component that has been unmounted.
17011704 fn destroy_panel_layouts ( panel_type : PanelType , responses : & mut VecDeque < Message > ) {
0 commit comments