diff --git a/examples/window.rs b/examples/window.rs index 6413f7dcf8..8fc8c07960 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -15,7 +15,7 @@ use rwh_05::HasRawDisplayHandle; use softbuffer::{Context, Surface}; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; -use winit::event::{DeviceEvent, DeviceId, Event, Ime, WindowEvent}; +use winit::event::{DeviceEvent, DeviceId, ElementState, Event, Ime, KeyEvent, WindowEvent}; use winit::event::{MouseButton, MouseScrollDelta}; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::keyboard::{Key, ModifiersState}; @@ -51,7 +51,7 @@ fn main() -> Result<(), Box> { } }); - let mut state = Application::new(&event_loop); + let mut app = Application::new(&event_loop); event_loop.run(move |event, event_loop| match event { Event::NewEvents(_) => (), @@ -59,23 +59,22 @@ fn main() -> Result<(), Box> { println!("Resumed the event loop"); // Create initial window. - state - .create_window(event_loop, None) + app.create_window(event_loop, None) .expect("failed to create initial window"); - state.print_help(); + app.print_help(); } Event::AboutToWait => { - if state.windows.is_empty() { + if app.windows.is_empty() { println!("No windows left, exiting..."); event_loop.exit(); } } Event::WindowEvent { window_id, event } => { - state.handle_window_event(event_loop, window_id, event) + app.handle_window_event(event_loop, window_id, event) } Event::DeviceEvent { device_id, event } => { - state.handle_device_event(event_loop, device_id, event) + app.handle_device_event(event_loop, device_id, event) } Event::UserEvent(event) => { println!("User event: {event:?}"); @@ -172,16 +171,62 @@ impl Application { window.recognize_rotation_gesture(true); } - let window_state = WindowState::new(self, window)?; - let window_id = window_state.window.id(); + #[cfg(not(any(android_platform, ios_platform)))] + let surface = { + // SAFETY: the surface is dropped before the `window` which + // provided it with handle, thus it doesn't outlive it. + let mut surface = unsafe { Surface::new(&self.context, &window)? }; + + let size = window.inner_size(); + if let (Some(width), Some(height)) = + (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) + { + surface + .resize(width, height) + .expect("failed to resize inner buffer"); + }; + + surface + }; + + let theme = window.theme().unwrap_or(Theme::Dark); + println!("Theme: {theme:?}"); + let named_idx = 0; + window.set_cursor(CURSORS[named_idx]); + + // Allow IME out of the box. + let ime = true; + window.set_ime_allowed(ime); + + let state = WindowState { + #[cfg(macos_platform)] + option_as_alt: window.option_as_alt(), + window, + custom_idx: self.custom_cursors.len() - 1, + cursor_grab: CursorGrabMode::None, + named_idx, + #[cfg(not(any(android_platform, ios_platform)))] + surface, + theme, + ime, + cursor_position: Default::default(), + cursor_hidden: Default::default(), + modifiers: Default::default(), + occluded: Default::default(), + rotated: Default::default(), + zoom: Default::default(), + }; + + let window_id = state.window.id(); println!("Created new window with id={window_id:?}"); - self.windows.insert(window_id, window_state); + self.windows.insert(window_id, state); Ok(window_id) } fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) { - // let cursor_position = self.cursor_position; - let window = self.windows.get_mut(&window_id).unwrap(); + let state = self.windows.get_mut(&window_id).unwrap(); + let window = &state.window; + println!("Executing action: {action:?}"); match action { Action::CloseWindow => { @@ -189,7 +234,7 @@ impl Application { } Action::CreateNewWindow => { #[cfg(any(x11_platform, wayland_platform))] - if let Err(err) = window.window.request_activation_token() { + if let Err(err) = window.request_activation_token() { println!("Failed to get activation token: {err}"); } else { return; @@ -199,26 +244,146 @@ impl Application { eprintln!("Error creating new window: {err}"); } } - Action::ToggleResizeIncrements => window.toggle_resize_increments(), - Action::ToggleCursorVisibility => window.toggle_cursor_visibility(), - Action::ToggleResizable => window.toggle_resizable(), - Action::ToggleDecorations => window.toggle_decorations(), - Action::ToggleFullscreen => window.toggle_fullscreen(), - Action::ToggleMaximize => window.toggle_maximize(), - Action::ToggleImeInput => window.toggle_ime(), - Action::Minimize => window.minimize(), - Action::NextCursor => window.next_cursor(), - Action::NextCustomCursor => window.next_custom_cursor(&self.custom_cursors), - Action::CycleCursorGrab => window.cycle_cursor_grab(), - Action::DragWindow => window.drag_window(), - Action::DragResizeWindow => window.drag_resize_window(), - Action::ShowWindowMenu => window.show_menu(), + Action::ToggleResizeIncrements => { + let new_increments = match window.resize_increments() { + Some(_) => None, + None => Some(LogicalSize::new(25.0, 25.0)), + }; + println!("Had increments: {}", new_increments.is_none()); + window.set_resize_increments(new_increments); + } + Action::ToggleCursorVisibility => { + state.cursor_hidden = !state.cursor_hidden; + window.set_cursor_visible(!state.cursor_hidden); + } + Action::ToggleResizable => { + let resizable = window.is_resizable(); + window.set_resizable(!resizable); + } + Action::ToggleDecorations => { + let decorated = window.is_decorated(); + window.set_decorations(!decorated); + } + Action::ToggleFullscreen => { + let fullscreen = if window.fullscreen().is_some() { + None + } else { + Some(Fullscreen::Borderless(None)) + }; + + window.set_fullscreen(fullscreen); + } + Action::ToggleMaximize => { + let maximized = window.is_maximized(); + window.set_maximized(!maximized); + } + Action::ToggleImeInput => { + state.ime = !state.ime; + window.set_ime_allowed(state.ime); + if let Some(position) = state.ime.then_some(state.cursor_position).flatten() { + window.set_ime_cursor_area(position, PhysicalSize::new(20, 20)); + } + } + Action::Minimize => { + window.set_minimized(true); + } + Action::NextCursor => { + // Pick the next cursor + state.named_idx = (state.named_idx + 1) % CURSORS.len(); + println!("Setting cursor to \"{:?}\"", CURSORS[state.named_idx]); + window.set_cursor(Cursor::Icon(CURSORS[state.named_idx])); + } + Action::NextCustomCursor => { + state.custom_idx = (state.custom_idx + 1) % self.custom_cursors.len(); + let cursor = Cursor::Custom(self.custom_cursors[state.custom_idx].clone()); + window.set_cursor(cursor); + } + Action::CycleCursorGrab => { + state.cursor_grab = match state.cursor_grab { + CursorGrabMode::None => CursorGrabMode::Confined, + CursorGrabMode::Confined => CursorGrabMode::Locked, + CursorGrabMode::Locked => CursorGrabMode::None, + }; + println!("Changing cursor grab mode to {:?}", state.cursor_grab); + if let Err(err) = window.set_cursor_grab(state.cursor_grab) { + eprintln!("Error setting cursor grab: {err}"); + } + } + Action::DragWindow => { + if let Err(err) = window.drag_window() { + println!("Error starting window drag: {err}"); + } else { + println!("Dragging window Window={:?}", window.id()); + } + } + Action::DragResizeWindow => { + let position = match state.cursor_position { + Some(position) => position, + None => { + println!("Drag-resize requires cursor to be inside the window"); + return; + } + }; + + let win_size = window.inner_size(); + let border_size = BORDER_SIZE * window.scale_factor(); + + let x_direction = if position.x < border_size { + ResizeDirection::West + } else if position.x > (win_size.width as f64 - border_size) { + ResizeDirection::East + } else { + // Use arbitrary direction instead of None for simplicity. + ResizeDirection::SouthEast + }; + + let y_direction = if position.y < border_size { + ResizeDirection::North + } else if position.y > (win_size.height as f64 - border_size) { + ResizeDirection::South + } else { + // Use arbitrary direction instead of None for simplicity. + ResizeDirection::SouthEast + }; + + let direction = match (x_direction, y_direction) { + (ResizeDirection::West, ResizeDirection::North) => ResizeDirection::NorthWest, + (ResizeDirection::West, ResizeDirection::South) => ResizeDirection::SouthWest, + (ResizeDirection::West, _) => ResizeDirection::West, + (ResizeDirection::East, ResizeDirection::North) => ResizeDirection::NorthEast, + (ResizeDirection::East, ResizeDirection::South) => ResizeDirection::SouthEast, + (ResizeDirection::East, _) => ResizeDirection::East, + (_, ResizeDirection::South) => ResizeDirection::South, + (_, ResizeDirection::North) => ResizeDirection::North, + _ => return, + }; + + if let Err(err) = window.drag_resize_window(direction) { + println!("Error starting window drag-resize: {err}"); + } else { + println!("Drag-resizing window Window={:?}", window.id()); + } + } + Action::ShowWindowMenu => { + if let Some(position) = state.cursor_position { + window.show_window_menu(position); + } + } Action::PrintHelp => self.print_help(), #[cfg(macos_platform)] - Action::CycleOptionAsAlt => window.cycle_option_as_alt(), + Action::CycleOptionAsAlt => { + state.option_as_alt = match state.option_as_alt { + OptionAsAlt::None => OptionAsAlt::OnlyLeft, + OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight, + OptionAsAlt::OnlyRight => OptionAsAlt::Both, + OptionAsAlt::Both => OptionAsAlt::None, + }; + println!("Setting option as alt {:?}", state.option_as_alt); + window.set_option_as_alt(state.option_as_alt); + } #[cfg(macos_platform)] Action::CreateNewTab => { - let tab_id = window.window.tabbing_identifier(); + let tab_id = window.tabbing_identifier(); if let Err(err) = self.create_window(event_loop, Some(tab_id)) { eprintln!("Error creating new window: {err}"); } @@ -232,14 +397,28 @@ impl Application { window_id: WindowId, event: WindowEvent, ) { - let window = match self.windows.get_mut(&window_id) { - Some(window) => window, + let state = match self.windows.get_mut(&window_id) { + Some(state) => state, None => return, }; + let window = &state.window; match event { - WindowEvent::Resized(size) => { - window.resize(size); + // Resize the surface to the new size + WindowEvent::Resized(_size) => { + #[cfg(not(any(android_platform, ios_platform)))] + { + let (width, height) = + match (NonZeroU32::new(_size.width), NonZeroU32::new(_size.height)) { + (Some(width), Some(height)) => (width, height), + _ => return, + }; + state + .surface + .resize(width, height) + .expect("failed to resize inner buffer"); + } + window.request_redraw(); } WindowEvent::Focused(focused) => { if focused { @@ -253,23 +432,51 @@ impl Application { } WindowEvent::ThemeChanged(theme) => { println!("Theme changed to {theme:?}"); - window.set_theme(theme); + state.theme = theme; + window.request_redraw(); } + #[cfg(not(any(android_platform, ios_platform)))] WindowEvent::RedrawRequested => { - if let Err(err) = window.draw() { - eprintln!("Error drawing window: {err}"); + // Draw the window contents. + + if state.occluded { + println!("Skipping drawing occluded window={:?}", window_id); } + + const WHITE: u32 = 0xFFFFFFFF; + const DARK_GRAY: u32 = 0xFF181818; + + let color = match state.theme { + Theme::Light => WHITE, + Theme::Dark => DARK_GRAY, + }; + + let mut buffer = state + .surface + .buffer_mut() + .expect("could not retrieve buffer"); + buffer.fill(color); + window.pre_present_notify(); + buffer.present().expect("failed presenting to window"); } + #[cfg(any(android_platform, ios_platform))] + WindowEvent::RedrawRequested => { + println!("Drawing but without rendering..."); + } + // Change window occlusion state. WindowEvent::Occluded(occluded) => { - window.set_occluded(occluded); + state.occluded = occluded; + if !occluded { + window.request_redraw(); + } } WindowEvent::CloseRequested => { println!("Closing Window={window_id:?}"); self.windows.remove(&window_id); } WindowEvent::ModifiersChanged(modifiers) => { - window.modifiers = modifiers.state(); - println!("Modifiers changed to {:?}", window.modifiers); + state.modifiers = modifiers.state(); + println!("Modifiers changed to {:?}", state.modifiers); } WindowEvent::MouseWheel { delta, .. } => match delta { MouseScrollDelta::LineDelta(x, y) => { @@ -280,42 +487,44 @@ impl Application { } }, WindowEvent::KeyboardInput { - event, + event: + KeyEvent { + // Dispatch actions only on press. + state: ElementState::Pressed, + logical_key, + .. + }, is_synthetic: false, .. } => { - let mods = window.modifiers; - - // Dispatch actions only on press. - if event.state.is_pressed() { - let action = if let Key::Character(ch) = event.logical_key.as_ref() { - Self::process_key_binding(&ch.to_uppercase(), &mods) - } else { - None - }; - - if let Some(action) = action { + if let Key::Character(ch) = logical_key.as_ref() { + let mods = state.modifiers; + if let Some(action) = Self::process_key_binding(&ch.to_uppercase(), &mods) { self.handle_action(event_loop, window_id, action); } } } - WindowEvent::MouseInput { button, state, .. } => { - let mods = window.modifiers; - if let Some(action) = state - .is_pressed() - .then(|| Self::process_mouse_binding(button, &mods)) - .flatten() - { + WindowEvent::KeyboardInput { .. } => {} + WindowEvent::MouseInput { + button, + state: ElementState::Pressed, + .. + } => { + if let Some(action) = Self::process_mouse_binding(button, &state.modifiers) { self.handle_action(event_loop, window_id, action); } } + WindowEvent::MouseInput { .. } => {} WindowEvent::CursorLeft { .. } => { println!("Cursor left Window={window_id:?}"); - window.cursor_left(); + state.cursor_position = None; } WindowEvent::CursorMoved { position, .. } => { println!("Moved cursor to {position:?}"); - window.cursor_moved(position); + state.cursor_position = Some(position); + if state.ime { + window.set_ime_cursor_area(position, PhysicalSize::new(20, 20)); + } } WindowEvent::ActivationTokenDone { token: _token, .. } => { #[cfg(any(x11_platform, wayland_platform))] @@ -337,8 +546,8 @@ impl Application { Ime::Disabled => println!("IME disabled for Window={window_id:?}"), }, WindowEvent::PinchGesture { delta, .. } => { - window.zoom += delta; - let zoom = window.zoom; + state.zoom += delta; + let zoom = state.zoom; if delta > 0.0 { println!("Zoomed in {delta:.5} (now: {zoom:.5})"); } else { @@ -346,8 +555,8 @@ impl Application { } } WindowEvent::RotationGesture { delta, .. } => { - window.rotated += delta; - let rotated = window.rotated; + state.rotated += delta; + let rotated = state.rotated; if delta > 0.0 { println!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})"); } else { @@ -359,7 +568,6 @@ impl Application { } WindowEvent::TouchpadPressure { .. } | WindowEvent::HoveredFileCancelled - | WindowEvent::KeyboardInput { .. } | WindowEvent::CursorEntered { .. } | WindowEvent::AxisMotion { .. } | WindowEvent::DroppedFile(_) @@ -416,8 +624,10 @@ impl Application { } } -/// State of the window. +/// Extra state on a window used in this example. struct WindowState { + /// The actual Winit window. + window: Window, /// IME input. ime: bool, /// Render surface. @@ -425,8 +635,6 @@ struct WindowState { /// NOTE: This surface must be dropped before the `Window`. #[cfg(not(any(android_platform, ios_platform)))] surface: Surface, - /// The actual winit Window. - window: Window, /// The window theme we're drawing with. theme: Theme, /// Cursor position over the window. @@ -451,282 +659,6 @@ struct WindowState { cursor_hidden: bool, } -impl WindowState { - fn new(application: &Application, window: Window) -> Result> { - // SAFETY: the surface is dropped before the `window` which provided it with handle, thus - // it doesn't outlive it. - #[cfg(not(any(android_platform, ios_platform)))] - let surface = unsafe { Surface::new(&application.context, &window)? }; - - let theme = window.theme().unwrap_or(Theme::Dark); - println!("Theme: {theme:?}"); - let named_idx = 0; - window.set_cursor(CURSORS[named_idx]); - - // Allow IME out of the box. - let ime = true; - window.set_ime_allowed(ime); - - let size = window.inner_size(); - let mut state = Self { - #[cfg(macos_platform)] - option_as_alt: window.option_as_alt(), - custom_idx: application.custom_cursors.len() - 1, - cursor_grab: CursorGrabMode::None, - named_idx, - #[cfg(not(any(android_platform, ios_platform)))] - surface, - window, - theme, - ime, - cursor_position: Default::default(), - cursor_hidden: Default::default(), - modifiers: Default::default(), - occluded: Default::default(), - rotated: Default::default(), - zoom: Default::default(), - }; - - state.resize(size); - Ok(state) - } - - pub fn toggle_ime(&mut self) { - self.ime = !self.ime; - self.window.set_ime_allowed(self.ime); - if let Some(position) = self.ime.then_some(self.cursor_position).flatten() { - self.window - .set_ime_cursor_area(position, PhysicalSize::new(20, 20)); - } - } - - pub fn minimize(&mut self) { - self.window.set_minimized(true); - } - - pub fn cursor_moved(&mut self, position: PhysicalPosition) { - self.cursor_position = Some(position); - if self.ime { - self.window - .set_ime_cursor_area(position, PhysicalSize::new(20, 20)); - } - } - - pub fn cursor_left(&mut self) { - self.cursor_position = None; - } - - /// Toggle maximized. - fn toggle_maximize(&self) { - let maximized = self.window.is_maximized(); - self.window.set_maximized(!maximized); - } - - /// Toggle window decorations. - fn toggle_decorations(&self) { - let decorated = self.window.is_decorated(); - self.window.set_decorations(!decorated); - } - - /// Toggle window resizable state. - fn toggle_resizable(&self) { - let resizable = self.window.is_resizable(); - self.window.set_resizable(!resizable); - } - - /// Toggle cursor visibility - fn toggle_cursor_visibility(&mut self) { - self.cursor_hidden = !self.cursor_hidden; - self.window.set_cursor_visible(!self.cursor_hidden); - } - - /// Toggle resize increments on a window. - fn toggle_resize_increments(&mut self) { - let new_increments = match self.window.resize_increments() { - Some(_) => None, - None => Some(LogicalSize::new(25.0, 25.0)), - }; - println!("Had increments: {}", new_increments.is_none()); - self.window.set_resize_increments(new_increments); - } - - /// Toggle fullscreen. - fn toggle_fullscreen(&self) { - let fullscreen = if self.window.fullscreen().is_some() { - None - } else { - Some(Fullscreen::Borderless(None)) - }; - - self.window.set_fullscreen(fullscreen); - } - - /// Cycle through the grab modes ignoring errors. - fn cycle_cursor_grab(&mut self) { - self.cursor_grab = match self.cursor_grab { - CursorGrabMode::None => CursorGrabMode::Confined, - CursorGrabMode::Confined => CursorGrabMode::Locked, - CursorGrabMode::Locked => CursorGrabMode::None, - }; - println!("Changing cursor grab mode to {:?}", self.cursor_grab); - if let Err(err) = self.window.set_cursor_grab(self.cursor_grab) { - eprintln!("Error setting cursor grab: {err}"); - } - } - - #[cfg(macos_platform)] - fn cycle_option_as_alt(&mut self) { - self.option_as_alt = match self.option_as_alt { - OptionAsAlt::None => OptionAsAlt::OnlyLeft, - OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight, - OptionAsAlt::OnlyRight => OptionAsAlt::Both, - OptionAsAlt::Both => OptionAsAlt::None, - }; - println!("Setting option as alt {:?}", self.option_as_alt); - self.window.set_option_as_alt(self.option_as_alt); - } - - /// Pick the next cursor. - fn next_cursor(&mut self) { - self.named_idx = (self.named_idx + 1) % CURSORS.len(); - println!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]); - self.window - .set_cursor(Cursor::Icon(CURSORS[self.named_idx])); - } - - /// Pick the next custom cursor. - fn next_custom_cursor(&mut self, custom_cursors: &[CustomCursor]) { - self.custom_idx = (self.custom_idx + 1) % custom_cursors.len(); - let cursor = Cursor::Custom(custom_cursors[self.custom_idx].clone()); - self.window.set_cursor(cursor); - } - - /// Resize the window to the new size. - fn resize(&mut self, _size: PhysicalSize) { - #[cfg(not(any(android_platform, ios_platform)))] - { - let (width, height) = - match (NonZeroU32::new(_size.width), NonZeroU32::new(_size.height)) { - (Some(width), Some(height)) => (width, height), - _ => return, - }; - self.surface - .resize(width, height) - .expect("failed to resize inner buffer"); - } - self.window.request_redraw(); - } - - /// Change the theme. - fn set_theme(&mut self, theme: Theme) { - self.theme = theme; - self.window.request_redraw(); - } - - /// Show window menu. - fn show_menu(&self) { - if let Some(position) = self.cursor_position { - self.window.show_window_menu(position); - } - } - - /// Drag the window. - fn drag_window(&self) { - if let Err(err) = self.window.drag_window() { - println!("Error starting window drag: {err}"); - } else { - println!("Dragging window Window={:?}", self.window.id()); - } - } - - /// Drag-resize the window. - fn drag_resize_window(&self) { - let position = match self.cursor_position { - Some(position) => position, - None => { - println!("Drag-resize requires cursor to be inside the window"); - return; - } - }; - - let win_size = self.window.inner_size(); - let border_size = BORDER_SIZE * self.window.scale_factor(); - - let x_direction = if position.x < border_size { - ResizeDirection::West - } else if position.x > (win_size.width as f64 - border_size) { - ResizeDirection::East - } else { - // Use arbitrary direction instead of None for simplicity. - ResizeDirection::SouthEast - }; - - let y_direction = if position.y < border_size { - ResizeDirection::North - } else if position.y > (win_size.height as f64 - border_size) { - ResizeDirection::South - } else { - // Use arbitrary direction instead of None for simplicity. - ResizeDirection::SouthEast - }; - - let direction = match (x_direction, y_direction) { - (ResizeDirection::West, ResizeDirection::North) => ResizeDirection::NorthWest, - (ResizeDirection::West, ResizeDirection::South) => ResizeDirection::SouthWest, - (ResizeDirection::West, _) => ResizeDirection::West, - (ResizeDirection::East, ResizeDirection::North) => ResizeDirection::NorthEast, - (ResizeDirection::East, ResizeDirection::South) => ResizeDirection::SouthEast, - (ResizeDirection::East, _) => ResizeDirection::East, - (_, ResizeDirection::South) => ResizeDirection::South, - (_, ResizeDirection::North) => ResizeDirection::North, - _ => return, - }; - - if let Err(err) = self.window.drag_resize_window(direction) { - println!("Error starting window drag-resize: {err}"); - } else { - println!("Drag-resizing window Window={:?}", self.window.id()); - } - } - - /// Change window occlusion state. - fn set_occluded(&mut self, occluded: bool) { - self.occluded = occluded; - if !occluded { - self.window.request_redraw(); - } - } - - /// Draw the window contents. - #[cfg(not(any(android_platform, ios_platform)))] - fn draw(&mut self) -> Result<(), Box> { - if self.occluded { - println!("Skipping drawing occluded window={:?}", self.window.id()); - return Ok(()); - } - - const WHITE: u32 = 0xFFFFFFFF; - const DARK_GRAY: u32 = 0xFF181818; - - let color = match self.theme { - Theme::Light => WHITE, - Theme::Dark => DARK_GRAY, - }; - - let mut buffer = self.surface.buffer_mut()?; - buffer.fill(color); - self.window.pre_present_notify(); - buffer.present()?; - Ok(()) - } - - #[cfg(any(android_platform, ios_platform))] - fn draw(&mut self) -> Result<(), Box> { - println!("Drawing but without rendering..."); - Ok(()) - } -} - struct Binding { trigger: T, mods: ModifiersState, @@ -747,6 +679,7 @@ impl Binding { } } +/// Helper enum describing the different kinds of actions this example can do. #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Action { CloseWindow,