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 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 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 self.window.as_ref().unwrap().request_redraw();
202 },
203 _ => (),
204 }
205 }
206}