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)] #[allow(clippy::struct_excessive_bools, clippy::struct_field_names)]
15struct ViewerState {
16 show_settings: bool,
18
19 show_inspection: bool,
21
22 show_memory: bool,
24}
25
26#[allow(clippy::struct_excessive_bools)]
27pub struct Viewer {
28 state: ViewerState,
29
30 document_widget: DocumentWidget,
32
33 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 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 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 self.viewer_app.handle_input(ctx, &mut self.document_widget);
163
164 egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
165 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 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 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 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 fn on_exit(&mut self) {
241 self.viewer_app.on_exit();
242 }
243}