Skip to main content

orinium_browser/platform/system/
app.rs

1//! Manages the application lifecycle, including window creation and event handling.
2//!
3//! This module is the OS shell. It owns the `winit` event loop, the windows,
4//! and the [`DrawSink`] each one presents through, and knows nothing about the
5//! browser: all browser interaction goes through the [`BrowserHost`] port
6//! declared in [`super::shell`].
7//!
8//! This is the only place that constructs a concrete sink. Everything above it
9//! — including the whole browser layer — sees [`DrawSink`] as a trait object.
10
11use std::collections::HashMap;
12use std::sync::Arc;
13use winit::application::ApplicationHandler;
14use winit::event::WindowEvent;
15use winit::event_loop::ActiveEventLoop;
16use winit::window::{Cursor, Window, WindowId};
17
18use crate::platform::renderer::draw_sink::DrawSink;
19use crate::platform::renderer::gpu::GpuRenderer;
20use crate::platform::system::shell::{BrowserHost, ShellCommand, WindowGeometry};
21
22pub struct WindowState {
23    pub window: Arc<Window>,
24    /// Presents this window's draw commands.
25    ///
26    /// Held as a trait object so the shell is the only layer that names a
27    /// concrete renderer. Swapping in an offscreen or remote sink is a change
28    /// to this one field.
29    pub sink: Box<dyn DrawSink>,
30}
31
32pub struct App {
33    windows: HashMap<WindowId, WindowState>,
34    host: Box<dyn BrowserHost>,
35}
36
37impl App {
38    /// Wraps a browser host in the OS event loop.
39    pub fn new(host: Box<dyn BrowserHost>) -> Self {
40        Self {
41            windows: HashMap::new(),
42            host,
43        }
44    }
45
46    /// Creates a window from `geometry` and registers it with the host.
47    ///
48    /// The size and scale factor the host asked for are only a starting
49    /// point: once the window exists the *observed* values win, because the
50    /// display scale factor is not known before that.
51    fn create_window(&mut self, event_loop: &ActiveEventLoop, geometry: WindowGeometry) {
52        let WindowGeometry { size, title, .. } = geometry;
53
54        let window = Arc::new(
55            event_loop
56                .create_window(
57                    Window::default_attributes()
58                        .with_inner_size(winit::dpi::PhysicalSize::new(size.0, size.1))
59                        .with_title(&title),
60                )
61                .unwrap(),
62        );
63        let window_id = window.id();
64        let initial_size = window.inner_size();
65        let scale_factor = window.scale_factor();
66        let gpu_renderer = pollster::block_on(GpuRenderer::new(Arc::clone(&window), None)).unwrap();
67
68        self.host.open_window(
69            window_id,
70            WindowGeometry {
71                size: (initial_size.width, initial_size.height),
72                title,
73                scale_factor,
74            },
75        );
76
77        let mut state = WindowState {
78            window,
79            sink: Box::new(gpu_renderer),
80        };
81
82        self.host
83            .apply_draw_commands(window_id, state.sink.as_mut());
84        state.window.request_redraw();
85
86        self.windows.insert(window_id, state);
87    }
88
89    /// The geometry the host wants new windows to have.
90    fn default_geometry(&self) -> WindowGeometry {
91        WindowGeometry {
92            size: self.host.default_window_size(),
93            title: self.host.default_window_title(),
94            // Replaced with the observed value once the window exists.
95            scale_factor: 1.0,
96        }
97    }
98}
99
100impl ApplicationHandler for App {
101    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
102        if !self.host.has_pending_default_window() {
103            log::error!("BrowserApp has no default UI configured");
104            return;
105        }
106
107        self.create_window(event_loop, self.default_geometry());
108    }
109
110    fn window_event(
111        &mut self,
112        event_loop: &ActiveEventLoop,
113        window_id: WindowId,
114        event: WindowEvent,
115    ) {
116        if !self.windows.contains_key(&window_id) {
117            return;
118        }
119
120        let cmd = {
121            let state = self.windows.get_mut(&window_id).unwrap();
122            self.host
123                .handle_window_event(window_id, event, state.sink.as_mut())
124        };
125
126        match cmd {
127            ShellCommand::Exit => {
128                self.windows.remove(&window_id);
129                self.host.close_window(window_id);
130                if self.windows.is_empty() {
131                    event_loop.exit();
132                }
133            }
134            ShellCommand::RequestRedraw => {
135                if let Some(state) = self.windows.get(&window_id) {
136                    state.window.request_redraw();
137                    state.window.set_title(&self.host.window_title(window_id));
138                }
139            }
140            ShellCommand::RenameWindowTitle => {
141                if let Some(state) = self.windows.get(&window_id) {
142                    state.window.set_title(&self.host.window_title(window_id));
143                }
144            }
145            ShellCommand::OpenNewWindow => {
146                let geometry = self.default_geometry();
147                self.create_window(event_loop, geometry);
148            }
149            ShellCommand::SetImeAllowed { allowed, position } => {
150                if let Some(state) = self.windows.get(&window_id) {
151                    state.window.set_ime_allowed(allowed);
152                    if allowed {
153                        state.window.set_ime_cursor_area(
154                            winit::dpi::PhysicalPosition::new(position.0, position.1 + 24.0),
155                            winit::dpi::PhysicalSize::new(1.0, 1.0),
156                        );
157                    }
158                    state.window.request_redraw();
159                }
160            }
161            ShellCommand::SetCursorIcon(icon) => {
162                if let Some(state) = self.windows.get(&window_id) {
163                    state.window.set_cursor(Cursor::from(icon));
164                }
165            }
166            ShellCommand::None => {}
167        }
168    }
169
170    fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
171        let window_ids: Vec<WindowId> = self.windows.keys().copied().collect();
172        for window_id in window_ids {
173            if self.host.poll_window(window_id)
174                && let Some(state) = self.windows.get(&window_id)
175            {
176                state.window.request_redraw();
177            }
178        }
179    }
180}