Skip to main content

runmat_plot/gui/
single_window_manager.rs

1//! Single Window Manager - V8-caliber EventLoop management
2//!
3//! Ensures only one interactive plot window exists at a time, avoiding
4//! winit's "EventLoop can't be recreated" error.
5
6use crate::gui::lifecycle::CloseSignal;
7use crate::gui::window::WindowConfig;
8use crate::plots::Figure;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::Mutex;
11
12/// Global flag to track if an EventLoop is currently active
13static WINDOW_ACTIVE: AtomicBool = AtomicBool::new(false);
14
15/// Thread-safe window manager for sequential plot display
16static WINDOW_MANAGER: Mutex<()> = Mutex::new(());
17
18/// Show a plot using a single, managed window approach
19pub fn show_plot_sequential(figure: Figure) -> Result<String, String> {
20    show_plot_sequential_with_signal(figure, None)
21}
22
23/// Same as [`show_plot_sequential`], but allows callers to provide a close
24/// signal that can terminate the window externally (e.g. from a figure
25/// lifecycle event).
26pub fn show_plot_sequential_with_signal(
27    figure: Figure,
28    close_signal: Option<CloseSignal>,
29) -> Result<String, String> {
30    // Acquire exclusive access to the window system
31    let _guard = WINDOW_MANAGER
32        .lock()
33        .map_err(|_| "Window manager lock failed".to_string())?;
34
35    // Check if a window is already active
36    if WINDOW_ACTIVE.load(Ordering::Acquire) {
37        return Err("Another plot window is already open. Please close it first.".to_string());
38    }
39
40    // Mark window as active
41    WINDOW_ACTIVE.store(true, Ordering::Release);
42
43    let result = show_plot_internal(figure, close_signal);
44
45    // Mark window as inactive when done
46    WINDOW_ACTIVE.store(false, Ordering::Release);
47
48    result
49}
50
51/// Internal function that actually creates and runs the window
52fn show_plot_internal(figure: Figure, close_signal: Option<CloseSignal>) -> Result<String, String> {
53    use crate::gui::PlotWindow;
54
55    // Create window directly on the current thread (main thread)
56    let config = WindowConfig::default();
57
58    // Use existing runtime or create one if none exists
59    let handle = tokio::runtime::Handle::try_current();
60
61    match handle {
62        Ok(handle) => {
63            // Use existing runtime
64            tokio::task::block_in_place(|| {
65                handle.block_on(async {
66                    let mut window = PlotWindow::new(config)
67                        .await
68                        .map_err(|e| format!("Failed to create plot window: {e}"))?;
69
70                    if let Some(signal) = close_signal.clone() {
71                        window.install_close_signal(signal);
72                    }
73
74                    // Set the figure data
75                    window.set_figure(figure);
76
77                    // Run the window (this will consume the EventLoop)
78                    window
79                        .run()
80                        .await
81                        .map_err(|e| format!("Window execution failed: {e}"))?;
82
83                    Ok("Plot window closed successfully".to_string())
84                })
85            })
86        }
87        Err(_) => {
88            // No runtime available, create one
89            let rt = tokio::runtime::Runtime::new()
90                .map_err(|e| format!("Failed to create async runtime: {e}"))?;
91
92            rt.block_on(async {
93                let mut window = PlotWindow::new(config)
94                    .await
95                    .map_err(|e| format!("Failed to create plot window: {e}"))?;
96
97                if let Some(signal) = close_signal {
98                    window.install_close_signal(signal);
99                }
100
101                // Set the figure data
102                window.set_figure(figure);
103
104                // Run the window (this will consume the EventLoop)
105                window
106                    .run()
107                    .await
108                    .map_err(|e| format!("Window execution failed: {e}"))?;
109
110                Ok("Plot window closed successfully".to_string())
111            })
112        }
113    }
114}
115
116/// Check if the window system is available
117pub fn is_window_available() -> bool {
118    !WINDOW_ACTIVE.load(Ordering::Acquire)
119}