Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use graph_craft::wasm_application_io::WasmApplicationIo;
use graphene_std::Color;
use graphene_std::raster::Image;
use graphite_editor::application::Editor;
use graphite_editor::consts::DEFAULT_DOCUMENT_NAME;
use graphite_editor::messages::prelude::*;
use std::fs;
use std::sync::Arc;
Expand Down Expand Up @@ -79,7 +78,8 @@ impl WinitApp {
String::new()
});
let message = PortfolioMessage::OpenDocumentFile {
document_name: path.file_stem().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
document_name: None,
document_path: Some(path),
document_serialized_content: content,
};
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into()));
Expand Down Expand Up @@ -294,7 +294,8 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
let Some(content) = load_string(&path) else { return };

let message = PortfolioMessage::OpenDocumentFile {
document_name: name.unwrap_or(DEFAULT_DOCUMENT_NAME.to_string()),
document_name: None,
document_path: Some(path),
document_serialized_content: content,
};
self.dispatch_message(message.into());
Expand Down
2 changes: 1 addition & 1 deletion editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";

// DOCUMENT
pub const FILE_EXTENSION: &str = "graphite";
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;

Expand Down
3 changes: 2 additions & 1 deletion editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,8 @@ mod test {
);

let responses = editor.editor.handle_message(PortfolioMessage::OpenDocumentFile {
document_name: document_name.into(),
document_name: Some(document_name.to_string()),
document_path: None,
document_serialized_content,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
ExportDialogMessage::ExportBounds { bounds } => self.bounds = bounds,

ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport {
file_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
file_type: self.file_type,
scale_factor: self.scale_factor,
bounds: self.bounds,
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::DeselectAllLayers),
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=DocumentMessage::DeselectAllLayers),
entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),
entry!(KeyDown(KeyS); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SaveDocumentAs),
entry!(KeyDown(KeyD); modifiers=[Accel], canonical, action_dispatch=DocumentMessage::DuplicateSelectedLayers),
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }),
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/portfolio/document/document_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub enum DocumentMessage {
RenderRulers,
RenderScrollbars,
SaveDocument,
SaveDocumentAs,
SavedDocument {
path: Option<PathBuf>,
},
Expand Down
54 changes: 39 additions & 15 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BO
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid};
use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::data_panel::{DataPanelMessageContext, DataPanelMessageHandler};
Expand Down Expand Up @@ -85,8 +85,6 @@ pub struct DocumentMessageHandler {
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
pub collapsed: CollapsedLayers,
/// The name of the document, which is displayed in the tab and title bar of the editor.
pub name: String,
/// The full Git commit hash of the Graphite repository that was used to build the editor.
/// We save this to provide a hint about which version of the editor was used to create the document.
pub commit_hash: String,
Expand All @@ -113,6 +111,12 @@ pub struct DocumentMessageHandler {
// Fields omitted from the saved document format
// =============================================
//
/// The name of the document, which is displayed in the tab and title bar of the editor.
#[serde(skip)]
pub name: String,
/// The path of the to the document file.
#[serde(skip)]
pub(crate) path: Option<PathBuf>,
/// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks
#[serde(skip)]
breadcrumb_network_path: Vec<NodeId>,
Expand All @@ -125,9 +129,6 @@ pub struct DocumentMessageHandler {
/// Stack of document network snapshots for future history states.
#[serde(skip)]
document_redo_history: VecDeque<NodeNetworkInterface>,
/// The path of the to the document file.
#[serde(skip)]
path: Option<PathBuf>,
/// Hash of the document snapshot that was most recently saved to disk by the user.
#[serde(skip)]
saved_hash: Option<u64>,
Expand Down Expand Up @@ -159,7 +160,6 @@ impl Default for DocumentMessageHandler {
// ============================================
network_interface: default_document_network_interface(),
collapsed: CollapsedLayers::default(),
name: DEFAULT_DOCUMENT_NAME.to_string(),
commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),
document_ptz: PTZ::default(),
document_mode: DocumentMode::DesignMode,
Expand All @@ -172,11 +172,12 @@ impl Default for DocumentMessageHandler {
// =============================================
// Fields omitted from the saved document format
// =============================================
name: DEFAULT_DOCUMENT_NAME.to_string(),
path: None,
breadcrumb_network_path: Vec::new(),
selection_network_path: Vec::new(),
document_undo_history: VecDeque::new(),
document_redo_history: VecDeque::new(),
path: None,
saved_hash: None,
auto_saved_hash: None,
layer_range_selection_reference: None,
Expand Down Expand Up @@ -947,7 +948,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(OverlaysMessage::Draw);
}
DocumentMessage::RenameDocument { new_name } => {
self.name = new_name;
self.name = new_name.clone();

self.path = None;
self.set_save_state(false);

responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}
Expand Down Expand Up @@ -1020,25 +1025,40 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
multiplier: scrollbar_multiplier.into(),
});
}
DocumentMessage::SaveDocument => {
DocumentMessage::SaveDocument | DocumentMessage::SaveDocumentAs => {
if let DocumentMessage::SaveDocumentAs = message {
self.path = None;
}

self.set_save_state(true);
responses.add(PortfolioMessage::AutoSaveActiveDocument);
// Update the save status of the just saved document
responses.add(PortfolioMessage::UpdateOpenDocumentsList);

let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
true => self.name.clone(),
false => self.name.clone() + FILE_SAVE_SUFFIX,
};
responses.add(FrontendMessage::TriggerSaveDocument {
document_id,
name,
name: format!("{}.{}", self.name.clone(), FILE_EXTENSION),
path: self.path.clone(),
content: self.serialize_document().into_bytes(),
})
}
DocumentMessage::SavedDocument { path } => {
self.path = path;

// Update the name to match the file stem
let document_name_from_path = self.path.as_ref().and_then(|path| {
if path.extension().is_some_and(|e| e == FILE_EXTENSION) {
path.file_stem().map(|n| n.to_string_lossy().to_string())
} else {
None
}
});
if let Some(name) = document_name_from_path {
self.name = name;

responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}
}
DocumentMessage::SelectParentLayer => {
let selected_nodes = self.network_interface.selected_nodes();
Expand Down Expand Up @@ -1571,6 +1591,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
ZoomCanvasToFitAll,
);

// Additional actions available on desktop
#[cfg(not(target_family = "wasm"))]
common.extend(actions!(DocumentMessageDiscriminant::SaveDocumentAs));

// Additional actions if there are any selected layers
if self.network_interface.selected_nodes().selected_layers(self.metadata()).next().is_some() {
let mut select = actions!(DocumentMessageDiscriminant;
Expand Down
27 changes: 19 additions & 8 deletions editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,25 @@ impl LayoutHolder for MenuBarMessageHandler {
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Save".into(),
icon: Some("Save".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument),
action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
disabled: no_active_document,
..MenuBarEntry::default()
}],
vec![
MenuBarEntry {
label: "Save".into(),
icon: Some("Save".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument),
action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
#[cfg(not(target_family = "wasm"))]
MenuBarEntry {
label: "Save As…".into(),
icon: Some("Save".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocumentAs),
action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocumentAs.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Import…".into(),
Expand Down
11 changes: 7 additions & 4 deletions editor/src/messages/portfolio/portfolio_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::messages::prelude::*;
use graphene_std::Color;
use graphene_std::raster::Image;
use graphene_std::text::Font;
use std::path::PathBuf;

#[impl_message(Message, Portfolio)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -66,18 +67,20 @@ pub enum PortfolioMessage {
NextDocument,
OpenDocument,
OpenDocumentFile {
document_name: String,
document_name: Option<String>,
document_path: Option<PathBuf>,
document_serialized_content: String,
},
ToggleResetNodesToDefinitionsOnOpen,
OpenDocumentFileWithId {
document_id: DocumentId,
document_name: String,
document_name: Option<String>,
document_path: Option<PathBuf>,
document_is_auto_saved: bool,
document_is_saved: bool,
document_serialized_content: String,
to_front: bool,
},
ToggleResetNodesToDefinitionsOnOpen,
PasteIntoFolder {
clipboard: Clipboard,
parent: LayerNodeIdentifier,
Expand Down Expand Up @@ -115,7 +118,7 @@ pub enum PortfolioMessage {
document_id: DocumentId,
},
SubmitDocumentExport {
file_name: String,
name: String,
file_type: FileType,
scale_factor: f64,
bounds: ExportBounds,
Expand Down
38 changes: 31 additions & 7 deletions editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::document::utility_types::document_metadata::LayerNodeIdentifier;
use super::document::utility_types::network_interface;
use super::utility_types::{PanelType, PersistentData};
use crate::application::generate_uuid;
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH};
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION};
use crate::messages::animation::TimingInformation;
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::simple_dialogs;
Expand Down Expand Up @@ -419,12 +419,14 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::OpenDocumentFile {
document_name,
document_path,
document_serialized_content,
} => {
let document_id = DocumentId(generate_uuid());
responses.add(PortfolioMessage::OpenDocumentFileWithId {
document_id,
document_name,
document_path,
document_is_auto_saved: false,
document_is_saved: true,
document_serialized_content,
Expand All @@ -439,6 +441,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
PortfolioMessage::OpenDocumentFileWithId {
document_id,
document_name,
document_path,
document_is_auto_saved,
document_is_saved,
document_serialized_content,
Expand All @@ -450,10 +453,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
let document_serialized_content = document_migration_string_preprocessing(document_serialized_content);

// Deserialize the document
let document = DocumentMessageHandler::deserialize_document(&document_serialized_content).map(|mut document| {
document.name.clone_from(&document_name);
document
});
let document = DocumentMessageHandler::deserialize_document(&document_serialized_content);

// Display an error to the user if the document could not be opened
let mut document = match document {
Expand Down Expand Up @@ -514,6 +514,30 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document.set_auto_save_state(document_is_auto_saved);
document.set_save_state(document_is_saved);

let document_name_from_path = document_path.as_ref().and_then(|path| {
if path.extension().is_some_and(|e| e == FILE_EXTENSION) {
path.file_stem().map(|n| n.to_string_lossy().to_string())
} else {
None
}
});

match (document_name, document_path, document_name_from_path) {
(Some(name), _, None) => {
document.name = name;
}
(_, Some(path), Some(name)) => {
document.name = name;
document.path = Some(path);
}
(_, _, Some(name)) => {
document.name = name;
}
_ => {
document.name = DEFAULT_DOCUMENT_NAME.to_string();
}
}

// Load the document into the portfolio so it opens in the editor
self.load_document(document, document_id, self.layers_panel_open, responses, to_front);
}
Expand Down Expand Up @@ -899,15 +923,15 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
}
PortfolioMessage::SubmitDocumentExport {
file_name,
name,
file_type,
scale_factor,
bounds,
transparent_background,
} => {
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render non-existent document");
let export_config = ExportConfig {
file_name,
name,
file_type,
scale_factor,
bounds,
Expand Down
Loading