Skip to main content

kozan_platform/
window_manager.rs

1//! Window manager — owns all windows, routes events, holds the renderer.
2//!
3//! Chrome: `BrowserMainThread` manages `RenderWidgetHostImpl` instances.
4//! The OS adapter (kozan-winit) calls methods here — it never touches
5//! threads, channels, or input state directly.
6//!
7//! Generic over `R: Renderer` — the renderer backend is injected at app
8//! startup. Neither the OS adapter nor the renderer know about each other;
9//! both only know about the traits defined in `kozan-platform`.
10
11use std::collections::HashMap;
12use std::sync::Arc;
13use std::time::Instant;
14
15use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
16
17use kozan_core::input::*;
18use kozan_primitives::geometry::{Offset, Point};
19
20use crate::context::ViewContext;
21use crate::event::{LifecycleEvent, ViewEvent};
22use crate::host::PlatformHost;
23use crate::id::WindowId;
24use crate::pipeline::render_loop::{OnSurfaceLost, RenderEvent};
25use crate::pipeline::{PipelineConfig, ViewportInfo, WindowPipeline};
26use crate::renderer::{Renderer, RendererError};
27use crate::view_thread::SpawnError;
28use crate::window_state::WindowState;
29
30/// Configuration for creating a window through the manager.
31///
32/// Contains everything except the window handle and the renderer —
33/// the manager holds the renderer, and the OS adapter passes the handle.
34pub struct WindowCreateConfig {
35    pub window_id: WindowId,
36    pub host: Arc<dyn PlatformHost>,
37    pub on_surface_lost: OnSurfaceLost,
38    pub viewport: ViewportInfo,
39}
40
41/// Error when window creation fails.
42#[derive(Debug)]
43pub enum CreateWindowError {
44    Renderer(RendererError),
45    Spawn(SpawnError),
46}
47
48impl std::fmt::Display for CreateWindowError {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            Self::Renderer(e) => write!(f, "renderer: {e}"),
52            Self::Spawn(e) => write!(f, "spawn: {e}"),
53        }
54    }
55}
56
57impl std::error::Error for CreateWindowError {}
58
59/// Owns all windows. The OS adapter calls methods on this.
60///
61/// Generic over `R: Renderer` — the renderer creates per-window GPU
62/// surfaces from raw window handles. The OS adapter provides the handles,
63/// the renderer creates the surfaces, and this manager owns the pipelines.
64pub struct WindowManager<R: Renderer> {
65    renderer: R,
66    windows: HashMap<WindowId, WindowState>,
67}
68
69impl<R: Renderer> WindowManager<R> {
70    pub fn new(renderer: R) -> Self {
71        Self {
72            renderer,
73            windows: HashMap::new(),
74        }
75    }
76
77    /// Create a window: create GPU surface from handle, spawn view + render threads.
78    ///
79    /// The OS adapter creates the OS window and passes the raw handle here.
80    /// This manager uses the renderer to create the surface, then spawns
81    /// the pipeline (render + view threads). The OS adapter keeps the window
82    /// handle alive until after `close_window` returns.
83    pub fn create_window<W, F>(
84        &mut self,
85        window_handle: &W,
86        config: WindowCreateConfig,
87        view_init: F,
88    ) -> Result<(), CreateWindowError>
89    where
90        W: HasWindowHandle + HasDisplayHandle,
91        F: FnOnce(&ViewContext) + Send + 'static,
92    {
93        let vp = config.viewport;
94        let surface = self
95            .renderer
96            .create_surface(window_handle, vp.width, vp.height)
97            .map_err(CreateWindowError::Renderer)?;
98
99        let pipeline_config = PipelineConfig {
100            surface,
101            on_surface_lost: config.on_surface_lost,
102            host: config.host,
103            window_id: config.window_id,
104            viewport: vp,
105        };
106
107        let pipeline =
108            WindowPipeline::spawn(pipeline_config, view_init).map_err(CreateWindowError::Spawn)?;
109
110        self.windows.insert(
111            config.window_id,
112            WindowState::new(pipeline, vp.scale_factor),
113        );
114        Ok(())
115    }
116
117    pub fn close_window(&mut self, id: WindowId) {
118        if let Some(mut state) = self.windows.remove(&id) {
119            state.shutdown();
120        }
121    }
122
123    pub fn has_windows(&self) -> bool {
124        !self.windows.is_empty()
125    }
126
127    pub fn shutdown_all(&mut self) {
128        for (_, mut state) in self.windows.drain() {
129            state.shutdown();
130        }
131    }
132
133    /// Scale factor for a window — used by the OS adapter for pixel delta conversion.
134    pub fn scale_factor(&self, id: WindowId) -> Option<f64> {
135        self.windows.get(&id).map(|s| s.input().scale_factor())
136    }
137
138    // ── Lifecycle events ─────────────────────────────────────
139
140    pub fn on_resize(&mut self, id: WindowId, width: u32, height: u32) {
141        let Some(state) = self.windows.get_mut(&id) else {
142            return;
143        };
144        state.send_to_render(RenderEvent::Resize { width, height });
145        state.send_to_view(ViewEvent::Lifecycle(LifecycleEvent::Resized {
146            width,
147            height,
148        }));
149    }
150
151    pub fn on_scale_factor_changed(
152        &mut self,
153        id: WindowId,
154        scale_factor: f64,
155        refresh_rate_millihertz: Option<u32>,
156    ) {
157        let Some(state) = self.windows.get_mut(&id) else {
158            return;
159        };
160        state.input_mut().set_scale_factor(scale_factor);
161        state.send_to_render(RenderEvent::ScaleFactorChanged(scale_factor));
162        state.send_to_view(ViewEvent::Lifecycle(LifecycleEvent::ScaleFactorChanged {
163            scale_factor,
164            refresh_rate_millihertz,
165        }));
166    }
167
168    pub fn on_focus_changed(&mut self, id: WindowId, focused: bool) {
169        let Some(state) = self.windows.get(&id) else {
170            return;
171        };
172        state.send_to_view(ViewEvent::Lifecycle(LifecycleEvent::Focused(focused)));
173    }
174
175    /// Trigger a repaint — sends Paint to the view thread.
176    pub fn on_redraw(&self, id: WindowId) {
177        let Some(state) = self.windows.get(&id) else {
178            return;
179        };
180        state.send_to_view(ViewEvent::Paint);
181    }
182
183    // ── Mouse events ─────────────────────────────────────────
184
185    pub fn on_cursor_moved(&mut self, id: WindowId, physical_x: f64, physical_y: f64) {
186        let Some(state) = self.windows.get_mut(&id) else {
187            return;
188        };
189        state
190            .input_mut()
191            .set_cursor_physical(physical_x, physical_y);
192        let (x, y) = state.input().cursor();
193        let modifiers = state.input().modifiers();
194        state.send_to_view(ViewEvent::Input(InputEvent::MouseMove(MouseMoveEvent {
195            x,
196            y,
197            modifiers,
198            timestamp: Instant::now(),
199        })));
200    }
201
202    pub fn on_cursor_entered(&mut self, id: WindowId) {
203        let Some(state) = self.windows.get(&id) else {
204            return;
205        };
206        let (x, y) = state.input().cursor();
207        let modifiers = state.input().modifiers();
208        state.send_to_view(ViewEvent::Input(InputEvent::MouseEnter(MouseEnterEvent {
209            x,
210            y,
211            modifiers,
212            timestamp: Instant::now(),
213        })));
214    }
215
216    pub fn on_cursor_left(&mut self, id: WindowId) {
217        let Some(state) = self.windows.get(&id) else {
218            return;
219        };
220        let modifiers = state.input().modifiers();
221        state.send_to_view(ViewEvent::Input(InputEvent::MouseLeave(MouseLeaveEvent {
222            modifiers,
223            timestamp: Instant::now(),
224        })));
225    }
226
227    pub fn on_mouse_input(&mut self, id: WindowId, button: MouseButton, btn_state: ButtonState) {
228        let Some(state) = self.windows.get_mut(&id) else {
229            return;
230        };
231        state.input_mut().update_button_modifier(&button, btn_state);
232        let (x, y) = state.input().cursor();
233        let modifiers = state.input().modifiers();
234        state.send_to_view(ViewEvent::Input(InputEvent::MouseButton(
235            MouseButtonEvent {
236                x,
237                y,
238                button,
239                state: btn_state,
240                modifiers,
241                click_count: 1,
242                timestamp: Instant::now(),
243            },
244        )));
245    }
246
247    // ── Scroll ───────────────────────────────────────────────
248
249    pub fn on_mouse_wheel(&mut self, id: WindowId, delta: WheelDelta) {
250        let Some(state) = self.windows.get(&id) else {
251            return;
252        };
253        let (x, y) = state.input().cursor();
254        let modifiers = state.input().modifiers();
255
256        state.send_to_render(RenderEvent::Scroll {
257            delta: Offset::new(-delta.px_dx(), -delta.px_dy()),
258            point: Point::new(x as f32, y as f32),
259        });
260
261        state.send_to_view(ViewEvent::Input(InputEvent::Wheel(wheel::WheelEvent {
262            x,
263            y,
264            delta,
265            modifiers,
266            timestamp: Instant::now(),
267        })));
268    }
269
270    // ── Keyboard ─────────────────────────────────────────────
271
272    pub fn on_keyboard_input(
273        &mut self,
274        id: WindowId,
275        key: KeyCode,
276        key_state: ButtonState,
277        text: Option<String>,
278        repeat: bool,
279    ) {
280        let Some(state) = self.windows.get(&id) else {
281            return;
282        };
283        let mut modifiers = state.input().modifiers();
284        if repeat {
285            modifiers = modifiers.with_auto_repeat();
286        }
287        state.send_to_view(ViewEvent::Input(InputEvent::Keyboard(
288            keyboard::KeyboardEvent {
289                key,
290                state: key_state,
291                modifiers,
292                text,
293                timestamp: Instant::now(),
294            },
295        )));
296    }
297
298    pub fn on_modifiers_changed(
299        &mut self,
300        id: WindowId,
301        shift: bool,
302        ctrl: bool,
303        alt: bool,
304        meta: bool,
305    ) {
306        let Some(state) = self.windows.get_mut(&id) else {
307            return;
308        };
309        state
310            .input_mut()
311            .set_modifiers_from_keyboard(shift, ctrl, alt, meta);
312    }
313}