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

Remove event stealing; revising handling of disabled events #453

Merged
merged 6 commits into from
Jun 27, 2024
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
47 changes: 20 additions & 27 deletions crates/kas-core/src/core/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,26 @@ pub fn _send<W: Events>(

do_handle_event = true;
} else {
if event.is_reusable() {
is_used = widget.steal_event(cx, data, &id, &event);
}
if !is_used {
cx.assert_post_steal_unused();

if let Some(index) = widget.find_child_index(&id) {
let translation = widget.translation();
let mut _found = false;
widget.as_node(data).for_child(index, |mut node| {
is_used = node._send(cx, id.clone(), event.clone() + translation);
_found = true;
});

#[cfg(debug_assertions)]
if !_found {
// This is an error in the widget. It's unlikely and not fatal
// so we ignore in release builds.
log::error!(
"_send: {} found index {index} for {id} but not child",
IdentifyWidget(widget.widget_name(), widget.id_ref())
);
}

if let Some(scroll) = cx.post_send(index) {
widget.handle_scroll(cx, data, scroll);
}
if let Some(index) = widget.find_child_index(&id) {
let translation = widget.translation();
let mut _found = false;
widget.as_node(data).for_child(index, |mut node| {
is_used = node._send(cx, id.clone(), event.clone() + translation);
_found = true;
});

#[cfg(debug_assertions)]
if !_found {
// This is an error in the widget. It's unlikely and not fatal
// so we ignore in release builds.
log::error!(
"_send: {} found index {index} for {id} but not child",
IdentifyWidget(widget.widget_name(), widget.id_ref())
);
}

if let Some(scroll) = cx.post_send(index) {
widget.handle_scroll(cx, data, scroll);
}
}

Expand Down
21 changes: 0 additions & 21 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,27 +176,6 @@ pub trait Events: Widget + Sized {
Unused
}

/// Potentially steal an event before it reaches a child
///
/// This is an optional event handler (see [documentation](crate::event)).
///
/// The method should *either* return [`Used`] or return [`Unused`] without
/// modifying `cx`; attempting to do otherwise (e.g. by calling
/// [`EventCx::set_scroll`] or leaving a message on the stack when returning
/// [`Unused`]) will result in a panic.
///
/// Default implementation: return [`Unused`].
fn steal_event(
&mut self,
cx: &mut EventCx,
data: &Self::Data,
id: &Id,
event: &Event,
) -> IsUsed {
let _ = (cx, data, id, event);
Unused
}

/// Handler for messages from children/descendants
///
/// This is the secondary event handler (see [documentation](crate::event)).
Expand Down
29 changes: 20 additions & 9 deletions crates/kas-core/src/event/cx/cx_pub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,19 +172,26 @@ impl EventState {
/// Disabled status applies to all descendants and blocks reception of
/// events ([`Unused`] is returned automatically when the
/// recipient or any ancestor is disabled).
pub fn set_disabled(&mut self, w_id: Id, state: bool) {
///
/// Disabling a widget clears navigation, selection and key focus when the
/// target is disabled, and also cancels press/pan grabs.
pub fn set_disabled(&mut self, target: Id, disable: bool) {
if disable {
self.clear_events(&target);
}

for (i, id) in self.disabled.iter().enumerate() {
if w_id == id {
if !state {
self.redraw(w_id);
if target == id {
if !disable {
self.redraw(target);
self.disabled.remove(i);
}
return;
}
}
if state {
self.action(&w_id, Action::REDRAW);
self.disabled.push(w_id);
if disable {
self.action(&target, Action::REDRAW);
self.disabled.push(target);
}
}

Expand Down Expand Up @@ -468,6 +475,10 @@ impl EventState {
/// grabs targets the widget to depress, or when a keyboard binding is used
/// to activate a widget (for the duration of the key-press).
///
/// Assumption: this method will only be called by handlers of a grab (i.e.
/// recipients of [`Event::PressStart`] after initiating a successful grab,
/// [`Event::PressMove`] or [`Event::PressEnd`]).
///
/// Queues a redraw and returns `true` if the depress target changes,
/// otherwise returns `false`.
pub fn set_grab_depress(&mut self, source: PressSource, target: Option<Id>) -> bool {
Expand Down Expand Up @@ -497,8 +508,8 @@ impl EventState {
redraw
}

/// Returns true if `id` or any descendant has a mouse or touch grab
pub fn any_pin_on(&self, id: &Id) -> bool {
/// Returns true if there is a mouse or touch grab on `id` or any descendant of `id`
pub fn any_grab_on(&self, id: &Id) -> bool {
if self
.mouse_grab
.as_ref()
Expand Down
92 changes: 73 additions & 19 deletions crates/kas-core/src/event/cx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ struct MouseGrab {
start_id: Id,
depress: Option<Id>,
details: GrabDetails,
cancel: bool,
}

impl<'a> EventCx<'a> {
Expand Down Expand Up @@ -111,6 +112,7 @@ struct TouchGrab {
coord: Coord,
mode: GrabMode,
pan_grab: (u16, u16),
cancel: bool,
}

impl TouchGrab {
Expand Down Expand Up @@ -336,26 +338,81 @@ impl EventState {
}
}

#[inline]
fn get_touch_index(&self, touch_id: u64) -> Option<usize> {
self.touch_grab
.iter()
.enumerate()
.find_map(|(i, grab)| (grab.id == touch_id).then_some(i))
}

#[inline]
fn get_touch(&mut self, touch_id: u64) -> Option<&mut TouchGrab> {
self.touch_grab.iter_mut().find(|grab| grab.id == touch_id)
}

// Clears touch grab and pan grab and redraws
fn remove_touch(&mut self, touch_id: u64) -> Option<TouchGrab> {
for i in 0..self.touch_grab.len() {
if self.touch_grab[i].id == touch_id {
let grab = self.touch_grab.remove(i);
log::trace!(
"remove_touch: touch_id={touch_id}, start_id={}",
grab.start_id
);
self.opt_action(grab.depress.clone(), Action::REDRAW);
self.remove_pan_grab(grab.pan_grab);
return Some(grab);
//
// Returns the grab. Panics on out-of-bounds error.
fn remove_touch(&mut self, index: usize) -> TouchGrab {
let mut grab = self.touch_grab.remove(index);
log::trace!(
"remove_touch: touch_id={}, start_id={}",
grab.id,
grab.start_id
);
self.opt_action(grab.depress.clone(), Action::REDRAW);
self.remove_pan_grab(grab.pan_grab);
self.action(Id::ROOT, grab.flush_click_move());
grab
}

/// Clear all active events on `target`
fn clear_events(&mut self, target: &Id) {
if let Some(id) = self.sel_focus.as_ref() {
if target.is_ancestor_of(id) {
if let Some(pending) = self.pending_sel_focus.as_mut() {
if pending.target.as_ref() == Some(id) {
pending.target = None;
pending.key_focus = false;
}
} else {
self.pending_sel_focus = Some(PendingSelFocus {
target: None,
key_focus: false,
source: FocusSource::Synthetic,
});
}
}
}

if let Some(id) = self.nav_focus.as_ref() {
if target.is_ancestor_of(id) {
if matches!(&self.pending_nav_focus, PendingNavFocus::Set { ref target, .. } if target.as_ref() == Some(id))
{
self.pending_nav_focus = PendingNavFocus::None;
}

if matches!(self.pending_nav_focus, PendingNavFocus::None) {
self.pending_nav_focus = PendingNavFocus::Set {
target: None,
source: FocusSource::Synthetic,
};
}
}
}

if let Some(grab) = self.mouse_grab.as_mut() {
if grab.start_id == target {
grab.cancel = true;
}
}

for grab in self.touch_grab.iter_mut() {
if grab.start_id == target {
grab.cancel = true;
}
}
None
}
}

Expand Down Expand Up @@ -485,7 +542,10 @@ impl<'a> EventCx<'a> {
// Clears mouse grab and pan grab, resets cursor and redraws
fn remove_mouse_grab(&mut self, success: bool) -> Option<(Id, Event)> {
if let Some(grab) = self.mouse_grab.take() {
log::trace!("remove_mouse_grab: start_id={}", grab.start_id);
log::trace!(
"remove_mouse_grab: start_id={}, success={success}",
grab.start_id
);
self.window.set_cursor_icon(self.hover_icon);
self.opt_action(grab.depress.clone(), Action::REDRAW);
if let GrabDetails::Pan(g) = grab.details {
Expand All @@ -506,12 +566,6 @@ impl<'a> EventCx<'a> {
}
}

pub(crate) fn assert_post_steal_unused(&self) {
if self.scroll != Scroll::None || self.messages.has_any() {
panic!("steal_event affected EventCx and returned Unused");
}
}

pub(crate) fn post_send(&mut self, index: usize) -> Option<Scroll> {
self.last_child = Some(index);
(self.scroll != Scroll::None).then_some(self.scroll)
Expand Down
35 changes: 32 additions & 3 deletions crates/kas-core/src/event/cx/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,38 @@ impl EventState {
}

cx.flush_mouse_grab_motion();
for i in 0..cx.touch_grab.len() {
if cx
.mouse_grab
.as_ref()
.map(|grab| grab.cancel)
.unwrap_or(false)
{
if let Some((id, event)) = cx.remove_mouse_grab(false) {
cx.send_event(win.as_node(data), id, event);
}
}

let mut i = 0;
while i < cx.touch_grab.len() {
let action = cx.touch_grab[i].flush_click_move();
cx.state.action |= action;

if cx.touch_grab[i].cancel {
let grab = cx.remove_touch(i);

let press = Press {
source: PressSource::Touch(grab.id),
id: grab.cur_id,
coord: grab.coord,
};
let event = Event::PressEnd {
press,
success: false,
};
cx.send_event(win.as_node(data), grab.start_id, event);
} else {
i += 1;
}
}

for gi in 0..cx.pan_grab.len() {
Expand Down Expand Up @@ -560,8 +589,8 @@ impl<'a> EventCx<'a> {
}
}
ev @ (TouchPhase::Ended | TouchPhase::Cancelled) => {
if let Some(mut grab) = self.remove_touch(touch.id) {
self.action(Id::ROOT, grab.flush_click_move());
if let Some(index) = self.get_touch_index(touch.id) {
let grab = self.remove_touch(index);

if grab.mode == GrabMode::Grab {
let id = grab.cur_id.clone();
Expand Down
5 changes: 4 additions & 1 deletion crates/kas-core/src/event/cx/press.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ impl GrabBuilder {
if grab.start_id != id
|| grab.button != button
|| grab.details.is_pan() != mode.is_pan()
|| grab.cancel
{
return Unused;
}
Expand All @@ -209,6 +210,7 @@ impl GrabBuilder {
start_id: id.clone(),
depress: Some(id.clone()),
details,
cancel: false,
});
}
if let Some(icon) = cursor {
Expand All @@ -217,7 +219,7 @@ impl GrabBuilder {
}
PressSource::Touch(touch_id) => {
if let Some(grab) = cx.get_touch(touch_id) {
if grab.mode.is_pan() != mode.is_pan() {
if grab.mode.is_pan() != mode.is_pan() || grab.cancel {
return Unused;
}

Expand All @@ -240,6 +242,7 @@ impl GrabBuilder {
coord,
mode,
pan_grab,
cancel: false,
});
}
}
Expand Down
15 changes: 11 additions & 4 deletions crates/kas-core/src/event/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,21 @@ impl Event {

/// Pass to disabled widgets?
///
/// Disabled status should disable input handling but not prevent other
/// notifications.
/// When a widget is disabled:
///
/// - New input events (`Command`, `PressStart`, `Scroll`) are not passed
/// - Continuing input actions (`PressMove`, `PressEnd`) are passed (or
/// the input sequence may be terminated).
/// - New focus notifications are not passed
/// - Focus-loss notifications are passed
/// - Requested events like `Timer` are passed
pub fn pass_when_disabled(&self) -> bool {
use Event::*;
match self {
Command(_, _) => false,
Key(_, _) | Scroll(_) | Pan { .. } => false,
CursorMove { .. } | PressStart { .. } | PressMove { .. } | PressEnd { .. } => false,
Key(_, _) | Scroll(_) => false,
CursorMove { .. } | PressStart { .. } => false,
Pan { .. } | PressMove { .. } | PressEnd { .. } => true,
Timer(_) | PopupClosed(_) => true,
NavFocus { .. } | SelFocus(_) | KeyFocus | MouseHover(_) => false,
LostNavFocus | LostKeyFocus | LostSelFocus => true,
Expand Down
5 changes: 1 addition & 4 deletions crates/kas-core/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@
//! inhibit calling of [`Events::handle_event`] on this widget (but still
//! unwind, calling [`Events::handle_event`] on ancestors)).
//! 3. Traverse *down* the widget tree from its root to the target according to
//! the [`Id`]. On each node (excluding the target),
//!
//! - Call [`Events::steal_event`]; if this method "steals" the event,
//! skip to step 5.
//! the [`Id`].
//! 4. In the normal case (when the target is not disabled and the event is
//! not stolen), [`Events::handle_event`] is called on the target.
//! 5. If the message stack is not empty, call [`Events::handle_messages`] on
Expand Down
Loading