|
|
|
@@ -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};
|
|
|
|
|
use super::utility_types::{PanelGroupId, 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;
|
|
|
|
@@ -24,7 +24,6 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|
|
|
|
use crate::messages::tool::utility_types::{HintData, ToolType};
|
|
|
|
|
use crate::messages::viewport::ToPhysical;
|
|
|
|
|
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
|
|
|
|
use derivative::*;
|
|
|
|
|
use glam::{DAffine2, DVec2};
|
|
|
|
|
use graph_craft::document::NodeId;
|
|
|
|
|
use graphene_std::Color;
|
|
|
|
@@ -48,12 +47,10 @@ pub struct PortfolioMessageContext<'a> {
|
|
|
|
|
pub viewport: &'a ViewportMessageHandler,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Derivative, ExtractField)]
|
|
|
|
|
#[derivative(Default)]
|
|
|
|
|
#[derive(Debug, Default, ExtractField)]
|
|
|
|
|
pub struct PortfolioMessageHandler {
|
|
|
|
|
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
|
|
|
|
document_ids: VecDeque<DocumentId>,
|
|
|
|
|
active_panel: PanelType,
|
|
|
|
|
pub(crate) active_document_id: Option<DocumentId>,
|
|
|
|
|
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
|
|
|
|
pub persistent_data: PersistentData,
|
|
|
|
@@ -61,11 +58,7 @@ pub struct PortfolioMessageHandler {
|
|
|
|
|
pub selection_mode: SelectionMode,
|
|
|
|
|
pub reset_node_definitions_on_open: bool,
|
|
|
|
|
pub focus_document: bool,
|
|
|
|
|
#[derivative(Default(value = "true"))]
|
|
|
|
|
pub properties_panel_open: bool,
|
|
|
|
|
#[derivative(Default(value = "true"))]
|
|
|
|
|
pub layers_panel_open: bool,
|
|
|
|
|
pub data_panel_open: bool,
|
|
|
|
|
pub workspace_panel_layout: WorkspacePanelLayout,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[message_handler_data]
|
|
|
|
@@ -95,9 +88,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|
|
|
|
current_tool,
|
|
|
|
|
preferences,
|
|
|
|
|
viewport,
|
|
|
|
|
data_panel_open: self.data_panel_open && !self.focus_document,
|
|
|
|
|
layers_panel_open: self.layers_panel_open && !self.focus_document,
|
|
|
|
|
properties_panel_open: self.properties_panel_open && !self.focus_document,
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
document.process_message(message, responses, document_inputs)
|
|
|
|
|
}
|
|
|
|
@@ -122,6 +115,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|
|
|
|
// Display the menu bar at the top of the window
|
|
|
|
|
responses.add(MenuBarMessage::SendLayout);
|
|
|
|
|
|
|
|
|
|
// Send the initial workspace panel layout to the frontend
|
|
|
|
|
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
|
|
|
|
|
|
|
|
|
// Send the information for tooltips and categories for each node/input.
|
|
|
|
|
responses.add(FrontendMessage::SendUIMetadata {
|
|
|
|
|
node_descriptions: document_node_definitions::collect_node_descriptions(),
|
|
|
|
@@ -159,9 +155,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|
|
|
|
current_tool,
|
|
|
|
|
preferences,
|
|
|
|
|
viewport,
|
|
|
|
|
data_panel_open: self.data_panel_open && !self.focus_document,
|
|
|
|
|
layers_panel_open: self.layers_panel_open && !self.focus_document,
|
|
|
|
|
properties_panel_open: self.properties_panel_open && !self.focus_document,
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
document.process_message(message, responses, document_inputs)
|
|
|
|
|
}
|
|
|
|
@@ -464,6 +460,48 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|
|
|
|
self.load_document(new_document, document_id, responses, false);
|
|
|
|
|
responses.add(PortfolioMessage::SelectDocument { document_id });
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::MovePanelTab {
|
|
|
|
|
source_group,
|
|
|
|
|
target_group,
|
|
|
|
|
insert_index,
|
|
|
|
|
} => {
|
|
|
|
|
if source_group == target_group {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let source_state = self.workspace_panel_layout.panel_group(source_group);
|
|
|
|
|
let Some(panel_type) = source_state.active_panel_type() else { return };
|
|
|
|
|
|
|
|
|
|
// Destroy layouts for the moved panel (so backend and frontend start in sync when it remounts)
|
|
|
|
|
// and for the panel that was previously active in the target panel group (it will be displaced by the incoming tab)
|
|
|
|
|
Self::destroy_panel_layouts(panel_type, responses);
|
|
|
|
|
if let Some(old_target_panel) = self.workspace_panel_layout.panel_group(target_group).active_panel_type() {
|
|
|
|
|
Self::destroy_panel_layouts(old_target_panel, responses);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove from source panel group
|
|
|
|
|
let source = self.workspace_panel_layout.panel_group_mut(source_group);
|
|
|
|
|
source.tabs.retain(|&t| t != panel_type);
|
|
|
|
|
source.active_tab_index = source.active_tab_index.min(source.tabs.len().saturating_sub(1));
|
|
|
|
|
|
|
|
|
|
// Insert into target panel group
|
|
|
|
|
let target = self.workspace_panel_layout.panel_group_mut(target_group);
|
|
|
|
|
let index = insert_index.min(target.tabs.len());
|
|
|
|
|
target.tabs.insert(index, panel_type);
|
|
|
|
|
target.active_tab_index = index;
|
|
|
|
|
|
|
|
|
|
responses.add(MenuBarMessage::SendLayout);
|
|
|
|
|
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
|
|
|
|
|
|
|
|
|
// Refresh the moved panel's content in its new location
|
|
|
|
|
self.refresh_panel_content(panel_type, responses);
|
|
|
|
|
|
|
|
|
|
// Refresh the source panel group's newly active tab (if any remain) so it's not left stale
|
|
|
|
|
if let Some(new_source_active) = self.workspace_panel_layout.panel_group(source_group).active_panel_type() {
|
|
|
|
|
Self::destroy_panel_layouts(new_source_active, responses);
|
|
|
|
|
self.refresh_panel_content(new_source_active, responses);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::NextDocument => {
|
|
|
|
|
if let Some(active_document_id) = self.active_document_id {
|
|
|
|
|
let current_index = self.document_index(active_document_id);
|
|
|
|
@@ -1071,6 +1109,25 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::ReorderPanelGroupTab { group, old_index, new_index } => {
|
|
|
|
|
let group_state = self.workspace_panel_layout.panel_group_mut(group);
|
|
|
|
|
|
|
|
|
|
if old_index < group_state.tabs.len() && new_index < group_state.tabs.len() && old_index != new_index {
|
|
|
|
|
let tab = group_state.tabs.remove(old_index);
|
|
|
|
|
group_state.tabs.insert(new_index, tab);
|
|
|
|
|
|
|
|
|
|
// Keep the active tab following the reorder
|
|
|
|
|
if group_state.active_tab_index == old_index {
|
|
|
|
|
group_state.active_tab_index = new_index;
|
|
|
|
|
} else if old_index < group_state.active_tab_index && new_index >= group_state.active_tab_index {
|
|
|
|
|
group_state.active_tab_index = group_state.active_tab_index.saturating_sub(1);
|
|
|
|
|
} else if old_index > group_state.active_tab_index && new_index <= group_state.active_tab_index {
|
|
|
|
|
group_state.active_tab_index += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::RequestWelcomeScreenButtonsLayout => {
|
|
|
|
|
let donate = "https://graphite.art/donate/";
|
|
|
|
|
|
|
|
|
@@ -1131,9 +1188,26 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|
|
|
|
layout_target: LayoutTarget::StatusBarInfo,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::SetActivePanel { panel } => {
|
|
|
|
|
self.active_panel = panel;
|
|
|
|
|
responses.add(DocumentMessage::SetActivePanel { active_panel: self.active_panel });
|
|
|
|
|
PortfolioMessage::SetPanelGroupActiveTab { group, tab_index } => {
|
|
|
|
|
let group_state = self.workspace_panel_layout.panel_group(group);
|
|
|
|
|
if tab_index < group_state.tabs.len() && tab_index != group_state.active_tab_index {
|
|
|
|
|
// Destroy layouts for the old and new panels so the backend's diffing state is in sync with the frontend's fresh mount
|
|
|
|
|
if let Some(old_panel_type) = group_state.active_panel_type() {
|
|
|
|
|
Self::destroy_panel_layouts(old_panel_type, responses);
|
|
|
|
|
}
|
|
|
|
|
let new_panel_type = group_state.tabs[tab_index];
|
|
|
|
|
Self::destroy_panel_layouts(new_panel_type, responses);
|
|
|
|
|
|
|
|
|
|
// Update the active tab index for the panel
|
|
|
|
|
self.workspace_panel_layout.panel_group_mut(group).active_tab_index = tab_index;
|
|
|
|
|
|
|
|
|
|
// Send the layout update first so the frontend mounts the new panel component before it receives content
|
|
|
|
|
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
|
|
|
|
|
|
|
|
|
if let Some(panel_type) = self.workspace_panel_layout.panel_group(group).active_panel_type() {
|
|
|
|
|
self.refresh_panel_content(panel_type, responses);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::SelectDocument { document_id } => {
|
|
|
|
|
// Auto-save the document we are leaving
|
|
|
|
@@ -1289,113 +1363,61 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|
|
|
|
self.focus_document = !self.focus_document;
|
|
|
|
|
responses.add(MenuBarMessage::SendLayout);
|
|
|
|
|
|
|
|
|
|
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 self.properties_panel_open {
|
|
|
|
|
responses.add(PropertiesPanelMessage::Clear);
|
|
|
|
|
responses.add(FrontendMessage::UpdatePropertiesPanelState { open: false });
|
|
|
|
|
if properties_present {
|
|
|
|
|
Self::destroy_panel_layouts(PanelType::Properties, responses);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.layers_panel_open {
|
|
|
|
|
responses.add(DocumentMessage::ClearLayersPanel);
|
|
|
|
|
responses.add(FrontendMessage::UpdateLayersPanelState { open: false });
|
|
|
|
|
if layers_present {
|
|
|
|
|
Self::destroy_panel_layouts(PanelType::Layers, responses);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.data_panel_open {
|
|
|
|
|
responses.add(DataPanelMessage::ClearLayout);
|
|
|
|
|
responses.add(FrontendMessage::UpdateDataPanelState { open: false });
|
|
|
|
|
if data_present {
|
|
|
|
|
Self::destroy_panel_layouts(PanelType::Data, responses);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if self.properties_panel_open {
|
|
|
|
|
responses.add(FrontendMessage::UpdatePropertiesPanelState { open: true });
|
|
|
|
|
}
|
|
|
|
|
if self.layers_panel_open {
|
|
|
|
|
responses.add(FrontendMessage::UpdateLayersPanelState { open: true });
|
|
|
|
|
}
|
|
|
|
|
if self.data_panel_open {
|
|
|
|
|
responses.add(FrontendMessage::UpdateDataPanelState { open: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run the graph to grab the data
|
|
|
|
|
if self.properties_panel_open || self.layers_panel_open || self.data_panel_open {
|
|
|
|
|
if properties_present || layers_present || data_present {
|
|
|
|
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.properties_panel_open {
|
|
|
|
|
if properties_present {
|
|
|
|
|
responses.add(PropertiesPanelMessage::Refresh);
|
|
|
|
|
}
|
|
|
|
|
if self.layers_panel_open && self.active_document_id.is_some() {
|
|
|
|
|
if layers_present && self.active_document_id.is_some() {
|
|
|
|
|
responses.add(DeferMessage::AfterGraphRun {
|
|
|
|
|
messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::TogglePropertiesPanelOpen => {
|
|
|
|
|
if self.focus_document {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.properties_panel_open = !self.properties_panel_open;
|
|
|
|
|
responses.add(MenuBarMessage::SendLayout);
|
|
|
|
|
|
|
|
|
|
// Run the graph to grab the data
|
|
|
|
|
if self.properties_panel_open {
|
|
|
|
|
responses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open });
|
|
|
|
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
|
|
|
responses.add(PropertiesPanelMessage::Refresh);
|
|
|
|
|
} else {
|
|
|
|
|
responses.add(PropertiesPanelMessage::Clear);
|
|
|
|
|
responses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open });
|
|
|
|
|
}
|
|
|
|
|
let panel_type = PanelType::Properties;
|
|
|
|
|
self.toggle_dockable_panel(panel_type, responses);
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::ToggleLayersPanelOpen => {
|
|
|
|
|
if self.focus_document {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.layers_panel_open = !self.layers_panel_open;
|
|
|
|
|
responses.add(MenuBarMessage::SendLayout);
|
|
|
|
|
|
|
|
|
|
// Run the graph to grab the data
|
|
|
|
|
if self.layers_panel_open {
|
|
|
|
|
// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display
|
|
|
|
|
responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });
|
|
|
|
|
|
|
|
|
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
|
|
|
if self.active_document_id.is_some() {
|
|
|
|
|
responses.add(DeferMessage::AfterGraphRun {
|
|
|
|
|
messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the layers panel next time it is opened
|
|
|
|
|
responses.add(DocumentMessage::ClearLayersPanel);
|
|
|
|
|
|
|
|
|
|
// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed
|
|
|
|
|
responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });
|
|
|
|
|
}
|
|
|
|
|
let panel_type = PanelType::Layers;
|
|
|
|
|
self.toggle_dockable_panel(panel_type, responses);
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::ToggleDataPanelOpen => {
|
|
|
|
|
if self.focus_document {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.data_panel_open = !self.data_panel_open;
|
|
|
|
|
responses.add(MenuBarMessage::SendLayout);
|
|
|
|
|
|
|
|
|
|
// Run the graph to grab the data
|
|
|
|
|
if self.data_panel_open {
|
|
|
|
|
// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display
|
|
|
|
|
responses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });
|
|
|
|
|
|
|
|
|
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
|
|
|
} else {
|
|
|
|
|
// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the data panel next time it is opened
|
|
|
|
|
responses.add(DataPanelMessage::ClearLayout);
|
|
|
|
|
|
|
|
|
|
// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed
|
|
|
|
|
responses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });
|
|
|
|
|
}
|
|
|
|
|
let panel_type = PanelType::Data;
|
|
|
|
|
self.toggle_dockable_panel(panel_type, responses);
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::ToggleRulers => {
|
|
|
|
|
if let Some(document) = self.active_document_mut() {
|
|
|
|
@@ -1410,6 +1432,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|
|
|
|
document.update_document_widgets(responses, animation.is_playing(), timing_information.animation_time);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::UpdateWorkspacePanelLayout => {
|
|
|
|
|
responses.add(FrontendMessage::UpdateWorkspacePanelLayout {
|
|
|
|
|
panel_layout: self.workspace_panel_layout.clone(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
PortfolioMessage::UpdateOpenDocumentsList => {
|
|
|
|
|
// Send the list of document tab names
|
|
|
|
|
let open_documents = self
|
|
|
|
@@ -1562,8 +1589,8 @@ impl PortfolioMessageHandler {
|
|
|
|
|
} else {
|
|
|
|
|
self.document_ids.push_back(document_id);
|
|
|
|
|
}
|
|
|
|
|
new_document.update_layers_panel_control_bar_widgets(self.layers_panel_open && !self.focus_document, responses);
|
|
|
|
|
new_document.update_layers_panel_bottom_bar_widgets(self.layers_panel_open && !self.focus_document, responses);
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
self.documents.insert(document_id, new_document);
|
|
|
|
|
|
|
|
|
@@ -1610,7 +1637,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.data_panel_open || self.focus_document {
|
|
|
|
|
if !self.workspace_panel_layout.is_panel_visible(PanelType::Data) || self.focus_document {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1624,4 +1651,85 @@ impl PortfolioMessageHandler {
|
|
|
|
|
|
|
|
|
|
selected_nodes.first().copied()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Remove a dockable panel type from whichever panel group currently contains it.
|
|
|
|
|
fn remove_panel_from_layout(&mut self, panel_type: PanelType) {
|
|
|
|
|
for group_id in [PanelGroupId::PropertiesGroup, PanelGroupId::LayersGroup, PanelGroupId::DataGroup] {
|
|
|
|
|
let group = self.workspace_panel_layout.panel_group_mut(group_id);
|
|
|
|
|
if let Some(index) = group.tabs.iter().position(|&t| t == panel_type) {
|
|
|
|
|
group.tabs.remove(index);
|
|
|
|
|
group.active_tab_index = group.active_tab_index.min(group.tabs.len().saturating_sub(1));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Toggle a dockable panel on or off. When toggling off, refresh the newly active tab in its panel group (if any).
|
|
|
|
|
fn toggle_dockable_panel(&mut self, panel_type: PanelType, responses: &mut VecDeque<Message>) {
|
|
|
|
|
if let Some(group_id) = self.workspace_panel_layout.find_panel(panel_type) {
|
|
|
|
|
// Panel is present, remove it
|
|
|
|
|
let was_visible = self.workspace_panel_layout.panel_group(group_id).is_visible(panel_type);
|
|
|
|
|
Self::destroy_panel_layouts(panel_type, responses);
|
|
|
|
|
self.remove_panel_from_layout(panel_type);
|
|
|
|
|
|
|
|
|
|
// If the removed panel was the active tab, refresh whichever panel is now active in that panel group
|
|
|
|
|
if was_visible && let Some(new_active) = self.workspace_panel_layout.panel_group(group_id).active_panel_type() {
|
|
|
|
|
Self::destroy_panel_layouts(new_active, responses);
|
|
|
|
|
self.refresh_panel_content(new_active, responses);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Panel is not present, add it to its default panel group
|
|
|
|
|
self.add_panel_to_its_default_group(panel_type);
|
|
|
|
|
self.refresh_panel_content(panel_type, responses);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
responses.add(MenuBarMessage::SendLayout);
|
|
|
|
|
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add a dockable panel type to its default panel group.
|
|
|
|
|
fn add_panel_to_its_default_group(&mut self, panel_type: PanelType) {
|
|
|
|
|
let group = self.workspace_panel_layout.panel_group_mut(panel_type.default_panel_group());
|
|
|
|
|
if !group.tabs.contains(&panel_type) {
|
|
|
|
|
group.tabs.push(panel_type);
|
|
|
|
|
group.active_tab_index = group.tabs.len() - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Destroy the stored layout for a panel that is no longer the active tab.
|
|
|
|
|
/// This resets the backend's diffing state so it won't try to send updates to a frontend component that has been unmounted.
|
|
|
|
|
fn destroy_panel_layouts(panel_type: PanelType, responses: &mut VecDeque<Message>) {
|
|
|
|
|
let targets: &[LayoutTarget] = match panel_type {
|
|
|
|
|
PanelType::Properties => &[LayoutTarget::PropertiesPanel],
|
|
|
|
|
PanelType::Layers => &[LayoutTarget::LayersPanelControlLeftBar, LayoutTarget::LayersPanelControlRightBar, LayoutTarget::LayersPanelBottomBar],
|
|
|
|
|
PanelType::Data => &[LayoutTarget::DataPanel],
|
|
|
|
|
PanelType::Document | PanelType::Welcome => return,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for &layout_target in targets {
|
|
|
|
|
responses.add(LayoutMessage::DestroyLayout { layout_target });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Trigger a content refresh for a panel that just became the active tab.
|
|
|
|
|
fn refresh_panel_content(&self, panel_type: PanelType, responses: &mut VecDeque<Message>) {
|
|
|
|
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
|
|
|
|
|
|
|
|
match panel_type {
|
|
|
|
|
PanelType::Properties => {
|
|
|
|
|
responses.add(PropertiesPanelMessage::Refresh);
|
|
|
|
|
}
|
|
|
|
|
PanelType::Layers => {
|
|
|
|
|
if self.active_document_id.is_some() {
|
|
|
|
|
responses.add(DeferMessage::AfterGraphRun {
|
|
|
|
|
messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PanelType::Data => {
|
|
|
|
|
// The Data panel's content is populated automatically as a side effect of the graph run completing, so there's nothing to do here
|
|
|
|
|
}
|
|
|
|
|
PanelType::Document | PanelType::Welcome => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|