use std::any::Any;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
use inset_embedder::{Platform, SystemMouseCursorKind, ViewId};
use inset_gestures::PointerEvent;
pub type MouseCursorRef = Rc<dyn MouseCursor>;
pub struct MouseCursorManager {
pub fallback_mouse_cursor: MouseCursorRef,
last_session: HashMap<i64, Box<dyn MouseCursorSession>>,
}
impl MouseCursorManager {
pub fn new(fallback_mouse_cursor: MouseCursorRef) -> MouseCursorManager {
debug_assert!(!fallback_mouse_cursor.is_defer());
MouseCursorManager {
fallback_mouse_cursor,
last_session: HashMap::new(),
}
}
pub fn debug_device_active_cursor(&self, device: i64) -> Option<MouseCursorRef> {
if !cfg!(debug_assertions) {
return None;
}
self.last_session
.get(&device)
.map(|session| session.cursor())
}
pub fn handle_device_cursor_update(
&mut self,
platform: &dyn Platform,
view: ViewId,
device: i64,
triggering_event: Option<&PointerEvent>,
cursor_candidates: impl IntoIterator<Item = MouseCursorRef>,
) {
if let Some(PointerEvent::Removed(_)) = triggering_event {
self.last_session.remove(&device);
return;
}
let next_cursor = first_non_deferred(cursor_candidates)
.unwrap_or_else(|| Rc::clone(&self.fallback_mouse_cursor));
debug_assert!(!next_cursor.is_defer());
if let Some(last_session) = self.last_session.get(&device)
&& *last_session.cursor() == *next_cursor
{
return;
}
let mut next_session = next_cursor.create_session(device);
if let Some(mut last_session) = self.last_session.remove(&device) {
last_session.dispose();
}
next_session.activate(platform, view);
self.last_session.insert(device, next_session);
}
}
pub trait MouseCursorSession {
fn cursor(&self) -> MouseCursorRef;
fn device(&self) -> i64;
fn activate(&mut self, platform: &dyn Platform, view: ViewId);
fn dispose(&mut self);
}
pub trait MouseCursor: fmt::Debug {
fn create_session(&self, device: i64) -> Box<dyn MouseCursorSession>;
fn debug_description(&self) -> String;
fn as_any(&self) -> &dyn Any;
fn eq_cursor(&self, other: &dyn MouseCursor) -> bool {
std::ptr::addr_eq(self as *const Self, other as *const dyn MouseCursor)
}
fn is_defer(&self) -> bool {
self.as_any().is::<DeferringMouseCursor>()
}
}
impl dyn MouseCursor {
pub fn defer() -> MouseCursorRef {
Rc::new(DeferringMouseCursor)
}
pub fn uncontrolled() -> MouseCursorRef {
Rc::new(NoopMouseCursor)
}
}
impl PartialEq for dyn MouseCursor {
fn eq(&self, other: &dyn MouseCursor) -> bool {
self.eq_cursor(other)
}
}
#[derive(Debug)]
pub struct DeferringMouseCursor;
impl MouseCursor for DeferringMouseCursor {
fn create_session(&self, _device: i64) -> Box<dyn MouseCursorSession> {
panic!("DeferringMouseCursor can not create a session");
}
fn debug_description(&self) -> String {
"defer".to_owned()
}
fn as_any(&self) -> &dyn Any {
self
}
fn eq_cursor(&self, other: &dyn MouseCursor) -> bool {
other.is_defer()
}
}
fn first_non_deferred(cursors: impl IntoIterator<Item = MouseCursorRef>) -> Option<MouseCursorRef> {
cursors.into_iter().find(|cursor| !cursor.is_defer())
}
struct NoopMouseCursorSession {
device: i64,
}
impl MouseCursorSession for NoopMouseCursorSession {
fn cursor(&self) -> MouseCursorRef {
<dyn MouseCursor>::uncontrolled()
}
fn device(&self) -> i64 {
self.device
}
fn activate(&mut self, _platform: &dyn Platform, _view: ViewId) {
}
fn dispose(&mut self) {
}
}
#[derive(Debug)]
pub struct NoopMouseCursor;
impl MouseCursor for NoopMouseCursor {
fn create_session(&self, device: i64) -> Box<dyn MouseCursorSession> {
Box::new(NoopMouseCursorSession { device })
}
fn debug_description(&self) -> String {
"uncontrolled".to_owned()
}
fn as_any(&self) -> &dyn Any {
self
}
fn eq_cursor(&self, other: &dyn MouseCursor) -> bool {
other.as_any().is::<NoopMouseCursor>()
}
}
struct SystemMouseCursorSession {
cursor: SystemMouseCursor,
device: i64,
}
impl MouseCursorSession for SystemMouseCursorSession {
fn cursor(&self) -> MouseCursorRef {
Rc::new(self.cursor)
}
fn device(&self) -> i64 {
self.device
}
fn activate(&mut self, platform: &dyn Platform, view: ViewId) {
if let Some(view) = platform.view(view) {
view.host()
.activate_system_cursor(self.device, self.cursor.kind());
}
}
fn dispose(&mut self) {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SystemMouseCursor {
kind: SystemMouseCursorKind,
}
impl SystemMouseCursor {
const fn new(kind: SystemMouseCursorKind) -> SystemMouseCursor {
SystemMouseCursor { kind }
}
pub fn kind(&self) -> SystemMouseCursorKind {
self.kind
}
}
impl MouseCursor for SystemMouseCursor {
fn create_session(&self, device: i64) -> Box<dyn MouseCursorSession> {
Box::new(SystemMouseCursorSession {
cursor: *self,
device,
})
}
fn debug_description(&self) -> String {
format!("SystemMouseCursor({:?})", self.kind)
}
fn as_any(&self) -> &dyn Any {
self
}
fn eq_cursor(&self, other: &dyn MouseCursor) -> bool {
other
.as_any()
.downcast_ref::<SystemMouseCursor>()
.is_some_and(|other| other.kind == self.kind)
}
}
impl From<SystemMouseCursor> for MouseCursorRef {
fn from(cursor: SystemMouseCursor) -> MouseCursorRef {
Rc::new(cursor)
}
}
pub struct SystemMouseCursors;
impl SystemMouseCursors {
pub const NONE: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::None);
pub const BASIC: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Basic);
pub const CLICK: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Click);
pub const FORBIDDEN: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::Forbidden);
pub const WAIT: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Wait);
pub const PROGRESS: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Progress);
pub const CONTEXT_MENU: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ContextMenu);
pub const HELP: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Help);
pub const TEXT: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Text);
pub const VERTICAL_TEXT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::VerticalText);
pub const CELL: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Cell);
pub const PRECISE: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Precise);
pub const MOVE: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Move);
pub const GRAB: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Grab);
pub const GRABBING: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Grabbing);
pub const NO_DROP: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::NoDrop);
pub const ALIAS: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Alias);
pub const COPY: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::Copy);
pub const DISAPPEARING: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::Disappearing);
pub const ALL_SCROLL: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::AllScroll);
pub const RESIZE_LEFT_RIGHT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeLeftRight);
pub const RESIZE_UP_DOWN: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpDown);
pub const RESIZE_UP_LEFT_DOWN_RIGHT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpLeftDownRight);
pub const RESIZE_UP_RIGHT_DOWN_LEFT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpRightDownLeft);
pub const RESIZE_UP: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeUp);
pub const RESIZE_DOWN: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeDown);
pub const RESIZE_LEFT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeLeft);
pub const RESIZE_RIGHT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeRight);
pub const RESIZE_UP_LEFT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpLeft);
pub const RESIZE_UP_RIGHT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeUpRight);
pub const RESIZE_DOWN_LEFT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeDownLeft);
pub const RESIZE_DOWN_RIGHT: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeDownRight);
pub const RESIZE_COLUMN: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeColumn);
pub const RESIZE_ROW: SystemMouseCursor =
SystemMouseCursor::new(SystemMouseCursorKind::ResizeRow);
pub const ZOOM_IN: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::ZoomIn);
pub const ZOOM_OUT: SystemMouseCursor = SystemMouseCursor::new(SystemMouseCursorKind::ZoomOut);
}
#[cfg(test)]
mod tests {
use inset_embedder::ViewRef;
use inset_gestures::{PointerHoverEvent, PointerRemovedEvent};
use inset_test::{TestPlatform, TestView};
use super::*;
const VIEW: ViewId = ViewId(3);
fn recording_platform() -> (TestPlatform, Rc<TestView>) {
let view = Rc::new(TestView::new(800.0, 600.0).with_id(VIEW));
let platform = TestPlatform::new().with_view(Rc::clone(&view) as ViewRef);
(platform, view)
}
fn hover() -> PointerEvent {
PointerEvent::Hover(PointerHoverEvent::default())
}
#[test]
fn system_cursors_compare_by_kind_and_special_cursors_by_class() {
let click: MouseCursorRef = SystemMouseCursors::CLICK.into();
let click_again: MouseCursorRef = SystemMouseCursors::CLICK.into();
let basic: MouseCursorRef = SystemMouseCursors::BASIC.into();
assert!(*click == *click_again);
assert!(*click != *basic);
assert!(*<dyn MouseCursor>::defer() == *<dyn MouseCursor>::defer());
assert!(*<dyn MouseCursor>::uncontrolled() == *<dyn MouseCursor>::uncontrolled());
assert!(*<dyn MouseCursor>::defer() != *<dyn MouseCursor>::uncontrolled());
assert!(*<dyn MouseCursor>::defer() != *click);
assert_eq!(click.debug_description(), "SystemMouseCursor(Click)");
assert_eq!(<dyn MouseCursor>::defer().debug_description(), "defer");
}
#[test]
fn manager_activates_the_first_non_deferred_candidate() {
let (platform, view) = recording_platform();
let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
manager.handle_device_cursor_update(
&platform,
VIEW,
1,
Some(&hover()),
[<dyn MouseCursor>::defer(), SystemMouseCursors::TEXT.into()],
);
assert_eq!(
view.test_host().cursors(),
[(1, SystemMouseCursorKind::Text)],
"the deferring region lets the text region behind it decide"
);
let text: MouseCursorRef = SystemMouseCursors::TEXT.into();
assert!(*manager.debug_device_active_cursor(1).unwrap() == *text);
}
#[test]
fn manager_falls_back_when_every_candidate_defers() {
let (platform, view) = recording_platform();
let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
manager.handle_device_cursor_update(
&platform,
VIEW,
1,
Some(&hover()),
[<dyn MouseCursor>::defer()],
);
assert_eq!(
view.test_host().cursors(),
[(1, SystemMouseCursorKind::Basic)]
);
}
#[test]
fn manager_does_not_reactivate_the_same_cursor() {
let (platform, view) = recording_platform();
let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
manager.handle_device_cursor_update(
&platform,
VIEW,
1,
Some(&hover()),
[SystemMouseCursors::CLICK.into()],
);
manager.handle_device_cursor_update(
&platform,
VIEW,
1,
None,
[SystemMouseCursors::CLICK.into()],
);
assert_eq!(view.test_host().cursors().len(), 1);
manager.handle_device_cursor_update(
&platform,
VIEW,
1,
None,
[SystemMouseCursors::GRAB.into()],
);
assert_eq!(view.test_host().cursors().len(), 2);
}
#[test]
fn an_uncontrolled_region_keeps_the_previous_system_request() {
let (platform, view) = recording_platform();
let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
manager.handle_device_cursor_update(
&platform,
VIEW,
1,
Some(&hover()),
[SystemMouseCursors::CLICK.into()],
);
manager.handle_device_cursor_update(
&platform,
VIEW,
1,
None,
[<dyn MouseCursor>::uncontrolled()],
);
assert_eq!(
view.test_host().cursors(),
[(1, SystemMouseCursorKind::Click)],
"the no-op session makes no request; the host keeps whatever it shows"
);
assert!(
*manager.debug_device_active_cursor(1).unwrap() == *<dyn MouseCursor>::uncontrolled()
);
}
#[test]
fn a_removed_device_forgets_its_session() {
let (platform, _view) = recording_platform();
let mut manager = MouseCursorManager::new(SystemMouseCursors::BASIC.into());
manager.handle_device_cursor_update(
&platform,
VIEW,
1,
Some(&hover()),
[SystemMouseCursors::CLICK.into()],
);
let removed = PointerEvent::Removed(PointerRemovedEvent::default());
manager.handle_device_cursor_update(&platform, VIEW, 1, Some(&removed), []);
assert!(manager.debug_device_active_cursor(1).is_none());
}
}