runmat_plot/gui/
native_window.rs1use crate::gui::lifecycle::CloseSignal;
8use crate::plots::Figure;
9use std::sync::{Arc, Mutex, OnceLock};
10
11#[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
29pub 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 pub fn new() -> Self {
43 Self {
44 is_initialized: false,
45 }
46 }
47
48 pub fn initialize(&mut self) -> Result<(), String> {
50 if self.is_initialized {
51 return Ok(());
52 }
53
54 #[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 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 #[cfg(target_os = "macos")]
95 {
96 self.show_plot_main_thread(figure, signal, window_title)
97 }
98
99 #[cfg(not(target_os = "macos"))]
101 {
102 self.show_plot_threaded(figure, signal, window_title)
103 }
104 }
105
106 #[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 let mut config = crate::gui::window::WindowConfig::default();
118 if let Some(title) = window_title {
119 config.title = title;
120 }
121 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 window.set_figure(figure);
132
133 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 #[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 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
180static NATIVE_WINDOW_MANAGER: OnceLock<Arc<Mutex<NativeWindowManager>>> = OnceLock::new();
182
183pub 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
195pub 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
229pub fn is_native_window_available() -> bool {
231 NATIVE_WINDOW_MANAGER.get().is_some()
232}