Remove `WidgetState::Cursor` (#710)

This commit is contained in:
Olivier FAURE 2024-10-22 15:41:13 +02:00 committed by GitHub
parent f322894e50
commit 30dba40300
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 73 additions and 101 deletions

View File

@ -16,9 +16,7 @@ use crate::render_root::{MutateCallback, RenderRootSignal, RenderRootState};
use crate::text::TextBrush;
use crate::tree_arena::{ArenaMutChildren, ArenaRefChildren};
use crate::widget::{WidgetMut, WidgetRef, WidgetState};
use crate::{
AllowRawMut, BoxConstraints, CursorIcon, Insets, Point, Rect, Size, Widget, WidgetId, WidgetPod,
};
use crate::{AllowRawMut, BoxConstraints, Insets, Point, Rect, Size, Widget, WidgetId, WidgetPod};
// Note - Most methods defined in this file revolve around `WidgetState` fields.
// Consider reading `WidgetState` documentation (especially the documented naming scheme)
@ -371,32 +369,13 @@ impl_context_method!(
// --- MARK: CURSOR ---
// Cursor-related impls.
impl_context_method!(EventCtx<'_>, {
// TODO - Rewrite doc
/// Set the cursor icon.
/// Notifies Masonry that the cursor returned by [`Widget::get_cursor`] has changed.
///
/// This setting will be retained until [`clear_cursor`] is called, but it will only take
/// effect when this widget [`is_hovered`] and/or [`has_pointer_capture`]. 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.)
///
/// [`clear_cursor`]: EventCtx::clear_cursor
/// [`override_cursor`]: EventCtx::override_cursor
/// [`is_hovered`]: EventCtx::is_hovered
/// [`has_pointer_capture`]: EventCtx::has_pointer_capture
pub fn set_cursor(&mut self, cursor: &CursorIcon) {
trace!("set_cursor {:?}", cursor);
self.widget_state.cursor = Some(*cursor);
}
/// 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) {
trace!("clear_cursor");
self.widget_state.cursor = None;
/// This is mostly meant for cases where the cursor changes even if the pointer doesn't
/// move, because the nature of the widget has changed somehow.
pub fn cursor_icon_changed(&mut self) {
trace!("cursor_icon_changed");
self.global_state.needs_pointer_pass = true;
}
});

View File

@ -10,7 +10,9 @@ use crate::passes::event::run_on_pointer_event_pass;
use crate::passes::{merge_state_up, recurse_on_children};
use crate::render_root::{RenderRoot, RenderRootSignal, RenderRootState};
use crate::tree_arena::ArenaMut;
use crate::{PointerEvent, RegisterCtx, Update, UpdateCtx, Widget, WidgetId, WidgetState};
use crate::{
PointerEvent, QueryCtx, RegisterCtx, Update, UpdateCtx, Widget, WidgetId, WidgetState,
};
// --- MARK: HELPERS ---
fn get_id_path(root: &RenderRoot, widget_id: Option<WidgetId>) -> Vec<WidgetId> {
@ -235,6 +237,11 @@ fn update_disabled_for_widget(
pub(crate) fn run_update_disabled_pass(root: &mut RenderRoot) {
let _span = info_span!("update_disabled").entered();
// If a widget was enabled or disabled, the pointer icon may need to change.
if root.root_state().needs_update_disabled {
root.global_state.needs_pointer_pass = true;
}
let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id());
update_disabled_for_widget(&mut root.global_state, root_widget, root_state, false);
}
@ -627,9 +634,21 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
.pointer_capture_target
.or(next_hovered_widget);
let new_cursor = if let Some(cursor_source) = cursor_source {
let new_cursor = if let (Some(cursor_source), Some(pos)) = (cursor_source, pointer_pos) {
let (widget, state) = root.widget_arena.get_pair(cursor_source);
state.item.cursor.unwrap_or(widget.item.get_cursor())
let ctx = QueryCtx {
global_state: &root.global_state,
widget_state_children: state.children,
widget_children: widget.children,
widget_state: state.item,
};
if state.item.is_disabled {
CursorIcon::Default
} else {
widget.item.get_cursor(&ctx, pos)
}
} else {
CursorIcon::Default
};

View File

@ -386,7 +386,7 @@ impl<S: 'static> Widget for ModularWidget<S> {
None
}
fn get_cursor(&self) -> CursorIcon {
fn get_cursor(&self, _ctx: &QueryCtx, _pos: Point) -> CursorIcon {
CursorIcon::Default
}
@ -586,8 +586,8 @@ impl<W: Widget> Widget for Recorder<W> {
self.child.get_debug_text()
}
fn get_cursor(&self) -> CursorIcon {
self.child.get_cursor()
fn get_cursor(&self, ctx: &QueryCtx, pos: Point) -> CursorIcon {
self.child.get_cursor(ctx, pos)
}
fn get_child_at_pos<'c>(

View File

@ -15,7 +15,7 @@ use crate::widget::label::LABEL_X_PADDING;
use crate::widget::{LineBreaking, WidgetMut};
use crate::{
AccessCtx, AccessEvent, BoxConstraints, CursorIcon, EventCtx, LayoutCtx, PaintCtx,
PointerEvent, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId,
};
/// The prose widget is a widget which displays text which can be
@ -150,15 +150,12 @@ impl Widget for Prose {
}
}
PointerEvent::PointerMove(state) => {
if !ctx.is_disabled() {
// TODO: Set cursor if over link
ctx.set_cursor(&CursorIcon::Text);
if ctx.has_pointer_capture()
&& self.text_layout.pointer_move(inner_origin, state)
{
// We might have changed text colours, so we need to re-request a layout
ctx.request_layout();
}
if !ctx.is_disabled()
&& ctx.has_pointer_capture()
&& self.text_layout.pointer_move(inner_origin, state)
{
// We might have changed text colours, so we need to re-request a layout
ctx.request_layout();
}
}
PointerEvent::PointerUp(button, state) => {
@ -265,6 +262,11 @@ impl Widget for Prose {
}
}
fn get_cursor(&self, _ctx: &QueryCtx, _pos: Point) -> CursorIcon {
// TODO: Set cursor if over link
CursorIcon::Text
}
fn accessibility_role(&self) -> Role {
Role::Document
}

View File

@ -8,7 +8,6 @@ use smallvec::{smallvec, SmallVec};
use tracing::{trace_span, warn, Span};
use vello::Scene;
use crate::dpi::LogicalPosition;
use crate::event::PointerButton;
use crate::kurbo::Line;
use crate::paint_scene_helpers::{fill_color, stroke};
@ -16,7 +15,7 @@ use crate::widget::flex::Axis;
use crate::widget::{WidgetMut, WidgetPod};
use crate::{
theme, AccessCtx, AccessEvent, BoxConstraints, Color, CursorIcon, EventCtx, LayoutCtx,
PaintCtx, Point, PointerEvent, Rect, RegisterCtx, Size, TextEvent, Widget, WidgetId,
PaintCtx, Point, PointerEvent, QueryCtx, Rect, RegisterCtx, Size, TextEvent, Widget, WidgetId,
};
// TODO - Have child widget type as generic argument
@ -31,11 +30,6 @@ pub struct Split {
min_bar_area: f64, // Integers only
solid: bool,
draggable: bool,
/// The split bar is hovered by the mouse. This state is locked to `true` if the
/// widget is active (the bar is being dragged) to avoid cursor and painting jitter
/// if the mouse moves faster than the layout and temporarily gets outside of the
/// bar area while still being dragged.
is_bar_hover: bool,
/// Offset from the split point (bar center) to the actual mouse position when the
/// bar was clicked. This is used to ensure a click without mouse move is a no-op,
/// instead of re-centering the bar on the mouse.
@ -60,7 +54,6 @@ impl Split {
min_bar_area: 6.0,
solid: false,
draggable: false,
is_bar_hover: false,
click_offset: 0.0,
child1: WidgetPod::new(child1).boxed(),
child2: WidgetPod::new(child2).boxed(),
@ -199,7 +192,7 @@ impl Split {
}
/// Returns true if the provided mouse position is inside the splitter bar area.
fn bar_hit_test(&self, size: Size, mouse_pos: LogicalPosition<f64>) -> bool {
fn bar_hit_test(&self, size: Size, mouse_pos: Point) -> bool {
let (edge1, edge2) = self.bar_edges(size);
match self.split_axis {
Axis::Horizontal => mouse_pos.x >= edge1 && mouse_pos.x <= edge2,
@ -375,7 +368,9 @@ impl Widget for Split {
if self.draggable {
match event {
PointerEvent::PointerDown(PointerButton::Primary, state) => {
if self.bar_hit_test(ctx.size(), state.position) {
let mouse_pos = Point::new(state.position.x, state.position.y);
let local_mouse_pos = mouse_pos - ctx.window_origin().to_vec2();
if self.bar_hit_test(ctx.size(), local_mouse_pos) {
ctx.set_handled();
ctx.capture_pointer();
// Save the delta between the mouse click position and the split point
@ -383,26 +378,6 @@ impl Widget for Split {
Axis::Horizontal => state.position.x,
Axis::Vertical => state.position.y,
} - self.bar_position(ctx.size());
// If not already hovering, force and change cursor appropriately
if !self.is_bar_hover {
self.is_bar_hover = true;
match self.split_axis {
Axis::Horizontal => ctx.set_cursor(&CursorIcon::EwResize),
Axis::Vertical => ctx.set_cursor(&CursorIcon::NsResize),
};
}
}
}
PointerEvent::PointerUp(PointerButton::Primary, state) => {
if ctx.has_pointer_capture() {
ctx.set_handled();
// Depending on where the mouse cursor is when the button is released,
// the cursor might or might not need to be changed
self.is_bar_hover =
ctx.is_hovered() && self.bar_hit_test(ctx.size(), state.position);
if !self.is_bar_hover {
ctx.clear_cursor();
}
}
}
PointerEvent::PointerMove(state) => {
@ -418,21 +393,6 @@ impl Widget for Split {
};
self.update_split_point(ctx.size(), effective_pos);
ctx.request_layout();
} else {
// If not active, set cursor when hovering state changes
let hover =
ctx.is_hovered() && self.bar_hit_test(ctx.size(), state.position);
if self.is_bar_hover != hover {
self.is_bar_hover = hover;
if hover {
match self.split_axis {
Axis::Horizontal => ctx.set_cursor(&CursorIcon::EwResize),
Axis::Vertical => ctx.set_cursor(&CursorIcon::NsResize),
};
} else {
ctx.clear_cursor();
}
}
}
}
_ => {}
@ -556,6 +516,20 @@ impl Widget for Split {
}
}
fn get_cursor(&self, ctx: &QueryCtx, pos: Point) -> CursorIcon {
let local_mouse_pos = pos - ctx.window_origin().to_vec2();
let is_bar_hovered = self.bar_hit_test(ctx.size(), local_mouse_pos);
if ctx.has_pointer_capture() || is_bar_hovered {
match self.split_axis {
Axis::Horizontal => CursorIcon::EwResize,
Axis::Vertical => CursorIcon::NsResize,
}
} else {
CursorIcon::Default
}
}
fn accessibility_role(&self) -> Role {
Role::Splitter
}

View File

@ -15,7 +15,7 @@ use crate::text::{TextBrush, TextEditor, TextWithSelection};
use crate::widget::{LineBreaking, WidgetMut};
use crate::{
AccessCtx, AccessEvent, BoxConstraints, CursorIcon, EventCtx, LayoutCtx, PaintCtx,
PointerEvent, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId,
};
const TEXTBOX_PADDING: f64 = 3.0;
@ -308,7 +308,7 @@ impl Widget for Textbox {
);
}
fn get_cursor(&self) -> CursorIcon {
fn get_cursor(&self, _ctx: &QueryCtx, _pos: Point) -> CursorIcon {
CursorIcon::Text
}

View File

@ -220,9 +220,11 @@ pub trait Widget: AsAny {
None
}
// TODO - Document
// TODO - Add &UpdateCtx argument
fn get_cursor(&self) -> CursorIcon {
/// Return the cursor icon for this widget.
///
/// **pos** - the mouse position in global coordinates (e.g. `(0,0)` is the top-left corner of the
/// window).
fn get_cursor(&self, ctx: &QueryCtx, pos: Point) -> CursorIcon {
CursorIcon::Default
}
@ -471,8 +473,8 @@ impl Widget for Box<dyn Widget> {
self.deref().get_debug_text()
}
fn get_cursor(&self) -> CursorIcon {
self.deref().get_cursor()
fn get_cursor(&self, ctx: &QueryCtx, pos: Point) -> CursorIcon {
self.deref().get_cursor(ctx, pos)
}
fn get_child_at_pos<'c>(

View File

@ -5,7 +5,7 @@
use vello::kurbo::{Insets, Point, Rect, Size, Vec2};
use crate::{CursorIcon, WidgetId};
use crate::WidgetId;
// TODO - Reduce WidgetState size.
// See https://github.com/linebender/xilem/issues/706
@ -126,9 +126,6 @@ pub(crate) struct WidgetState {
pub(crate) children_changed: bool,
// TODO - Remove and handle in WidgetRoot instead
pub(crate) cursor: Option<CursorIcon>,
// --- STATUS ---
/// This widget has been disabled.
pub(crate) is_explicitly_disabled: bool,
@ -192,7 +189,6 @@ impl WidgetState {
needs_update_stashed: true,
focus_chain: Vec::new(),
children_changed: true,
cursor: None,
update_focus_chain: true,
#[cfg(debug_assertions)]
widget_name,