Skip to content

Commit

Permalink
Merge pull request #453 from kas-gui/work2
Browse files Browse the repository at this point in the history
Remove event stealing; revising handling of disabled events
  • Loading branch information
dhardy authored Jun 27, 2024
2 parents 34b7f08 + ee3c3e4 commit 4b97ded
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 129 deletions.
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

0 comments on commit 4b97ded

Please sign in to comment.