vsvg_viewer/
lib.rs

1#![warn(clippy::pedantic)]
2#![allow(clippy::module_name_repetitions)]
3#![allow(clippy::missing_errors_doc)]
4#![allow(clippy::missing_panics_doc)]
5#![allow(clippy::let_underscore_untyped)]
6
7mod document_widget;
8mod engine;
9mod frame_history;
10mod painters;
11#[cfg(puffin)]
12mod profiler;
13mod render_data;
14pub mod viewer;
15#[cfg(target_arch = "wasm32")]
16pub mod web_handle;
17
18use eframe::CreationContext;
19use std::sync::Arc;
20pub use viewer::Viewer;
21
22pub use crate::document_widget::DocumentWidget;
23
24/// Export of core dependencies in addition to what vsvg already re-exports.
25pub mod exports {
26    pub use ::eframe;
27    pub use ::egui;
28    pub use ::wgpu;
29}
30
31/// Viewer app for [`show()`] and [`show_tolerance()`].
32#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
33struct ShowViewerApp {
34    document: Arc<vsvg::Document>,
35    tolerance: f64,
36}
37
38impl ViewerApp for ShowViewerApp {
39    fn setup(
40        &mut self,
41        _cc: &CreationContext,
42        document_widget: &mut DocumentWidget,
43    ) -> anyhow::Result<()> {
44        document_widget.set_tolerance(self.tolerance);
45        document_widget.set_document(self.document.clone());
46        Ok(())
47    }
48}
49
50const DEFAULT_RENDERER_TOLERANCE: f64 = 0.01;
51
52/// Show a document in a window.
53///
54/// For native use only.
55#[cfg(not(target_arch = "wasm32"))]
56pub fn show(document: Arc<vsvg::Document>) -> anyhow::Result<()> {
57    show_tolerance(document, DEFAULT_RENDERER_TOLERANCE)
58}
59
60/// Show a document in a window with a custom renderer tolerance.
61///
62/// The renderer tolerance is used to convert curves into line segments before rendering. Smaller
63/// values yield less visible artifacts but require more CPU to render.
64///
65/// For native use only.
66#[cfg(not(target_arch = "wasm32"))]
67pub fn show_tolerance(document: Arc<vsvg::Document>, tolerance: f64) -> anyhow::Result<()> {
68    let native_options = eframe::NativeOptions::default();
69
70    eframe::run_native(
71        "vsvg-viewer",
72        native_options,
73        Box::new(move |cc| {
74            let style = egui::Style {
75                visuals: egui::Visuals::light(),
76                ..egui::Style::default()
77            };
78            cc.egui_ctx.set_style(style);
79
80            Box::new(
81                Viewer::new(
82                    cc,
83                    Box::new(ShowViewerApp {
84                        document: document.clone(),
85                        tolerance,
86                    }),
87                )
88                .expect("viewer requires wgpu backend"),
89            )
90        }),
91    )?;
92
93    Ok(())
94}
95
96/// Implement this trait to build a custom viewer app based on [`Viewer`].
97pub trait ViewerApp {
98    fn setup(
99        &mut self,
100        _cc: &eframe::CreationContext,
101        _document_widget: &mut DocumentWidget,
102    ) -> anyhow::Result<()> {
103        Ok(())
104    }
105
106    /// Handle input
107    ///
108    /// This is call very early in the frame loop to allow consuming input before egui.
109    fn handle_input(&mut self, _ctx: &egui::Context, _document_widget: &mut DocumentWidget) {}
110
111    /// Hook to show side panels
112    ///
113    /// This hook is called before the central panel is drawn, as per the [`egui`] documentation.
114    fn show_panels(
115        &mut self,
116        _ctx: &egui::Context,
117        _document_widget: &mut DocumentWidget,
118    ) -> anyhow::Result<()> {
119        Ok(())
120    }
121
122    /// Hook to show the central panel.
123    ///
124    /// This is call after the wgpu render callback that displays the document.
125    fn show_central_panel(
126        &mut self,
127        _ui: &mut egui::Ui,
128        _document_widget: &mut DocumentWidget,
129    ) -> anyhow::Result<()> {
130        Ok(())
131    }
132
133    /// Hook to modify the native options before starting the app.
134    #[cfg(not(target_arch = "wasm32"))]
135    fn native_options(&self) -> eframe::NativeOptions {
136        eframe::NativeOptions::default()
137    }
138
139    /// Window title
140    fn title(&self) -> String {
141        "vsvg ViewerApp".to_owned()
142    }
143
144    /// Hook to load persistent data.
145    ///
146    /// Use [`eframe::get_value`] to retrieve the data.
147    fn load(&mut self, _storage: &dyn eframe::Storage) {}
148
149    /// Hook to save persistent data.
150    ///
151    /// Use [`eframe::set_value`] to store the data.
152    fn save(&self, _storage: &mut dyn eframe::Storage) {}
153
154    /// Hook executed before shutting down the app.
155    fn on_exit(&mut self) {}
156}
157
158/// Show a custom [`ViewerApp`].
159///
160/// For native use only.
161#[cfg(not(target_arch = "wasm32"))]
162pub fn show_with_viewer_app(viewer_app: impl ViewerApp + 'static) -> anyhow::Result<()> {
163    vsvg::trace_function!();
164
165    let viewer_app = Box::new(viewer_app);
166
167    let native_options = viewer_app.native_options();
168
169    eframe::run_native(
170        viewer_app.title().as_str(),
171        native_options,
172        Box::new(move |cc| {
173            let style = egui::Style {
174                visuals: egui::Visuals::light(),
175                ..egui::Style::default()
176            };
177            cc.egui_ctx.set_style(style);
178
179            Box::new(Viewer::new(cc, viewer_app).expect("viewer requires wgpu backend"))
180        }),
181    )?;
182
183    Ok(())
184}