Skip to content

Commit 919fe1e

Browse files
committed
Generalize recursive panel group splits
1 parent 0eb440d commit 919fe1e

File tree

10 files changed

+653
-445
lines changed

10 files changed

+653
-445
lines changed

desktop/src/cef/consts.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use graphite_desktop_wrapper::DOUBLE_CLICK_MILLISECONDS;
12
use std::time::Duration;
23

34
pub(crate) const RESOURCE_SCHEME: &str = "resources";
@@ -18,5 +19,5 @@ pub(crate) const SCROLL_SPEED_Y: f32 = 1.0;
1819

1920
pub(crate) const PINCH_ZOOM_SPEED: f64 = 300.0;
2021

21-
pub(crate) const MULTICLICK_TIMEOUT: Duration = Duration::from_millis(500);
22+
pub(crate) const MULTICLICK_TIMEOUT: Duration = Duration::from_millis(DOUBLE_CLICK_MILLISECONDS);
2223
pub(crate) const MULTICLICK_ALLOWED_TRAVEL: usize = 4;

desktop/wrapper/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use graphite_editor::messages::prelude::{FrontendMessage, Message};
44
use message_dispatcher::DesktopWrapperMessageDispatcher;
55
use messages::{DesktopFrontendMessage, DesktopWrapperMessage};
66

7-
pub use graphite_editor::consts::FILE_EXTENSION;
7+
pub use graphite_editor::consts::{DOUBLE_CLICK_MILLISECONDS, FILE_EXTENSION};
88
pub use wgpu_executor::TargetTexture;
99
pub use wgpu_executor::WgpuContext;
1010
pub use wgpu_executor::WgpuContextBuilder;

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
22
use super::document::utility_types::network_interface;
3-
use super::utility_types::{PanelGroupId, PanelType, PersistentData, WorkspacePanelLayout};
3+
use super::utility_types::{PanelType, PersistentData, WorkspacePanelLayout};
44
use crate::application::{Editor, generate_uuid};
55
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION};
66
use 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

Comments
 (0)