diff --git a/Cargo.lock b/Cargo.lock index a3f8ede..5f05ee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1240,6 +1240,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -3931,6 +3940,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.10" @@ -4088,6 +4103,8 @@ name = "vsvg-sketch" version = "0.1.0-alpha.0" dependencies = [ "anyhow", + "convert_case", + "eframe", "egui", "geo", "itertools 0.11.0", diff --git a/vsvg-sketch-derive/src/lib.rs b/vsvg-sketch-derive/src/lib.rs index ebb5bcd..99133f2 100644 --- a/vsvg-sketch-derive/src/lib.rs +++ b/vsvg-sketch-derive/src/lib.rs @@ -68,7 +68,11 @@ pub fn sketch_derive(input: TokenStream) -> TokenStream { } TokenStream::from(quote! { - impl ::vsvg_sketch::SketchApp for #name { } + impl ::vsvg_sketch::SketchApp for #name { + fn name(&self) -> String { + stringify!(#name).to_string() + } + } impl ::vsvg_sketch::SketchUI for #name { fn ui(&mut self, ui: &mut egui::Ui) -> bool { diff --git a/vsvg-sketch/Cargo.toml b/vsvg-sketch/Cargo.toml index 8dfd140..1547e5d 100644 --- a/vsvg-sketch/Cargo.toml +++ b/vsvg-sketch/Cargo.toml @@ -13,10 +13,12 @@ rand_distr.workspace = true kurbo.workspace = true anyhow.workspace = true egui.workspace = true +eframe.workspace = true web-time.workspace = true rfd = { version = "0.12.0", default_features = false, features = [ "xdg-portal", ] } +convert_case = "0.6.0" # for examples [dev-dependencies] diff --git a/vsvg-sketch/src/lib.rs b/vsvg-sketch/src/lib.rs index 3fd79e6..377f538 100644 --- a/vsvg-sketch/src/lib.rs +++ b/vsvg-sketch/src/lib.rs @@ -26,4 +26,8 @@ pub trait SketchUI { fn ui(&mut self, ui: &mut egui::Ui) -> bool; } -pub trait SketchApp: App + SketchUI {} +pub trait SketchApp: App + SketchUI { + /// The name of the sketch, used the window title, the default output file name, and persistent + /// settings. + fn name(&self) -> String; +} diff --git a/vsvg-sketch/src/runner/mod.rs b/vsvg-sketch/src/runner/mod.rs index 1e41e63..39abc15 100644 --- a/vsvg-sketch/src/runner/mod.rs +++ b/vsvg-sketch/src/runner/mod.rs @@ -1,6 +1,7 @@ mod save_ui; use crate::Sketch; +use convert_case::Casing; use rand::SeedableRng; use vsvg::{PageSize, Unit}; @@ -63,11 +64,14 @@ pub struct Runner<'a> { impl Runner<'_> { /// Create a new [`Runner`] with the provided [`SketchApp`] instance. pub fn new(app: impl crate::SketchApp + 'static) -> Self { + let mut save_ui = save_ui::SaveUI::default(); + save_ui.base_name = app.name().to_case(convert_case::Case::Snake); + Self { app: Box::new(app), last_sketch: None, dirty: true, - save_ui: save_ui::SaveUI::default(), + save_ui, enable_seed: true, seed: 0, enable_time: true, @@ -149,7 +153,7 @@ impl Runner<'_> { impl Runner<'static> { /// Execute the sketch app. pub fn run(self) -> anyhow::Result<()> { - vsvg_viewer::show_with_viewer_app(Box::new(self)) + vsvg_viewer::show_with_viewer_app(self) } } @@ -443,4 +447,12 @@ impl vsvg_viewer::ViewerApp for Runner<'_> { Ok(()) } + + fn options(&self, native_option: &mut eframe::NativeOptions) { + native_option.app_id = Some(format!("vsvg.sketch.{}", self.title())); + } + + fn title(&self) -> String { + self.app.name() + } } diff --git a/vsvg-sketch/src/runner/save_ui.rs b/vsvg-sketch/src/runner/save_ui.rs index b8dec07..5d0af5f 100644 --- a/vsvg-sketch/src/runner/save_ui.rs +++ b/vsvg-sketch/src/runner/save_ui.rs @@ -10,7 +10,7 @@ pub(super) struct SaveUI { destination_dir: Option, /// The output file base name. - base_name: String, + pub(super) base_name: String, /// The last save result, if any. /// diff --git a/vsvg-viewer/examples/custom_panel.rs b/vsvg-viewer/examples/custom_panel.rs index 4f73e61..b12ffeb 100644 --- a/vsvg-viewer/examples/custom_panel.rs +++ b/vsvg-viewer/examples/custom_panel.rs @@ -65,5 +65,5 @@ impl ViewerApp for SidePanelViewerApp { } fn main() -> anyhow::Result<()> { - show_with_viewer_app(Box::new(SidePanelViewerApp::new())) + show_with_viewer_app(SidePanelViewerApp::new()) } diff --git a/vsvg-viewer/src/lib.rs b/vsvg-viewer/src/lib.rs index ea8970a..2fb42c5 100644 --- a/vsvg-viewer/src/lib.rs +++ b/vsvg-viewer/src/lib.rs @@ -27,7 +27,7 @@ pub fn show(document: &Document) -> anyhow::Result<()> { let document_data = Arc::new(DocumentData::new(document)); eframe::run_native( - "vsvg", + "vsvg-viewer", native_options, Box::new(move |cc| { let style = egui::Style { @@ -65,14 +65,25 @@ pub trait ViewerApp { ) -> anyhow::Result<()> { Ok(()) } + + /// Hook to modify the native options before starting the app. + fn options(&self, _native_option: &mut eframe::NativeOptions) {} + + /// Window title + fn title(&self) -> String { + "vsvg ViewerApp".to_owned() + } } -pub fn show_with_viewer_app(viewer_app: Box) -> anyhow::Result<()> { - let native_options = eframe::NativeOptions::default(); +pub fn show_with_viewer_app(viewer_app: impl ViewerApp + 'static) -> anyhow::Result<()> { + let viewer_app = Box::new(viewer_app); let document_data = Arc::new(DocumentData::default()); + let mut native_options = eframe::NativeOptions::default(); + viewer_app.options(&mut native_options); + eframe::run_native( - "vsvg", + viewer_app.title().as_str(), native_options, Box::new(move |cc| { let style = egui::Style { diff --git a/vsvg-viewer/src/viewer.rs b/vsvg-viewer/src/viewer.rs index e8d799d..58b4331 100644 --- a/vsvg-viewer/src/viewer.rs +++ b/vsvg-viewer/src/viewer.rs @@ -7,6 +7,8 @@ use crate::document_widget::DocumentWidget; use crate::ViewerApp; use std::sync::Arc; +const VSVG_VIEWER_STORAGE_KEY: &str = "vsvg-viewer-state"; + #[derive(serde::Deserialize, serde::Serialize, Default)] #[serde(default)] // if we add new fields, give them default values when deserializing old state struct ViewerState { @@ -40,7 +42,7 @@ impl Viewer { mut viewer_app: Box, ) -> Option { let state = if let Some(storage) = cc.storage { - eframe::get_value(storage, "hello").unwrap_or_default() + eframe::get_value(storage, VSVG_VIEWER_STORAGE_KEY).unwrap_or_default() } else { ViewerState::default() }; @@ -183,6 +185,6 @@ impl eframe::App for Viewer { /// Called by the framework to save state before shutdown. fn save(&mut self, storage: &mut dyn eframe::Storage) { - eframe::set_value(storage, "hello", &self.state); + eframe::set_value(storage, VSVG_VIEWER_STORAGE_KEY, &self.state); } }