Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add window::Action::CursorGrab and mouse::Event::MouseMotion #2614

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion core/src/mouse/event.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Point;
use crate::{Point, Vector};

use super::Button;

Expand All @@ -22,6 +22,21 @@ pub enum Event {
position: Point,
},

/// The mouse was moved.
///
/// This will fire in situations where [`CursorMoved`] might not,
/// such as the mouse being outside of the window or hitting the edge
/// of the monitor, and can be used to get the correct motion when
/// [`CursorGrab`] is set to something other than [`None`].
///
/// [`CursorMoved`]: Event::CursorMoved
/// [`CursorGrab`]: super::super::window::CursorGrab
/// [`None`]: super::super::window::CursorGrab::None
MouseMotion {
/// The change in position of the mouse cursor
delta: Vector,
},

/// A mouse button was pressed.
ButtonPressed(Button),

Expand Down
30 changes: 30 additions & 0 deletions core/src/point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ where
}
}

impl<T> std::ops::AddAssign<Vector<T>> for Point<T>
where
T: std::ops::AddAssign,
{
fn add_assign(&mut self, vector: Vector<T>) {
self.x += vector.x;
self.y += vector.y;
}
}

impl<T> std::ops::Sub<Vector<T>> for Point<T>
where
T: std::ops::Sub<Output = T>,
Expand All @@ -88,6 +98,16 @@ where
}
}

impl<T> std::ops::SubAssign<Vector<T>> for Point<T>
where
T: std::ops::SubAssign,
{
fn sub_assign(&mut self, vector: Vector<T>) {
self.x -= vector.x;
self.y -= vector.y;
}
}

impl<T> std::ops::Sub<Point<T>> for Point<T>
where
T: std::ops::Sub<Output = T>,
Expand All @@ -99,6 +119,16 @@ where
}
}

impl<T> std::ops::SubAssign<Point<T>> for Point<T>
where
T: std::ops::SubAssign,
{
fn sub_assign(&mut self, point: Point<T>) {
self.x -= point.x;
self.y -= point.y;
}
}

impl<T> fmt::Display for Point<T>
where
T: fmt::Display,
Expand Down
59 changes: 55 additions & 4 deletions core/src/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,18 @@ where
{
type Output = Self;

fn add(self, b: Self) -> Self {
Self::new(self.x + b.x, self.y + b.y)
fn add(self, rhs: Self) -> Self {
Self::new(self.x + rhs.x, self.y + rhs.y)
}
}

impl<T> std::ops::AddAssign for Vector<T>
where
T: std::ops::AddAssign,
{
fn add_assign(&mut self, rhs: Self) {
self.x += rhs.x;
self.y += rhs.y;
}
}

Expand All @@ -48,8 +58,18 @@ where
{
type Output = Self;

fn sub(self, b: Self) -> Self {
Self::new(self.x - b.x, self.y - b.y)
fn sub(self, rhs: Self) -> Self {
Self::new(self.x - rhs.x, self.y - rhs.y)
}
}

impl<T> std::ops::SubAssign for Vector<T>
where
T: std::ops::SubAssign,
{
fn sub_assign(&mut self, rhs: Self) {
self.x -= rhs.x;
self.y -= rhs.y;
}
}

Expand All @@ -64,6 +84,37 @@ where
}
}

impl<T> std::ops::MulAssign<T> for Vector<T>
where
T: std::ops::MulAssign + Copy,
{
fn mul_assign(&mut self, scale: T) {
self.x *= scale;
self.y *= scale;
}
}

impl<T> std::ops::Div<T> for Vector<T>
where
T: std::ops::Div<Output = T> + Copy,
{
type Output = Self;

fn div(self, scale: T) -> Self {
Self::new(self.x / scale, self.y / scale)
}
}

impl<T> std::ops::DivAssign<T> for Vector<T>
where
T: std::ops::DivAssign + Copy,
{
fn div_assign(&mut self, scale: T) {
self.x /= scale;
self.y /= scale;
}
}

impl<T> Default for Vector<T>
where
T: Default,
Expand Down
2 changes: 2 additions & 0 deletions core/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod icon;
pub mod settings;

mod cursor_grab;
mod event;
mod id;
mod level;
Expand All @@ -10,6 +11,7 @@ mod position;
mod redraw_request;
mod user_attention;

pub use cursor_grab::CursorGrab;
pub use event::Event;
pub use icon::Icon;
pub use id::Id;
Expand Down
29 changes: 29 additions & 0 deletions core/src/window/cursor_grab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// The behavior of cursor grabbing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CursorGrab {
/// No grabbing of the cursor is performed.
#[default]
None,

/// The cursor is confined to the window area.
///
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
/// want to do so.
///
/// ## Platform-specific
///
/// - **macOS:** Not implemented.
/// - **iOS / Android / Web:** Unsupported.
Confined,

/// The cursor is locked inside the window area to the certain position.
///
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
/// want to do so.
///
/// ## Platform-specific
///
/// - **X11 / Windows:** Not implemented.
/// - **iOS / Android:** Unsupported.
Locked,
}
75 changes: 50 additions & 25 deletions examples/game_of_life/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl GameOfLife {
match message {
Message::Grid(message, version) => {
if version == self.version {
self.grid.update(message);
return self.grid.update(message).discard();
}
}
Message::Tick | Message::Next => {
Expand Down Expand Up @@ -195,6 +195,9 @@ mod grid {
use iced::widget::canvas;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
use iced::window;
use iced::window::CursorGrab;
use iced::Task;
use iced::{
Color, Element, Fill, Point, Rectangle, Renderer, Size, Theme, Vector,
};
Expand All @@ -219,6 +222,7 @@ mod grid {
pub enum Message {
Populate(Cell),
Unpopulate(Cell),
Panning(bool),
Translated(Vector),
Scaled(f32, Option<Vector>),
Ticked {
Expand Down Expand Up @@ -282,7 +286,7 @@ mod grid {
})
}

pub fn update(&mut self, message: Message) {
pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::Populate(cell) => {
self.state.populate(cell);
Expand All @@ -296,6 +300,22 @@ mod grid {

self.preset = Preset::Custom;
}
Message::Panning(panning) => {
#[cfg(any(target_os = "linux", target_os = "windows"))]
let cursor_grab = CursorGrab::Confined;
#[cfg(any(target_os = "macos", target_arch = "wasm32"))]
let cursor_grab = CursorGrab::Locked;
return window::get_oldest().then(move |id| {
window::cursor_grab(
id.expect("there is only a single window so it must be the oldest"),
if panning {
cursor_grab
} else {
CursorGrab::None
},
)
});
}
Message::Translated(translation) => {
self.translation = translation;

Expand Down Expand Up @@ -327,6 +347,8 @@ mod grid {
dbg!(error);
}
}

Task::none()
}

pub fn view(&self) -> Element<Message> {
Expand Down Expand Up @@ -379,13 +401,17 @@ mod grid {

fn update(
&self,
interaction: &mut Interaction,
mut interaction: &mut Interaction,
event: Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
*interaction = Interaction::None;
return (
event::Status::Captured,
Some(Message::Panning(false)),
);
}

let Some(cursor_position) = cursor.position_in(bounds) else {
Expand Down Expand Up @@ -430,36 +456,35 @@ mod grid {
mouse::Button::Right => {
*interaction = Interaction::Panning {
translation: self.translation,
start: cursor_position,
};

None
Some(Message::Panning(true))
}
_ => None,
};

(event::Status::Captured, message)
}
mouse::Event::CursorMoved { .. } => {
let message = match *interaction {
Interaction::Drawing => populate,
Interaction::Erasing => unpopulate,
Interaction::Panning { translation, start } => {
Some(Message::Translated(
translation
+ (cursor_position - start)
* (1.0 / self.scaling),
))
mouse::Event::CursorMoved { .. } => match *interaction {
Interaction::Drawing => {
(event::Status::Captured, populate)
}
Interaction::Erasing => {
(event::Status::Captured, unpopulate)
}
_ => (event::Status::Ignored, None),
},
mouse::Event::MouseMotion { delta } => {
match &mut interaction {
Interaction::Panning { translation } => {
*translation += delta / self.scaling;
(
event::Status::Captured,
Some(Message::Translated(*translation)),
)
}
Interaction::None => None,
};

let event_status = match interaction {
Interaction::None => event::Status::Ignored,
_ => event::Status::Captured,
};

(event_status, message)
_ => (event::Status::Ignored, None),
}
}
mouse::Event::WheelScrolled { delta } => match delta {
mouse::ScrollDelta::Lines { y, .. }
Expand Down Expand Up @@ -881,7 +906,7 @@ mod grid {
None,
Drawing,
Erasing,
Panning { translation: Vector, start: Point },
Panning { translation: Vector },
}

impl Default for Interaction {
Expand Down
13 changes: 13 additions & 0 deletions runtime/src/window.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Build window-based GUI applications.
pub mod screenshot;

use iced_core::window::CursorGrab;
pub use screenshot::Screenshot;

use crate::core::time::Instant;
Expand Down Expand Up @@ -159,6 +160,13 @@ pub enum Action {
/// This enables mouse events for the window and stops mouse events
/// from being passed to whatever is underneath.
DisableMousePassthrough(Id),

/// Constraints the cursor in a window.
///
/// ## Platform-specific
/// -- **Web / macOS:** [`CursorGrab::Confined`] unsupported.
/// -- **Windows / X11:** [`CursorGrab::Locked`] unsupported.
CursorGrab(Id, CursorGrab),
}

/// Subscribes to the frames of the window of the running application.
Expand Down Expand Up @@ -434,3 +442,8 @@ pub fn enable_mouse_passthrough<Message>(id: Id) -> Task<Message> {
pub fn disable_mouse_passthrough<Message>(id: Id) -> Task<Message> {
task::effect(crate::Action::Window(Action::DisableMousePassthrough(id)))
}

/// Constraints the cursor in a window.
pub fn cursor_grab<Message>(id: Id, cursor_grab: CursorGrab) -> Task<Message> {
task::effect(crate::Action::Window(Action::CursorGrab(id, cursor_grab)))
}
Loading
Loading