Skip to main content

libdqg/
app.rs

1use std::time::Instant;
2use winit::application::ApplicationHandler;
3use winit::dpi::{PhysicalSize, Size};
4use winit::event::{ElementState, MouseButton, WindowEvent};
5use winit::window::{CursorIcon, ResizeDirection, Window, WindowAttributes, WindowButtons, WindowId};
6use crate::scene::{Scene, SceneManager};
7use crate::renderer::Renderer;
8use crate::input::{InputState, MouseState};
9use crate::titlebar::{TitleBar, TitleBarButton, TitleBarHit};
10use crate::types::Color;
11
12const RESIZE_BORDER: f32 = 6.0;
13
14fn resize_direction(x: f32, y: f32, w: f32, h: f32) -> Option<ResizeDirection> {
15    let left = x <= RESIZE_BORDER;
16    let right = x >= w - RESIZE_BORDER;
17    let top = y <= RESIZE_BORDER;
18    let bottom = y >= h - RESIZE_BORDER;
19
20    match (left, right, top, bottom) {
21        (true, _, true, _) => Some(ResizeDirection::NorthWest),
22        (_, true, true, _) => Some(ResizeDirection::NorthEast),
23        (true, _, _, true) => Some(ResizeDirection::SouthWest),
24        (_, true, _, true) => Some(ResizeDirection::SouthEast),
25        (true, _, _, _) => Some(ResizeDirection::West),
26        (_, true, _, _) => Some(ResizeDirection::East),
27        (_, _, true, _) => Some(ResizeDirection::North),
28        (_, _, _, true) => Some(ResizeDirection::South),
29        _ => None,
30    }
31}
32
33pub(crate) struct App<'a> {
34    title: String,
35    width: u32,
36    height: u32,
37    window: Option<std::sync::Arc<Window>>,
38    scene_manager: SceneManager,
39    renderer: Option<Renderer<'a>>,
40    input_state: InputState,
41    mouse_state: MouseState,
42    frame_start: Instant,
43    titlebar: Option<TitleBar>,
44    clear_color: Color,
45    resizable: bool,
46    cursor_pos: (f32, f32),
47}
48
49impl<'a> App<'a> {
50    pub fn new(title: String, width: u32, height: u32, integrated_titlebar: bool, clear_color: Color, resizable: bool) -> Self {
51        let scene_manager = SceneManager::new();
52        Self {
53            title,
54            width,
55            height,
56            window: None,
57            scene_manager,
58            renderer: None,
59            input_state: InputState::new(),
60            mouse_state: MouseState::new(),
61            frame_start: Instant::now(),
62            titlebar: integrated_titlebar.then(|| TitleBar::new(resizable)),
63            clear_color: clear_color,
64            resizable: resizable,
65            cursor_pos: (0.0, 0.0),
66        }
67    }
68
69    pub fn add_scene(&mut self, scene: Box<dyn Scene>) {
70        self.scene_manager.add_scene(scene);
71    }
72}
73
74impl<'a> ApplicationHandler for App<'a> {
75    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
76        let mut attrs = WindowAttributes::default();
77        attrs.title = self.title.clone();
78        attrs.inner_size = Some(Size::Physical(PhysicalSize::new(self.width, self.height)));
79        attrs.resizable = self.resizable;
80        attrs.enabled_buttons = self.resizable.then(
81            || WindowButtons::all()
82        ).unwrap_or(WindowButtons::MINIMIZE |WindowButtons::CLOSE);
83        if self.titlebar.is_some() {
84            attrs.decorations = false;
85        }
86        match event_loop.create_window(attrs) {
87            Ok(window) => {
88                crate::platform::round_window_corners(&window);
89                self.window = Some(std::sync::Arc::new(window));
90            },
91            Err(e) => panic!("Failed to create window: {:?}", e),
92        };
93        let renderer = pollster::block_on(Renderer::new(self.window.as_ref().unwrap().clone()));
94        self.renderer = Some(renderer);
95    }
96
97    fn window_event(
98        &mut self,
99        event_loop: &winit::event_loop::ActiveEventLoop,
100        _: WindowId,
101        event: winit::event::WindowEvent,
102    ) {
103        self.scene_manager.on_window_event(&event);
104        match event {
105            WindowEvent::KeyboardInput { event, .. } => {
106                self.input_state.handle_event(&event);
107            }
108            WindowEvent::CloseRequested => {
109                println!("The close button was pressed; stopping");
110                event_loop.exit();
111            },
112            WindowEvent::CursorMoved { position, .. } => {
113                self.cursor_pos = (position.x as f32, position.y as f32);
114                self.mouse_state.set_position(self.cursor_pos.0, self.cursor_pos.1);
115                if let Some(window) = self.window.as_ref() {
116                    let size = window.inner_size();
117                    if let Some(ref mut titlebar) = self.titlebar {
118                        titlebar.update_hover(self.cursor_pos.0, self.cursor_pos.1, size.width as f32);
119                    }
120                    let cursor = if self.resizable {
121                        match resize_direction(self.cursor_pos.0, self.cursor_pos.1, size.width as f32, size.height as f32) {
122                            Some(dir) => CursorIcon::from(dir),
123                            None => CursorIcon::Default,
124                        }
125                    } else {
126                        CursorIcon::Default
127                    };
128                    window.set_cursor(cursor);
129                }
130            },
131            WindowEvent::MouseInput { state, button, .. } => {
132                self.mouse_state.handle_button(button, state);
133                if state == ElementState::Pressed && button == MouseButton::Left {
134                    if let Some(window) = self.window.clone() {
135                        let size = window.inner_size();
136                        let (x, y) = self.cursor_pos;
137                        let dir = if self.resizable {
138                            resize_direction(x, y, size.width as f32, size.height as f32)
139                        } else {
140                            None
141                        };
142                        if let Some(dir) = dir {
143                            let _ = window.drag_resize_window(dir);
144                        } else {
145                            if let Some(ref titlebar) = self.titlebar {
146                                match titlebar.hit_test(x, y, size.width as f32) {
147                                    TitleBarHit::Button(TitleBarButton::Close) => event_loop.exit(),
148                                    TitleBarHit::Button(TitleBarButton::Minimize) => window.set_minimized(true),
149                                    TitleBarHit::Button(TitleBarButton::Maximize) => window.set_maximized(!window.is_maximized()),
150                                    TitleBarHit::Drag => { let _ = window.drag_window(); },
151                                    TitleBarHit::None => {},
152                                }
153                            }
154                        }
155                    }
156                }
157            },
158            WindowEvent::MouseWheel { delta, .. } => {
159                self.mouse_state.handle_wheel(delta);
160            },
161            WindowEvent::Resized(physical_size) => {
162                if let Some(renderer) = self.renderer.as_mut() {
163                    let window = self.window.as_ref().unwrap();
164                    renderer.resize(physical_size);
165                    window.request_redraw();
166                }
167            },
168            WindowEvent::RedrawRequested => {
169                let frame_time = self.frame_start.elapsed();
170                self.frame_start = Instant::now();
171                // Handle redraw here
172                let renderer_opt = self.renderer.as_mut();
173                self.scene_manager.update(frame_time.as_secs_f32(), &self.input_state, &self.mouse_state, renderer_opt);
174                if let Some(renderer) = self.renderer.as_mut() {
175                    let clear_color = self.clear_color;
176                    let titlebar = &self.titlebar;
177                    let window = self.window.as_ref().unwrap();
178                    let maximized = window.is_maximized();
179                    let screen_w = window.inner_size().width;
180                    renderer.render(
181                        clear_color,
182                        &mut self.scene_manager,
183                        |scene_manager, pass| {
184                            scene_manager.render(pass);
185                        },
186                        |scene_manager, device, queue, encoder, view| {
187                            scene_manager.render_overlay(device, queue, encoder, view);
188                        },
189                        |_scene_manager, pass| {
190                            // Drawn after the overlay pass (e.g. egui) so the titlebar always
191                            // stays on top of an editor's UI rather than being painted over.
192                            if let Some(tb) = titlebar.as_ref() {
193                                tb.render(pass, screen_w, maximized);
194                            }
195                        },
196                    );
197                }
198                self.input_state.clear_frame_states();
199                self.mouse_state.clear_frame_states();
200                // Request another redraw
201                self.window.as_ref().unwrap().request_redraw();
202            },
203            _ => (),
204        }
205    }
206}