use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use kozan_core::input::*;
use kozan_primitives::geometry::{Offset, Point};
use crate::context::ViewContext;
use crate::event::{LifecycleEvent, ViewEvent};
use crate::host::PlatformHost;
use crate::id::WindowId;
use crate::pipeline::render_loop::{OnSurfaceLost, RenderEvent};
use crate::pipeline::{PipelineConfig, ViewportInfo, WindowPipeline};
use crate::renderer::{Renderer, RendererError};
use crate::view_thread::SpawnError;
use crate::window_state::WindowState;
pub struct WindowCreateConfig {
pub window_id: WindowId,
pub host: Arc<dyn PlatformHost>,
pub on_surface_lost: OnSurfaceLost,
pub viewport: ViewportInfo,
}
#[derive(Debug)]
pub enum CreateWindowError {
Renderer(RendererError),
Spawn(SpawnError),
}
impl std::fmt::Display for CreateWindowError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Renderer(e) => write!(f, "renderer: {e}"),
Self::Spawn(e) => write!(f, "spawn: {e}"),
}
}
}
impl std::error::Error for CreateWindowError {}
pub struct WindowManager<R: Renderer> {
renderer: R,
windows: HashMap<WindowId, WindowState>,
}
impl<R: Renderer> WindowManager<R> {
pub fn new(renderer: R) -> Self {
Self {
renderer,
windows: HashMap::new(),
}
}
pub fn create_window<W, F>(
&mut self,
window_handle: &W,
config: WindowCreateConfig,
view_init: F,
) -> Result<(), CreateWindowError>
where
W: HasWindowHandle + HasDisplayHandle,
F: FnOnce(&ViewContext) + Send + 'static,
{
let vp = config.viewport;
let surface = self
.renderer
.create_surface(window_handle, vp.width, vp.height)
.map_err(CreateWindowError::Renderer)?;
let pipeline_config = PipelineConfig {
surface,
on_surface_lost: config.on_surface_lost,
host: config.host,
window_id: config.window_id,
viewport: vp,
};
let pipeline =
WindowPipeline::spawn(pipeline_config, view_init).map_err(CreateWindowError::Spawn)?;
self.windows.insert(
config.window_id,
WindowState::new(pipeline, vp.scale_factor),
);
Ok(())
}
pub fn close_window(&mut self, id: WindowId) {
if let Some(mut state) = self.windows.remove(&id) {
state.shutdown();
}
}
pub fn has_windows(&self) -> bool {
!self.windows.is_empty()
}
pub fn shutdown_all(&mut self) {
for (_, mut state) in self.windows.drain() {
state.shutdown();
}
}
pub fn scale_factor(&self, id: WindowId) -> Option<f64> {
self.windows.get(&id).map(|s| s.input().scale_factor())
}
pub fn on_resize(&mut self, id: WindowId, width: u32, height: u32) {
let Some(state) = self.windows.get_mut(&id) else {
return;
};
state.send_to_render(RenderEvent::Resize { width, height });
state.send_to_view(ViewEvent::Lifecycle(LifecycleEvent::Resized {
width,
height,
}));
}
pub fn on_scale_factor_changed(
&mut self,
id: WindowId,
scale_factor: f64,
refresh_rate_millihertz: Option<u32>,
) {
let Some(state) = self.windows.get_mut(&id) else {
return;
};
state.input_mut().set_scale_factor(scale_factor);
state.send_to_render(RenderEvent::ScaleFactorChanged(scale_factor));
state.send_to_view(ViewEvent::Lifecycle(LifecycleEvent::ScaleFactorChanged {
scale_factor,
refresh_rate_millihertz,
}));
}
pub fn on_focus_changed(&mut self, id: WindowId, focused: bool) {
let Some(state) = self.windows.get(&id) else {
return;
};
state.send_to_view(ViewEvent::Lifecycle(LifecycleEvent::Focused(focused)));
}
pub fn on_redraw(&self, id: WindowId) {
let Some(state) = self.windows.get(&id) else {
return;
};
state.send_to_view(ViewEvent::Paint);
}
pub fn on_cursor_moved(&mut self, id: WindowId, physical_x: f64, physical_y: f64) {
let Some(state) = self.windows.get_mut(&id) else {
return;
};
state
.input_mut()
.set_cursor_physical(physical_x, physical_y);
let (x, y) = state.input().cursor();
let modifiers = state.input().modifiers();
state.send_to_view(ViewEvent::Input(InputEvent::MouseMove(MouseMoveEvent {
x,
y,
modifiers,
timestamp: Instant::now(),
})));
}
pub fn on_cursor_entered(&mut self, id: WindowId) {
let Some(state) = self.windows.get(&id) else {
return;
};
let (x, y) = state.input().cursor();
let modifiers = state.input().modifiers();
state.send_to_view(ViewEvent::Input(InputEvent::MouseEnter(MouseEnterEvent {
x,
y,
modifiers,
timestamp: Instant::now(),
})));
}
pub fn on_cursor_left(&mut self, id: WindowId) {
let Some(state) = self.windows.get(&id) else {
return;
};
let modifiers = state.input().modifiers();
state.send_to_view(ViewEvent::Input(InputEvent::MouseLeave(MouseLeaveEvent {
modifiers,
timestamp: Instant::now(),
})));
}
pub fn on_mouse_input(&mut self, id: WindowId, button: MouseButton, btn_state: ButtonState) {
let Some(state) = self.windows.get_mut(&id) else {
return;
};
state.input_mut().update_button_modifier(&button, btn_state);
let (x, y) = state.input().cursor();
let modifiers = state.input().modifiers();
state.send_to_view(ViewEvent::Input(InputEvent::MouseButton(
MouseButtonEvent {
x,
y,
button,
state: btn_state,
modifiers,
click_count: 1,
timestamp: Instant::now(),
},
)));
}
pub fn on_mouse_wheel(&mut self, id: WindowId, delta: WheelDelta) {
let Some(state) = self.windows.get(&id) else {
return;
};
let (x, y) = state.input().cursor();
let modifiers = state.input().modifiers();
state.send_to_render(RenderEvent::Scroll {
delta: Offset::new(-delta.px_dx(), -delta.px_dy()),
point: Point::new(x as f32, y as f32),
});
state.send_to_view(ViewEvent::Input(InputEvent::Wheel(wheel::WheelEvent {
x,
y,
delta,
modifiers,
timestamp: Instant::now(),
})));
}
pub fn on_keyboard_input(
&mut self,
id: WindowId,
key: KeyCode,
key_state: ButtonState,
text: Option<String>,
repeat: bool,
) {
let Some(state) = self.windows.get(&id) else {
return;
};
let mut modifiers = state.input().modifiers();
if repeat {
modifiers = modifiers.with_auto_repeat();
}
state.send_to_view(ViewEvent::Input(InputEvent::Keyboard(
keyboard::KeyboardEvent {
key,
state: key_state,
modifiers,
text,
timestamp: Instant::now(),
},
)));
}
pub fn on_modifiers_changed(
&mut self,
id: WindowId,
shift: bool,
ctrl: bool,
alt: bool,
meta: bool,
) {
let Some(state) = self.windows.get_mut(&id) else {
return;
};
state
.input_mut()
.set_modifiers_from_keyboard(shift, ctrl, alt, meta);
}
}