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,37 @@ 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
475+ // Validate that the target group exists before modifying the source
476+ if self . workspace_panel_layout . panel_group ( target_group) . is_none ( ) {
477+ log:: error!( "Target panel group {target_group:?} not found" ) ;
478+ return ;
479+ }
480+
475481 // Destroy layouts for the moved panel (so backend and frontend start in sync when it remounts)
476482 // and for the panel that was previously active in the target panel group (it will be displaced by the incoming tab)
477483 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 ( ) {
484+ if let Some ( old_target_panel) = self . workspace_panel_layout . panel_group ( target_group) . and_then ( |g| g . active_panel_type ( ) ) {
479485 Self :: destroy_panel_layouts ( old_target_panel, responses) ;
480486 }
481487
482488 // 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 ) ) ;
489+ if let Some ( source) = self . workspace_panel_layout . panel_group_mut ( source_group) {
490+ source. tabs . retain ( |& t| t != panel_type) ;
491+ source. active_tab_index = source. active_tab_index . min ( source. tabs . len ( ) . saturating_sub ( 1 ) ) ;
492+ }
486493
487494 // 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;
495+ if let Some ( target) = self . workspace_panel_layout . panel_group_mut ( target_group) {
496+ let index = insert_index. min ( target. tabs . len ( ) ) ;
497+ target. tabs . insert ( index, panel_type) ;
498+ target. active_tab_index = index;
499+ }
500+
501+ // Remove empty panel groups from the tree
502+ self . workspace_panel_layout . prune ( ) ;
492503
493504 responses. add ( MenuBarMessage :: SendLayout ) ;
494505 responses. add ( PortfolioMessage :: UpdateWorkspacePanelLayout ) ;
@@ -497,7 +508,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
497508 self . refresh_panel_content ( panel_type, responses) ;
498509
499510 // 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 ( ) {
511+ if let Some ( new_source_active) = self . workspace_panel_layout . panel_group ( source_group) . and_then ( |g| g . active_panel_type ( ) ) {
501512 Self :: destroy_panel_layouts ( new_source_active, responses) ;
502513 self . refresh_panel_content ( new_source_active, responses) ;
503514 }
@@ -1110,7 +1121,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
11101121 }
11111122 }
11121123 PortfolioMessage :: ReorderPanelGroupTab { group, old_index, new_index } => {
1113- let group_state = self . workspace_panel_layout . panel_group_mut ( group) ;
1124+ let Some ( group_state) = self . workspace_panel_layout . panel_group_mut ( group) else { return } ;
11141125
11151126 if old_index < group_state. tabs . len ( ) && new_index < group_state. tabs . len ( ) && old_index != new_index {
11161127 let tab = group_state. tabs . remove ( old_index) ;
@@ -1189,7 +1200,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
11891200 } ) ;
11901201 }
11911202 PortfolioMessage :: SetPanelGroupActiveTab { group, tab_index } => {
1192- let group_state = self . workspace_panel_layout . panel_group ( group) ;
1203+ let Some ( group_state) = self . workspace_panel_layout . panel_group ( group) else { return } ;
11931204 if tab_index < group_state. tabs . len ( ) && tab_index != group_state. active_tab_index {
11941205 // Destroy layouts for the old and new panels so the backend's diffing state is in sync with the frontend's fresh mount
11951206 if let Some ( old_panel_type) = group_state. active_panel_type ( ) {
@@ -1199,12 +1210,14 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
11991210 Self :: destroy_panel_layouts ( new_panel_type, responses) ;
12001211
12011212 // Update the active tab index for the panel
1202- self . workspace_panel_layout . panel_group_mut ( group) . active_tab_index = tab_index;
1213+ if let Some ( group_state) = self . workspace_panel_layout . panel_group_mut ( group) {
1214+ group_state. active_tab_index = tab_index;
1215+ }
12031216
12041217 // Send the layout update first so the frontend mounts the new panel component before it receives content
12051218 responses. add ( PortfolioMessage :: UpdateWorkspacePanelLayout ) ;
12061219
1207- if let Some ( panel_type) = self . workspace_panel_layout . panel_group ( group) . active_panel_type ( ) {
1220+ if let Some ( panel_type) = self . workspace_panel_layout . panel_group ( group) . and_then ( |g| g . active_panel_type ( ) ) {
12081221 self . refresh_panel_content ( panel_type, responses) ;
12091222 }
12101223 }
@@ -1433,6 +1446,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
14331446 }
14341447 }
14351448 PortfolioMessage :: UpdateWorkspacePanelLayout => {
1449+ self . workspace_panel_layout . recalculate_default_sizes ( ) ;
1450+
14361451 responses. add ( FrontendMessage :: UpdateWorkspacePanelLayout {
14371452 panel_layout : self . workspace_panel_layout . clone ( ) ,
14381453 } ) ;
@@ -1652,50 +1667,44 @@ impl PortfolioMessageHandler {
16521667 selected_nodes. first ( ) . copied ( )
16531668 }
16541669
1655- /// Remove a dockable panel type from whichever panel group currently contains it.
1670+ /// Remove a dockable panel type from whichever panel group currently contains it, then prune empty groups .
16561671 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- }
1672+ // Save the panel's current position so it can be restored there later
1673+ self . workspace_panel_layout . save_panel_position ( panel_type) ;
1674+
1675+ if let Some ( group_id) = self . workspace_panel_layout . find_panel ( panel_type)
1676+ && let Some ( group) = self . workspace_panel_layout . panel_group_mut ( group_id)
1677+ {
1678+ group. tabs . retain ( |& t| t != panel_type) ;
1679+ group. active_tab_index = group. active_tab_index . min ( group. tabs . len ( ) . saturating_sub ( 1 ) ) ;
16641680 }
1681+
1682+ self . workspace_panel_layout . prune ( ) ;
16651683 }
16661684
16671685 /// Toggle a dockable panel on or off. When toggling off, refresh the newly active tab in its panel group (if any).
16681686 fn toggle_dockable_panel ( & mut self , panel_type : PanelType , responses : & mut VecDeque < Message > ) {
16691687 if let Some ( group_id) = self . workspace_panel_layout . find_panel ( panel_type) {
16701688 // Panel is present, remove it
1671- let was_visible = self . workspace_panel_layout . panel_group ( group_id) . is_visible ( panel_type) ;
1689+ let was_visible = self . workspace_panel_layout . panel_group ( group_id) . is_some_and ( |g| g . is_visible ( panel_type) ) ;
16721690 Self :: destroy_panel_layouts ( panel_type, responses) ;
16731691 self . remove_panel_from_layout ( panel_type) ;
16741692
16751693 // 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 ( ) {
1694+ if was_visible && let Some ( new_active) = self . workspace_panel_layout . panel_group ( group_id) . and_then ( |g| g . active_panel_type ( ) ) {
16771695 Self :: destroy_panel_layouts ( new_active, responses) ;
16781696 self . refresh_panel_content ( new_active, responses) ;
16791697 }
16801698 } else {
1681- // Panel is not present, add it to its default panel group
1682- self . add_panel_to_its_default_group ( panel_type) ;
1699+ // Panel is not present, restore it to its default position in the layout tree
1700+ self . workspace_panel_layout . restore_panel ( panel_type) ;
16831701 self . refresh_panel_content ( panel_type, responses) ;
16841702 }
16851703
16861704 responses. add ( MenuBarMessage :: SendLayout ) ;
16871705 responses. add ( PortfolioMessage :: UpdateWorkspacePanelLayout ) ;
16881706 }
16891707
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-
16991708 /// Destroy the stored layout for a panel that is no longer the active tab.
17001709 /// This resets the backend's diffing state so it won't try to send updates to a frontend component that has been unmounted.
17011710 fn destroy_panel_layouts ( panel_type : PanelType , responses : & mut VecDeque < Message > ) {
0 commit comments