1#[cfg(feature = "gui")]
4use super::plot_overlay::{OverlayConfig, OverlayMetrics, PlotOverlay};
5#[cfg(feature = "gui")]
6use super::{PlotWindow, WindowConfig};
7#[cfg(feature = "gui")]
8use crate::core::PipelineType;
9#[cfg(feature = "gui")]
10use egui_winit::State as EguiState;
11#[cfg(feature = "gui")]
12use glam::{Mat4, Vec2, Vec3, Vec4};
13#[cfg(feature = "gui")]
14use runmat_time::Instant;
15#[cfg(feature = "gui")]
16use std::sync::atomic::{AtomicBool, Ordering};
17#[cfg(feature = "gui")]
18use std::sync::Arc;
19#[cfg(feature = "gui")]
20use tracing::debug;
21#[cfg(feature = "gui")]
22use winit::{dpi::PhysicalSize, event::Event, event_loop::EventLoop, window::WindowBuilder};
23#[cfg(feature = "gui")]
24impl<'window> PlotWindow<'window> {
25 fn update_subplot_camera_aspects_for_rect(&mut self, plot_rect: egui::Rect) {
26 let (rows, cols) = self.plot_renderer.figure_axes_grid();
27 if rows * cols <= 1 {
28 let plot_width = plot_rect.width();
29 let plot_height = plot_rect.height();
30 if plot_width > 0.0 && plot_height > 0.0 {
31 self.plot_renderer
32 .camera_mut()
33 .update_aspect_ratio(plot_width / plot_height);
34 }
35 return;
36 }
37 let rects: Vec<egui::Rect> = if self.plot_overlay.axes_plot_rects().len() == rows * cols {
38 self.plot_overlay.axes_plot_rects().to_vec()
39 } else {
40 self.plot_overlay
41 .compute_subplot_plot_rects(plot_rect, &self.plot_renderer, 1.0)
42 };
43 for (i, r) in rects.iter().enumerate() {
44 let w = r.width();
45 let h = r.height();
46 if w > 0.0 && h > 0.0 {
47 if let Some(cam) = self.plot_renderer.axes_camera_mut(i) {
48 cam.update_aspect_ratio(w / h);
49 }
50 }
51 }
52 }
53
54 pub async fn new(config: WindowConfig) -> Result<Self, Box<dyn std::error::Error>> {
56 let event_loop =
58 EventLoop::new().map_err(|e| format!("Failed to create EventLoop: {e}"))?;
59 let window = WindowBuilder::new()
60 .with_title(&config.title)
61 .with_inner_size(PhysicalSize::new(config.width, config.height))
62 .with_resizable(config.resizable)
63 .with_maximized(config.maximized)
64 .build(&event_loop)?;
65 let window = Arc::new(window);
66
67 let shared_ctx = crate::context::shared_wgpu_context();
69 let (instance, surface, shared_ctx) = if let Some(ctx) = shared_ctx {
70 let surface = ctx.instance.create_surface(window.clone())?;
71 (ctx.instance.clone(), surface, Some(ctx))
72 } else {
73 let instance = Arc::new(wgpu::Instance::new(wgpu::InstanceDescriptor {
74 backends: wgpu::Backends::all(),
75 ..Default::default()
76 }));
77 let surface = instance.create_surface(window.clone())?;
78 (instance, surface, None)
79 };
80
81 let (adapter, device, queue) = if let Some(ctx) = shared_ctx {
82 (ctx.adapter, ctx.device, ctx.queue)
83 } else {
84 let adapter = instance
85 .request_adapter(&wgpu::RequestAdapterOptions {
86 power_preference: wgpu::PowerPreference::HighPerformance,
87 compatible_surface: Some(&surface),
88 force_fallback_adapter: false,
89 })
90 .await
91 .ok_or("Failed to request adapter")?;
92
93 let (device, queue) = adapter
94 .request_device(
95 &wgpu::DeviceDescriptor {
96 label: Some("RunMat Plot Device"),
97 required_features: wgpu::Features::empty(),
98 required_limits: wgpu::Limits::default(),
99 },
100 None,
101 )
102 .await?;
103
104 (Arc::new(adapter), Arc::new(device), Arc::new(queue))
105 };
106
107 let surface_caps = surface.get_capabilities(adapter.as_ref());
109 let surface_format = surface_caps
110 .formats
111 .iter()
112 .find(|f| f.is_srgb())
113 .copied()
114 .unwrap_or(surface_caps.formats[0]);
115
116 let surface_config = wgpu::SurfaceConfiguration {
117 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
118 format: surface_format,
119 width: config.width,
120 height: config.height,
121 present_mode: if config.vsync {
122 wgpu::PresentMode::AutoVsync
123 } else {
124 wgpu::PresentMode::AutoNoVsync
125 },
126 alpha_mode: surface_caps.alpha_modes[0],
127 view_formats: vec![],
128 desired_maximum_frame_latency: 2,
129 };
130 surface.configure(&device, &surface_config);
131
132 let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
134 label: Some("Depth Texture"),
135 size: wgpu::Extent3d {
136 width: config.width,
137 height: config.height,
138 depth_or_array_layers: 1,
139 },
140 mip_level_count: 1,
141 sample_count: 1,
142 dimension: wgpu::TextureDimension::D2,
143 format: wgpu::TextureFormat::Depth32Float,
144 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
145 view_formats: &[],
146 });
147
148 let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
149
150 let plot_renderer =
152 crate::core::PlotRenderer::new(device.clone(), queue.clone(), surface_config).await?;
153 let plot_overlay = crate::gui::PlotOverlay::new();
154
155 let egui_ctx = egui::Context::default();
157
158 let theme = crate::styling::ModernDarkTheme::default();
160 theme.apply_to_egui(&egui_ctx);
161
162 let egui_state = EguiState::new(
163 egui_ctx.clone(),
164 egui::viewport::ViewportId::ROOT,
165 &window,
166 Some(window.scale_factor() as f32),
167 None,
168 );
169
170 let egui_renderer = egui_wgpu::Renderer::new(
171 &device,
172 surface_format,
173 None, 1,
175 );
176
177 Ok(Self {
178 window,
179 event_loop: Some(event_loop),
180 plot_renderer,
181 plot_overlay,
182 surface,
183 depth_texture,
184 depth_view,
185 egui_ctx,
186 egui_state,
187 egui_renderer,
188 config,
189 mouse_position: Vec2::ZERO,
190 is_mouse_over_plot: true,
191 needs_initial_redraw: true,
192 pixels_per_point: 1.0,
193 mouse_left_down: false,
194 active_drag_axes: None,
195 close_signal: None,
196 })
197 }
198
199 pub fn add_test_plot(&mut self) {
201 use crate::core::vertex_utils;
202
203 let x_data: Vec<f64> = (0..100).map(|i| i as f64 * 0.1).collect();
205 let y_data: Vec<f64> = x_data.iter().map(|x| x.sin()).collect();
206
207 let vertices =
209 vertex_utils::create_line_plot(&x_data, &y_data, Vec4::new(0.0, 0.5, 1.0, 1.0));
210
211 let mut render_data = crate::core::RenderData {
213 pipeline_type: PipelineType::Lines,
214 vertices,
215 indices: None,
216 gpu_vertices: None,
217 bounds: None,
218 material: crate::core::Material::default(),
219 draw_calls: vec![crate::core::DrawCall {
220 vertex_offset: 0,
221 vertex_count: (x_data.len() - 1) * 2, index_offset: None,
223 index_count: None,
224 instance_count: 1,
225 }],
226 image: None,
227 };
228
229 render_data.material.albedo = Vec4::new(0.0, 0.5, 1.0, 1.0);
231
232 let node = crate::core::SceneNode {
233 id: 0, name: "Test Line Plot".to_string(),
235 transform: Mat4::IDENTITY,
236 visible: true,
237 cast_shadows: false,
238 receive_shadows: false,
239 axes_index: 0,
240 parent: None,
241 children: Vec::new(),
242 render_data: Some(render_data),
243 bounds: crate::core::BoundingBox::from_points(
244 &x_data
245 .iter()
246 .zip(y_data.iter())
247 .map(|(&x, &y)| Vec3::new(x as f32, y as f32, 0.0))
248 .collect::<Vec<_>>(),
249 ),
250 lod_levels: Vec::new(),
251 current_lod: 0,
252 };
253
254 self.plot_renderer.scene.add_node(node);
255
256 let bounds_min = Vec3::new(-1.0, -1.5, -1.0);
258 let bounds_max = Vec3::new(10.0, 1.5, 1.0);
259 self.plot_renderer
260 .camera_mut()
261 .fit_bounds(bounds_min, bounds_max);
262 }
263
264 pub fn set_figure(&mut self, figure: crate::plots::Figure) {
266 self.plot_renderer.set_figure(figure);
268 }
269
270 pub fn install_close_signal(&mut self, signal: Arc<AtomicBool>) {
272 self.close_signal = Some(signal);
273 }
274
275 pub async fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
277 let event_loop = self
278 .event_loop
279 .take()
280 .ok_or("Event loop already consumed")?;
281 let window = self.window.clone();
282 let mut last_render_time = Instant::now();
283 let close_signal = self.close_signal.clone();
284
285 event_loop.run(move |event, target| {
286 if let Some(signal) = close_signal.as_ref() {
287 if signal.load(Ordering::Relaxed) {
288 target.exit();
289 return;
290 }
291 }
292 target.set_control_flow(winit::event_loop::ControlFlow::Poll);
293
294 static mut MODIFIERS: Option<winit::keyboard::ModifiersState> = None;
296
297 let mut repaint = false;
299 let mut egui_consumed = false;
300 if let Event::WindowEvent { ref event, .. } = event {
301 let response = self.egui_state.on_window_event(&window, event);
302 repaint = response.repaint;
303 egui_consumed = response.consumed;
304 }
305 if repaint {
306 window.request_redraw();
307 }
308
309 match event {
310 winit::event::Event::WindowEvent {
311 window_id,
312 event: winit::event::WindowEvent::ModifiersChanged(mods),
313 } if window_id == window.id() => unsafe {
314 MODIFIERS = Some(mods.state());
315 },
316 winit::event::Event::WindowEvent {
317 window_id,
318 event: winit::event::WindowEvent::CloseRequested,
319 } if window_id == window.id() => {
320 target.exit();
321 }
322
323 winit::event::Event::WindowEvent {
324 window_id,
325 event: winit::event::WindowEvent::Resized(new_size),
326 } if window_id == window.id() => {
327 if new_size.width > 0 && new_size.height > 0 {
329 self.resize(new_size.width, new_size.height);
330 }
331 }
332
333 winit::event::Event::WindowEvent {
334 window_id,
335 event: winit::event::WindowEvent::RedrawRequested,
336 } if window_id == window.id() => {
337 let now = Instant::now();
338 let dt = now - last_render_time;
339 last_render_time = now;
340
341 match self.render(dt) {
342 Ok(_) => {}
343 Err(wgpu::SurfaceError::Lost) => {
344 self.resize(self.config.width, self.config.height)
345 }
346 Err(wgpu::SurfaceError::OutOfMemory) => target.exit(),
347 Err(e) => eprintln!("Render error: {e:?}"),
348 }
349 }
350
351 winit::event::Event::WindowEvent {
353 window_id,
354 event:
355 winit::event::WindowEvent::KeyboardInput {
356 event: key_event, ..
357 },
358 } if window_id == window.id() => {
359 if key_event.state == winit::event::ElementState::Pressed {
360 if let winit::keyboard::PhysicalKey::Code(
361 winit::keyboard::KeyCode::Escape,
362 ) = key_event.physical_key
363 {
364 target.exit();
365 }
366 if let Some(text) = key_event.text {
368 if text == "\u{11}" { }
369 }
370 if let winit::keyboard::PhysicalKey::Code(winit::keyboard::KeyCode::KeyQ) =
372 key_event.physical_key
373 {
374 let mods = unsafe {
375 MODIFIERS.unwrap_or_else(winit::keyboard::ModifiersState::empty)
376 };
377 if mods.super_key() || mods.control_key() {
378 target.exit();
379 }
380 }
381 }
382 }
383
384 winit::event::Event::WindowEvent {
385 window_id,
386 event: winit::event::WindowEvent::MouseInput { button, state, .. },
387 } if window_id == window.id() => {
388 let mut route = !egui_consumed;
390 if let Some(plot_rect) = self.plot_overlay.plot_area() {
391 let ppp = self.pixels_per_point.max(0.5);
392 let mx = self.mouse_position.x;
393 let my = self.mouse_position.y;
394 let px_min_x = plot_rect.min.x * ppp;
395 let px_min_y = plot_rect.min.y * ppp;
396 let px_w = plot_rect.width() * ppp;
397 let px_h = plot_rect.height() * ppp;
398 if mx >= px_min_x
399 && mx <= px_min_x + px_w
400 && my >= px_min_y
401 && my <= px_min_y + px_h
402 {
403 route = true;
404 if let Some(tb) = self.plot_overlay.toolbar_rect() {
405 if my >= tb.min.y * ppp && my <= tb.max.y * ppp {
406 route = false;
407 }
408 }
409 if let Some(sb) = self.plot_overlay.sidebar_rect() {
410 if mx >= sb.min.x * ppp
411 && mx <= sb.max.x * ppp
412 && my >= sb.min.y * ppp
413 && my <= sb.max.y * ppp
414 {
415 route = false;
416 }
417 }
418 }
419 }
420 if route {
421 use winit::event::{ElementState, MouseButton};
423 if button == MouseButton::Left {
424 self.mouse_left_down = state == ElementState::Pressed;
425 }
426 self.handle_mouse_input(button, state);
427 window.request_redraw();
428 }
429 }
430
431 winit::event::Event::WindowEvent {
432 window_id,
433 event: winit::event::WindowEvent::CursorMoved { position, .. },
434 } if window_id == window.id() => {
435 let mut route = !egui_consumed;
436 if let Some(plot_rect) = self.plot_overlay.plot_area() {
437 let ppp = self.pixels_per_point.max(0.5);
438 let mx = position.x as f32;
439 let my = position.y as f32;
440 let px_min_x = plot_rect.min.x * ppp;
441 let px_min_y = plot_rect.min.y * ppp;
442 let px_w = plot_rect.width() * ppp;
443 let px_h = plot_rect.height() * ppp;
444 if mx >= px_min_x
445 && mx <= px_min_x + px_w
446 && my >= px_min_y
447 && my <= px_min_y + px_h
448 {
449 route = true;
450 }
451 }
452 if route {
453 self.handle_mouse_move(position);
454 window.request_redraw();
455 }
456 }
457
458 winit::event::Event::WindowEvent {
459 window_id,
460 event: winit::event::WindowEvent::MouseWheel { delta, .. },
461 } if window_id == window.id() => {
462 let mut route = !egui_consumed;
463 if let Some(plot_rect) = self.plot_overlay.plot_area() {
464 let ppp = self.pixels_per_point.max(0.5);
465 let mx = self.mouse_position.x;
466 let my = self.mouse_position.y;
467 let px_min_x = plot_rect.min.x * ppp;
468 let px_min_y = plot_rect.min.y * ppp;
469 let px_w = plot_rect.width() * ppp;
470 let px_h = plot_rect.height() * ppp;
471 if mx >= px_min_x
472 && mx <= px_min_x + px_w
473 && my >= px_min_y
474 && my <= px_min_y + px_h
475 {
476 route = true;
477 }
478 }
479 if route {
480 self.handle_mouse_scroll(delta);
481 window.request_redraw();
482 }
483 }
484
485 winit::event::Event::AboutToWait => {
486 if self.needs_initial_redraw || repaint {
488 self.needs_initial_redraw = false;
489 window.request_redraw();
490 }
491 }
492
493 _ => {}
494 }
495 })?;
496
497 Ok(())
498 }
499
500 fn resize(&mut self, width: u32, height: u32) {
502 if width == 0 || height == 0 {
503 return; }
505
506 self.config.width = width;
507 self.config.height = height;
508
509 let surface_config = wgpu::SurfaceConfiguration {
511 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
512 format: self.plot_renderer.wgpu_renderer.surface_config.format,
513 width,
514 height,
515 present_mode: if self.config.vsync {
516 wgpu::PresentMode::AutoVsync
517 } else {
518 wgpu::PresentMode::AutoNoVsync
519 },
520 alpha_mode: wgpu::CompositeAlphaMode::Auto,
521 view_formats: vec![],
522 desired_maximum_frame_latency: 2,
523 };
524
525 self.plot_renderer.wgpu_renderer.surface_config = surface_config.clone();
527 self.surface
528 .configure(&self.plot_renderer.wgpu_renderer.device, &surface_config);
529
530 self.depth_texture =
532 self.plot_renderer
533 .wgpu_renderer
534 .device
535 .create_texture(&wgpu::TextureDescriptor {
536 label: Some("Depth Texture"),
537 size: wgpu::Extent3d {
538 width,
539 height,
540 depth_or_array_layers: 1,
541 },
542 mip_level_count: 1,
543 sample_count: 1,
544 dimension: wgpu::TextureDimension::D2,
545 format: wgpu::TextureFormat::Depth32Float,
546 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
547 | wgpu::TextureUsages::TEXTURE_BINDING,
548 view_formats: &[],
549 });
550
551 self.depth_view = self
552 .depth_texture
553 .create_view(&wgpu::TextureViewDescriptor::default());
554
555 let (rows, cols) = self.plot_renderer.figure_axes_grid();
556 if rows * cols > 1 {
557 if let Some(plot_rect) = self.plot_overlay.plot_area() {
558 self.update_subplot_camera_aspects_for_rect(plot_rect);
559 }
560 } else {
561 self.plot_renderer
562 .camera_mut()
563 .update_aspect_ratio(width as f32 / height as f32);
564 }
565 }
566
567 fn render(&mut self, _dt: std::time::Duration) -> Result<(), wgpu::SurfaceError> {
569 let output = self.surface.get_current_texture()?;
571 let view = output
572 .texture
573 .create_view(&wgpu::TextureViewDescriptor::default());
574
575 let mut encoder = self
579 .plot_renderer
580 .wgpu_renderer
581 .device
582 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
583 label: Some("Render Encoder"),
584 });
585
586 let raw_input = self.egui_state.take_egui_input(&self.window);
588
589 let scene_stats = self.plot_renderer.scene.statistics();
591 let _camera_pos = self
592 .plot_renderer
593 .axes_camera(0)
594 .unwrap_or_else(|| self.plot_renderer.camera())
595 .position;
596
597 let mut plot_area: Option<egui::Rect> = None;
599
600 let _ = self.plot_renderer.calculate_data_bounds();
602
603 let full_output = self.egui_ctx.run(raw_input, |ctx| {
604 let overlay_config = OverlayConfig {
606 show_grid: self.plot_renderer.overlay_show_grid(),
608 show_axes: true,
609 show_title: true,
610 title: self
611 .plot_renderer
612 .overlay_title()
613 .cloned()
614 .or(Some("Plot".to_string())),
615 x_label: self
616 .plot_renderer
617 .overlay_x_label()
618 .cloned()
619 .or(Some("X".to_string())),
620 y_label: self
621 .plot_renderer
622 .overlay_y_label()
623 .cloned()
624 .or(Some("Y".to_string())),
625 ..Default::default()
626 };
627 let overlay_metrics = OverlayMetrics {
628 vertex_count: scene_stats.total_vertices,
629 triangle_count: scene_stats.total_triangles,
630 render_time_ms: 0.0, fps: 60.0, };
633
634 let frame_info = self.plot_overlay.render(
635 ctx,
636 &self.plot_renderer,
637 &overlay_config,
638 overlay_metrics,
639 );
640 plot_area = frame_info.plot_area;
641 });
642
643 let ppp_now = full_output.pixels_per_point;
645 if ppp_now > 0.0 {
646 self.pixels_per_point = ppp_now;
649 }
650 let _data_bounds = self.plot_renderer.data_bounds();
652
653 let (save_png, save_svg, reset_view, toggle_grid_opt, toggle_legend_opt) =
655 self.plot_overlay.take_toolbar_actions();
656 if let Some(show) = toggle_grid_opt {
657 if let Some(mut fig) = self.plot_renderer.last_figure.clone() {
659 let (rows, cols) = fig.axes_grid();
660 let axes_count = (rows * cols).max(1);
661 for idx in 0..axes_count {
662 fig.set_axes_grid_enabled(idx, show);
663 }
664 self.plot_renderer.set_figure(fig);
665 }
666 }
667 if let Some(show) = toggle_legend_opt {
668 if let Some(mut fig) = self.plot_renderer.last_figure.clone() {
669 fig.legend_enabled = show;
670 self.plot_renderer.set_figure(fig);
671 }
672 }
673 if reset_view {
674 self.plot_renderer.fit_extents();
676 }
677 if save_png || save_svg {
678 #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
680 {
681 if save_png {
682 if let Some(path) = rfd::FileDialog::new()
683 .add_filter("PNG Image", &["png"])
684 .set_file_name("plot.png")
685 .save_file()
686 {
687 let mut fig_for_save = self.plot_renderer.export_figure_clone();
688 let _ = std::thread::spawn(move || {
689 let rt = tokio::runtime::Builder::new_current_thread()
690 .enable_all()
691 .build();
692 if let Ok(rt) = rt {
693 rt.block_on(async move {
694 if let Ok(exporter) =
695 crate::export::image::ImageExporter::new().await
696 {
697 let _ = exporter.export_png(&mut fig_for_save, &path).await;
698 }
699 });
700 }
701 });
702 }
703 }
704 if save_svg {
705 if let Some(path) = rfd::FileDialog::new()
706 .add_filter("SVG", &["svg"])
707 .set_file_name("plot.svg")
708 .save_file()
709 {
710 let mut fig_for_save = self.plot_renderer.export_figure_clone();
711 let exporter = crate::export::vector::VectorExporter::new();
712 let _ = exporter.export_svg(&mut fig_for_save, &path);
713 }
714 }
715 }
716 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
717 {
718 if save_png {
720 let mut fig = self.plot_renderer.export_figure_clone();
721 let tmp = std::env::temp_dir().join("runmat_export.png");
722 let _ = std::thread::spawn(move || {
723 let rt = tokio::runtime::Builder::new_current_thread()
724 .enable_all()
725 .build();
726 if let Ok(rt) = rt {
727 rt.block_on(async move {
728 if let Ok(exporter) =
729 crate::export::image::ImageExporter::new().await
730 {
731 let _ = exporter.export_png(&mut fig, &tmp).await;
732 }
733 });
734 }
735 });
736 }
737 if save_svg {
738 let mut fig = self.plot_renderer.export_figure_clone();
739 let tmp = std::env::temp_dir().join("runmat_export.svg");
740 let exporter = crate::export::vector::VectorExporter::new();
741 let _ = exporter.export_svg(&mut fig, &tmp);
742 }
743 }
744 }
745
746 if let Some(plot_rect) = plot_area {
748 self.update_subplot_camera_aspects_for_rect(plot_rect);
750 }
751
752 self.egui_state
753 .handle_platform_output(&self.window, full_output.platform_output);
754
755 let tris = self
756 .egui_ctx
757 .tessellate(full_output.shapes, full_output.pixels_per_point);
758 for (id, image_delta) in &full_output.textures_delta.set {
759 self.egui_renderer.update_texture(
760 &self.plot_renderer.wgpu_renderer.device,
761 &self.plot_renderer.wgpu_renderer.queue,
762 *id,
763 image_delta,
764 );
765 }
766
767 self.egui_renderer.update_buffers(
768 &self.plot_renderer.wgpu_renderer.device,
769 &self.plot_renderer.wgpu_renderer.queue,
770 &mut encoder,
771 &tris,
772 &egui_wgpu::ScreenDescriptor {
773 size_in_pixels: [self.config.width, self.config.height],
774 pixels_per_point: full_output.pixels_per_point,
775 },
776 );
777
778 if let Some(plot_rect) = plot_area {
780 let ppp = self.pixels_per_point.max(0.5);
782 let (rows, cols) = self.plot_renderer.figure_axes_grid();
783 let axes_plot_rects = if rows * cols > 1 {
784 if self.plot_overlay.axes_plot_rects().len() == rows * cols {
785 self.plot_overlay.axes_plot_rects().to_vec()
786 } else {
787 self.plot_overlay.compute_subplot_plot_rects_snapped(
788 plot_rect,
789 &self.plot_renderer,
790 1.0,
791 ppp,
792 )
793 }
794 } else {
795 vec![PlotOverlay::snap_rect_to_pixels(plot_rect, ppp)]
796 };
797 let axes_plot_sizes_px: Vec<(u32, u32)> = axes_plot_rects
798 .iter()
799 .map(|r| {
800 (
801 (r.width() * ppp).round().max(1.0) as u32,
802 (r.height() * ppp).round().max(1.0) as u32,
803 )
804 })
805 .collect();
806 self.plot_renderer
807 .ensure_scene_viewport_dependent_geometry_for_axes(&axes_plot_sizes_px);
808 let primary_rect = axes_plot_rects.first().copied().unwrap_or(plot_rect);
809 let vx = (primary_rect.min.x * ppp).round();
810 let vy = (primary_rect.min.y * ppp).round();
811 let vw = (primary_rect.width() * ppp).round().max(1.0);
812 let vh = (primary_rect.height() * ppp).round().max(1.0);
813
814 let sw = self.config.width as f32;
816 let sh = self.config.height as f32;
817 let cvx = vx.max(0.0);
818 let cvy = vy.max(0.0);
819 let mut cvw = vw;
820 let mut cvh = vh;
821 if cvx + cvw > sw {
822 cvw = (sw - cvx).max(1.0);
823 }
824 if cvy + cvh > sh {
825 cvh = (sh - cvy).max(1.0);
826 }
827
828 let scissor = (cvx as u32, cvy as u32, cvw as u32, cvh as u32);
830
831 {
832 let bg = self
833 .plot_renderer
834 .theme
835 .build_theme()
836 .get_background_color();
837 let clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
838 label: Some("runmat-window-overlay-clear"),
839 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
840 view: &view,
841 resolve_target: None,
842 ops: wgpu::Operations {
843 load: wgpu::LoadOp::Clear(wgpu::Color {
844 r: bg.x as f64,
845 g: bg.y as f64,
846 b: bg.z as f64,
847 a: bg.w as f64,
848 }),
849 store: wgpu::StoreOp::Store,
850 },
851 })],
852 depth_stencil_attachment: None,
853 occlusion_query_set: None,
854 timestamp_writes: None,
855 });
856 drop(clear_pass);
857 }
858
859 if rows * cols > 1 {
861 let mut viewports: Vec<(u32, u32, u32, u32)> = Vec::new();
862 let mut hovered_axes: Option<usize> = None;
863 let mouse_pos = self.mouse_position;
865 for (i, r) in axes_plot_rects.iter().enumerate() {
866 let rx = (r.min.x * ppp).round();
867 let ry = (r.min.y * ppp).round();
868 let rw = (r.width() * ppp).round().max(1.0);
869 let rh = (r.height() * ppp).round().max(1.0);
870 let svx = rx.max(0.0);
872 let svy = ry.max(0.0);
873 let mut svw = rw;
874 let mut svh = rh;
875 if svx + svw > sw {
876 svw = (sw - svx).max(1.0);
877 }
878 if svy + svh > sh {
879 svh = (sh - svy).max(1.0);
880 }
881 debug!(
882 target: "runmat_plot.axes_viewport_native",
883 axes_index = i,
884 viewport_x = svx as u32,
885 viewport_y = svy as u32,
886 viewport_w = svw as u32,
887 viewport_h = svh as u32,
888 content_min_x = r.min.x,
889 content_min_y = r.min.y,
890 content_max_x = r.max.x,
891 content_max_y = r.max.y,
892 pixels_per_point = ppp,
893 "prepared native subplot viewport"
894 );
895 viewports.push((svx as u32, svy as u32, svw as u32, svh as u32));
896
897 if hovered_axes.is_none() {
898 let px_min_x = rx;
899 let px_min_y = ry;
900 if mouse_pos.x >= px_min_x
901 && mouse_pos.x <= px_min_x + rw
902 && mouse_pos.y >= px_min_y
903 && mouse_pos.y <= px_min_y + rh
904 {
905 hovered_axes = Some(i);
906 }
907 }
908 }
909 let subplot_cfg = crate::core::plot_renderer::PlotRenderConfig {
911 width: self.plot_renderer.wgpu_renderer.surface_config.width.max(1),
912 height: self
913 .plot_renderer
914 .wgpu_renderer
915 .surface_config
916 .height
917 .max(1),
918 msaa_samples: 4,
919 theme: self.plot_renderer.theme.clone(),
920 background_color: self
921 .plot_renderer
922 .theme
923 .build_theme()
924 .get_background_color(),
925 ..Default::default()
926 };
927 let _ = self.plot_renderer.render_axes_to_viewports(
928 &mut encoder,
929 &view,
930 &viewports,
931 4,
932 &subplot_cfg,
933 );
934 } else {
935 debug!(
936 target: "runmat_plot.axes_viewport_native",
937 axes_index = 0,
938 viewport_x = scissor.0,
939 viewport_y = scissor.1,
940 viewport_w = scissor.2,
941 viewport_h = scissor.3,
942 content_min_x = primary_rect.min.x,
943 content_min_y = primary_rect.min.y,
944 content_max_x = primary_rect.max.x,
945 content_max_y = primary_rect.max.y,
946 pixels_per_point = ppp,
947 "prepared native single-axes viewport"
948 );
949 let cfg = crate::core::plot_renderer::PlotRenderConfig {
951 width: scissor.2,
952 height: scissor.3,
953 msaa_samples: 4,
954 theme: self.plot_renderer.theme.clone(),
955 background_color: self
956 .plot_renderer
957 .theme
958 .build_theme()
959 .get_background_color(),
960 ..Default::default()
961 };
962 let cam = self.plot_renderer.camera().clone();
963 let _ = self.plot_renderer.render_camera_to_viewport(
964 &mut encoder,
965 &view,
966 scissor,
967 &cfg,
968 &cam,
969 0,
970 true,
971 );
972 }
973 }
974
975 {
977 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
978 label: Some("Egui Render Pass"),
979 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
980 view: &view,
981 resolve_target: None,
982 ops: wgpu::Operations {
983 load: wgpu::LoadOp::Load,
984 store: wgpu::StoreOp::Store,
985 },
986 })],
987 depth_stencil_attachment: None,
988 occlusion_query_set: None,
989 timestamp_writes: None,
990 });
991 self.egui_renderer.render(
992 &mut render_pass,
993 &tris,
994 &egui_wgpu::ScreenDescriptor {
995 size_in_pixels: [self.config.width, self.config.height],
996 pixels_per_point: full_output.pixels_per_point,
997 },
998 );
999 }
1000
1001 for id in &full_output.textures_delta.free {
1002 self.egui_renderer.free_texture(id);
1003 }
1004
1005 self.plot_renderer
1007 .wgpu_renderer
1008 .queue
1009 .submit(std::iter::once(encoder.finish()));
1010 output.present();
1011
1012 Ok(())
1013 }
1014
1015 fn handle_mouse_input(
1017 &mut self,
1018 button: winit::event::MouseButton,
1019 state: winit::event::ElementState,
1020 ) {
1021 use winit::event::{ElementState, MouseButton};
1022
1023 match (button, state) {
1024 (MouseButton::Left, ElementState::Pressed) => {
1025 self.is_mouse_over_plot = false;
1027 self.active_drag_axes = None;
1028 if let Some(plot_rect) = self.plot_overlay.plot_area() {
1029 let mx = self.mouse_position.x;
1030 let my = self.mouse_position.y;
1031 let (rows, cols) = self.plot_renderer.figure_axes_grid();
1032 if rows * cols > 1 {
1033 let rects = self.plot_overlay.compute_subplot_plot_rects(
1034 plot_rect,
1035 &self.plot_renderer,
1036 1.0,
1037 );
1038 for (i, r) in rects.into_iter().enumerate() {
1039 let rx = r.min.x * self.pixels_per_point;
1040 let ry = r.min.y * self.pixels_per_point;
1041 let rw = r.width() * self.pixels_per_point;
1042 let rh = r.height() * self.pixels_per_point;
1043 if mx >= rx && mx <= rx + rw && my >= ry && my <= ry + rh {
1044 self.is_mouse_over_plot = true;
1045 self.active_drag_axes = Some(i);
1046 break;
1047 }
1048 }
1049 } else {
1050 let ppp = self.pixels_per_point.max(0.5);
1051 let px_min_x = plot_rect.min.x * ppp;
1052 let px_min_y = plot_rect.min.y * ppp;
1053 let px_w = plot_rect.width() * ppp;
1054 let px_h = plot_rect.height() * ppp;
1055 self.is_mouse_over_plot = mx >= px_min_x
1056 && mx <= px_min_x + px_w
1057 && my >= px_min_y
1058 && my <= px_min_y + px_h;
1059 if self.is_mouse_over_plot {
1060 self.active_drag_axes = Some(0);
1061 }
1062 }
1063 }
1064 }
1065 (MouseButton::Left, ElementState::Released) => {
1066 self.is_mouse_over_plot = false;
1067 self.active_drag_axes = None;
1068 }
1069 _ => {}
1070 }
1071 }
1072
1073 fn handle_mouse_move(&mut self, position: winit::dpi::PhysicalPosition<f64>) {
1075 let new_position = glam::Vec2::new(position.x as f32, position.y as f32);
1076 let delta = if self.mouse_left_down {
1077 new_position - self.mouse_position
1078 } else {
1079 glam::Vec2::ZERO
1080 };
1081 self.mouse_position = new_position;
1082
1083 if self.is_mouse_over_plot && delta.length() > 0.0 {
1085 if let Some(plot_rect) = self.plot_overlay.plot_area() {
1086 let (rows, cols) = self.plot_renderer.figure_axes_grid();
1087 if rows * cols > 1 {
1088 if let Some(i) = self.active_drag_axes {
1090 let rects = self.plot_overlay.compute_subplot_plot_rects(
1091 plot_rect,
1092 &self.plot_renderer,
1093 1.0,
1094 );
1095 if let Some(r) = rects.get(i) {
1096 let rw = r.width() * self.pixels_per_point;
1097 let rh = r.height() * self.pixels_per_point;
1098 if let Some(cam) = self.plot_renderer.axes_camera_mut(i) {
1099 if let crate::core::camera::ProjectionType::Orthographic {
1100 left,
1101 right,
1102 bottom,
1103 top,
1104 ..
1105 } = cam.projection
1106 {
1107 let pw = rw.max(1.0);
1108 let ph = rh.max(1.0);
1109 let world_w = right - left;
1110 let world_h = top - bottom;
1111 let dx_world = (delta.x / pw) * world_w;
1112 let dy_world = (delta.y / ph) * world_h;
1113 cam.projection =
1114 crate::core::camera::ProjectionType::Orthographic {
1115 left: left - dx_world,
1116 right: right - dx_world,
1117 bottom: bottom + dy_world,
1118 top: top + dy_world,
1119 near: -1.0,
1120 far: 1.0,
1121 };
1122 cam.mark_dirty();
1123 self.plot_renderer.note_axes_camera_interaction(i);
1124 }
1125 }
1126 }
1127 }
1128 } else {
1129 let cam = self.plot_renderer.camera_mut();
1130 if let crate::core::camera::ProjectionType::Orthographic {
1131 left,
1132 right,
1133 bottom,
1134 top,
1135 ..
1136 } = &mut cam.projection
1137 {
1138 let pw = (plot_rect.width() * self.pixels_per_point).max(1.0);
1139 let ph = (plot_rect.height() * self.pixels_per_point).max(1.0);
1140 let world_w = *right - *left;
1141 let world_h = *top - *bottom;
1142 let dx_world = (delta.x / pw) * world_w;
1143 let dy_world = (delta.y / ph) * world_h;
1144 *left -= dx_world;
1145 *right -= dx_world;
1146 *bottom += dy_world;
1147 *top += dy_world;
1148 cam.mark_dirty();
1149 self.plot_renderer.note_axes_camera_interaction(0);
1150 }
1151 }
1152 }
1153 }
1154 }
1155
1156 fn handle_mouse_scroll(&mut self, delta: winit::event::MouseScrollDelta) {
1158 let scroll_delta = match delta {
1159 winit::event::MouseScrollDelta::LineDelta(_, y) => y,
1160 winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y as f32 / 100.0,
1161 };
1162
1163 if let Some(plot_rect) = self.plot_overlay.plot_area() {
1165 let (rows, cols) = self.plot_renderer.figure_axes_grid();
1166 if rows * cols > 1 {
1167 let rects = self.plot_overlay.compute_subplot_plot_rects(
1168 plot_rect,
1169 &self.plot_renderer,
1170 1.0,
1171 );
1172 for (i, r) in rects.iter().enumerate() {
1173 let rx = r.min.x * self.pixels_per_point;
1174 let ry = r.min.y * self.pixels_per_point;
1175 let rw = r.width() * self.pixels_per_point;
1176 let rh = r.height() * self.pixels_per_point;
1177 let mx = self.mouse_position.x;
1178 let my = self.mouse_position.y;
1179 if mx >= rx && mx <= rx + rw && my >= ry && my <= ry + rh {
1180 if let Some(cam) = self.plot_renderer.axes_camera_mut(i) {
1181 if let crate::core::camera::ProjectionType::Orthographic {
1182 left,
1183 right,
1184 bottom,
1185 top,
1186 ..
1187 } = cam.projection
1188 {
1189 let factor = (1.0 - scroll_delta * 0.1).clamp(0.2, 5.0);
1190 let tx = (mx - rx) / rw;
1191 let ty = (my - ry) / rh;
1192 let w = right - left;
1193 let h = top - bottom;
1194 let pivot_x = left + tx * w;
1195 let pivot_y = top - ty * h;
1196 let new_left = pivot_x - (pivot_x - left) * factor;
1197 let new_right = pivot_x + (right - pivot_x) * factor;
1198 let new_bottom = pivot_y - (pivot_y - bottom) * factor;
1199 let new_top = pivot_y + (top - pivot_y) * factor;
1200 cam.projection =
1201 crate::core::camera::ProjectionType::Orthographic {
1202 left: new_left,
1203 right: new_right,
1204 bottom: new_bottom,
1205 top: new_top,
1206 near: -1.0,
1207 far: 1.0,
1208 };
1209 cam.mark_dirty();
1210 self.plot_renderer.note_axes_camera_interaction(i);
1211 }
1212 }
1213 break;
1214 }
1215 }
1216 } else {
1217 let cam = self.plot_renderer.camera_mut();
1218 if let crate::core::camera::ProjectionType::Orthographic {
1219 left,
1220 right,
1221 bottom,
1222 top,
1223 ..
1224 } = &mut cam.projection
1225 {
1226 let factor = (1.0 - scroll_delta * 0.1).clamp(0.2, 5.0);
1227 let px_min_x = plot_rect.min.x * self.pixels_per_point;
1228 let px_min_y = plot_rect.min.y * self.pixels_per_point;
1229 let px_w = plot_rect.width() * self.pixels_per_point;
1230 let px_h = plot_rect.height() * self.pixels_per_point;
1231 let mx = self.mouse_position.x;
1232 let my = self.mouse_position.y;
1233 let mut pivot_x = (*left + *right) * 0.5;
1234 let mut pivot_y = (*bottom + *top) * 0.5;
1235 if mx >= px_min_x
1236 && mx <= px_min_x + px_w
1237 && my >= px_min_y
1238 && my <= px_min_y + px_h
1239 {
1240 let tx = (mx - px_min_x) / px_w;
1241 let ty = (my - px_min_y) / px_h;
1242 let w = *right - *left;
1243 let h = *top - *bottom;
1244 pivot_x = *left + tx * w;
1245 pivot_y = *top - ty * h;
1246 }
1247 let new_left = pivot_x - (pivot_x - *left) * factor;
1248 let new_right = pivot_x + (*right - pivot_x) * factor;
1249 let new_bottom = pivot_y - (pivot_y - *bottom) * factor;
1250 let new_top = pivot_y + (*top - pivot_y) * factor;
1251 *left = new_left;
1252 *right = new_right;
1253 *bottom = new_bottom;
1254 *top = new_top;
1255 cam.mark_dirty();
1256 self.plot_renderer.note_axes_camera_interaction(0);
1257 }
1258 }
1259 }
1260 }
1261}