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        if !self.is_initialized {
80            return Err("Native window manager not initialized".to_string());
81        }
82
83        // On macOS, run directly on main thread
84        #[cfg(target_os = "macos")]
85        {
86            self.show_plot_main_thread(figure, signal)
87        }
88
89        // On other platforms, can use thread-based approach
90        #[cfg(not(target_os = "macos"))]
91        {
92            self.show_plot_threaded(figure, signal)
93        }
94    }
95
96    /// Show plot directly on main thread (macOS)
97    #[cfg(target_os = "macos")]
98    fn show_plot_main_thread(
99        &self,
100        figure: Figure,
101        signal: Option<CloseSignal>,
102    ) -> Result<NativeWindowResult, String> {
103        use pollster;
104
105        // Create and run the plot window directly using our WGPU rendering engine
106        let config = crate::gui::window::WindowConfig::default();
107        // We don't need a tokio runtime for the direct approach on macOS
108        // pollster handles the async execution for us
109
110        // Use pollster to block on the async PlotWindow creation
111        match pollster::block_on(crate::gui::PlotWindow::new(config)) {
112            Ok(mut window) => {
113                if let Some(sig) = signal {
114                    window.install_close_signal(sig);
115                }
116                // Set the figure data
117                window.set_figure(figure);
118
119                // Run the window event loop (this will block until the window closes)
120                match pollster::block_on(window.run()) {
121                    Ok(_) => Ok(NativeWindowResult::Success(
122                        "Plot window closed successfully".to_string(),
123                    )),
124                    Err(e) => Err(format!("Window runtime error: {e}")),
125                }
126            }
127            Err(e) => Err(format!("Failed to create plot window: {e}")),
128        }
129    }
130
131    /// Show plot using threaded approach (non-macOS)
132    #[cfg(not(target_os = "macos"))]
133    fn show_plot_threaded(
134        &self,
135        figure: Figure,
136        signal: Option<CloseSignal>,
137    ) -> Result<NativeWindowResult, String> {
138        // For non-macOS platforms, we can use the existing thread-based approach
139        // This would use the thread_manager system
140        match crate::gui::show_plot_global_with_signal(figure, signal) {
141            Ok(result) => match result {
142                crate::gui::GuiOperationResult::Success(msg) => {
143                    Ok(NativeWindowResult::Success(msg))
144                }
145                crate::gui::GuiOperationResult::Cancelled(_msg) => {
146                    Ok(NativeWindowResult::WindowClosed)
147                }
148                crate::gui::GuiOperationResult::Error { message, .. } => {
149                    Ok(NativeWindowResult::Error(message))
150                }
151            },
152            Err(result) => match result {
153                crate::gui::GuiOperationResult::Success(msg) => {
154                    Ok(NativeWindowResult::Success(msg))
155                }
156                crate::gui::GuiOperationResult::Cancelled(_msg) => {
157                    Ok(NativeWindowResult::WindowClosed)
158                }
159                crate::gui::GuiOperationResult::Error { message, .. } => Err(message),
160            },
161        }
162    }
163}
164
165/// Global native window manager
166static NATIVE_WINDOW_MANAGER: OnceLock<Arc<Mutex<NativeWindowManager>>> = OnceLock::new();
167
168/// Initialize the native window system
169pub fn initialize_native_window() -> Result<(), String> {
170    let manager_mutex =
171        NATIVE_WINDOW_MANAGER.get_or_init(|| Arc::new(Mutex::new(NativeWindowManager::new())));
172
173    let mut manager = manager_mutex
174        .lock()
175        .map_err(|_| "Failed to acquire manager lock".to_string())?;
176
177    manager.initialize()
178}
179
180/// Show a plot using native window
181pub fn show_plot_native_window(figure: Figure) -> Result<String, String> {
182    show_plot_native_window_with_signal(figure, None)
183}
184
185pub fn show_plot_native_window_with_signal(
186    figure: Figure,
187    signal: Option<CloseSignal>,
188) -> Result<String, String> {
189    let manager_mutex = NATIVE_WINDOW_MANAGER
190        .get()
191        .ok_or_else(|| "Native window system not initialized".to_string())?;
192
193    let manager = manager_mutex
194        .lock()
195        .map_err(|_| "Failed to acquire manager lock".to_string())?;
196
197    match manager.show_plot_native_with_signal(figure, signal) {
198        Ok(NativeWindowResult::Success(msg)) => Ok(msg),
199        Ok(NativeWindowResult::WindowClosed) => Ok("Plot window closed by user".to_string()),
200        Ok(NativeWindowResult::Error(msg)) => Err(msg),
201        Err(msg) => Err(msg),
202    }
203}
204
205/// Check if native window system is available
206pub fn is_native_window_available() -> bool {
207    NATIVE_WINDOW_MANAGER.get().is_some()
208}