Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-core/src/window.rs
StratoSDK / crates / strato-core / src / window.rs
1//! Window management for StratoUI
2//!
3//! This module provides cross-platform window creation and management
4//! capabilities. It handles window lifecycle, events, and properties.
5 
6use crate::{
7 error::{StratoError, StratoResult},
8 event::Event,
9 types::{Point, Rect, Size},
10};
11use std::collections::HashMap;
12 
13/// Unique identifier for windows
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct WindowId(pub u64);
16 
17impl WindowId {
18 /// Create a new window ID
19 pub fn new() -> Self {
20 use std::sync::atomic::{AtomicU64, Ordering};
21 static COUNTER: AtomicU64 = AtomicU64::new(1);
22 Self(COUNTER.fetch_add(1, Ordering::Relaxed))
23 }
24}
25 
26impl Default for WindowId {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31 
32/// Window configuration
33#[derive(Debug, Clone)]
34pub struct WindowConfig {
35 /// Window title
36 pub title: String,
37 /// Initial window size
38 pub size: Size,
39 /// Initial window position
40 pub position: Option<Point>,
41 /// Whether the window is resizable
42 pub resizable: bool,
43 /// Whether the window has decorations (title bar, borders)
44 pub decorated: bool,
45 /// Whether the window is always on top
46 pub always_on_top: bool,
47 /// Whether the window is maximized
48 pub maximized: bool,
49 /// Whether the window is minimized
50 pub minimized: bool,
51 /// Whether the window is visible
52 pub visible: bool,
53 /// Whether the window is transparent
54 pub transparent: bool,
55 /// Minimum window size
56 pub min_size: Option<Size>,
57 /// Maximum window size
58 pub max_size: Option<Size>,
59}
60 
61impl Default for WindowConfig {
62 fn default() -> Self {
63 Self {
64 title: "StratoUI Window".to_string(),
65 size: Size::new(800.0, 600.0),
66 position: None,
67 resizable: true,
68 decorated: true,
69 always_on_top: false,
70 maximized: false,
71 minimized: false,
72 visible: true,
73 transparent: false,
74 min_size: Some(Size::new(200.0, 150.0)),
75 max_size: None,
76 }
77 }
78}
79 
80/// Window state
81#[derive(Debug, Clone, PartialEq)]
82pub enum WindowState {
83 /// Window is normal
84 Normal,
85 /// Window is minimized
86 Minimized,
87 /// Window is maximized
88 Maximized,
89 /// Window is fullscreen
90 Fullscreen,
91}
92 
93/// Window theme
94#[derive(Debug, Clone, PartialEq)]
95pub enum WindowTheme {
96 /// Light theme
97 Light,
98 /// Dark theme
99 Dark,
100 /// System theme
101 System,
102}
103 
104/// Window properties that can be changed at runtime
105#[derive(Debug, Clone)]
106pub struct WindowProperties {
107 /// Window title
108 pub title: String,
109 /// Window size
110 pub size: Size,
111 /// Window position
112 pub position: Point,
113 /// Window state
114 pub state: WindowState,
115 /// Whether the window is visible
116 pub visible: bool,
117 /// Whether the window is focused
118 pub focused: bool,
119 /// Window theme
120 pub theme: WindowTheme,
121 /// Custom properties
122 pub custom: HashMap<String, String>,
123}
124 
125impl Default for WindowProperties {
126 fn default() -> Self {
127 Self {
128 title: "StratoUI Window".to_string(),
129 size: Size::new(800.0, 600.0),
130 position: Point::new(100.0, 100.0),
131 state: WindowState::Normal,
132 visible: true,
133 focused: false,
134 theme: WindowTheme::System,
135 custom: HashMap::new(),
136 }
137 }
138}
139 
140/// Window event handler trait
141pub trait WindowEventHandler: Send + Sync {
142 /// Handle window close request
143 fn on_close_requested(&mut self, window_id: WindowId) -> bool;
144 
145 /// Handle window resize
146 fn on_resize(&mut self, window_id: WindowId, size: Size);
147 
148 /// Handle window move
149 fn on_move(&mut self, window_id: WindowId, position: Point);
150 
151 /// Handle window focus change
152 fn on_focus_changed(&mut self, window_id: WindowId, focused: bool);
153 
154 /// Handle window state change
155 fn on_state_changed(&mut self, window_id: WindowId, state: WindowState);
156 
157 /// Handle window theme change
158 fn on_theme_changed(&mut self, window_id: WindowId, theme: WindowTheme);
159 
160 /// Handle generic window event
161 fn on_event(&mut self, window_id: WindowId, event: &Event);
162}
163 
164/// Default window event handler that does nothing
165#[derive(Debug, Default)]
166pub struct DefaultWindowEventHandler;
167 
168impl WindowEventHandler for DefaultWindowEventHandler {
169 fn on_close_requested(&mut self, _window_id: WindowId) -> bool {
170 true // Allow close by default
171 }
172 
173 fn on_resize(&mut self, _window_id: WindowId, _size: Size) {}
174 
175 fn on_move(&mut self, _window_id: WindowId, _position: Point) {}
176 
177 fn on_focus_changed(&mut self, _window_id: WindowId, _focused: bool) {}
178 
179 fn on_state_changed(&mut self, _window_id: WindowId, _state: WindowState) {}
180 
181 fn on_theme_changed(&mut self, _window_id: WindowId, _theme: WindowTheme) {}
182 
183 fn on_event(&mut self, _window_id: WindowId, _event: &Event) {}
184}
185 
186/// Window trait for platform-specific implementations
187pub trait Window: Send + Sync {
188 /// Get the window ID
189 fn id(&self) -> WindowId;
190 
191 /// Get window properties
192 fn properties(&self) -> &WindowProperties;
193 
194 /// Set window title
195 fn set_title(&mut self, title: &str) -> StratoResult<()>;
196 
197 /// Set window size
198 fn set_size(&mut self, size: Size) -> StratoResult<()>;
199 
200 /// Set window position
201 fn set_position(&mut self, position: Point) -> StratoResult<()>;
202 
203 /// Set window state
204 fn set_state(&mut self, state: WindowState) -> StratoResult<()>;
205 
206 /// Set window visibility
207 fn set_visible(&mut self, visible: bool) -> StratoResult<()>;
208 
209 /// Focus the window
210 fn focus(&mut self) -> StratoResult<()>;
211 
212 /// Close the window
213 fn close(&mut self) -> StratoResult<()>;
214 
215 /// Check if the window should close
216 fn should_close(&self) -> bool;
217 
218 /// Get the window's content area
219 fn content_area(&self) -> Rect;
220 
221 /// Get the window's scale factor
222 fn scale_factor(&self) -> f32;
223 
224 /// Request a redraw
225 fn request_redraw(&mut self);
226 
227 /// Set the window theme
228 fn set_theme(&mut self, theme: WindowTheme) -> StratoResult<()>;
229 
230 /// Get the current cursor position relative to the window
231 fn cursor_position(&self) -> Option<Point>;
232 
233 /// Set the cursor icon
234 fn set_cursor_icon(&mut self, icon: CursorIcon) -> StratoResult<()>;
235 
236 /// Set the cursor visibility
237 fn set_cursor_visible(&mut self, visible: bool) -> StratoResult<()>;
238}
239 
240/// Cursor icon types
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum CursorIcon {
243 /// Default cursor
244 Default,
245 /// Pointer cursor (hand)
246 Pointer,
247 /// Text cursor (I-beam)
248 Text,
249 /// Crosshair cursor
250 Crosshair,
251 /// Move cursor
252 Move,
253 /// Resize cursor (north-south)
254 ResizeNS,
255 /// Resize cursor (east-west)
256 ResizeEW,
257 /// Resize cursor (northeast-southwest)
258 ResizeNESW,
259 /// Resize cursor (northwest-southeast)
260 ResizeNWSE,
261 /// Wait cursor
262 Wait,
263 /// Not allowed cursor
264 NotAllowed,
265 /// Help cursor
266 Help,
267 /// Progress cursor
268 Progress,
269}
270 
271/// Window builder for creating windows with a fluent API
272pub struct WindowBuilder {
273 config: WindowConfig,
274 event_handler: Option<Box<dyn WindowEventHandler>>,
275}
276 
277impl WindowBuilder {
278 /// Create a new window builder
279 pub fn new() -> Self {
280 Self {
281 config: WindowConfig::default(),
282 event_handler: None,
283 }
284 }
285 
286 /// Set the window title
287 pub fn title<S: Into<String>>(mut self, title: S) -> Self {
288 self.config.title = title.into();
289 self
290 }
291 
292 /// Set the window size
293 pub fn size(mut self, size: Size) -> Self {
294 self.config.size = size;
295 self
296 }
297 
298 /// Set the window position
299 pub fn position(mut self, position: Point) -> Self {
300 self.config.position = Some(position);
301 self
302 }
303 
304 /// Set whether the window is resizable
305 pub fn resizable(mut self, resizable: bool) -> Self {
306 self.config.resizable = resizable;
307 self
308 }
309 
310 /// Set whether the window has decorations
311 pub fn decorated(mut self, decorated: bool) -> Self {
312 self.config.decorated = decorated;
313 self
314 }
315 
316 /// Set whether the window is always on top
317 pub fn always_on_top(mut self, always_on_top: bool) -> Self {
318 self.config.always_on_top = always_on_top;
319 self
320 }
321 
322 /// Set whether the window starts maximized
323 pub fn maximized(mut self, maximized: bool) -> Self {
324 self.config.maximized = maximized;
325 self
326 }
327 
328 /// Set whether the window starts visible
329 pub fn visible(mut self, visible: bool) -> Self {
330 self.config.visible = visible;
331 self
332 }
333 
334 /// Set whether the window is transparent
335 pub fn transparent(mut self, transparent: bool) -> Self {
336 self.config.transparent = transparent;
337 self
338 }
339 
340 /// Set the minimum window size
341 pub fn min_size(mut self, min_size: Size) -> Self {
342 self.config.min_size = Some(min_size);
343 self
344 }
345 
346 /// Set the maximum window size
347 pub fn max_size(mut self, max_size: Size) -> Self {
348 self.config.max_size = Some(max_size);
349 self
350 }
351 
352 /// Set the event handler
353 pub fn event_handler<H: WindowEventHandler + 'static>(mut self, handler: H) -> Self {
354 self.event_handler = Some(Box::new(handler));
355 self
356 }
357 
358 /// Build the window
359 pub fn build(self) -> StratoResult<Box<dyn Window>> {
360 // This would be implemented by the platform-specific backend
361 Err(StratoError::NotImplemented {
362 message: "Window creation not implemented".to_string(),
363 context: None,
364 })
365 }
366}
367 
368impl Default for WindowBuilder {
369 fn default() -> Self {
370 Self::new()
371 }
372}
373 
374/// Window manager for handling multiple windows
375pub struct WindowManager {
376 windows: HashMap<WindowId, Box<dyn Window>>,
377 event_handlers: HashMap<WindowId, Box<dyn WindowEventHandler>>,
378 active_window: Option<WindowId>,
379}
380 
381impl WindowManager {
382 /// Create a new window manager
383 pub fn new() -> Self {
384 Self {
385 windows: HashMap::new(),
386 event_handlers: HashMap::new(),
387 active_window: None,
388 }
389 }
390 
391 /// Create a new window
392 pub fn create_window(&mut self, builder: WindowBuilder) -> StratoResult<WindowId> {
393 let window = builder.build()?;
394 let id = window.id();
395 self.windows.insert(id, window);
396 
397 if self.active_window.is_none() {
398 self.active_window = Some(id);
399 }
400 
401 Ok(id)
402 }
403 
404 /// Get a window by ID
405 pub fn get_window(&self, id: WindowId) -> Option<&dyn Window> {
406 self.windows.get(&id).map(|w| w.as_ref())
407 }
408 
409 /// Get a mutable window by ID
410 pub fn get_window_mut(&mut self, id: WindowId) -> Option<&mut Box<dyn Window>> {
411 self.windows.get_mut(&id)
412 }
413 
414 /// Close a window
415 pub fn close_window(&mut self, id: WindowId) -> StratoResult<()> {
416 if let Some(mut window) = self.windows.remove(&id) {
417 window.close()?;
418 self.event_handlers.remove(&id);
419 
420 if self.active_window == Some(id) {
421 self.active_window = self.windows.keys().next().copied();
422 }
423 }
424 Ok(())
425 }
426 
427 /// Get the active window ID
428 pub fn active_window(&self) -> Option<WindowId> {
429 self.active_window
430 }
431 
432 /// Set the active window
433 pub fn set_active_window(&mut self, id: WindowId) -> StratoResult<()> {
434 if self.windows.contains_key(&id) {
435 self.active_window = Some(id);
436 if let Some(window) = self.get_window_mut(id) {
437 window.focus()?;
438 }
439 }
440 Ok(())
441 }
442 
443 /// Get all window IDs
444 pub fn window_ids(&self) -> Vec<WindowId> {
445 self.windows.keys().copied().collect()
446 }
447 
448 /// Handle an event for a specific window
449 pub fn handle_event(&mut self, window_id: WindowId, event: &Event) -> StratoResult<()> {
450 if let Some(handler) = self.event_handlers.get_mut(&window_id) {
451 handler.on_event(window_id, event);
452 }
453 Ok(())
454 }
455 
456 /// Update all windows
457 pub fn update(&mut self) -> StratoResult<()> {
458 // Process window events and updates
459 for (id, window) in &mut self.windows {
460 if window.should_close() {
461 // Handle close request through event handler
462 if let Some(handler) = self.event_handlers.get_mut(id) {
463 if handler.on_close_requested(*id) {
464 // Window should be closed
465 continue;
466 }
467 }
468 }
469 }
470 
471 // Remove windows that should be closed
472 let mut to_remove = Vec::new();
473 for (id, window) in &self.windows {
474 if window.should_close() {
475 to_remove.push(*id);
476 }
477 }
478 
479 for id in to_remove {
480 self.close_window(id)?;
481 }
482 
483 Ok(())
484 }
485 
486 /// Check if there are any open windows
487 pub fn has_windows(&self) -> bool {
488 !self.windows.is_empty()
489 }
490}
491 
492impl Default for WindowManager {
493 fn default() -> Self {
494 Self::new()
495 }
496}
497 
498#[cfg(test)]
499mod tests {
500 use super::*;
501 
502 #[test]
503 fn test_window_id_generation() {
504 let id1 = WindowId::new();
505 let id2 = WindowId::new();
506 assert_ne!(id1, id2);
507 }
508 
509 #[test]
510 fn test_window_config_default() {
511 let config = WindowConfig::default();
512 assert_eq!(config.title, "StratoUI Window");
513 assert_eq!(config.size, Size::new(800.0, 600.0));
514 assert!(config.resizable);
515 assert!(config.decorated);
516 assert!(!config.always_on_top);
517 }
518 
519 #[test]
520 fn test_window_builder() {
521 let builder = WindowBuilder::new()
522 .title("Test Window")
523 .size(Size::new(1024.0, 768.0))
524 .resizable(false)
525 .decorated(true);
526 
527 assert_eq!(builder.config.title, "Test Window");
528 assert_eq!(builder.config.size, Size::new(1024.0, 768.0));
529 assert!(!builder.config.resizable);
530 assert!(builder.config.decorated);
531 }
532 
533 #[test]
534 fn test_window_properties_default() {
535 let props = WindowProperties::default();
536 assert_eq!(props.title, "StratoUI Window");
537 assert_eq!(props.state, WindowState::Normal);
538 assert!(props.visible);
539 assert!(!props.focused);
540 assert_eq!(props.theme, WindowTheme::System);
541 }
542 
543 #[test]
544 fn test_window_manager() {
545 let manager = WindowManager::new();
546 assert!(!manager.has_windows());
547 assert_eq!(manager.window_ids().len(), 0);
548 assert_eq!(manager.active_window(), None);
549 }
550 
551 #[test]
552 fn test_cursor_icon_variants() {
553 let icons = [
554 CursorIcon::Default,
555 CursorIcon::Pointer,
556 CursorIcon::Text,
557 CursorIcon::Crosshair,
558 CursorIcon::Move,
559 CursorIcon::ResizeNS,
560 CursorIcon::ResizeEW,
561 CursorIcon::ResizeNESW,
562 CursorIcon::ResizeNWSE,
563 CursorIcon::Wait,
564 CursorIcon::NotAllowed,
565 CursorIcon::Help,
566 CursorIcon::Progress,
567 ];
568 
569 // Test that all variants are distinct
570 for (i, icon1) in icons.iter().enumerate() {
571 for (j, icon2) in icons.iter().enumerate() {
572 if i != j {
573 assert_ne!(icon1, icon2);
574 }
575 }
576 }
577 }
578}
579