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::UiEvent;
11use blitz_traits::{BlitzMouseButtonEvent, MouseEventButton, MouseEventButtons, 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(cx) {
182 #[cfg(feature = "accessibility")]
183 {
184 let changed = std::mem::take(&mut self.doc.changed);
186 if !changed.is_empty() {
187 self.accessibility.build_tree(&self.doc);
188 }
189 }
190
191 self.request_redraw();
192 return true;
193 }
194 }
195
196 false
197 }
198
199 pub fn request_redraw(&self) {
200 if self.renderer.is_active() {
201 self.window.request_redraw();
202 }
203 }
204
205 pub fn redraw(&mut self) {
206 self.doc.resolve();
207 let (width, height) = self.doc.viewport().window_size;
208 let scale = self.doc.viewport().scale_f64();
209 self.renderer
210 .render(|scene| paint_scene(scene, &self.doc, scale, width, height));
211
212 if self.doc.is_animating() {
213 self.request_redraw();
214 }
215 }
216
217 pub fn window_id(&self) -> WindowId {
218 self.window.id()
219 }
220
221 #[inline]
222 pub fn with_viewport(&mut self, cb: impl FnOnce(&mut Viewport)) {
223 let mut viewport = self.doc.viewport_mut();
224 cb(&mut viewport);
225 drop(viewport);
226 let (width, height) = self.doc.viewport().window_size;
227 if width > 0 && height > 0 {
228 self.renderer.set_size(width, height);
229 self.request_redraw();
230 }
231 }
232
233 #[cfg(feature = "accessibility")]
234 pub fn build_accessibility_tree(&mut self) {
235 self.accessibility.build_tree(&self.doc);
236 }
237
238 pub fn handle_winit_event(&mut self, event: WindowEvent) {
239 match event {
240 WindowEvent::Destroyed => {}
242 WindowEvent::ActivationTokenDone { .. } => {},
243 WindowEvent::CloseRequested => {
244 }
246 WindowEvent::RedrawRequested => {
247 self.redraw();
248 }
249
250 WindowEvent::Moved(_) => {}
252 WindowEvent::Occluded(_) => {},
253 WindowEvent::Resized(physical_size) => {
254 self.with_viewport(|v| v.window_size = (physical_size.width, physical_size.height));
255 }
256 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
257 self.with_viewport(|v| v.set_hidpi_scale(scale_factor as f32));
258 }
259
260 WindowEvent::ThemeChanged(theme) => {
262 let color_scheme = theme_to_color_scheme(self.theme_override.unwrap_or(theme));
263 self.doc.viewport_mut().color_scheme = color_scheme;
264 }
265
266 WindowEvent::Ime(ime_event) => {
268 self.doc.handle_event(UiEvent::Ime(winit_ime_to_blitz(ime_event)));
269 self.request_redraw();
270 },
271 WindowEvent::ModifiersChanged(new_state) => {
272 self.keyboard_modifiers = new_state;
274 }
275 WindowEvent::KeyboardInput { event, .. } => {
276 let PhysicalKey::Code(key_code) = event.physical_key else {
277 return;
278 };
279
280 if event.state.is_pressed() {
281 let ctrl = self.keyboard_modifiers.state().control_key();
282 let meta = self.keyboard_modifiers.state().super_key();
283 let alt = self.keyboard_modifiers.state().alt_key();
284
285 if ctrl | meta {
287 match key_code {
288 KeyCode::Equal => self.doc.viewport_mut().zoom_by(0.1),
289 KeyCode::Minus => self.doc.viewport_mut().zoom_by(-0.1),
290 KeyCode::Digit0 => self.doc.viewport_mut().set_zoom(1.0),
291 _ => {}
292 };
293 }
294
295 if alt {
297 match key_code {
298 KeyCode::KeyD => {
299 self.doc.devtools_mut().toggle_show_layout();
300 self.request_redraw();
301 }
302 KeyCode::KeyH => {
303 self.doc.devtools_mut().toggle_highlight_hover();
304 self.request_redraw();
305 }
306 KeyCode::KeyT => self.doc.print_taffy_tree(),
307 _ => {}
308 };
309 }
310
311 }
312
313 let key_event_data = winit_key_event_to_blitz(&event, self.keyboard_modifiers.state());
315 let event = if event.state.is_pressed() {
316 UiEvent::KeyDown(key_event_data)
317 } else {
318 UiEvent::KeyUp(key_event_data)
319 };
320
321 self.doc.handle_event(event);
322 self.request_redraw();
323 }
324
325
326 WindowEvent::CursorEntered { .. } => {}
328 WindowEvent::CursorLeft { .. } => {}
329 WindowEvent::CursorMoved { position, .. } => {
330 let winit::dpi::LogicalPosition::<f32> { x, y } = position.to_logical(self.window.scale_factor());
331 self.mouse_pos = (x, y);
332 let event = UiEvent::MouseMove(BlitzMouseButtonEvent {
333 x,
334 y,
335 button: Default::default(),
336 buttons: self.buttons,
337 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
338 });
339 self.doc.handle_event(event);
340 self.request_redraw();
341 }
342 WindowEvent::MouseInput { button, state, .. } => {
343 let button = match button {
344 MouseButton::Left => MouseEventButton::Main,
345 MouseButton::Right => MouseEventButton::Secondary,
346 _ => return,
347 };
348
349 match state {
350 ElementState::Pressed => self.buttons |= button.into(),
351 ElementState::Released => self.buttons ^= button.into(),
352 }
353
354 let event = BlitzMouseButtonEvent {
355 x: self.mouse_pos.0,
356 y: self.mouse_pos.1,
357 button,
358 buttons: self.buttons,
359 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
360 };
361
362 let event = match state {
363 ElementState::Pressed => UiEvent::MouseDown(event),
364 ElementState::Released => UiEvent::MouseUp(event),
365 };
366 self.doc.handle_event(event);
367 self.request_redraw();
368 }
369 WindowEvent::MouseWheel { delta, .. } => {
370 let (scroll_x, scroll_y)= match delta {
371 winit::event::MouseScrollDelta::LineDelta(x, y) => (x as f64 * 20.0, y as f64 * 20.0),
372 winit::event::MouseScrollDelta::PixelDelta(offsets) => (offsets.x, offsets.y)
373 };
374
375 if let Some(hover_node_id) = self.doc.get_hover_node_id() {
376 self.doc.scroll_node_by(hover_node_id, scroll_x, scroll_y);
377 } else {
378 self.doc.scroll_viewport_by(scroll_x, scroll_y);
379 }
380 self.request_redraw();
381 }
382
383 WindowEvent::DroppedFile(_) => {}
385 WindowEvent::HoveredFile(_) => {}
386 WindowEvent::HoveredFileCancelled => {}
387 WindowEvent::Focused(_) => {}
388
389 WindowEvent::Touch(_) => {}
392 WindowEvent::TouchpadPressure { .. } => {}
393 WindowEvent::AxisMotion { .. } => {}
394 WindowEvent::PinchGesture { .. } => {},
395 WindowEvent::PanGesture { .. } => {},
396 WindowEvent::DoubleTapGesture { .. } => {},
397 WindowEvent::RotationGesture { .. } => {},
398 }
399 }
400}