Skip to main content

khora_infra/platform/window/
winit.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A `winit`-based implementation of the `KhoraWindow` trait.
16
17use khora_core::platform::window::{KhoraWindow, KhoraWindowHandle};
18use raw_window_handle::{
19    DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
20};
21use std::sync::Arc;
22use winit::{
23    dpi::LogicalSize,
24    error::OsError,
25    event_loop::ActiveEventLoop,
26    window::{Icon, Window},
27};
28
29/// A wrapper around a `winit::window::Window` that implements the `KhoraWindow` trait.
30///
31/// This struct provides a concrete window implementation for desktop platforms,
32/// abstracting the engine's core logic from the specifics of the `winit` crate.
33/// It uses an `Arc` internally to allow for cheap cloning and shared ownership.
34#[derive(Debug, Clone)]
35pub struct WinitWindow {
36    inner: Arc<Window>,
37}
38
39impl WinitWindow {
40    /// Returns a reference to the raw `winit::window::Window`.
41    pub fn winit_window(&self) -> &Window {
42        &self.inner
43    }
44
45    /// Returns a clone of the inner `Arc<Window>` so the window can be
46    /// shared with subsystems that need a long-lived handle (e.g., an egui
47    /// overlay that calls `begin_frame` each tick).
48    pub fn clone_winit_arc(&self) -> Arc<Window> {
49        Arc::clone(&self.inner)
50    }
51}
52
53/// A builder for creating `WinitWindow` instances.
54///
55/// This follows the builder pattern to provide an ergonomic API for window creation.
56pub struct WinitWindowBuilder {
57    title: String,
58    width: u32,
59    height: u32,
60    icon: Option<Icon>,
61}
62
63impl WinitWindowBuilder {
64    /// Creates a new `WinitWindowBuilder` with default settings.
65    pub fn new() -> Self {
66        Self {
67            title: "Khora Engine".to_string(),
68            width: 1024,
69            height: 768,
70            icon: None,
71        }
72    }
73
74    /// Sets the title of the window to be built.
75    pub fn with_title(mut self, title: impl Into<String>) -> Self {
76        self.title = title.into();
77        self
78    }
79
80    /// Sets the initial inner dimensions of the window to be built.
81    pub fn with_dimensions(mut self, width: u32, height: u32) -> Self {
82        self.width = width;
83        self.height = height;
84        self
85    }
86
87    /// Sets the window icon from raw RGBA data.
88    pub fn with_icon_rgba(mut self, rgba: Vec<u8>, width: u32, height: u32) -> Self {
89        match Icon::from_rgba(rgba, width, height) {
90            Ok(icon) => self.icon = Some(icon),
91            Err(e) => log::warn!("Failed to build window icon: {e}"),
92        }
93        self
94    }
95
96    /// Builds the `WinitWindow` using the provided `winit` event loop.
97    ///
98    /// # Errors
99    /// Returns an `OsError` if the underlying `winit` window creation fails.
100    pub fn build(self, event_loop: &ActiveEventLoop) -> Result<WinitWindow, OsError> {
101        log::info!(
102            "Building window with title: '{}' and size: {}x{}",
103            self.title,
104            self.width,
105            self.height
106        );
107
108        let window_attributes = Window::default_attributes()
109            .with_title(self.title)
110            .with_inner_size(LogicalSize::new(self.width, self.height))
111            .with_window_icon(self.icon)
112            .with_visible(true);
113
114        let window = event_loop.create_window(window_attributes)?;
115
116        log::info!("Winit window created successfully (id: {:?}).", window.id());
117        Ok(WinitWindow {
118            inner: Arc::new(window),
119        })
120    }
121}
122
123impl Default for WinitWindowBuilder {
124    /// Creates a new `WinitWindowBuilder` with default settings.
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130impl HasWindowHandle for WinitWindow {
131    /// Provides the raw window handle required by graphics backends.
132    fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
133        self.inner.window_handle()
134    }
135}
136
137impl HasDisplayHandle for WinitWindow {
138    /// Provides the raw display handle required by graphics backends.
139    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
140        self.inner.display_handle()
141    }
142}
143
144impl KhoraWindow for WinitWindow {
145    /// Returns the physical dimensions (width, height) of the window's inner area.
146    fn inner_size(&self) -> (u32, u32) {
147        let size = self.inner.inner_size();
148        (size.width, size.height)
149    }
150
151    /// Returns the display's scale factor, used for HiDPI rendering.
152    fn scale_factor(&self) -> f64 {
153        self.inner.scale_factor()
154    }
155
156    /// Requests that the window be redrawn.
157    fn request_redraw(&self) {
158        self.inner.request_redraw();
159    }
160
161    /// Clones a thread-safe, reference-counted handle to the window.
162    fn clone_handle_arc(&self) -> KhoraWindowHandle {
163        self.inner.clone()
164    }
165
166    /// Returns a stable, unique identifier for the window.
167    fn id(&self) -> u64 {
168        use std::collections::hash_map::DefaultHasher;
169        use std::hash::{Hash, Hasher};
170
171        let mut hasher = DefaultHasher::new();
172        self.inner.id().hash(&mut hasher);
173        hasher.finish()
174    }
175}