vsvg_viewer/
viewer.rs

1use crate::frame_history::FrameHistory;
2use eframe::Frame;
3use egui::{Color32, Ui};
4
5#[cfg(puffin)]
6use crate::profiler::Profiler;
7use crate::{document_widget::DocumentWidget, ViewerApp};
8
9const VSVG_VIEWER_STATE_STORAGE_KEY: &str = "vsvg-viewer-state";
10const VSVG_VIEWER_ANTIALIAS_STORAGE_KEY: &str = "vsvg-viewer-aa";
11
12#[derive(serde::Deserialize, serde::Serialize, Default)]
13#[serde(default)] // if we add new fields, give them default values when deserializing old state
14#[allow(clippy::struct_excessive_bools, clippy::struct_field_names)]
15struct ViewerState {
16    /// Show settings window.
17    show_settings: bool,
18
19    /// Show inspection window.
20    show_inspection: bool,
21
22    /// Show memory window.
23    show_memory: bool,
24}
25
26#[allow(clippy::struct_excessive_bools)]
27pub struct Viewer {
28    state: ViewerState,
29
30    /// widget to display the [`vsvg::Document`]
31    document_widget: DocumentWidget,
32
33    /// Record frame performance
34    frame_history: FrameHistory,
35
36    viewer_app: Box<dyn ViewerApp>,
37
38    #[cfg(puffin)]
39    profiler: crate::profiler::Profiler,
40}
41
42impl Viewer {
43    #[must_use]
44    pub(crate) fn new<'a>(
45        cc: &'a eframe::CreationContext<'a>,
46        mut viewer_app: Box<dyn ViewerApp>,
47    ) -> Option<Self> {
48        let mut document_widget = DocumentWidget::new(cc)?;
49
50        let state = if let Some(storage) = cc.storage {
51            viewer_app.load(storage);
52
53            if let Some(aa) = eframe::get_value(storage, VSVG_VIEWER_ANTIALIAS_STORAGE_KEY) {
54                document_widget.set_antialias(aa);
55            };
56
57            eframe::get_value(storage, VSVG_VIEWER_STATE_STORAGE_KEY).unwrap_or_default()
58        } else {
59            ViewerState::default()
60        };
61
62        //TODO: better error handling
63        viewer_app
64            .setup(cc, &mut document_widget)
65            .expect("viewer app setup failed");
66
67        Some(Viewer {
68            state,
69            document_widget,
70            frame_history: FrameHistory::default(),
71            viewer_app,
72            #[cfg(puffin)]
73            profiler: Profiler::default(),
74        })
75    }
76
77    #[allow(clippy::unused_self)]
78    #[cfg(not(target_arch = "wasm32"))]
79    fn menu_file(&self, ui: &mut Ui) {
80        ui.menu_button("File", |ui| {
81            if ui.button("Quit").clicked() {
82                ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
83            }
84        });
85    }
86
87    fn menu_debug(&mut self, ui: &mut Ui) {
88        ui.menu_button("Debug", |ui| {
89            if ui.button("Show settings window").clicked() {
90                self.state.show_settings = true;
91                ui.close_menu();
92            }
93            if ui.button("Show inspection window").clicked() {
94                self.state.show_inspection = true;
95                ui.close_menu();
96            }
97            if ui.button("Show memory window").clicked() {
98                self.state.show_memory = true;
99                ui.close_menu();
100            }
101
102            #[cfg(puffin)]
103            if ui.button("Show profiler window").clicked() {
104                self.profiler.start();
105                ui.close_menu();
106            }
107
108            #[cfg(debug_assertions)]
109            {
110                ui.separator();
111                Self::egui_debug_options_ui(ui);
112            }
113        });
114    }
115
116    #[cfg(debug_assertions)]
117    fn egui_debug_options_ui(ui: &mut Ui) {
118        // copied from rerun!
119
120        let mut debug = ui.style().debug;
121        let mut any_clicked = false;
122
123        any_clicked |= ui
124            .checkbox(&mut debug.debug_on_hover, "Ui debug on hover")
125            .on_hover_text("Hover over widgets to see their rectangles")
126            .changed();
127        any_clicked |= ui
128            .checkbox(&mut debug.show_expand_width, "Show expand width")
129            .on_hover_text("Show which widgets make their parent wider")
130            .changed();
131        any_clicked |= ui
132            .checkbox(&mut debug.show_expand_height, "Show expand height")
133            .on_hover_text("Show which widgets make their parent higher")
134            .changed();
135        any_clicked |= ui.checkbox(&mut debug.show_resize, "Show resize").changed();
136        any_clicked |= ui
137            .checkbox(
138                &mut debug.show_interactive_widgets,
139                "Show interactive widgets",
140            )
141            .on_hover_text("Show an overlay on all interactive widgets")
142            .changed();
143
144        if any_clicked {
145            let mut style = (*ui.ctx().style()).clone();
146            style.debug = debug;
147            ui.ctx().set_style(style);
148        }
149    }
150}
151
152impl eframe::App for Viewer {
153    fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
154        #[cfg(feature = "puffin")]
155        {
156            puffin::GlobalProfiler::lock().new_frame();
157        }
158
159        vsvg::trace_function!();
160
161        // hook to handle input (called early to allow capturing input before egui)
162        self.viewer_app.handle_input(ctx, &mut self.document_widget);
163
164        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
165            // The top panel is often a good place for a menu bar:
166            egui::menu::bar(ui, |ui| {
167                #[cfg(not(target_arch = "wasm32"))]
168                self.menu_file(ui);
169
170                self.document_widget.view_menu_ui(ui);
171                self.document_widget.layer_menu_ui(ui);
172                self.menu_debug(ui);
173                self.frame_history.ui(ui);
174                ui.add_enabled(
175                    false,
176                    egui::Label::new(format!("Vertices: {}", self.document_widget.vertex_count())),
177                );
178                egui::warn_if_debug_build(ui);
179            });
180        });
181
182        // hook for creating side panels
183        //TODO: better error management
184        self.viewer_app
185            .show_panels(ctx, &mut self.document_widget)
186            .expect("ViewerApp failed!!!");
187
188        let panel_frame = egui::Frame::central_panel(&ctx.style())
189            .inner_margin(egui::Margin::same(0.))
190            .fill(Color32::from_rgb(242, 242, 242));
191
192        egui::CentralPanel::default()
193            .frame(panel_frame)
194            .show(ctx, |ui| {
195                self.document_widget.ui(ui);
196
197                //TODO: better error management
198                self.viewer_app
199                    .show_central_panel(ui, &mut self.document_widget)
200                    .expect("ViewerApp failed!!!");
201            });
202
203        egui::Window::new("🔧 Settings")
204            .open(&mut self.state.show_settings)
205            .vscroll(true)
206            .show(ctx, |ui| {
207                ctx.settings_ui(ui);
208            });
209
210        egui::Window::new("🔍 Inspection")
211            .open(&mut self.state.show_inspection)
212            .vscroll(true)
213            .show(ctx, |ui| {
214                ctx.inspection_ui(ui);
215            });
216
217        egui::Window::new("📝 Memory")
218            .open(&mut self.state.show_memory)
219            .resizable(false)
220            .show(ctx, |ui| {
221                ctx.memory_ui(ui);
222            });
223
224        self.frame_history
225            .on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
226    }
227
228    /// Called by the framework to save state before shutdown.
229    fn save(&mut self, storage: &mut dyn eframe::Storage) {
230        eframe::set_value(storage, VSVG_VIEWER_STATE_STORAGE_KEY, &self.state);
231        eframe::set_value(
232            storage,
233            VSVG_VIEWER_ANTIALIAS_STORAGE_KEY,
234            &self.document_widget.antialias(),
235        );
236        self.viewer_app.save(storage);
237    }
238
239    /// Called by the framework before shutting down.
240    fn on_exit(&mut self) {
241        self.viewer_app.on_exit();
242    }
243}