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

Make the cursor API stateful and widget-local. #1433

Merged
merged 6 commits into from
Dec 4, 2020
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ You can find its changes [documented below](#060---2020-06-01).
- `Delegate::command` now returns `Handled`, not `bool` ([#1298] by [@jneem])
- `TextBox` selects all contents when tabbed to on macOS ([#1283] by [@cmyr])
- All Image formats are now optional, reducing compile time and binary size by default ([#1340] by [@JAicewizard])
- The `Cursor` API has changed to a stateful one ([#1433] by [@jneem])

### Deprecated

Expand Down Expand Up @@ -542,6 +543,7 @@ Last release without a changelog :(
[#1361]: https://github.com/linebender/druid/pull/1361
[#1371]: https://github.com/linebender/druid/pull/1371
[#1410]: https://github.com/linebender/druid/pull/1410
[#1433]: https://github.com/linebender/druid/pull/1433
[#1438]: https://github.com/linebender/druid/pull/1438
[#1441]: https://github.com/linebender/druid/pull/1441

Expand Down
7 changes: 4 additions & 3 deletions druid-shell/src/mouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,9 @@ impl std::fmt::Debug for MouseButtons {
}

//NOTE: this currently only contains cursors that are included by default on
//both Windows and macOS. We may want to provide polyfills for various additional cursors,
//and we will also want to add some mechanism for adding custom cursors.
//both Windows and macOS. We may want to provide polyfills for various additional cursors.
/// Mouse cursors.
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub enum Cursor {
/// The default arrow cursor.
Arrow,
Expand All @@ -254,6 +253,8 @@ pub enum Cursor {
NotAllowed,
ResizeLeftRight,
ResizeUpDown,
// The platform cursor should be small. Any image data that it uses should be shared (i.e.
// behind an `Arc` or using a platform API that does the sharing).
Custom(platform::window::CustomCursor),
}

Expand Down
2 changes: 1 addition & 1 deletion druid-shell/src/platform/gtk/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ pub(crate) struct WindowState {
deferred_queue: RefCell<Vec<DeferredOp>>,
}

#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct CustomCursor(gdk::Cursor);

impl WindowBuilder {
Expand Down
2 changes: 1 addition & 1 deletion druid-shell/src/platform/mac/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ struct ViewState {
text: PietText,
}

#[derive(Clone)]
#[derive(Clone, PartialEq)]
// TODO: support custom cursors
pub struct CustomCursor;

Expand Down
2 changes: 1 addition & 1 deletion druid-shell/src/platform/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ struct WindowState {
}

// TODO: support custom cursors
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct CustomCursor;

impl WindowState {
Expand Down
3 changes: 2 additions & 1 deletion druid-shell/src/platform/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,10 @@ struct DxgiState {
swap_chain: *mut IDXGISwapChain1,
}

#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct CustomCursor(Arc<HCursor>);

#[derive(PartialEq)]
struct HCursor(HCURSOR);

impl Drop for HCursor {
Expand Down
2 changes: 1 addition & 1 deletion druid-shell/src/platform/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ struct PresentData {
last_ust: Option<u64>,
}

#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct CustomCursor(xproto::Cursor);

impl Window {
Expand Down
4 changes: 2 additions & 2 deletions druid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ piet-common = { version = "=0.2.0-pre6", features = ["png"] }

[[example]]
name = "cursor"
required-features = ["image"]
required-features = ["image", "png"]

[[example]]
name = "image"
required-features = ["image"]
required-features = ["image", "png"]

[[example]]
name = "invalidation"
Expand Down
75 changes: 39 additions & 36 deletions druid/examples/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ use druid::{
use druid::widget::prelude::*;
use druid::widget::{Button, Controller};

use std::rc::Rc;

/// This Controller switches the current cursor based on the selection.
/// The crucial part of this code is actually making and initialising
/// the cursor. This happens here. Because we cannot make the cursor
Expand All @@ -44,28 +42,31 @@ impl<W: Widget<AppState>> Controller<AppState, W> for CursorArea {
data: &mut AppState,
env: &Env,
) {
match event {
Event::WindowConnected => {
data.custom = ctx.window().make_cursor(&data.custom_desc).map(Rc::new);
}
Event::MouseMove(_) => {
// Because the cursor is reset to the default on every `MouseMove`
// event we have to explicitly overwrite this every event.
ctx.set_cursor(&data.cursor);
}
_ => {}
if let Event::WindowConnected = event {
data.custom = ctx.window().make_cursor(&data.custom_desc);
}
child.event(ctx, event, data, env);
}

fn update(
&mut self,
child: &mut W,
ctx: &mut UpdateCtx,
old_data: &AppState,
data: &AppState,
env: &Env,
) {
if data.cursor != old_data.cursor {
ctx.set_cursor(&data.cursor);
}
child.update(ctx, old_data, data, env);
}
}

fn ui_builder() -> impl Widget<AppState> {
Button::new("Change cursor")
.on_click(|ctx, data: &mut AppState, _env| {
data.cursor = next_cursor(&data.cursor, data.custom.clone());
// After changing the cursor, we need to update the active cursor
// via the context in order for the change to take effect immediately.
ctx.set_cursor(&data.cursor);
.on_click(|_ctx, data: &mut AppState, _env| {
data.next_cursor();
})
.padding(50.0)
.controller(CursorArea {})
Expand All @@ -75,31 +76,33 @@ fn ui_builder() -> impl Widget<AppState> {

#[derive(Clone, Data, Lens)]
struct AppState {
cursor: Rc<Cursor>,
custom: Option<Rc<Cursor>>,
cursor: Cursor,
custom: Option<Cursor>,
// To see what #[data(ignore)] does look at the docs.rs page on `Data`:
// https://docs.rs/druid/0.6.0/druid/trait.Data.html
#[data(ignore)]
custom_desc: CursorDesc,
}

fn next_cursor(c: &Cursor, custom: Option<Rc<Cursor>>) -> Rc<Cursor> {
Rc::new(match c {
Cursor::Arrow => Cursor::IBeam,
Cursor::IBeam => Cursor::Crosshair,
Cursor::Crosshair => Cursor::OpenHand,
Cursor::OpenHand => Cursor::NotAllowed,
Cursor::NotAllowed => Cursor::ResizeLeftRight,
Cursor::ResizeLeftRight => Cursor::ResizeUpDown,
Cursor::ResizeUpDown => {
if let Some(custom) = custom {
return custom;
} else {
Cursor::Arrow
impl AppState {
fn next_cursor(&mut self) {
self.cursor = match self.cursor {
Cursor::Arrow => Cursor::IBeam,
Cursor::IBeam => Cursor::Crosshair,
Cursor::Crosshair => Cursor::OpenHand,
Cursor::OpenHand => Cursor::NotAllowed,
Cursor::NotAllowed => Cursor::ResizeLeftRight,
Cursor::ResizeLeftRight => Cursor::ResizeUpDown,
Cursor::ResizeUpDown => {
if let Some(custom) = &self.custom {
custom.clone()
} else {
Cursor::Arrow
}
}
}
Cursor::Custom(_) => Cursor::Arrow,
})
Cursor::Custom(_) => Cursor::Arrow,
};
}
}

pub fn main() {
Expand All @@ -110,7 +113,7 @@ pub fn main() {
let custom_desc = CursorDesc::new(cursor_image, (0.0, 0.0));

let data = AppState {
cursor: Rc::new(Cursor::Arrow),
cursor: Cursor::Arrow,
custom: None,
custom_desc,
};
Expand Down
49 changes: 34 additions & 15 deletions druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::{
time::Duration,
};

use crate::core::{CommandQueue, FocusChange, WidgetState};
use crate::core::{CommandQueue, CursorChange, FocusChange, WidgetState};
use crate::env::KeyLike;
use crate::piet::{Piet, PietText, RenderContext};
use crate::shell::Region;
Expand Down Expand Up @@ -67,7 +67,6 @@ pub struct EventCtx<'a, 'b> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) notifications: &'a mut VecDeque<Notification>,
pub(crate) cursor: &'a mut Option<Cursor>,
pub(crate) is_handled: bool,
pub(crate) is_root: bool,
}
Expand Down Expand Up @@ -95,7 +94,6 @@ pub struct LifeCycleCtx<'a, 'b> {
pub struct UpdateCtx<'a, 'b> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) cursor: &'a mut Option<Cursor>,
pub(crate) prev_env: Option<&'a Env>,
pub(crate) env: &'a Env,
}
Expand Down Expand Up @@ -258,20 +256,41 @@ impl_context_method!(
impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, {
/// Set the cursor icon.
///
/// Call this when handling a mouse move event, to set the cursor for the
/// widget. A container widget can safely call this method, then recurse
/// to its children, as a sequence of calls within an event propagation
/// only has the effect of the last one (ie no need to worry about
/// flashing).
/// This setting will be retained until [`clear_cursor`] is called, but it will only take
/// effect when this widget is either [`hot`] or [`active`]. If a child widget also sets a
/// cursor, the child widget's cursor will take precedence. (If that isn't what you want, use
/// [`override_cursor`] instead.)
///
/// This method is expected to be called mostly from the [`MouseMove`]
/// event handler, but can also be called in response to other events,
/// for example pressing a key to change the behavior of a widget, or
/// in response to data changes.
///
/// [`MouseMove`]: enum.Event.html#variant.MouseMove
/// [`clear_cursor`]: EventCtx::clear_cursor
/// [`override_cursor`]: EventCtx::override_cursor
/// [`hot`]: EventCtx::is_hot
/// [`active`]: EventCtx::is_active
pub fn set_cursor(&mut self, cursor: &Cursor) {
*self.cursor = Some(cursor.clone());
self.widget_state.cursor_change = CursorChange::Set(cursor.clone());
}

/// Override the cursor icon.
///
/// This setting will be retained until [`clear_cursor`] is called, but it will only take
/// effect when this widget is either [`hot`] or [`active`]. This will override the cursor
/// preferences of a child widget. (If that isn't what you want, use [`set_cursor`] instead.)
///
/// [`clear_cursor`]: EventCtx::clear_cursor
/// [`set_cursor`]: EventCtx::override_cursor
/// [`hot`]: EventCtx::is_hot
/// [`active`]: EventCtx::is_active
pub fn override_cursor(&mut self, cursor: &Cursor) {
self.widget_state.cursor_change = CursorChange::Override(cursor.clone());
}

/// Clear the cursor icon.
///
/// This undoes the effect of [`set_cursor`] and [`override_cursor`].
///
/// [`override_cursor`]: EventCtx::override_cursor
/// [`set_cursor`]: EventCtx::set_cursor
pub fn clear_cursor(&mut self) {
self.widget_state.cursor_change = CursorChange::Default;
}
});

Expand Down
Loading