Skip to content

Commit

Permalink
Apply is/has naming convention for flags and methods (#831)
Browse files Browse the repository at this point in the history
Rename has_pointer_capture to is_pointer_capture_target.
Rename has_focus to has_focus_target.
Rename is_focused to is_focus_target.
Add has_hovered flag.
Add ChildHoverChanged event.
  • Loading branch information
PoignardAzur authored Jan 16, 2025
1 parent eb63bf6 commit 320159b
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 67 deletions.
2 changes: 1 addition & 1 deletion masonry/examples/calc_masonry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ impl Widget for CalcButton {
}
}
PointerEvent::PointerUp(_, _) => {
if ctx.has_pointer_capture() && !ctx.is_disabled() {
if ctx.is_pointer_capture_target() && !ctx.is_disabled() {
let color = self.base_color;
// See `update` for why we use `mutate_later` here.
ctx.mutate_later(&mut self.inner, move |mut inner| {
Expand Down
33 changes: 20 additions & 13 deletions masonry/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ impl EventCtx<'_> {
/// [text focus]: crate::doc::doc_06_masonry_concepts#text-focus
pub fn resign_focus(&mut self) {
trace!("resign_focus");
if self.has_focus() {
if self.has_focus_target() {
self.global_state.next_focused_widget = None;
} else {
warn!(
Expand Down Expand Up @@ -773,25 +773,32 @@ impl_context_method!(
/// will respond to pointer (usually mouse) interaction.
///
/// The hovered status is computed from the widget's layout rect. In a
/// container hierarchy, all widgets with layout rects containing the
/// pointer position have hovered status.
/// container hierarchy, the innermost widget with a layout rect containing
/// the pointer position has hovered status.
///
/// If the pointer is [captured], then only that widget and its parents
/// can have hovered status. If the pointer is captured but not hovering
/// over the captured widget, then no widget has the hovered status.
/// If the pointer is [captured], then only that widget can have hovered
/// status. If the pointer is captured but not hovering over the captured
/// widget, then no widget has the hovered status.
///
/// [captured]: crate::doc::doc_06_masonry_concepts#pointer-capture
pub fn is_hovered(&self) -> bool {
self.widget_state.is_hovered
}

/// Whether this widget or any of its descendants are hovered.
///
/// To check if only this specific widget is hovered use [`is_hovered`](Self::is_hovered).
pub fn has_hovered(&self) -> bool {
self.widget_state.has_hovered
}

/// Whether a pointer is [captured] by this widget.
///
/// The pointer will usually be the mouse. In future versions, this
/// function will take a pointer id as input to test a specific pointer.
///
/// [captured]: crate::doc::doc_06_masonry_concepts#pointer-capture
pub fn has_pointer_capture(&self) -> bool {
pub fn is_pointer_capture_target(&self) -> bool {
self.global_state.pointer_capture_target == Some(self.widget_state.id)
}

Expand All @@ -800,19 +807,19 @@ impl_context_method!(
/// The focused widget is the one that receives keyboard events.
///
/// Returns `true` if this specific widget is focused.
/// To check if any descendants are focused use [`has_focus`].
/// To check if any descendants are focused use [`has_focus_target`].
///
/// [text focus]: crate::doc::doc_06_masonry_concepts#text-focus
/// [`has_focus`]: Self::has_focus
pub fn is_focused(&self) -> bool {
/// [`has_focus_target`]: Self::has_focus_target
pub fn is_focus_target(&self) -> bool {
self.global_state.focused_widget == Some(self.widget_id())
}

/// Whether this widget or any of its descendants are focused.
///
/// To check if only this specific widget is focused use [`is_focused`](Self::is_focused).
pub fn has_focus(&self) -> bool {
self.widget_state.has_focus
/// To check if only this specific widget is focused use [`is_focus_target`](Self::is_focus_target).
pub fn has_focus_target(&self) -> bool {
self.widget_state.has_focus_target
}

/// Whether the window is focused.
Expand Down
29 changes: 15 additions & 14 deletions masonry/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,28 +343,28 @@ pub enum Update {
/// [`EventCtx::request_scroll_to_this`](crate::EventCtx::request_scroll_to_this).
RequestPanToChild(Rect),

/// Called when the "hovered" status changes.
/// Called when the [hovered] status of the current widget changes.
///
/// This will always be called _before_ the event that triggered it; that is,
/// when the mouse moves over a widget, that widget will receive
/// `Update::HoveredChanged` before it receives `Event::MouseMove`.
///
/// See [`is_hovered`](crate::EventCtx::is_hovered) for
/// discussion about the hovered status.
/// [hovered]: crate::doc::doc_06_masonry_concepts#widget-status
HoveredChanged(bool),

/// Called when the focus status changes.
/// Called when the [hovered] status of the current widget or a descendant changes.
///
/// This will always be called immediately after a new widget gains focus.
/// The newly focused widget will receive this with `true` and the widget
/// that lost focus will receive this with `false`.
/// This is sent before [`Update::HoveredChanged`].
///
/// See [`EventCtx::is_focused`] for more information about focus.
/// [hovered]: crate::doc::doc_06_masonry_concepts#widget-status
ChildHoveredChanged(bool),

/// Called when the [focused] status of the current widget changes.
///
/// [`EventCtx::is_focused`]: crate::EventCtx::is_focused
/// [focused]: crate::doc::doc_06_masonry_concepts#text-focus
FocusChanged(bool),

/// Called when a widget becomes or no longer is parent of a focused widget.
/// Called when the [focused] status of the current widget or a descendant changes.
///
/// This is sent before [`Update::FocusChanged`].
///
/// [focused]: crate::doc::doc_06_masonry_concepts#text-focus
ChildFocusChanged(bool),
}

Expand Down Expand Up @@ -552,6 +552,7 @@ impl Update {
Self::StashedChanged(_) => "StashedChanged",
Self::RequestPanToChild(_) => "RequestPanToChild",
Self::HoveredChanged(_) => "HoveredChanged",
Self::ChildHoveredChanged(_) => "ChildHoveredChanged",
Self::FocusChanged(_) => "FocusChanged",
Self::ChildFocusChanged(_) => "ChildFocusChanged",
}
Expand Down
2 changes: 1 addition & 1 deletion masonry/src/passes/accessibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ fn build_access_node(
if ctx.accepts_focus() && !ctx.is_disabled() && !ctx.is_stashed() {
node.add_action(accesskit::Action::Focus);
}
if ctx.is_focused() {
if ctx.is_focus_target() {
node.add_action(accesskit::Action::Blur);
}

Expand Down
58 changes: 41 additions & 17 deletions masonry/src/passes/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,9 +343,9 @@ fn update_focus_chain_for_widget(
return;
}

// Replace has_focus to check if the value changed in the meantime
state.item.has_focus = global_state.focused_widget == Some(id);
let had_focus = state.item.has_focus;
// Replace has_focused to check if the value changed in the meantime
state.item.has_focus_target = global_state.focused_widget == Some(id);
let had_focus = state.item.has_focus_target;

state.item.focus_chain.clear();
if state.item.accepts_focus {
Expand Down Expand Up @@ -373,14 +373,14 @@ fn update_focus_chain_for_widget(
parent_focus_chain.extend(&state.item.focus_chain);
}

// had_focus is the old focus value. state.has_focus was replaced with parent_ctx.is_focused().
// Therefore if had_focus is true but state.has_focus is false then the widget which is
// had_focus is the old focus value. state.has_focused was replaced with parent_ctx.is_focused().
// Therefore if had_focus is true but state.has_focused is false then the widget which is
// currently focused is not part of the functional tree anymore and should resign the focus.
if had_focus && !state.item.has_focus {
if had_focus && !state.item.has_focus_target {
// Not sure about this logic, might remove
global_state.next_focused_widget = None;
}
state.item.has_focus = had_focus;
state.item.has_focus_target = had_focus;
}

pub(crate) fn run_update_focus_chain_pass(root: &mut RenderRoot) {
Expand Down Expand Up @@ -477,27 +477,35 @@ pub(crate) fn run_update_focus_pass(root: &mut RenderRoot) {
focused_set: &HashSet<WidgetId>,
) {
run_targeted_update_pass(root, Some(widget_id), |widget, ctx| {
let has_focus = focused_set.contains(&ctx.widget_id());
let has_focused = focused_set.contains(&ctx.widget_id());

if ctx.widget_state.has_focus != has_focus {
widget.update(ctx, &Update::ChildFocusChanged(has_focus));
if ctx.widget_state.has_focus_target != has_focused {
widget.update(ctx, &Update::ChildFocusChanged(has_focused));
}
ctx.widget_state.has_focus = has_focus;
ctx.widget_state.has_focus_target = has_focused;
});
}

// TODO - Add unit test to check items are iterated from the bottom up.
for widget_id in prev_focused_path.iter().copied() {
if root.widget_arena.has(widget_id)
&& root.widget_arena.get_state_mut(widget_id).item.has_focus
&& root
.widget_arena
.get_state_mut(widget_id)
.item
.has_focus_target
!= focused_set.contains(&widget_id)
{
update_focused_status_of(root, widget_id, &focused_set);
}
}
for widget_id in next_focused_path.iter().copied() {
if root.widget_arena.has(widget_id)
&& root.widget_arena.get_state_mut(widget_id).item.has_focus
&& root
.widget_arena
.get_state_mut(widget_id)
.item
.has_focus_target
!= focused_set.contains(&widget_id)
{
update_focused_status_of(root, widget_id, &focused_set);
Expand Down Expand Up @@ -624,6 +632,7 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
// "Hovered path" means the widget which is considered hovered, and all its parents.
let prev_hovered_path = std::mem::take(&mut root.global_state.hovered_path);
let next_hovered_path = get_id_path(root, next_hovered_widget);
let prev_hovered_widget = prev_hovered_path.first().copied();

// We don't just compare `prev_focused` and `next_focused` they could be the same widget
// but one of their ancestors could have been reparented.
Expand All @@ -650,12 +659,12 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
hovered_set: &HashSet<WidgetId>,
) {
run_targeted_update_pass(root, Some(widget_id), |widget, ctx| {
let is_hovered = hovered_set.contains(&ctx.widget_id());
let has_hovered = hovered_set.contains(&ctx.widget_id());

if ctx.widget_state.is_hovered != is_hovered {
widget.update(ctx, &Update::HoveredChanged(is_hovered));
if ctx.widget_state.has_hovered != has_hovered {
widget.update(ctx, &Update::ChildHoveredChanged(has_hovered));
}
ctx.widget_state.is_hovered = is_hovered;
ctx.widget_state.has_hovered = has_hovered;
});
}

Expand All @@ -678,6 +687,21 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
}
}

if prev_hovered_widget != next_hovered_widget {
if let Some(prev_hovered_widget) = prev_hovered_widget {
run_single_update_pass(root, prev_hovered_widget, |widget, ctx| {
ctx.widget_state.is_hovered = false;
widget.update(ctx, &Update::HoveredChanged(false));
});
}
if let Some(next_hovered_widget) = next_hovered_widget {
run_single_update_pass(root, next_hovered_widget, |widget, ctx| {
ctx.widget_state.is_hovered = true;
widget.update(ctx, &Update::HoveredChanged(true));
});
}
}

// -- UPDATE CURSOR ICON --

// If the pointer is captured, its icon always reflects the
Expand Down
4 changes: 2 additions & 2 deletions masonry/src/widget/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl Widget for Button {
}
}
PointerEvent::PointerUp(button, _) => {
if ctx.has_pointer_capture() && ctx.is_hovered() && !ctx.is_disabled() {
if ctx.is_pointer_capture_target() && ctx.is_hovered() && !ctx.is_disabled() {
ctx.submit_action(Action::ButtonPressed(*button));
trace!("Button {:?} released", ctx.widget_id());
}
Expand Down Expand Up @@ -157,7 +157,7 @@ impl Widget for Button {
}

fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
let is_active = ctx.has_pointer_capture() && !ctx.is_disabled();
let is_active = ctx.is_pointer_capture_target() && !ctx.is_disabled();
let is_hovered = ctx.is_hovered();
let size = ctx.size();
let stroke_width = theme::BUTTON_BORDER_WIDTH;
Expand Down
2 changes: 1 addition & 1 deletion masonry/src/widget/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl Widget for Checkbox {
}
}
PointerEvent::PointerUp(_, _) => {
if ctx.has_pointer_capture() && ctx.is_hovered() && !ctx.is_disabled() {
if ctx.is_pointer_capture_target() && ctx.is_hovered() && !ctx.is_disabled() {
self.checked = !self.checked;
ctx.submit_action(Action::CheckboxToggled(self.checked));
trace!("Checkbox {:?} released", ctx.widget_id());
Expand Down
4 changes: 2 additions & 2 deletions masonry/src/widget/split.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ impl Widget for Split {
}
}
PointerEvent::PointerMove(state) => {
if ctx.has_pointer_capture() {
if ctx.is_pointer_capture_target() {
// If widget has pointer capture, assume always it's hovered
let effective_pos = match self.split_axis {
Axis::Horizontal => {
Expand Down Expand Up @@ -520,7 +520,7 @@ impl Widget for Split {
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 {
if ctx.is_pointer_capture_target() || is_bar_hovered {
match self.split_axis {
Axis::Horizontal => CursorIcon::EwResize,
Axis::Vertical => CursorIcon::NsResize,
Expand Down
32 changes: 21 additions & 11 deletions masonry/src/widget/tests/status_change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ fn is_hovered(harness: &TestHarness, id: WidgetId) -> bool {
harness.get_widget(id).ctx().is_hovered()
}

fn has_hovered(harness: &TestHarness, id: WidgetId) -> bool {
harness.get_widget(id).ctx().has_hovered()
}

fn next_hovered_changed(recording: &Recording) -> Option<bool> {
while let Some(event) = recording.next() {
match event {
Expand All @@ -34,6 +38,7 @@ fn next_hovered_changed(recording: &Recording) -> Option<bool> {
None
}

// TODO - Rewrite test to be more clear
#[test]
fn propagate_hovered() {
let [button, pad, root, empty] = widget_ids();
Expand Down Expand Up @@ -82,11 +87,15 @@ fn propagate_hovered() {
eprintln!("pad: {pad:?}");
eprintln!("button: {button:?}");

assert!(is_hovered(&harness, root));
assert!(is_hovered(&harness, empty));
assert!(!is_hovered(&harness, pad));

assert_eq!(next_hovered_changed(&root_rec), Some(true));
assert!(!is_hovered(&harness, root));
assert!(has_hovered(&harness, root));

assert!(!has_hovered(&harness, pad));

// TODO - Detect ChildHoveredChanged
assert_eq!(next_hovered_changed(&root_rec), None);
assert_eq!(next_hovered_changed(&padding_rec), None);
assert_eq!(next_hovered_changed(&button_rec), None);
root_rec.clear();
Expand All @@ -100,7 +109,6 @@ fn propagate_hovered() {
assert!(is_hovered(&harness, pad));
assert!(!is_hovered(&harness, empty));
assert!(!is_hovered(&harness, button));
assert!(is_hovered(&harness, pad));

assert_eq!(next_hovered_changed(&root_rec), None);
assert_eq!(next_hovered_changed(&padding_rec), Some(true));
Expand All @@ -111,12 +119,13 @@ fn propagate_hovered() {

harness.mouse_move_to(button);

assert!(is_hovered(&harness, root));
assert!(has_hovered(&harness, root));
assert!(has_hovered(&harness, pad));
assert!(!is_hovered(&harness, empty));
assert!(is_hovered(&harness, button));
assert!(is_hovered(&harness, pad));

assert_eq!(next_hovered_changed(&padding_rec), None);
// TODO - Detect ChildHoveredChanged
assert_eq!(next_hovered_changed(&padding_rec), Some(false));
assert_eq!(next_hovered_changed(&button_rec), Some(true));
root_rec.clear();
padding_rec.clear();
Expand All @@ -126,13 +135,14 @@ fn propagate_hovered() {

harness.mouse_move_to(empty);

assert!(is_hovered(&harness, root));
assert!(has_hovered(&harness, root));
assert!(is_hovered(&harness, empty));
assert!(!has_hovered(&harness, pad));
assert!(!is_hovered(&harness, button));
assert!(!is_hovered(&harness, pad));

// TODO - Detect ChildHoveredChanged
assert_eq!(next_hovered_changed(&root_rec), None);
assert_eq!(next_hovered_changed(&padding_rec), Some(false));
assert_eq!(next_hovered_changed(&padding_rec), None);
assert_eq!(next_hovered_changed(&button_rec), Some(false));
}

Expand All @@ -142,7 +152,7 @@ fn update_hovered_on_mouse_leave() {

let button_rec = Recording::default();

let widget = Button::new("hello").with_id(button_id).record(&button_rec);
let widget = Button::new("hello").record(&button_rec).with_id(button_id);

let mut harness = TestHarness::create(widget);

Expand Down
Loading

0 comments on commit 320159b

Please sign in to comment.