Skip to main content

zenthra_platform/
app.rs

1use crate::window::Window;
2use crate::event::PlatformEvent;
3use winit::{
4    application::ApplicationHandler,
5    event::WindowEvent,
6    event_loop::{ActiveEventLoop, EventLoop},
7    window::WindowId,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum WindowAction {
12    Drag,
13    Minimize,
14    Maximize,
15    Close,
16}
17
18pub struct Frame<'a> {
19    pub window: &'a mut Window,
20    pub events: &'a [PlatformEvent],
21    pub request_redraw_at: Option<std::time::Instant>,
22    pub window_actions: Vec<WindowAction>,
23}
24
25impl<'a> Frame<'a> {
26    pub fn width(&self) -> u32 {
27        self.window.width()
28    }
29    pub fn height(&self) -> u32 {
30        self.window.height()
31    }
32    pub fn scale_factor(&self) -> f64 {
33        self.window.scale_factor()
34    }
35}
36
37pub struct App {
38    title: String,
39    width: u32,
40    height: u32,
41    decorations: bool,
42    draw_fn: Option<Box<dyn FnMut(&mut Frame) -> bool + 'static>>,
43}
44
45impl App {
46    pub fn new() -> Self {
47        Self {
48            title: "Zenthra".to_string(),
49            width: 800,
50            height: 600,
51            decorations: true,
52            draw_fn: None,
53        }
54    }
55
56    pub fn title(mut self, t: &str) -> Self {
57        self.title = t.to_string();
58        self
59    }
60
61    pub fn size(mut self, w: u32, h: u32) -> Self {
62        self.width = w;
63        self.height = h;
64        self
65    }
66
67    pub fn decorations(mut self, dec: bool) -> Self {
68        self.decorations = dec;
69        self
70    }
71
72    pub fn with_ui<F>(mut self, f: F) -> Self
73    where
74        F: FnMut(&mut Frame) -> bool + 'static,
75    {
76        self.draw_fn = Some(Box::new(f));
77        self
78    }
79
80    pub fn run(self) {
81        let event_loop = EventLoop::new().unwrap();
82        self.run_with_event_loop(event_loop);
83    }
84
85    pub fn run_with_event_loop(self, event_loop: EventLoop<()>) {
86        let mut runner = AppRunner {
87            title: self.title,
88            width: self.width,
89            height: self.height,
90            decorations: self.decorations,
91            draw_fn: self.draw_fn,
92            window: None,
93            pending_events: Vec::new(),
94            next_wakeup: None,
95        };
96        event_loop.run_app(&mut runner).unwrap();
97    }
98}
99
100struct AppRunner {
101    title: String,
102    width: u32,
103    height: u32,
104    decorations: bool,
105    draw_fn: Option<Box<dyn FnMut(&mut Frame) -> bool + 'static>>,
106    window: Option<Window>,
107    pending_events: Vec<PlatformEvent>,
108    next_wakeup: Option<std::time::Instant>,
109}
110
111impl AppRunner {
112    fn render(&mut self, event_loop: &ActiveEventLoop) {
113        let Some(window) = &mut self.window else { return };
114
115        let mut needs_redraw = false;
116        let mut request_redraw_at = None;
117        let mut actions = Vec::new();
118        if let Some(draw_fn) = &mut self.draw_fn {
119            let mut frame = Frame { 
120                window,
121                events: &self.pending_events,
122                request_redraw_at: None,
123                window_actions: Vec::new(),
124            };
125            needs_redraw = draw_fn(&mut frame);
126            request_redraw_at = frame.request_redraw_at;
127            actions = frame.window_actions;
128        }
129        self.pending_events.clear();
130        
131        self.next_wakeup = request_redraw_at;
132        
133        for action in actions {
134            match action {
135                WindowAction::Drag => {
136                    let _ = window.winit_window.drag_window();
137                }
138                WindowAction::Minimize => {
139                    window.winit_window.set_minimized(true);
140                }
141                WindowAction::Maximize => {
142                    let is_max = window.winit_window.is_maximized();
143                    window.winit_window.set_maximized(!is_max);
144                }
145                WindowAction::Close => {
146                    event_loop.exit();
147                }
148            }
149        }
150        
151        if needs_redraw {
152            window.request_redraw();
153        }
154    }
155}
156
157impl ApplicationHandler for AppRunner {
158    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
159        if self.window.is_none() {
160            let window = pollster::block_on(Window::new(
161                event_loop,
162                &self.title,
163                self.width,
164                self.height,
165                self.decorations,
166            ));
167            window.request_redraw();
168            self.window = Some(window);
169        }
170    }
171
172    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
173        match event {
174            WindowEvent::CloseRequested => {
175                event_loop.exit();
176            }
177            WindowEvent::Resized(size) => {
178                if let Some(w) = &mut self.window {
179                    w.resize(size);
180                    w.request_redraw();
181                }
182            }
183            WindowEvent::CursorMoved { position, .. } => {
184                self.pending_events.push(PlatformEvent::MouseMoved {
185                    x: position.x,
186                    y: position.y,
187                });
188                if let Some(w) = &mut self.window { w.request_redraw(); }
189            }
190            WindowEvent::MouseInput { button, state, .. } => {
191                println!("MouseButton: {:?} {:?}", button, state); self.pending_events.push(PlatformEvent::MouseButton { button, state });
192                if let Some(w) = &mut self.window { w.request_redraw(); }
193            }
194            WindowEvent::MouseWheel { delta, .. } => {
195                let (x, y) = match delta {
196                    winit::event::MouseScrollDelta::LineDelta(x, y) => (x, y),
197                    winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
198                };
199                self.pending_events.push(PlatformEvent::MouseWheel { delta_x: x, delta_y: y });
200                if let Some(w) = &mut self.window { w.request_redraw(); }
201            }
202            WindowEvent::Touch(touch) => { 
203                self.pending_events.push(PlatformEvent::Touch {
204                    id: touch.id,
205                    phase: touch.phase,
206                    x: touch.location.x,
207                    y: touch.location.y,
208                });
209
210                // Map first touch to mouse for basic interaction support
211                if touch.id == 0 {
212                    match touch.phase {
213                        winit::event::TouchPhase::Started => {
214                            self.pending_events.push(PlatformEvent::MouseMoved { x: touch.location.x, y: touch.location.y });
215                            self.pending_events.push(PlatformEvent::MouseButton { 
216                                button: winit::event::MouseButton::Left, 
217                                state: winit::event::ElementState::Pressed 
218                            });
219                        }
220                        winit::event::TouchPhase::Moved => {
221                            self.pending_events.push(PlatformEvent::MouseMoved { x: touch.location.x, y: touch.location.y });
222                        }
223                        winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
224                            self.pending_events.push(PlatformEvent::MouseButton { 
225                                button: winit::event::MouseButton::Left, 
226                                state: winit::event::ElementState::Released 
227                            });
228                        }
229                    }
230                }
231                if let Some(w) = &mut self.window { w.request_redraw(); }
232            }
233            WindowEvent::KeyboardInput { event, .. } => {
234                if event.state == winit::event::ElementState::Pressed {
235                    if let winit::keyboard::PhysicalKey::Code(key) = event.physical_key {
236                         println!("KeyDown: {:?}", key); self.pending_events.push(PlatformEvent::KeyDown { key });
237                    }
238                    
239                    if let Some(text) = event.text {
240                        for c in text.chars() {
241                            if !c.is_control() {
242                                println!("CharTyped: {}", c); self.pending_events.push(PlatformEvent::CharTyped(c));
243                            }
244                        }
245                    }
246                }
247                if let Some(w) = &mut self.window { w.request_redraw(); }
248            }
249            WindowEvent::RedrawRequested => {
250                self.render(event_loop);
251            }
252            _ => {}
253        }
254    }
255
256    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
257        if let Some(wakeup) = self.next_wakeup {
258            event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(wakeup));
259        } else {
260            event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait);
261        }
262    }
263}