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    let title = figure.window_title(None);
31    show_plot_sequential_with_signal_and_title(figure, close_signal, Some(title))
32}
33
34pub fn show_plot_sequential_with_signal_and_title(
35    figure: Figure,
36    close_signal: Option<CloseSignal>,
37    window_title: Option<String>,
38) -> Result<String, String> {
39    // Acquire exclusive access to the window system
40    let _guard = WINDOW_MANAGER
41        .lock()
42        .map_err(|_| "Window manager lock failed".to_string())?;
43
44    // Check if a window is already active
45    if WINDOW_ACTIVE.load(Ordering::Acquire) {
46        return Err("Another plot window is already open. Please close it first.".to_string());
47    }
48
49    // Mark window as active
50    WINDOW_ACTIVE.store(true, Ordering::Release);
51
52    let result = show_plot_internal(figure, close_signal, window_title);
53
54    // Mark window as inactive when done
55    WINDOW_ACTIVE.store(false, Ordering::Release);
56
57    result
58}
59
60/// Internal function that actually creates and runs the window
61fn show_plot_internal(
62    figure: Figure,
63    close_signal: Option<CloseSignal>,
64    window_title: Option<String>,
65) -> Result<String, String> {
66    use crate::gui::PlotWindow;
67
68    // Create window directly on the current thread (main thread)
69    let mut config = WindowConfig::default();
70    if let Some(title) = window_title {
71        config.title = title;
72    }
73
74    // Use existing runtime or create one if none exists
75    let handle = tokio::runtime::Handle::try_current();
76
77    match handle {
78        Ok(handle) => {
79            // Use existing runtime
80            tokio::task::block_in_place(|| {
81                handle.block_on(async {
82                    let mut window = PlotWindow::new(config)
83                        .await
84                        .map_err(|e| format!("Failed to create plot window: {e}"))?;
85
86                    if let Some(signal) = close_signal.clone() {
87                        window.install_close_signal(signal);
88                    }
89
90                    // Set the figure data
91                    window.set_figure(figure);
92
93                    // Run the window (this will consume the EventLoop)
94                    window
95                        .run()
96                        .await
97                        .map_err(|e| format!("Window execution failed: {e}"))?;
98
99                    Ok("Plot window closed successfully".to_string())
100                })
101            })
102        }
103        Err(_) => {
104            // No runtime available, create one
105            let rt = tokio::runtime::Runtime::new()
106                .map_err(|e| format!("Failed to create async runtime: {e}"))?;
107
108            rt.block_on(async {
109                let mut window = PlotWindow::new(config)
110                    .await
111                    .map_err(|e| format!("Failed to create plot window: {e}"))?;
112
113                if let Some(signal) = close_signal {
114                    window.install_close_signal(signal);
115                }
116
117                // Set the figure data
118                window.set_figure(figure);
119
120                // Run the window (this will consume the EventLoop)
121                window
122                    .run()
123                    .await
124                    .map_err(|e| format!("Window execution failed: {e}"))?;
125
126                Ok("Plot window closed successfully".to_string())
127            })
128        }
129    }
130}
131
132/// Check if the window system is available
133pub fn is_window_available() -> bool {
134    !WINDOW_ACTIVE.load(Ordering::Acquire)
135}