From 14982187e75c52f699ccd187862d3e3527bae407 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Fri, 27 Nov 2020 15:39:58 -0600 Subject: [PATCH 1/6] Make the cursor API stateful and widget-local. --- druid/Cargo.toml | 4 ++-- druid/examples/cursor.rs | 31 ++++++++++++++----------- druid/src/contexts.rs | 50 ++++++++++++++++++++++++++++------------ druid/src/core.rs | 37 ++++++++++++++++++++++++----- druid/src/window.rs | 16 ++++--------- 5 files changed, 90 insertions(+), 48 deletions(-) diff --git a/druid/Cargo.toml b/druid/Cargo.toml index 813ad82cfd..b8263b72e0 100644 --- a/druid/Cargo.toml +++ b/druid/Cargo.toml @@ -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" diff --git a/druid/examples/cursor.rs b/druid/examples/cursor.rs index 2f5fd1eb24..8112105219 100644 --- a/druid/examples/cursor.rs +++ b/druid/examples/cursor.rs @@ -44,28 +44,31 @@ impl> Controller 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).map(Rc::new); } child.event(ctx, event, data, env); } + + fn update( + &mut self, + child: &mut W, + ctx: &mut UpdateCtx, + old_data: &AppState, + data: &AppState, + env: &Env, + ) { + if !Rc::ptr_eq(&data.cursor, &old_data.cursor) { + ctx.set_cursor(&data.cursor); + } + child.update(ctx, old_data, data, env); + } } fn ui_builder() -> impl Widget { Button::new("Change cursor") - .on_click(|ctx, data: &mut AppState, _env| { + .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); }) .padding(50.0) .controller(CursorArea {}) diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index 542faab245..e705884f3a 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -18,10 +18,11 @@ use std::{ any::{Any, TypeId}, collections::VecDeque, ops::{Deref, DerefMut}, + rc::Rc, time::Duration, }; -use crate::core::{CommandQueue, FocusChange, WidgetState}; +use crate::core::{CommandQueue, CursorSetting, FocusChange, WidgetState}; use crate::env::KeyLike; use crate::piet::{Piet, PietText, RenderContext}; use crate::shell::Region; @@ -67,7 +68,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, - pub(crate) cursor: &'a mut Option, pub(crate) is_handled: bool, pub(crate) is_root: bool, } @@ -95,7 +95,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, pub(crate) prev_env: Option<&'a Env>, pub(crate) env: &'a Env, } @@ -258,20 +257,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_setting = CursorSetting::Set(Rc::new(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_setting = CursorSetting::Override(Rc::new(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_setting = CursorSetting::Default; } }); diff --git a/druid/src/core.rs b/druid/src/core.rs index 33ab292d1c..bef699736a 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -15,13 +15,14 @@ //! The fundamental druid types. use std::collections::{HashMap, VecDeque}; +use std::rc::Rc; use crate::bloom::Bloom; use crate::contexts::ContextState; use crate::kurbo::{Affine, Insets, Point, Rect, Shape, Size, Vec2}; use crate::util::ExtendDrain; use crate::{ - ArcStr, BoxConstraints, Color, Command, Data, Env, Event, EventCtx, InternalEvent, + ArcStr, BoxConstraints, Color, Command, Cursor, Data, Env, Event, EventCtx, InternalEvent, InternalLifeCycle, LayoutCtx, LifeCycle, LifeCycleCtx, Notification, PaintCtx, Region, RenderContext, Target, TextLayout, TimerToken, UpdateCtx, Widget, WidgetId, }; @@ -125,6 +126,10 @@ pub(crate) struct WidgetState { pub(crate) children_changed: bool, /// Associate timers with widgets that requested them. pub(crate) timers: HashMap, + /// The cursor that was set using one of the context methods. + pub(crate) cursor_setting: CursorSetting, + /// The result of merging up children cursors. + pub(crate) cursor: Option>, } /// Methods by which a widget can attempt to change focus state. @@ -140,6 +145,18 @@ pub(crate) enum FocusChange { Previous, } +/// The possible cursor states for a widget. +#[derive(Clone)] +pub(crate) enum CursorSetting { + /// No cursor has been set. + Default, + /// Someone set a cursor, but if a child widget also set their cursor then we'll use theirs + /// instead of ours. + Set(Rc), + /// Someone set a cursor, and we'll use it regardless of what the children say. + Override(Rc), +} + impl> WidgetPod { /// Create a new widget pod. /// @@ -569,7 +586,6 @@ impl> WidgetPod { state: parent_ctx.state, widget_state: &mut self.state, notifications: parent_ctx.notifications, - cursor: parent_ctx.cursor, is_handled: false, is_root: false, }; @@ -759,7 +775,6 @@ impl> WidgetPod { if recurse { let mut notifications = VecDeque::new(); let mut inner_ctx = EventCtx { - cursor: ctx.cursor, state: ctx.state, widget_state: &mut self.state, notifications: &mut notifications, @@ -795,14 +810,12 @@ impl> WidgetPod { env: &Env, ) { let EventCtx { - cursor, state, notifications: parent_notifications, .. } = ctx; let mut sentinal = VecDeque::new(); let mut inner_ctx = EventCtx { - cursor, state, notifications: &mut sentinal, widget_state: &mut self.state, @@ -983,7 +996,6 @@ impl> WidgetPod { let mut child_ctx = UpdateCtx { state: ctx.state, widget_state: &mut self.state, - cursor: ctx.cursor, prev_env, env, }; @@ -1031,6 +1043,8 @@ impl WidgetState { children: Bloom::new(), children_changed: false, timers: HashMap::new(), + cursor_setting: CursorSetting::Default, + cursor: None, } } @@ -1069,6 +1083,17 @@ impl WidgetState { self.request_update |= child_state.request_update; self.request_focus = child_state.request_focus.take().or(self.request_focus); self.timers.extend_drain(&mut child_state.timers); + + let child_cursor = child_state.cursor.take(); + if child_state.has_active || child_state.is_hot { + if let CursorSetting::Override(cursor) = &self.cursor_setting { + self.cursor = Some(Rc::clone(cursor)); + } else if child_cursor.is_some() { + self.cursor = child_cursor; + } else if let CursorSetting::Set(cursor) = &child_state.cursor_setting { + self.cursor = Some(Rc::clone(cursor)); + } + } } #[inline] diff --git a/druid/src/window.rs b/druid/src/window.rs index f99181d349..c12690eb07 100644 --- a/druid/src/window.rs +++ b/druid/src/window.rs @@ -184,11 +184,6 @@ impl Window { _ => (), } - let mut cursor = match event { - Event::MouseMove(..) => Some(Cursor::Arrow), - _ => None, - }; - let event = match event { Event::Timer(token) => { if let Some(widget_id) = self.timers.get(&token) { @@ -217,7 +212,6 @@ impl Window { ContextState::new::(queue, &self.ext_handle, &self.handle, self.id, self.focus); let mut notifications = VecDeque::new(); let mut ctx = EventCtx { - cursor: &mut cursor, state: &mut state, notifications: &mut notifications, widget_state: &mut widget_state, @@ -252,8 +246,10 @@ impl Window { } } - if let Some(cursor) = cursor { + if let Some(cursor) = &widget_state.cursor { self.handle.set_cursor(&cursor); + } else if matches!(event, Event::MouseMove(..)) { + self.handle.set_cursor(&Cursor::Arrow); } self.post_event_processing(&mut widget_state, queue, data, env, false); @@ -286,18 +282,16 @@ impl Window { let mut widget_state = WidgetState::new(self.root.id(), Some(self.size)); let mut state = ContextState::new::(queue, &self.ext_handle, &self.handle, self.id, self.focus); - let mut cursor = None; let mut update_ctx = UpdateCtx { widget_state: &mut widget_state, - cursor: &mut cursor, state: &mut state, prev_env: None, env, }; self.root.update(&mut update_ctx, data, env); - if let Some(cursor) = cursor { - self.handle.set_cursor(&cursor); + if let Some(cursor) = &widget_state.cursor { + self.handle.set_cursor(cursor); } self.post_event_processing(&mut widget_state, queue, data, env, false); From fcee3becf8115bd4eb8c0200b65b974e1440a6b8 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Thu, 3 Dec 2020 10:38:16 -0600 Subject: [PATCH 2/6] Address review comments. --- druid-shell/src/mouse.rs | 7 ++-- druid-shell/src/platform/gtk/window.rs | 2 +- druid-shell/src/platform/mac/window.rs | 2 +- druid-shell/src/platform/web/window.rs | 2 +- druid-shell/src/platform/windows/window.rs | 2 +- druid-shell/src/platform/x11/window.rs | 2 +- druid/examples/cursor.rs | 48 +++++++++++----------- druid/src/contexts.rs | 9 ++-- druid/src/core.rs | 34 ++++++++------- druid/src/mouse.rs | 8 +++- 10 files changed, 63 insertions(+), 53 deletions(-) diff --git a/druid-shell/src/mouse.rs b/druid-shell/src/mouse.rs index 938e2b8af1..f3b74ca240 100644 --- a/druid-shell/src/mouse.rs +++ b/druid-shell/src/mouse.rs @@ -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, @@ -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), } diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 9947f34b12..b19e700771 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -164,7 +164,7 @@ pub(crate) struct WindowState { deferred_queue: RefCell>, } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct CustomCursor(gdk::Cursor); impl WindowBuilder { diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index a02db38c9a..8f43d80732 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -146,7 +146,7 @@ struct ViewState { text: PietText, } -#[derive(Clone)] +#[derive(Clone, PartialEq)] // TODO: support custom cursors pub struct CustomCursor; diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index 6dc8dc0bef..c94b6d1fb0 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -100,7 +100,7 @@ struct WindowState { } // TODO: support custom cursors -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct CustomCursor; impl WindowState { diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 3e4cfe9fbc..209740a984 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -237,7 +237,7 @@ struct DxgiState { swap_chain: *mut IDXGISwapChain1, } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct CustomCursor(Arc); struct HCursor(HCURSOR); diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index 5ebf1d7159..ed7feef20b 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -532,7 +532,7 @@ struct PresentData { last_ust: Option, } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct CustomCursor(xproto::Cursor); impl Window { diff --git a/druid/examples/cursor.rs b/druid/examples/cursor.rs index 8112105219..a7596210a1 100644 --- a/druid/examples/cursor.rs +++ b/druid/examples/cursor.rs @@ -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 @@ -45,7 +43,7 @@ impl> Controller for CursorArea { env: &Env, ) { if let Event::WindowConnected = event { - data.custom = ctx.window().make_cursor(&data.custom_desc).map(Rc::new); + data.custom = ctx.window().make_cursor(&data.custom_desc); } child.event(ctx, event, data, env); } @@ -58,7 +56,7 @@ impl> Controller for CursorArea { data: &AppState, env: &Env, ) { - if !Rc::ptr_eq(&data.cursor, &old_data.cursor) { + if data.cursor != old_data.cursor { ctx.set_cursor(&data.cursor); } child.update(ctx, old_data, data, env); @@ -68,7 +66,7 @@ impl> Controller for CursorArea { fn ui_builder() -> impl Widget { Button::new("Change cursor") .on_click(|_ctx, data: &mut AppState, _env| { - data.cursor = next_cursor(&data.cursor, data.custom.clone()); + data.next_cursor(); }) .padding(50.0) .controller(CursorArea {}) @@ -78,31 +76,33 @@ fn ui_builder() -> impl Widget { #[derive(Clone, Data, Lens)] struct AppState { - cursor: Rc, - custom: Option>, + cursor: Cursor, + custom: Option, // 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 { - 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() { @@ -113,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, }; diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index e705884f3a..e6be0470ba 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -18,11 +18,10 @@ use std::{ any::{Any, TypeId}, collections::VecDeque, ops::{Deref, DerefMut}, - rc::Rc, time::Duration, }; -use crate::core::{CommandQueue, CursorSetting, FocusChange, WidgetState}; +use crate::core::{CommandQueue, CursorChange, FocusChange, WidgetState}; use crate::env::KeyLike; use crate::piet::{Piet, PietText, RenderContext}; use crate::shell::Region; @@ -267,7 +266,7 @@ impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, { /// [`hot`]: EventCtx::is_hot /// [`active`]: EventCtx::is_active pub fn set_cursor(&mut self, cursor: &Cursor) { - self.widget_state.cursor_setting = CursorSetting::Set(Rc::new(cursor.clone())); + self.widget_state.cursor_change = CursorChange::Set(cursor.clone()); } /// Override the cursor icon. @@ -281,7 +280,7 @@ impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, { /// [`hot`]: EventCtx::is_hot /// [`active`]: EventCtx::is_active pub fn override_cursor(&mut self, cursor: &Cursor) { - self.widget_state.cursor_setting = CursorSetting::Override(Rc::new(cursor.clone())); + self.widget_state.cursor_change = CursorChange::Override(cursor.clone()); } /// Clear the cursor icon. @@ -291,7 +290,7 @@ impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, { /// [`override_cursor`]: EventCtx::override_cursor /// [`set_cursor`]: EventCtx::set_cursor pub fn clear_cursor(&mut self) { - self.widget_state.cursor_setting = CursorSetting::Default; + self.widget_state.cursor_change = CursorChange::Default; } }); diff --git a/druid/src/core.rs b/druid/src/core.rs index bef699736a..ff22022552 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -15,7 +15,6 @@ //! The fundamental druid types. use std::collections::{HashMap, VecDeque}; -use std::rc::Rc; use crate::bloom::Bloom; use crate::contexts::ContextState; @@ -127,9 +126,10 @@ pub(crate) struct WidgetState { /// Associate timers with widgets that requested them. pub(crate) timers: HashMap, /// The cursor that was set using one of the context methods. - pub(crate) cursor_setting: CursorSetting, - /// The result of merging up children cursors. - pub(crate) cursor: Option>, + pub(crate) cursor_change: CursorChange, + /// The result of merging up children cursors. This gets cleared when merging state up (unlike + /// cursor_change, which is persistent). + pub(crate) cursor: Option, } /// Methods by which a widget can attempt to change focus state. @@ -147,14 +147,14 @@ pub(crate) enum FocusChange { /// The possible cursor states for a widget. #[derive(Clone)] -pub(crate) enum CursorSetting { +pub(crate) enum CursorChange { /// No cursor has been set. Default, /// Someone set a cursor, but if a child widget also set their cursor then we'll use theirs /// instead of ours. - Set(Rc), + Set(Cursor), /// Someone set a cursor, and we'll use it regardless of what the children say. - Override(Rc), + Override(Cursor), } impl> WidgetPod { @@ -1043,7 +1043,7 @@ impl WidgetState { children: Bloom::new(), children_changed: false, timers: HashMap::new(), - cursor_setting: CursorSetting::Default, + cursor_change: CursorChange::Default, cursor: None, } } @@ -1084,14 +1084,18 @@ impl WidgetState { self.request_focus = child_state.request_focus.take().or(self.request_focus); self.timers.extend_drain(&mut child_state.timers); + // We reset `child_state.cursor` no matter what, so that on the every pass through the tree, + // things will be recalculated just from `cursor_change`. let child_cursor = child_state.cursor.take(); - if child_state.has_active || child_state.is_hot { - if let CursorSetting::Override(cursor) = &self.cursor_setting { - self.cursor = Some(Rc::clone(cursor)); - } else if child_cursor.is_some() { - self.cursor = child_cursor; - } else if let CursorSetting::Set(cursor) = &child_state.cursor_setting { - self.cursor = Some(Rc::clone(cursor)); + if let CursorChange::Override(cursor) = &self.cursor_change { + self.cursor = Some(cursor.clone()); + } else if child_state.has_active || child_state.is_hot { + self.cursor = child_cursor; + } + + if self.cursor.is_none() { + if let CursorChange::Set(cursor) = &self.cursor_change { + self.cursor = Some(cursor.clone()); } } } diff --git a/druid/src/mouse.rs b/druid/src/mouse.rs index ed006a6096..bdb2c5ae9b 100644 --- a/druid/src/mouse.rs +++ b/druid/src/mouse.rs @@ -15,7 +15,7 @@ //! The mousey bits use crate::kurbo::{Point, Vec2}; -use crate::{Modifiers, MouseButton, MouseButtons}; +use crate::{Cursor, Data, Modifiers, MouseButton, MouseButtons}; /// The state of the mouse for a click, mouse-up, move, or wheel event. /// @@ -94,3 +94,9 @@ impl From for MouseEvent { } } } + +impl Data for Cursor { + fn same(&self, other: &Cursor) -> bool { + self == other + } +} From 5b44293426a041eae2180dee87c740aa80959ea1 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Thu, 3 Dec 2020 10:40:17 -0600 Subject: [PATCH 3/6] Add changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f29ff23d5..8d34610814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 From 500f7d99ef8b7d65522e48ca20bd1f3400a6accb Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Thu, 3 Dec 2020 10:47:11 -0600 Subject: [PATCH 4/6] Fix Split to work the the new API. --- druid/src/widget/split.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/druid/src/widget/split.rs b/druid/src/widget/split.rs index 22313a76b1..3d2c0053a8 100644 --- a/druid/src/widget/split.rs +++ b/druid/src/widget/split.rs @@ -299,11 +299,15 @@ impl Widget for Split { ctx.request_layout(); } - if ctx.is_hot() && self.bar_hit_test(ctx.size(), mouse.pos) || ctx.is_active() { - match self.split_axis { - Axis::Horizontal => ctx.set_cursor(&Cursor::ResizeLeftRight), - Axis::Vertical => ctx.set_cursor(&Cursor::ResizeUpDown), - }; + if ctx.is_hot() || ctx.is_active() { + if self.bar_hit_test(ctx.size(), mouse.pos) { + match self.split_axis { + Axis::Horizontal => ctx.set_cursor(&Cursor::ResizeLeftRight), + Axis::Vertical => ctx.set_cursor(&Cursor::ResizeUpDown), + }; + } else { + ctx.clear_cursor(); + } } } _ => {} From 72b9d334b349d53953f9159564cba0838ef3bfa2 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Thu, 3 Dec 2020 11:22:23 -0600 Subject: [PATCH 5/6] Add a missing PartialEq. --- druid-shell/src/platform/windows/window.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 209740a984..e51c0e1218 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -240,6 +240,7 @@ struct DxgiState { #[derive(Clone, PartialEq)] pub struct CustomCursor(Arc); +#[derive(PartialEq)] struct HCursor(HCURSOR); impl Drop for HCursor { From 9ab2806f6f09adf2fa1b158c1f1241ea714bd4b8 Mon Sep 17 00:00:00 2001 From: jneem Date: Thu, 3 Dec 2020 16:09:38 -0600 Subject: [PATCH 6/6] Update druid-shell/src/mouse.rs Co-authored-by: Colin Rofls --- druid-shell/src/mouse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druid-shell/src/mouse.rs b/druid-shell/src/mouse.rs index f3b74ca240..a20e4cda53 100644 --- a/druid-shell/src/mouse.rs +++ b/druid-shell/src/mouse.rs @@ -254,7 +254,7 @@ pub enum Cursor { 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. + // behind an `Arc` or using a platform API that does the sharing). Custom(platform::window::CustomCursor), }