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, warn};
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 if save_svg {
679 warn!("SVG export is no longer supported");
680 }
681 #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
683 {
684 if save_png {
685 if let Some(path) = rfd::FileDialog::new()
686 .add_filter("PNG Image", &["png"])
687 .set_file_name("plot.png")
688 .save_file()
689 {
690 let mut fig_for_save = self.plot_renderer.export_figure_clone();
691 let _ = std::thread::spawn(move || {
692 let rt = tokio::runtime::Builder::new_current_thread()
693 .enable_all()
694 .build();
695 if let Ok(rt) = rt {
696 rt.block_on(async move {
697 if let Ok(exporter) =
698 crate::export::image::ImageExporter::new().await
699 {
700 let _ = exporter.export_png(&mut fig_for_save, &path).await;
701 }
702 });
703 }
704 });
705 }
706 }
707 }
708 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
709 {
710 if save_png {
712 let mut fig = self.plot_renderer.export_figure_clone();
713 let tmp = std::env::temp_dir().join("runmat_export.png");
714 let _ = std::thread::spawn(move || {
715 let rt = tokio::runtime::Builder::new_current_thread()
716 .enable_all()
717 .build();
718 if let Ok(rt) = rt {
719 rt.block_on(async move {
720 if let Ok(exporter) =
721 crate::export::image::ImageExporter::new().await
722 {
723 let _ = exporter.export_png(&mut fig, &tmp).await;
724 }
725 });
726 }
727 });
728 }
729 }
730 }
731
732 if let Some(plot_rect) = plot_area {
734 self.update_subplot_camera_aspects_for_rect(plot_rect);
736 }
737
738 self.egui_state
739 .handle_platform_output(&self.window, full_output.platform_output);
740
741 let tris = self
742 .egui_ctx
743 .tessellate(full_output.shapes, full_output.pixels_per_point);
744 for (id, image_delta) in &full_output.textures_delta.set {
745 self.egui_renderer.update_texture(
746 &self.plot_renderer.wgpu_renderer.device,
747 &self.plot_renderer.wgpu_renderer.queue,
748 *id,
749 image_delta,
750 );
751 }
752
753 self.egui_renderer.update_buffers(
754 &self.plot_renderer.wgpu_renderer.device,
755 &self.plot_renderer.wgpu_renderer.queue,
756 &mut encoder,
757 &tris,
758 &egui_wgpu::ScreenDescriptor {
759 size_in_pixels: [self.config.width, self.config.height],
760 pixels_per_point: full_output.pixels_per_point,
761 },
762 );
763
764 if let Some(plot_rect) = plot_area {
766 let ppp = self.pixels_per_point.max(0.5);
768 let (rows, cols) = self.plot_renderer.figure_axes_grid();
769 let axes_plot_rects = if rows * cols > 1 {
770 if self.plot_overlay.axes_plot_rects().len() == rows * cols {
771 self.plot_overlay.axes_plot_rects().to_vec()
772 } else {
773 self.plot_overlay.compute_subplot_plot_rects_snapped(
774 plot_rect,
775 &self.plot_renderer,
776 1.0,
777 ppp,
778 )
779 }
780 } else {
781 vec![PlotOverlay::snap_rect_to_pixels(plot_rect, ppp)]
782 };
783 let axes_plot_sizes_px: Vec<(u32, u32)> = axes_plot_rects
784 .iter()
785 .map(|r| {
786 (
787 (r.width() * ppp).round().max(1.0) as u32,
788 (r.height() * ppp).round().max(1.0) as u32,
789 )
790 })
791 .collect();
792 self.plot_renderer
793 .ensure_scene_viewport_dependent_geometry_for_axes(&axes_plot_sizes_px);
794 let primary_rect = axes_plot_rects.first().copied().unwrap_or(plot_rect);
795 let vx = (primary_rect.min.x * ppp).round();
796 let vy = (primary_rect.min.y * ppp).round();
797 let vw = (primary_rect.width() * ppp).round().max(1.0);
798 let vh = (primary_rect.height() * ppp).round().max(1.0);
799
800 let sw = self.config.width as f32;
802 let sh = self.config.height as f32;
803 let cvx = vx.max(0.0);
804 let cvy = vy.max(0.0);
805 let mut cvw = vw;
806 let mut cvh = vh;
807 if cvx + cvw > sw {
808 cvw = (sw - cvx).max(1.0);
809 }
810 if cvy + cvh > sh {
811 cvh = (sh - cvy).max(1.0);
812 }
813
814 let scissor = (cvx as u32, cvy as u32, cvw as u32, cvh as u32);
816
817 {
818 let bg = self
819 .plot_renderer
820 .theme
821 .build_theme()
822 .get_background_color();
823 let clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
824 label: Some("runmat-window-overlay-clear"),
825 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
826 view: &view,
827 resolve_target: None,
828 ops: wgpu::Operations {
829 load: wgpu::LoadOp::Clear(wgpu::Color {
830 r: bg.x as f64,
831 g: bg.y as f64,
832 b: bg.z as f64,
833 a: bg.w as f64,
834 }),
835 store: wgpu::StoreOp::Store,
836 },
837 })],
838 depth_stencil_attachment: None,
839 occlusion_query_set: None,
840 timestamp_writes: None,
841 });
842 drop(clear_pass);
843 }
844
845 if rows * cols > 1 {
847 let mut viewports: Vec<(u32, u32, u32, u32)> = Vec::new();
848 let mut hovered_axes: Option<usize> = None;
849 let mouse_pos = self.mouse_position;
851 for (i, r) in axes_plot_rects.iter().enumerate() {
852 let rx = (r.min.x * ppp).round();
853 let ry = (r.min.y * ppp).round();
854 let rw = (r.width() * ppp).round().max(1.0);
855 let rh = (r.height() * ppp).round().max(1.0);
856 let svx = rx.max(0.0);
858 let svy = ry.max(0.0);
859 let mut svw = rw;
860 let mut svh = rh;
861 if svx + svw > sw {
862 svw = (sw - svx).max(1.0);
863 }
864 if svy + svh > sh {
865 svh = (sh - svy).max(1.0);
866 }
867 debug!(
868 target: "runmat_plot.axes_viewport_native",
869 axes_index = i,
870 viewport_x = svx as u32,
871 viewport_y = svy as u32,
872 viewport_w = svw as u32,
873 viewport_h = svh as u32,
874 content_min_x = r.min.x,
875 content_min_y = r.min.y,
876 content_max_x = r.max.x,
877 content_max_y = r.max.y,
878 pixels_per_point = ppp,
879 "prepared native subplot viewport"
880 );
881 viewports.push((svx as u32, svy as u32, svw as u32, svh as u32));
882
883 if hovered_axes.is_none() {
884 let px_min_x = rx;
885 let px_min_y = ry;
886 if mouse_pos.x >= px_min_x
887 && mouse_pos.x <= px_min_x + rw
888 && mouse_pos.y >= px_min_y
889 && mouse_pos.y <= px_min_y + rh
890 {
891 hovered_axes = Some(i);
892 }
893 }
894 }
895 let subplot_cfg = crate::core::plot_renderer::PlotRenderConfig {
897 width: self.plot_renderer.wgpu_renderer.surface_config.width.max(1),
898 height: self
899 .plot_renderer
900 .wgpu_renderer
901 .surface_config
902 .height
903 .max(1),
904 msaa_samples: 4,
905 theme: self.plot_renderer.theme.clone(),
906 background_color: self
907 .plot_renderer
908 .theme
909 .build_theme()
910 .get_background_color(),
911 ..Default::default()
912 };
913 let _ = self.plot_renderer.render_axes_to_viewports(
914 &mut encoder,
915 &view,
916 &viewports,
917 4,
918 &subplot_cfg,
919 );
920 } else {
921 debug!(
922 target: "runmat_plot.axes_viewport_native",
923 axes_index = 0,
924 viewport_x = scissor.0,
925 viewport_y = scissor.1,
926 viewport_w = scissor.2,
927 viewport_h = scissor.3,
928 content_min_x = primary_rect.min.x,
929 content_min_y = primary_rect.min.y,
930 content_max_x = primary_rect.max.x,
931 content_max_y = primary_rect.max.y,
932 pixels_per_point = ppp,
933 "prepared native single-axes viewport"
934 );
935 let cfg = crate::core::plot_renderer::PlotRenderConfig {
937 width: scissor.2,
938 height: scissor.3,
939 msaa_samples: 4,
940 theme: self.plot_renderer.theme.clone(),
941 background_color: self
942 .plot_renderer
943 .theme
944 .build_theme()
945 .get_background_color(),
946 ..Default::default()
947 };
948 let cam = self.plot_renderer.camera().clone();
949 let _ = self.plot_renderer.render_camera_to_viewport(
950 &mut encoder,
951 &view,
952 scissor,
953 &cfg,
954 &cam,
955 0,
956 true,
957 );
958 }
959 }
960
961 {
963 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
964 label: Some("Egui Render Pass"),
965 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
966 view: &view,
967 resolve_target: None,
968 ops: wgpu::Operations {
969 load: wgpu::LoadOp::Load,
970 store: wgpu::StoreOp::Store,
971 },
972 })],
973 depth_stencil_attachment: None,
974 occlusion_query_set: None,
975 timestamp_writes: None,
976 });
977 self.egui_renderer.render(
978 &mut render_pass,
979 &tris,
980 &egui_wgpu::ScreenDescriptor {
981 size_in_pixels: [self.config.width, self.config.height],
982 pixels_per_point: full_output.pixels_per_point,
983 },
984 );
985 }
986
987 for id in &full_output.textures_delta.free {
988 self.egui_renderer.free_texture(id);
989 }
990
991 self.plot_renderer
993 .wgpu_renderer
994 .queue
995 .submit(std::iter::once(encoder.finish()));
996 output.present();
997
998 Ok(())
999 }
1000
1001 fn handle_mouse_input(
1003 &mut self,
1004 button: winit::event::MouseButton,
1005 state: winit::event::ElementState,
1006 ) {
1007 use winit::event::{ElementState, MouseButton};
1008
1009 match (button, state) {
1010 (MouseButton::Left, ElementState::Pressed) => {
1011 self.is_mouse_over_plot = false;
1013 self.active_drag_axes = None;
1014 if let Some(plot_rect) = self.plot_overlay.plot_area() {
1015 let mx = self.mouse_position.x;
1016 let my = self.mouse_position.y;
1017 let (rows, cols) = self.plot_renderer.figure_axes_grid();
1018 if rows * cols > 1 {
1019 let rects = self.plot_overlay.compute_subplot_plot_rects(
1020 plot_rect,
1021 &self.plot_renderer,
1022 1.0,
1023 );
1024 for (i, r) in rects.into_iter().enumerate() {
1025 let rx = r.min.x * self.pixels_per_point;
1026 let ry = r.min.y * self.pixels_per_point;
1027 let rw = r.width() * self.pixels_per_point;
1028 let rh = r.height() * self.pixels_per_point;
1029 if mx >= rx && mx <= rx + rw && my >= ry && my <= ry + rh {
1030 self.is_mouse_over_plot = true;
1031 self.active_drag_axes = Some(i);
1032 break;
1033 }
1034 }
1035 } else {
1036 let ppp = self.pixels_per_point.max(0.5);
1037 let px_min_x = plot_rect.min.x * ppp;
1038 let px_min_y = plot_rect.min.y * ppp;
1039 let px_w = plot_rect.width() * ppp;
1040 let px_h = plot_rect.height() * ppp;
1041 self.is_mouse_over_plot = mx >= px_min_x
1042 && mx <= px_min_x + px_w
1043 && my >= px_min_y
1044 && my <= px_min_y + px_h;
1045 if self.is_mouse_over_plot {
1046 self.active_drag_axes = Some(0);
1047 }
1048 }
1049 }
1050 }
1051 (MouseButton::Left, ElementState::Released) => {
1052 self.is_mouse_over_plot = false;
1053 self.active_drag_axes = None;
1054 }
1055 _ => {}
1056 }
1057 }
1058
1059 fn handle_mouse_move(&mut self, position: winit::dpi::PhysicalPosition<f64>) {
1061 let new_position = glam::Vec2::new(position.x as f32, position.y as f32);
1062 let delta = if self.mouse_left_down {
1063 new_position - self.mouse_position
1064 } else {
1065 glam::Vec2::ZERO
1066 };
1067 self.mouse_position = new_position;
1068
1069 if self.is_mouse_over_plot && delta.length() > 0.0 {
1071 if let Some(plot_rect) = self.plot_overlay.plot_area() {
1072 let (rows, cols) = self.plot_renderer.figure_axes_grid();
1073 if rows * cols > 1 {
1074 if let Some(i) = self.active_drag_axes {
1076 let rects = self.plot_overlay.compute_subplot_plot_rects(
1077 plot_rect,
1078 &self.plot_renderer,
1079 1.0,
1080 );
1081 if let Some(r) = rects.get(i) {
1082 let rw = r.width() * self.pixels_per_point;
1083 let rh = r.height() * self.pixels_per_point;
1084 if let Some(cam) = self.plot_renderer.axes_camera_mut(i) {
1085 if let crate::core::camera::ProjectionType::Orthographic {
1086 left,
1087 right,
1088 bottom,
1089 top,
1090 ..
1091 } = cam.projection
1092 {
1093 let pw = rw.max(1.0);
1094 let ph = rh.max(1.0);
1095 let world_w = right - left;
1096 let world_h = top - bottom;
1097 let dx_world = (delta.x / pw) * world_w;
1098 let dy_world = (delta.y / ph) * world_h;
1099 cam.projection =
1100 crate::core::camera::ProjectionType::Orthographic {
1101 left: left - dx_world,
1102 right: right - dx_world,
1103 bottom: bottom + dy_world,
1104 top: top + dy_world,
1105 near: -1.0,
1106 far: 1.0,
1107 };
1108 cam.mark_dirty();
1109 self.plot_renderer.note_axes_camera_interaction(i);
1110 }
1111 }
1112 }
1113 }
1114 } else {
1115 let cam = self.plot_renderer.camera_mut();
1116 if let crate::core::camera::ProjectionType::Orthographic {
1117 left,
1118 right,
1119 bottom,
1120 top,
1121 ..
1122 } = &mut cam.projection
1123 {
1124 let pw = (plot_rect.width() * self.pixels_per_point).max(1.0);
1125 let ph = (plot_rect.height() * self.pixels_per_point).max(1.0);
1126 let world_w = *right - *left;
1127 let world_h = *top - *bottom;
1128 let dx_world = (delta.x / pw) * world_w;
1129 let dy_world = (delta.y / ph) * world_h;
1130 *left -= dx_world;
1131 *right -= dx_world;
1132 *bottom += dy_world;
1133 *top += dy_world;
1134 cam.mark_dirty();
1135 self.plot_renderer.note_axes_camera_interaction(0);
1136 }
1137 }
1138 }
1139 }
1140 }
1141
1142 fn handle_mouse_scroll(&mut self, delta: winit::event::MouseScrollDelta) {
1144 let scroll_delta = match delta {
1145 winit::event::MouseScrollDelta::LineDelta(_, y) => y,
1146 winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y as f32 / 100.0,
1147 };
1148
1149 if let Some(plot_rect) = self.plot_overlay.plot_area() {
1151 let (rows, cols) = self.plot_renderer.figure_axes_grid();
1152 if rows * cols > 1 {
1153 let rects = self.plot_overlay.compute_subplot_plot_rects(
1154 plot_rect,
1155 &self.plot_renderer,
1156 1.0,
1157 );
1158 for (i, r) in rects.iter().enumerate() {
1159 let rx = r.min.x * self.pixels_per_point;
1160 let ry = r.min.y * self.pixels_per_point;
1161 let rw = r.width() * self.pixels_per_point;
1162 let rh = r.height() * self.pixels_per_point;
1163 let mx = self.mouse_position.x;
1164 let my = self.mouse_position.y;
1165 if mx >= rx && mx <= rx + rw && my >= ry && my <= ry + rh {
1166 if let Some(cam) = self.plot_renderer.axes_camera_mut(i) {
1167 if let crate::core::camera::ProjectionType::Orthographic {
1168 left,
1169 right,
1170 bottom,
1171 top,
1172 ..
1173 } = cam.projection
1174 {
1175 let factor = (1.0 - scroll_delta * 0.1).clamp(0.2, 5.0);
1176 let tx = (mx - rx) / rw;
1177 let ty = (my - ry) / rh;
1178 let w = right - left;
1179 let h = top - bottom;
1180 let pivot_x = left + tx * w;
1181 let pivot_y = top - ty * h;
1182 let new_left = pivot_x - (pivot_x - left) * factor;
1183 let new_right = pivot_x + (right - pivot_x) * factor;
1184 let new_bottom = pivot_y - (pivot_y - bottom) * factor;
1185 let new_top = pivot_y + (top - pivot_y) * factor;
1186 cam.projection =
1187 crate::core::camera::ProjectionType::Orthographic {
1188 left: new_left,
1189 right: new_right,
1190 bottom: new_bottom,
1191 top: new_top,
1192 near: -1.0,
1193 far: 1.0,
1194 };
1195 cam.mark_dirty();
1196 self.plot_renderer.note_axes_camera_interaction(i);
1197 }
1198 }
1199 break;
1200 }
1201 }
1202 } else {
1203 let cam = self.plot_renderer.camera_mut();
1204 if let crate::core::camera::ProjectionType::Orthographic {
1205 left,
1206 right,
1207 bottom,
1208 top,
1209 ..
1210 } = &mut cam.projection
1211 {
1212 let factor = (1.0 - scroll_delta * 0.1).clamp(0.2, 5.0);
1213 let px_min_x = plot_rect.min.x * self.pixels_per_point;
1214 let px_min_y = plot_rect.min.y * self.pixels_per_point;
1215 let px_w = plot_rect.width() * self.pixels_per_point;
1216 let px_h = plot_rect.height() * self.pixels_per_point;
1217 let mx = self.mouse_position.x;
1218 let my = self.mouse_position.y;
1219 let mut pivot_x = (*left + *right) * 0.5;
1220 let mut pivot_y = (*bottom + *top) * 0.5;
1221 if mx >= px_min_x
1222 && mx <= px_min_x + px_w
1223 && my >= px_min_y
1224 && my <= px_min_y + px_h
1225 {
1226 let tx = (mx - px_min_x) / px_w;
1227 let ty = (my - px_min_y) / px_h;
1228 let w = *right - *left;
1229 let h = *top - *bottom;
1230 pivot_x = *left + tx * w;
1231 pivot_y = *top - ty * h;
1232 }
1233 let new_left = pivot_x - (pivot_x - *left) * factor;
1234 let new_right = pivot_x + (*right - pivot_x) * factor;
1235 let new_bottom = pivot_y - (pivot_y - *bottom) * factor;
1236 let new_top = pivot_y + (*top - pivot_y) * factor;
1237 *left = new_left;
1238 *right = new_right;
1239 *bottom = new_bottom;
1240 *top = new_top;
1241 cam.mark_dirty();
1242 self.plot_renderer.note_axes_camera_interaction(0);
1243 }
1244 }
1245 }
1246 }
1247}