use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use denise::{
DamageTracker, ElementState, InputEvent, InputSource, MAX_DAMAGE_RECTS, Point, PointerButton,
Rect, Size, Surface,
};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{MouseButton, MouseScrollDelta, StartCause, TouchPhase, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow};
use winit::keyboard::ModifiersState;
use winit::window::{Window, WindowId};
#[cfg(feature = "gpu")]
use crate::GpuSurface;
use crate::{
Build, DeniseApp, Error, LINE_HEIGHT_PX, Modality, PlatformSurface, Present, Waker,
WindowConfig, WindowRequest, keymap, owner,
};
enum Backend {
Software(Box<PlatformSurface>),
#[cfg(feature = "gpu")]
Gpu(Box<GpuSurface>),
}
impl Backend {
fn size(&self) -> Size {
match self {
Backend::Software(s) => s.size(),
#[cfg(feature = "gpu")]
Backend::Gpu(s) => s.size(),
}
}
fn scale_factor(&self) -> f32 {
match self {
Backend::Software(s) => s.scale_factor(),
#[cfg(feature = "gpu")]
Backend::Gpu(s) => s.scale_factor(),
}
}
fn window(&self) -> &Arc<Window> {
match self {
Backend::Software(s) => s.window(),
#[cfg(feature = "gpu")]
Backend::Gpu(s) => s.window(),
}
}
fn resize(&mut self, size: Size, scale_factor: f32) {
match self {
Backend::Software(s) => s.resize(size, scale_factor),
#[cfg(feature = "gpu")]
Backend::Gpu(s) => s.resize(size, scale_factor),
}
}
fn poll(&mut self, out: &mut Vec<InputEvent>) {
match self {
Backend::Software(s) => s.poll(out),
#[cfg(feature = "gpu")]
Backend::Gpu(s) => s.poll(out),
}
}
fn push_event(&mut self, event: InputEvent) {
match self {
Backend::Software(s) => s.push_event(event),
#[cfg(feature = "gpu")]
Backend::Gpu(s) => s.push_event(event),
}
}
}
struct WindowState {
window: Arc<Window>,
surface: Backend,
app: Box<dyn DeniseApp>,
damage: DamageTracker,
events: Vec<InputEvent>,
modifiers: ModifiersState,
cursor: Point,
next_frame: Option<Instant>,
owner: Option<WindowId>,
modality: Modality,
frame_interval: Duration,
title: String,
placed: Option<(Point, bool)>,
}
pub(crate) struct Runner {
pub(crate) config: WindowConfig,
pub(crate) build: Option<Build>,
windows: HashMap<WindowId, WindowState>,
main: Option<WindowId>,
pending: Vec<(WindowId, WindowRequest)>,
pub(crate) error: Option<Error>,
waker: Waker,
}
impl Runner {
pub(crate) fn new(config: WindowConfig, build: Build, waker: Waker) -> Self {
Self {
waker,
config,
build: Some(build),
windows: HashMap::new(),
main: None,
pending: Vec::new(),
error: None,
}
}
fn fail(&mut self, event_loop: &ActiveEventLoop, err: Error) {
self.error = Some(err);
event_loop.exit();
}
fn spawn(
&mut self,
event_loop: &ActiveEventLoop,
config: &WindowConfig,
build: Build,
owned_by: Option<WindowId>,
modality: Modality,
) -> Result<WindowId, Error> {
let mut attrs = Window::default_attributes()
.with_title(config.title.clone())
.with_resizable(config.resizable);
let monitor = config
.fill_monitor
.then(|| {
event_loop
.primary_monitor()
.or_else(|| event_loop.available_monitors().next())
})
.flatten();
attrs = match monitor {
Some(m) => {
let s = m.size();
attrs.with_inner_size(PhysicalSize::new(s.width, s.height))
}
None => attrs.with_inner_size(LogicalSize::new(config.size.width, config.size.height)),
};
let place_at = config
.position
.filter(|at| reachable(monitor_bounds(event_loop), *at));
if let Some(at) = place_at {
attrs = attrs.with_position(PhysicalPosition::new(at.x, at.y));
}
if config.maximized {
attrs = attrs.with_maximized(true);
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
if let Some(app_id) = &config.app_id {
attrs = winit::platform::wayland::WindowAttributesExtWayland::with_name(
attrs,
app_id.clone(),
app_id.clone(),
);
}
if modality != Modality::Independent
&& let Some(owner_state) = owned_by.and_then(|id| self.windows.get(&id))
{
attrs = owner::own(attrs, &owner_state.window);
}
let window = Arc::new(event_loop.create_window(attrs)?);
if let Some(at) = place_at.filter(|_| !config.maximized) {
window.set_outer_position(PhysicalPosition::new(at.x, at.y));
}
let surface = match config.present {
Present::Software => Backend::Software(Box::new(PlatformSurface::new(window.clone())?)),
#[cfg(feature = "gpu")]
Present::Gpu => Backend::Gpu(Box::new(GpuSurface::new(window.clone())?)),
#[cfg(not(feature = "gpu"))]
Present::Gpu => {
return Err(Error::Gpu(
"denise-winit was built without the `gpu` feature".into(),
));
}
#[cfg(feature = "gpu")]
Present::GpuOrSoftware => match GpuSurface::new(window.clone()) {
Ok(gpu) => Backend::Gpu(Box::new(gpu)),
Err(Error::Gpu(reason) | Error::Present(reason)) => {
eprintln!(
"denise: cannot draw through the GPU ({reason}); drawing in software"
);
Backend::Software(Box::new(PlatformSurface::new(window.clone())?))
}
Err(err) => return Err(err),
},
#[cfg(not(feature = "gpu"))]
Present::GpuOrSoftware => {
Backend::Software(Box::new(PlatformSurface::new(window.clone())?))
}
};
let id = window.id();
if modality != Modality::Independent
&& let Some(owner_state) = owned_by.and_then(|id| self.windows.get(&id))
{
owner::adopt(&owner_state.window, &window);
if modality == Modality::Modal {
owner::set_enabled(&owner_state.window, false);
}
}
let mut app = build(surface.size(), surface.scale_factor());
app.set_waker(self.waker.clone());
app.presenting(match surface {
Backend::Software(_) => Present::Software,
#[cfg(feature = "gpu")]
Backend::Gpu(_) => Present::Gpu,
});
self.windows.insert(
id,
WindowState {
damage: DamageTracker::new(surface.size()),
title: config.title.clone(),
window,
surface,
app,
events: Vec::new(),
modifiers: ModifiersState::empty(),
cursor: Point::ZERO,
next_frame: Some(Instant::now()),
owner: owned_by.filter(|_| modality != Modality::Independent),
modality,
frame_interval: config.frame_interval,
placed: None,
},
);
self.report_place(id);
Ok(id)
}
fn close(&mut self, id: WindowId) {
let mut unblocked = Vec::new();
let links: Vec<Link> = self.links().collect();
for doomed in closing_order(&links, id) {
let Some(mut closed) = self.windows.remove(&doomed) else {
continue;
};
closed.app.exiting();
if let (Modality::Modal, Some(owner_id)) = (closed.modality, closed.owner) {
unblocked.push(owner_id);
}
}
for owner_id in unblocked {
if self.blocker(owner_id).is_some() {
continue;
}
if let Some(owner_state) = self.windows.get(&owner_id) {
owner::set_enabled(&owner_state.window, true);
owner_state.window.focus_window();
}
}
}
fn links(&self) -> impl Iterator<Item = Link> + '_ {
self.windows
.iter()
.map(|(id, state)| (*id, state.owner, state.modality))
}
fn blocker(&self, id: WindowId) -> Option<&WindowState> {
let blocker = blocker_of(self.links(), id)?;
self.windows.get(&blocker)
}
fn report_place(&mut self, id: WindowId) {
let Some(state) = self.windows.get_mut(&id) else {
return;
};
let Ok(at) = state.window.outer_position() else {
return;
};
let position = Point::new(at.x, at.y);
let maximized = state.window.is_maximized();
if state.placed == Some((position, maximized)) {
return;
}
state.placed = Some((position, maximized));
state.events.push(InputEvent::SurfaceMoved {
position,
maximized,
});
}
fn on_resize(&mut self, id: WindowId, size: PhysicalSize<u32>) {
let Some(state) = self.windows.get_mut(&id) else {
return;
};
let size = Size::new(size.width, size.height);
let scale = state.surface.window().scale_factor() as f32;
state.surface.resize(size, scale);
state.damage.resize(size);
state.events.push(InputEvent::SurfaceResized {
size,
scale_factor: scale,
});
self.report_place(id);
}
fn draw(&mut self, event_loop: &ActiveEventLoop, id: WindowId) {
match self.draw_frame(id) {
Ok(_) => {}
Err(err) => return self.fail(event_loop, err),
}
let Some(state) = self.windows.get_mut(&id) else {
return;
};
let now = Instant::now();
let interval = state.frame_interval;
state.next_frame = state
.app
.next_frame_in()
.map(|asked| now + asked.max(interval));
if let Some(wanted) = state.app.title()
&& wanted != state.title
{
state.window.set_title(wanted);
state.title.clear();
state.title.push_str(wanted);
}
if state.app.exit_requested() {
if self.main == Some(id) {
event_loop.exit();
} else {
self.close(id);
}
}
}
fn draw_frame(&mut self, id: WindowId) -> Result<bool, Error> {
let requests = {
let Some(state) = self.windows.get_mut(&id) else {
return Ok(false);
};
state.surface.poll(&mut state.events);
state.app.update(&state.events, &mut state.damage);
state.events.clear();
state.app.take_windows()
};
self.pending
.extend(requests.into_iter().map(|request| (id, request)));
self.paint(id)
}
fn paint(&mut self, id: WindowId) -> Result<bool, Error> {
let Some(state) = self.windows.get_mut(&id) else {
return Ok(false);
};
if state.damage.is_clean() {
return Ok(false);
}
let WindowState {
surface,
app,
damage,
..
} = state;
match surface {
Backend::Software(surface) => {
let mut frame = match surface.acquire() {
Ok(frame) => frame,
Err(denise::SurfaceError::NotReady) => return Ok(false),
Err(err) => return Err(err.into()),
};
let mut resolved = [Rect::ZERO; MAX_DAMAGE_RECTS];
let count = {
let src = damage.resolve(frame.age());
resolved[..src.len()].copy_from_slice(src);
src.len()
};
let region = &resolved[..count];
app.render(&mut frame, region);
drop(frame);
surface.present(region)?;
damage.end_frame();
Ok(true)
}
#[cfg(feature = "gpu")]
Backend::Gpu(surface) => {
let age = surface.age();
let mut resolved = [Rect::ZERO; MAX_DAMAGE_RECTS];
let count = {
let src = damage.resolve(age);
resolved[..src.len()].copy_from_slice(src);
src.len()
};
let region = &resolved[..count];
let mut supported = true;
let drawn = surface.paint(region, |pen| {
supported = app.paint(pen, age, region);
})?;
if !supported {
return Err(Error::Gpu(
"this application draws into frames only; implement \
`DeniseApp::paint` to present through the GPU"
.into(),
));
}
if drawn {
damage.end_frame();
}
Ok(drawn)
}
}
}
fn open_pending(&mut self, event_loop: &ActiveEventLoop) {
for (opener, request) in std::mem::take(&mut self.pending) {
if !self.windows.contains_key(&opener) {
continue;
}
let WindowRequest {
config,
modality,
build,
} = request;
if let Err(err) = self.spawn(event_loop, &config, build, Some(opener), modality) {
return self.fail(event_loop, err);
}
}
}
}
impl ApplicationHandler for Runner {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.main.is_some() {
return;
}
let Some(build) = self.build.take() else {
return;
};
let config = self.config.clone();
match self.spawn(event_loop, &config, build, None, Modality::Independent) {
Ok(id) => self.main = Some(id),
Err(err) => self.fail(event_loop, err),
}
}
fn user_event(&mut self, _event_loop: &ActiveEventLoop, (): ()) {
let now = Instant::now();
for state in self.windows.values_mut() {
state.next_frame = Some(now);
}
}
fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: StartCause) {
let now = Instant::now();
for state in self.windows.values() {
if state.next_frame.is_some_and(|next| now >= next) {
state.window.request_redraw();
}
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
if !self.windows.contains_key(&id) {
return;
}
match event {
WindowEvent::RedrawRequested => return self.draw(event_loop, id),
WindowEvent::Resized(size) => return self.on_resize(id, size),
WindowEvent::Moved(_) => return self.report_place(id),
WindowEvent::ScaleFactorChanged { .. } => {
let size = self.windows.get(&id).map(|s| s.window.inner_size());
if let Some(size) = size {
self.on_resize(id, size);
}
return;
}
_ => {}
}
if let Some(blocker) = self.blocker(id) {
let raise = matches!(
event,
WindowEvent::CloseRequested
| WindowEvent::MouseInput {
state: winit::event::ElementState::Pressed,
..
}
);
if raise {
blocker.window.focus_window();
}
return;
}
match event {
WindowEvent::CloseRequested => {
let Some(state) = self.windows.get_mut(&id) else {
return;
};
state.surface.push_event(InputEvent::CloseRequested);
if !state.app.close_requested() {
return;
}
if self.main == Some(id) {
event_loop.exit();
} else {
self.close(id);
}
return;
}
WindowEvent::ModifiersChanged(mods) => {
if let Some(state) = self.windows.get_mut(&id) {
state.modifiers = mods.state();
}
return;
}
WindowEvent::CursorMoved { position, .. } => {
if let Some(state) = self.windows.get_mut(&id) {
state.cursor = Point::new(position.x as i32, position.y as i32);
}
}
_ => {}
}
let Some(state) = self.windows.get_mut(&id) else {
return;
};
let modifiers = keymap::modifiers(state.modifiers);
let cursor = state.cursor;
let surface = &mut state.surface;
match event {
WindowEvent::CursorMoved { .. } => {
surface.push_event(InputEvent::PointerMoved { position: cursor });
}
WindowEvent::CursorLeft { .. } => surface.push_event(InputEvent::PointerLeft),
WindowEvent::MouseInput { state, button, .. } => {
surface.push_event(InputEvent::PointerButton {
button: match button {
MouseButton::Left => PointerButton::Left,
MouseButton::Right => PointerButton::Right,
MouseButton::Middle => PointerButton::Middle,
MouseButton::Back => PointerButton::Other(3),
MouseButton::Forward => PointerButton::Other(4),
MouseButton::Other(n) => PointerButton::Other(n),
},
state: element_state(state),
position: cursor,
modifiers,
});
}
WindowEvent::MouseWheel { delta, .. } => {
let (dx, dy) = match delta {
MouseScrollDelta::LineDelta(x, y) => (x * LINE_HEIGHT_PX, y * LINE_HEIGHT_PX),
MouseScrollDelta::PixelDelta(p) => (p.x as f32, p.y as f32),
};
surface.push_event(InputEvent::PointerScroll {
delta_x: -dx,
delta_y: -dy,
position: cursor,
});
}
WindowEvent::KeyboardInput { event, .. } => {
surface.push_event(InputEvent::Key {
code: keymap::key_code(event.physical_key),
state: element_state(event.state),
repeat: event.repeat,
modifiers,
});
if event.state.is_pressed()
&& let Some(text) = event.text
{
for ch in text.chars().filter(|c| !c.is_control()) {
surface.push_event(InputEvent::Text { ch });
}
}
}
WindowEvent::Touch(touch) => {
let position = Point::new(touch.location.x as i32, touch.location.y as i32);
let id = touch.id;
surface.push_event(match touch.phase {
TouchPhase::Started => InputEvent::TouchDown { id, position },
TouchPhase::Moved => InputEvent::TouchMoved { id, position },
TouchPhase::Ended => InputEvent::TouchUp {
id,
position,
cancelled: false,
},
TouchPhase::Cancelled => InputEvent::TouchUp {
id,
position,
cancelled: true,
},
});
}
_ => {}
}
state.next_frame = Some(Instant::now());
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if self.error.is_some() {
return;
}
self.open_pending(event_loop);
if self.windows.is_empty() {
event_loop.exit();
return;
}
let next = self
.windows
.values()
.filter_map(|state| state.next_frame)
.min();
event_loop.set_control_flow(match next {
Some(next) => ControlFlow::WaitUntil(next),
None => ControlFlow::Wait,
});
}
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
let links: Vec<Link> = self.links().collect();
for id in exiting_order(&links, self.main) {
if let Some(state) = self.windows.get_mut(&id) {
state.app.exiting();
}
}
}
}
fn element_state(state: winit::event::ElementState) -> ElementState {
match state {
winit::event::ElementState::Pressed => ElementState::Down,
winit::event::ElementState::Released => ElementState::Up,
}
}
type Link = (WindowId, Option<WindowId>, Modality);
fn blocker_of(links: impl Iterator<Item = Link>, id: WindowId) -> Option<WindowId> {
links
.into_iter()
.find(|(_, owner, modality)| *owner == Some(id) && *modality == Modality::Modal)
.map(|(blocker, _, _)| blocker)
}
fn closing_order(links: &[Link], id: WindowId) -> Vec<WindowId> {
let mut order = Vec::new();
for (child, owner, _) in links {
if *owner == Some(id) {
order.extend(closing_order(links, *child));
}
}
order.push(id);
order
}
fn exiting_order(links: &[Link], main: Option<WindowId>) -> Vec<WindowId> {
let open = |id| links.iter().any(|(window, _, _)| *window == id);
let mut order = Vec::with_capacity(links.len());
for (window, owner, _) in links {
let root = !owner.is_some_and(open);
if root && Some(*window) != main {
order.extend(closing_order(links, *window));
}
}
if let Some(main) = main.filter(|main| open(*main)) {
order.extend(closing_order(links, main));
}
order
}
fn monitor_bounds(event_loop: &ActiveEventLoop) -> impl Iterator<Item = Rect> {
event_loop.available_monitors().map(|monitor| {
let origin = monitor.position();
let size = monitor.size();
Rect::new(origin.x, origin.y, size.width as i32, size.height as i32)
})
}
fn reachable(monitors: impl IntoIterator<Item = Rect>, at: Point) -> bool {
let mut any = false;
for screen in monitors {
any = true;
if screen.contains(at) {
return true;
}
}
!any
}
#[cfg(test)]
mod tests {
use super::*;
fn id(raw: u64) -> WindowId {
WindowId::from(raw)
}
fn tree() -> Vec<Link> {
vec![
(id(1), None, Modality::Independent),
(id(2), Some(id(1)), Modality::Owned),
(id(3), Some(id(2)), Modality::Modal),
(id(4), Some(id(1)), Modality::Modal),
]
}
#[test]
fn a_modal_blocks_its_own_owner_and_nobody_else() {
assert_eq!(blocker_of(tree().into_iter(), id(1)), Some(id(4)));
assert_eq!(blocker_of(tree().into_iter(), id(2)), Some(id(3)));
assert_eq!(blocker_of(tree().into_iter(), id(3)), None);
assert_eq!(blocker_of(tree().into_iter(), id(4)), None);
}
#[test]
fn a_modeless_form_blocks_nothing() {
let links = [
(id(1), None, Modality::Independent),
(id(2), Some(id(1)), Modality::Owned),
];
assert_eq!(blocker_of(links.iter().copied(), id(1)), None);
}
#[test]
fn closing_a_form_closes_what_it_opened_first() {
assert_eq!(closing_order(&tree(), id(2)), vec![id(3), id(2)]);
}
#[test]
fn closing_the_main_window_closes_the_tree_owners_last() {
let order = closing_order(&tree(), id(1));
assert_eq!(order.len(), 4);
assert_eq!(*order.last().expect("the opener comes last"), id(1));
let at = |window| {
order
.iter()
.position(|listed| *listed == window)
.expect("every window in the tree is listed")
};
assert!(
at(id(3)) < at(id(2)),
"a modal closes before the form under it"
);
assert!(at(id(2)) < at(id(1)));
assert!(at(id(4)) < at(id(1)));
}
fn desk() -> Vec<Rect> {
vec![
Rect::new(0, 0, 1512, 982),
Rect::new(-1920, -400, 1920, 1080),
]
}
#[test]
fn a_position_on_a_display_that_is_still_there_is_honoured() {
assert!(reachable(desk(), Point::new(40, 40)));
assert!(reachable(desk(), Point::new(-1800, -200)));
}
#[test]
fn a_position_on_a_display_that_has_been_unplugged_is_not() {
assert!(!reachable(desk(), Point::new(-1800, 900)), "below it");
assert!(!reachable(desk(), Point::new(2000, 40)), "right of both");
}
#[test]
fn a_machine_that_reports_no_displays_is_taken_at_its_word() {
assert!(reachable(Vec::new(), Point::new(4000, 4000)));
}
#[test]
fn a_window_that_owns_nothing_closes_alone() {
assert_eq!(closing_order(&tree(), id(4)), vec![id(4)]);
}
#[test]
fn an_independent_window_is_not_cascaded() {
let links = [
(id(1), None, Modality::Independent),
(id(2), None, Modality::Independent),
];
assert_eq!(closing_order(&links, id(1)), vec![id(1)]);
}
#[test]
fn the_owner_stays_blocked_until_the_last_modal_goes() {
let mut links = vec![
(id(1), None, Modality::Independent),
(id(2), Some(id(1)), Modality::Modal),
(id(3), Some(id(1)), Modality::Modal),
];
links.retain(|(window, _, _)| *window != id(2));
assert_eq!(blocker_of(links.iter().copied(), id(1)), Some(id(3)));
links.retain(|(window, _, _)| *window != id(3));
assert_eq!(blocker_of(links.iter().copied(), id(1)), None);
}
#[test]
fn the_end_of_the_run_tells_every_window_owners_last() {
let mut links = tree();
links.push((id(5), None, Modality::Independent));
links.push((id(6), Some(id(5)), Modality::Owned));
let order = exiting_order(&links, Some(id(1)));
assert_eq!(order.len(), links.len());
let at = |window| {
order
.iter()
.position(|listed| *listed == window)
.expect("every open window is told")
};
for (window, owner, _) in &links {
if let Some(owner) = owner {
assert!(at(*window) < at(*owner), "{window:?} before {owner:?}");
}
}
assert_eq!(order.last(), Some(&id(1)));
}
#[test]
fn a_window_whose_owner_is_gone_is_still_told() {
let links = [
(id(1), None, Modality::Independent),
(id(2), Some(id(9)), Modality::Owned),
];
assert_eq!(exiting_order(&links, Some(id(1))), vec![id(2), id(1)]);
}
#[test]
fn a_run_without_its_main_window_tells_what_is_open() {
let links = [(id(2), None, Modality::Independent)];
assert_eq!(exiting_order(&links, Some(id(1))), vec![id(2)]);
assert_eq!(exiting_order(&links, None), vec![id(2)]);
}
}