1use crate::BlitzShellProvider;
2use crate::convert_events::{
3 color_scheme_to_theme, theme_to_color_scheme, winit_ime_to_blitz, winit_key_event_to_blitz,
4 winit_modifiers_to_kbt_modifiers,
5};
6use crate::event::{BlitzShellEvent, create_waker};
7use anyrender::WindowRenderer;
8use blitz_dom::Document;
9use blitz_paint::paint_scene;
10use blitz_traits::events::{BlitzMouseButtonEvent, MouseEventButton, MouseEventButtons, UiEvent};
11use blitz_traits::shell::Viewport;
12use winit::keyboard::PhysicalKey;
13
14use std::sync::Arc;
15use std::task::Waker;
16use winit::event::{ElementState, MouseButton};
17use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
18use winit::window::{Theme, WindowAttributes, WindowId};
19use winit::{event::Modifiers, event::WindowEvent, keyboard::KeyCode, window::Window};
20
21#[cfg(feature = "accessibility")]
22use crate::accessibility::AccessibilityState;
23
24pub struct WindowConfig<Rend: WindowRenderer> {
25 doc: Box<dyn Document>,
26 attributes: WindowAttributes,
27 renderer: Rend,
28}
29
30impl<Rend: WindowRenderer> WindowConfig<Rend> {
31 pub fn new(doc: Box<dyn Document>, renderer: Rend) -> Self {
32 Self::with_attributes(doc, renderer, Window::default_attributes())
33 }
34
35 pub fn with_attributes(
36 doc: Box<dyn Document>,
37 renderer: Rend,
38 attributes: WindowAttributes,
39 ) -> Self {
40 WindowConfig {
41 doc,
42 attributes,
43 renderer,
44 }
45 }
46}
47
48pub struct View<Rend: WindowRenderer> {
49 pub doc: Box<dyn Document>,
50
51 pub renderer: Rend,
52 pub waker: Option<Waker>,
53
54 pub event_loop_proxy: EventLoopProxy<BlitzShellEvent>,
55 pub window: Arc<Window>,
56
57 pub theme_override: Option<Theme>,
60 pub keyboard_modifiers: Modifiers,
61 pub buttons: MouseEventButtons,
62 pub mouse_pos: (f32, f32),
63
64 #[cfg(feature = "accessibility")]
65 pub accessibility: AccessibilityState,
67}
68
69impl<Rend: WindowRenderer> View<Rend> {
70 pub fn init(
71 config: WindowConfig<Rend>,
72 event_loop: &ActiveEventLoop,
73 proxy: &EventLoopProxy<BlitzShellEvent>,
74 ) -> Self {
75 let winit_window = Arc::from(event_loop.create_window(config.attributes).unwrap());
76
77 winit_window.set_ime_allowed(true);
79
80 let size = winit_window.inner_size();
82 let scale = winit_window.scale_factor() as f32;
83 let theme = winit_window.theme().unwrap_or(Theme::Light);
84 let color_scheme = theme_to_color_scheme(theme);
85 let viewport = Viewport::new(size.width, size.height, scale, color_scheme);
86
87 let shell_provider = BlitzShellProvider::new(winit_window.clone());
89
90 let mut doc = config.doc;
91 doc.set_viewport(viewport);
92 doc.set_shell_provider(Arc::new(shell_provider));
93
94 let title = doc.find_title_node().map(|node| node.text_content());
98 if let Some(title) = title {
99 winit_window.set_title(&title);
100 }
101
102 Self {
103 renderer: config.renderer,
104 waker: None,
105 keyboard_modifiers: Default::default(),
106 event_loop_proxy: proxy.clone(),
107 window: winit_window.clone(),
108 doc,
109 theme_override: None,
110 buttons: MouseEventButtons::None,
111 mouse_pos: Default::default(),
112 #[cfg(feature = "accessibility")]
113 accessibility: AccessibilityState::new(&winit_window, proxy.clone()),
114 }
115 }
116
117 pub fn replace_document(&mut self, new_doc: Box<dyn Document>, retain_scroll_position: bool) {
118 let scroll = self.doc.viewport_scroll();
119 let viewport = self.doc.viewport().clone();
120 let shell_provider = self.doc.shell_provider.clone();
121
122 self.doc = new_doc;
123 self.doc.set_viewport(viewport);
124 self.doc.set_shell_provider(shell_provider);
125 self.poll();
126 self.request_redraw();
127
128 if retain_scroll_position {
129 self.doc.set_viewport_scroll(scroll);
130 }
131 }
132
133 pub fn theme_override(&self) -> Option<Theme> {
134 self.theme_override
135 }
136
137 pub fn current_theme(&self) -> Theme {
138 color_scheme_to_theme(self.doc.viewport().color_scheme)
139 }
140
141 pub fn set_theme_override(&mut self, theme: Option<Theme>) {
142 self.theme_override = theme;
143 let theme = theme.or(self.window.theme()).unwrap_or(Theme::Light);
144 self.with_viewport(|v| v.color_scheme = theme_to_color_scheme(theme));
145 }
146
147 pub fn downcast_doc_mut<T: 'static>(&mut self) -> &mut T {
148 self.doc.as_any_mut().downcast_mut::<T>().unwrap()
149 }
150}
151
152impl<Rend: WindowRenderer> View<Rend> {
153 pub fn resume(&mut self) {
154 self.doc.resolve();
156
157 let (width, height) = self.doc.viewport().window_size;
159 let scale = self.doc.viewport().scale_f64();
160 self.renderer.resume(self.window.clone(), width, height);
161 if !self.renderer.is_active() {
162 panic!("Renderer failed to resume");
163 };
164
165 self.renderer
167 .render(|scene| paint_scene(scene, &self.doc, scale, width, height));
168
169 self.waker = Some(create_waker(&self.event_loop_proxy, self.window_id()));
171 }
172
173 pub fn suspend(&mut self) {
174 self.waker = None;
175 self.renderer.suspend();
176 }
177
178 pub fn poll(&mut self) -> bool {
179 if let Some(waker) = &self.waker {
180 let cx = std::task::Context::from_waker(waker);
181 if self.doc.poll(Some(cx)) {
182 #[cfg(feature = "accessibility")]
183 {
184 if self.doc.has_changes() {
185 self.accessibility.update_tree(&self.doc);
186 }
187 }
188
189 self.request_redraw();
190 return true;
191 }
192 }
193
194 false
195 }
196
197 pub fn request_redraw(&self) {
198 if self.renderer.is_active() {
199 self.window.request_redraw();
200 }
201 }
202
203 pub fn redraw(&mut self) {
204 self.doc.resolve();
205 let (width, height) = self.doc.viewport().window_size;
206 let scale = self.doc.viewport().scale_f64();
207 self.renderer
208 .render(|scene| paint_scene(scene, &self.doc, scale, width, height));
209
210 if self.doc.is_animating() {
211 self.request_redraw();
212 }
213 }
214
215 pub fn window_id(&self) -> WindowId {
216 self.window.id()
217 }
218
219 #[inline]
220 pub fn with_viewport(&mut self, cb: impl FnOnce(&mut Viewport)) {
221 let mut viewport = self.doc.viewport_mut();
222 cb(&mut viewport);
223 drop(viewport);
224 let (width, height) = self.doc.viewport().window_size;
225 if width > 0 && height > 0 {
226 self.renderer.set_size(width, height);
227 self.request_redraw();
228 }
229 }
230
231 #[cfg(feature = "accessibility")]
232 pub fn build_accessibility_tree(&mut self) {
233 self.accessibility.update_tree(&self.doc);
234 }
235
236 pub fn handle_winit_event(&mut self, event: WindowEvent) {
237 match event {
238 WindowEvent::Destroyed => {}
240 WindowEvent::ActivationTokenDone { .. } => {},
241 WindowEvent::CloseRequested => {
242 }
244 WindowEvent::RedrawRequested => {
245 self.redraw();
246 }
247
248 WindowEvent::Moved(_) => {}
250 WindowEvent::Occluded(_) => {},
251 WindowEvent::Resized(physical_size) => {
252 self.with_viewport(|v| v.window_size = (physical_size.width, physical_size.height));
253 }
254 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
255 self.with_viewport(|v| v.set_hidpi_scale(scale_factor as f32));
256 }
257
258 WindowEvent::ThemeChanged(theme) => {
260 let color_scheme = theme_to_color_scheme(self.theme_override.unwrap_or(theme));
261 self.doc.viewport_mut().color_scheme = color_scheme;
262 }
263
264 WindowEvent::Ime(ime_event) => {
266 self.doc.handle_ui_event(UiEvent::Ime(winit_ime_to_blitz(ime_event)));
267 self.request_redraw();
268 },
269 WindowEvent::ModifiersChanged(new_state) => {
270 self.keyboard_modifiers = new_state;
272 }
273 WindowEvent::KeyboardInput { event, .. } => {
274 let PhysicalKey::Code(key_code) = event.physical_key else {
275 return;
276 };
277
278 if event.state.is_pressed() {
279 let ctrl = self.keyboard_modifiers.state().control_key();
280 let meta = self.keyboard_modifiers.state().super_key();
281 let alt = self.keyboard_modifiers.state().alt_key();
282
283 if ctrl | meta {
285 match key_code {
286 KeyCode::Equal => self.doc.viewport_mut().zoom_by(0.1),
287 KeyCode::Minus => self.doc.viewport_mut().zoom_by(-0.1),
288 KeyCode::Digit0 => self.doc.viewport_mut().set_zoom(1.0),
289 _ => {}
290 };
291 }
292
293 if alt {
295 match key_code {
296 KeyCode::KeyD => {
297 self.doc.devtools_mut().toggle_show_layout();
298 self.request_redraw();
299 }
300 KeyCode::KeyH => {
301 self.doc.devtools_mut().toggle_highlight_hover();
302 self.request_redraw();
303 }
304 KeyCode::KeyT => self.doc.print_taffy_tree(),
305 _ => {}
306 };
307 }
308
309 }
310
311 let key_event_data = winit_key_event_to_blitz(&event, self.keyboard_modifiers.state());
313 let event = if event.state.is_pressed() {
314 UiEvent::KeyDown(key_event_data)
315 } else {
316 UiEvent::KeyUp(key_event_data)
317 };
318
319 self.doc.handle_ui_event(event);
320 self.request_redraw();
321 }
322
323
324 WindowEvent::CursorEntered { .. } => {}
326 WindowEvent::CursorLeft { .. } => {}
327 WindowEvent::CursorMoved { position, .. } => {
328 let winit::dpi::LogicalPosition::<f32> { x, y } = position.to_logical(self.window.scale_factor());
329 self.mouse_pos = (x, y);
330 let event = UiEvent::MouseMove(BlitzMouseButtonEvent {
331 x,
332 y,
333 button: Default::default(),
334 buttons: self.buttons,
335 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
336 });
337 self.doc.handle_ui_event(event);
338 self.request_redraw();
339 }
340 WindowEvent::MouseInput { button, state, .. } => {
341 let button = match button {
342 MouseButton::Left => MouseEventButton::Main,
343 MouseButton::Right => MouseEventButton::Secondary,
344 _ => return,
345 };
346
347 match state {
348 ElementState::Pressed => self.buttons |= button.into(),
349 ElementState::Released => self.buttons ^= button.into(),
350 }
351
352 let event = BlitzMouseButtonEvent {
353 x: self.mouse_pos.0,
354 y: self.mouse_pos.1,
355 button,
356 buttons: self.buttons,
357 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
358 };
359
360 let event = match state {
361 ElementState::Pressed => UiEvent::MouseDown(event),
362 ElementState::Released => UiEvent::MouseUp(event),
363 };
364 self.doc.handle_ui_event(event);
365 self.request_redraw();
366 }
367 WindowEvent::MouseWheel { delta, .. } => {
368 let (scroll_x, scroll_y)= match delta {
369 winit::event::MouseScrollDelta::LineDelta(x, y) => (x as f64 * 20.0, y as f64 * 20.0),
370 winit::event::MouseScrollDelta::PixelDelta(offsets) => (offsets.x, offsets.y)
371 };
372
373 if let Some(hover_node_id) = self.doc.get_hover_node_id() {
374 self.doc.scroll_node_by(hover_node_id, scroll_x, scroll_y);
375 } else {
376 self.doc.scroll_viewport_by(scroll_x, scroll_y);
377 }
378 self.request_redraw();
379 }
380
381 WindowEvent::DroppedFile(_) => {}
383 WindowEvent::HoveredFile(_) => {}
384 WindowEvent::HoveredFileCancelled => {}
385 WindowEvent::Focused(_) => {}
386
387 WindowEvent::Touch(_) => {}
390 WindowEvent::TouchpadPressure { .. } => {}
391 WindowEvent::AxisMotion { .. } => {}
392 WindowEvent::PinchGesture { .. } => {},
393 WindowEvent::PanGesture { .. } => {},
394 WindowEvent::DoubleTapGesture { .. } => {},
395 WindowEvent::RotationGesture { .. } => {},
396 }
397 }
398}