diff --git a/Cargo.lock b/Cargo.lock index 8c00ccef..b07f82e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2365,6 +2365,7 @@ dependencies = [ "ron", "serde", "serde-wasm-bindgen", + "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/desktop/src/app.rs b/desktop/src/app.rs index bec70c35..761d97a6 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -214,6 +214,15 @@ impl WinitApp { self.dispatch_desktop_wrapper_message(message); } } + DesktopFrontendMessage::PersistenceWritePreferences { preferences } => { + self.persistent_data.write_preferences(preferences); + } + DesktopFrontendMessage::PersistenceLoadPreferences => { + if let Some(preferences) = self.persistent_data.load_preferences() { + let message = DesktopWrapperMessage::LoadPreferences { preferences }; + self.dispatch_desktop_wrapper_message(message); + } + } } } diff --git a/desktop/src/consts.rs b/desktop/src/consts.rs index ee241ba6..c4f6e4fb 100644 --- a/desktop/src/consts.rs +++ b/desktop/src/consts.rs @@ -1,7 +1,10 @@ pub(crate) static APP_NAME: &str = "Graphite"; pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor"; + pub(crate) static APP_DIRECTORY_NAME: &str = "graphite-editor"; -pub(crate) static APP_AUTOSAVE_DIRECTORY_NAME: &str = "documents"; +pub(crate) static APP_STATE_FILE_NAME: &str = "state.ron"; +pub(crate) static APP_PREFERENCES_FILE_NAME: &str = "preferences.ron"; +pub(crate) static APP_DOCUMENTS_DIRECTORY_NAME: &str = "documents"; // CEF configuration constants pub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 60; diff --git a/desktop/src/dirs.rs b/desktop/src/dirs.rs index af693efb..94f62c68 100644 --- a/desktop/src/dirs.rs +++ b/desktop/src/dirs.rs @@ -1,7 +1,7 @@ use std::fs::create_dir_all; use std::path::PathBuf; -use crate::consts::{APP_AUTOSAVE_DIRECTORY_NAME, APP_DIRECTORY_NAME}; +use crate::consts::{APP_DIRECTORY_NAME, APP_DOCUMENTS_DIRECTORY_NAME}; pub(crate) fn ensure_dir_exists(path: &PathBuf) { if !path.exists() { @@ -16,7 +16,7 @@ pub(crate) fn graphite_data_dir() -> PathBuf { } pub(crate) fn graphite_autosave_documents_dir() -> PathBuf { - let path = graphite_data_dir().join(APP_AUTOSAVE_DIRECTORY_NAME); + let path = graphite_data_dir().join(APP_DOCUMENTS_DIRECTORY_NAME); ensure_dir_exists(&path); path } diff --git a/desktop/src/persist.rs b/desktop/src/persist.rs index 6b4e6590..c47ad862 100644 --- a/desktop/src/persist.rs +++ b/desktop/src/persist.rs @@ -1,4 +1,4 @@ -use graphite_desktop_wrapper::messages::{Document, DocumentId}; +use graphite_desktop_wrapper::messages::{Document, DocumentId, Preferences}; #[derive(Default, serde::Serialize, serde::Deserialize)] pub(crate) struct PersistentData { @@ -6,6 +6,7 @@ pub(crate) struct PersistentData { current_document: Option, #[serde(skip)] document_order: Option>, + preferences: Option, } impl PersistentData { @@ -72,21 +73,37 @@ impl PersistentData { self.flush(); } + pub(crate) fn write_preferences(&mut self, preferences: Preferences) { + let Ok(preferences) = ron::ser::to_string_pretty(&preferences, Default::default()) else { + tracing::error!("Failed to serialize preferences"); + return; + }; + std::fs::write(Self::preferences_file_path(), &preferences).unwrap_or_else(|e| { + tracing::error!("Failed to write preferences to disk: {e}"); + }); + } + + pub(crate) fn load_preferences(&self) -> Option { + let data = std::fs::read_to_string(Self::preferences_file_path()).ok()?; + let preferences = ron::from_str(&data).ok()?; + Some(preferences) + } + fn flush(&self) { - let data = match ron::to_string(self) { + let data = match ron::ser::to_string_pretty(self, Default::default()) { Ok(d) => d, Err(e) => { tracing::error!("Failed to serialize persistent data: {e}"); return; } }; - if let Err(e) = std::fs::write(Self::persistence_file_path(), data) { + if let Err(e) = std::fs::write(Self::state_file_path(), data) { tracing::error!("Failed to write persistent data to disk: {e}"); } } pub(crate) fn load_from_disk(&mut self) { - let path = Self::persistence_file_path(); + let path = Self::state_file_path(); let data = match std::fs::read_to_string(&path) { Ok(d) => d, Err(e) if e.kind() == std::io::ErrorKind::NotFound => { @@ -108,9 +125,15 @@ impl PersistentData { *self = loaded; } - fn persistence_file_path() -> std::path::PathBuf { + fn state_file_path() -> std::path::PathBuf { let mut path = crate::dirs::graphite_data_dir(); - path.push(format!("{}.ron", crate::consts::APP_AUTOSAVE_DIRECTORY_NAME)); + path.push(crate::consts::APP_STATE_FILE_NAME); + path + } + + fn preferences_file_path() -> std::path::PathBuf { + let mut path = crate::dirs::graphite_data_dir(); + path.push(crate::consts::APP_PREFERENCES_FILE_NAME); path } } diff --git a/desktop/wrapper/src/handle_desktop_wrapper_message.rs b/desktop/wrapper/src/handle_desktop_wrapper_message.rs index 66ad7cd7..c5cfa32e 100644 --- a/desktop/wrapper/src/handle_desktop_wrapper_message.rs +++ b/desktop/wrapper/src/handle_desktop_wrapper_message.rs @@ -1,7 +1,7 @@ use graphene_std::Color; use graphene_std::raster::Image; use graphite_editor::messages::app_window::app_window_message_handler::AppWindowPlatform; -use graphite_editor::messages::prelude::{AppWindowMessage, DocumentMessage, PortfolioMessage}; +use graphite_editor::messages::prelude::{AppWindowMessage, DocumentMessage, PortfolioMessage, PreferencesMessage}; use crate::messages::Platform; @@ -140,5 +140,9 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess let message = PortfolioMessage::SelectDocument { document_id: id }; dispatcher.queue_editor_message(message.into()); } + DesktopWrapperMessage::LoadPreferences { preferences } => { + let message = PreferencesMessage::Load { preferences }; + dispatcher.queue_editor_message(message.into()); + } } } diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index 26a6a4f1..1b6bed3e 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -107,6 +107,12 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD FrontendMessage::TriggerLoadRestAutoSaveDocuments => { dispatcher.respond(DesktopFrontendMessage::PersistenceLoadRemainingDocuments); } + FrontendMessage::TriggerSavePreferences { preferences } => { + dispatcher.respond(DesktopFrontendMessage::PersistenceWritePreferences { preferences }); + } + FrontendMessage::TriggerLoadPreferences => { + dispatcher.respond(DesktopFrontendMessage::PersistenceLoadPreferences); + } m => return Some(m), } None diff --git a/desktop/wrapper/src/messages.rs b/desktop/wrapper/src/messages.rs index b4db4613..c335b78d 100644 --- a/desktop/wrapper/src/messages.rs +++ b/desktop/wrapper/src/messages.rs @@ -1,8 +1,11 @@ pub use graphite_editor::messages::prelude::DocumentId; use graphite_editor::messages::prelude::FrontendMessage; -pub(crate) use graphite_editor::messages::prelude::Message as EditorMessage; use std::path::PathBuf; +pub(crate) use graphite_editor::messages::prelude::Message as EditorMessage; + +pub use graphite_editor::messages::prelude::PreferencesMessageHandler as Preferences; + pub enum DesktopFrontendMessage { ToWeb(Vec), OpenFileDialog { @@ -48,6 +51,10 @@ pub enum DesktopFrontendMessage { PersistenceUpdateDocumentsList { ids: Vec, }, + PersistenceWritePreferences { + preferences: Preferences, + }, + PersistenceLoadPreferences, CloseWindow, } @@ -93,6 +100,9 @@ pub enum DesktopWrapperMessage { SelectDocument { id: DocumentId, }, + LoadPreferences { + preferences: Preferences, + }, } #[derive(Clone, serde::Serialize, serde::Deserialize, Debug)] diff --git a/editor/src/messages/preferences/preferences_message.rs b/editor/src/messages/preferences/preferences_message.rs index b8988fa3..00a6eca5 100644 --- a/editor/src/messages/preferences/preferences_message.rs +++ b/editor/src/messages/preferences/preferences_message.rs @@ -6,7 +6,7 @@ use crate::messages::prelude::*; #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum PreferencesMessage { // Management messages - Load { preferences: String }, + Load { preferences: PreferencesMessageHandler }, ResetToDefaults, // Per-preference messages diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index 214903b9..d0582938 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -50,15 +50,13 @@ impl MessageHandler for PreferencesMessageHandler { match message { // Management messages PreferencesMessage::Load { preferences } => { - if let Ok(deserialized_preferences) = serde_json::from_str::(&preferences) { - *self = deserialized_preferences; + *self = preferences; - responses.add(PortfolioMessage::EditorPreferences); - responses.add(PortfolioMessage::UpdateVelloPreference); - responses.add(PreferencesMessage::ModifyLayout { - zoom_with_scroll: self.zoom_with_scroll, - }); - } + responses.add(PortfolioMessage::EditorPreferences); + responses.add(PortfolioMessage::UpdateVelloPreference); + responses.add(PreferencesMessage::ModifyLayout { + zoom_with_scroll: self.zoom_with_scroll, + }); } PreferencesMessage::ResetToDefaults => { refresh_dialog(responses); diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index cdb0ad3e..e4105a53 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -40,6 +40,7 @@ math-parser = { workspace = true } wgpu = { workspace = true } web-sys = { workspace = true } ron = { workspace = true } +serde_json = { workspace = true } [package.metadata.wasm-pack.profile.dev] wasm-opt = false diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index da190181..ea4b5d12 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -415,6 +415,11 @@ impl EditorHandle { #[wasm_bindgen(js_name = loadPreferences)] pub fn load_preferences(&self, preferences: String) { + let Ok(preferences) = serde_json::from_str(&preferences) else { + log::error!("Failed to deserialize preferences"); + return; + }; + let message = PreferencesMessage::Load { preferences }; self.dispatch(message);