Skip to main content

runmat_plot/gui/
native_window.rs

1//! Native cross-platform window management
2//!
3//! This module provides a robust, cross-platform native window implementation
4//! that properly handles platform-specific requirements (especially macOS EventLoop)
5//! while leveraging our world-class WGPU rendering engine.
6
7use crate::gui::lifecycle::CloseSignal;
8use crate::plots::Figure;
9use std::sync::{Arc, Mutex, OnceLock};
10
11/// Result from native window operations
12#[derive(Debug, Clone)]
13pub enum NativeWindowResult {
14    Success(String),
15    Error(String),
16    WindowClosed,
17}
18
19impl std::fmt::Display for NativeWindowResult {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            NativeWindowResult::Success(msg) => write!(f, "Success: {msg}"),
23            NativeWindowResult::Error(msg) => write!(f, "Error: {msg}"),
24            NativeWindowResult::WindowClosed => write!(f, "Window closed by user"),
25        }
26    }
27}
28
29/// Native window manager that properly handles cross-platform requirements
30pub struct NativeWindowManager {
31    is_initialized: bool,
32}
33
34impl Default for NativeWindowManager {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl NativeWindowManager {
41    /// Create a new native window manager
42    pub fn new() -> Self {
43        Self {
44            is_initialized: false,
45        }
46    }
47
48    /// Initialize the native window system
49    pub fn initialize(&mut self) -> Result<(), String> {
50        if self.is_initialized {
51            return Ok(());
52        }
53
54        // Verify main thread requirements on macOS
55        #[cfg(target_os = "macos")]
56        {
57            if !crate::gui::thread_manager::is_main_thread() {
58                return Err(
59                    "Native window manager must be initialized on the main thread on macOS"
60                        .to_string(),
61                );
62            }
63        }
64
65        self.is_initialized = true;
66        Ok(())
67    }
68
69    /// Show a plot using native window with proper cross-platform handling
70    pub fn show_plot_native(&self, figure: Figure) -> Result<NativeWindowResult, String> {
71        self.show_plot_native_with_signal(figure, None)
72    }
73
74    pub fn show_plot_native_with_signal(
75        &self,
76        figure: Figure,
77        signal: Option<CloseSignal>,
78    ) -> Result<NativeWindowResult, String> {
79        let title = figure.window_title(None);
80        self.show_plot_native_with_signal_and_title(figure, signal, Some(title))
81    }
82
83    pub fn show_plot_native_with_signal_and_title(
84        &self,
85        figure: Figure,
86        signal: Option<CloseSignal>,
87        window_title: Option<String>,
88    ) -> Result<NativeWindowResult, String> {
89        if !self.is_initialized {
90            return Err("Native window manager not initialized".to_string());
91        }
92
93        // On macOS, run directly on main thread
94        #[cfg(target_os = "macos")]
95        {
96            self.show_plot_main_thread(figure, signal, window_title)
97        }
98
99        // On other platforms, can use thread-based approach
100        #[cfg(not(target_os = "macos"))]
101        {
102            self.show_plot_threaded(figure, signal, window_title)
103        }
104    }
105
106    /// Show plot directly on main thread (macOS)
107    #[cfg(target_os = "macos")]
108    fn show_plot_main_thread(
109        &self,
110        figure: Figure,
111        signal: Option<CloseSignal>,
112        window_title: Option<String>,
113    ) -> Result<NativeWindowResult, String> {
114        use pollster;
115
116        // Create and run the plot window directly using our WGPU rendering engine
117        let mut config = crate::gui::window::WindowConfig::default();
118        if let Some(title) = window_title {
119            config.title = title;
120        }
121        // We don't need a tokio runtime for the direct approach on macOS
122        // pollster handles the async execution for us
123
124        // Use pollster to block on the async PlotWindow creation
125        match pollster::block_on(crate::gui::PlotWindow::new(config)) {
126            Ok(mut window) => {
127                if let Some(sig) = signal {
128                    window.install_close_signal(sig);
129                }
130                // Set the figure data
131                window.set_figure(figure);
132
133                // Run the window event loop (this will block until the window closes)
134                match pollster::block_on(window.run()) {
135                    Ok(_) => Ok(NativeWindowResult::Success(
136                        "Plot window closed successfully".to_string(),
137                    )),
138                    Err(e) => Err(format!("Window runtime error: {e}")),
139                }
140            }
141            Err(e) => Err(format!("Failed to create plot window: {e}")),
142        }
143    }
144
145    /// Show plot using threaded approach (non-macOS)
146    #[cfg(not(target_os = "macos"))]
147    fn show_plot_threaded(
148        &self,
149        figure: Figure,
150        signal: Option<CloseSignal>,
151        window_title: Option<String>,
152    ) -> Result<NativeWindowResult, String> {
153        // For non-macOS platforms, we can use the existing thread-based approach
154        // This would use the thread_manager system
155        match crate::gui::show_plot_global_with_signal_and_title(figure, signal, window_title) {
156            Ok(result) => match result {
157                crate::gui::GuiOperationResult::Success(msg) => {
158                    Ok(NativeWindowResult::Success(msg))
159                }
160                crate::gui::GuiOperationResult::Cancelled(_msg) => {
161                    Ok(NativeWindowResult::WindowClosed)
162                }
163                crate::gui::GuiOperationResult::Error { message, .. } => {
164                    Ok(NativeWindowResult::Error(message))
165                }
166            },
167            Err(result) => match result {
168                crate::gui::GuiOperationResult::Success(msg) => {
169                    Ok(NativeWindowResult::Success(msg))
170                }
171                crate::gui::GuiOperationResult::Cancelled(_msg) => {
172                    Ok(NativeWindowResult::WindowClosed)
173                }
174                crate::gui::GuiOperationResult::Error { message, .. } => Err(message),
175            },
176        }
177    }
178}
179
180/// Global native window manager
181static NATIVE_WINDOW_MANAGER: OnceLock<Arc<Mutex<NativeWindowManager>>> = OnceLock::new();
182
183/// Initialize the native window system
184pub fn initialize_native_window() -> Result<(), String> {
185    let manager_mutex =
186        NATIVE_WINDOW_MANAGER.get_or_init(|| Arc::new(Mutex::new(NativeWindowManager::new())));
187
188    let mut manager = manager_mutex
189        .lock()
190        .map_err(|_| "Failed to acquire manager lock".to_string())?;
191
192    manager.initialize()
193}
194
195/// Show a plot using native window
196pub fn show_plot_native_window(figure: Figure) -> Result<String, String> {
197    show_plot_native_window_with_signal(figure, None)
198}
199
200pub fn show_plot_native_window_with_signal(
201    figure: Figure,
202    signal: Option<CloseSignal>,
203) -> Result<String, String> {
204    let title = figure.window_title(None);
205    show_plot_native_window_with_signal_and_title(figure, signal, Some(title))
206}
207
208pub fn show_plot_native_window_with_signal_and_title(
209    figure: Figure,
210    signal: Option<CloseSignal>,
211    window_title: Option<String>,
212) -> Result<String, String> {
213    let manager_mutex = NATIVE_WINDOW_MANAGER
214        .get()
215        .ok_or_else(|| "Native window system not initialized".to_string())?;
216
217    let manager = manager_mutex
218        .lock()
219        .map_err(|_| "Failed to acquire manager lock".to_string())?;
220
221    match manager.show_plot_native_with_signal_and_title(figure, signal, window_title) {
222        Ok(NativeWindowResult::Success(msg)) => Ok(msg),
223        Ok(NativeWindowResult::WindowClosed) => Ok("Plot window closed by user".to_string()),
224        Ok(NativeWindowResult::Error(msg)) => Err(msg),
225        Err(msg) => Err(msg),
226    }
227}
228
229/// Check if native window system is available
230pub fn is_native_window_available() -> bool {
231    NATIVE_WINDOW_MANAGER.get().is_some()
232}