Skip to content

Commit

Permalink
Merge pull request #429 from kas-gui/work3
Browse files Browse the repository at this point in the history
Support message sending
  • Loading branch information
dhardy authored Dec 18, 2023
2 parents 2e64f9c + 6791f6e commit 8961548
Show file tree
Hide file tree
Showing 28 changed files with 219 additions and 189 deletions.
21 changes: 7 additions & 14 deletions crates/kas-core/src/core/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,28 @@
use crate::event::{ConfigCx, Event, EventCx, FocusSource, IsUsed, Scroll, Unused, Used};
#[cfg(debug_assertions)] use crate::util::IdentifyWidget;
use crate::{messages::Erased, Events, Id, Layout, NavAdvance, Node, Widget};
use crate::{Events, Id, Layout, NavAdvance, Node, Widget};

/// Generic implementation of [`Widget::_send`]
pub fn _send<W: Events>(
widget: &mut W,
cx: &mut EventCx,
data: &<W as Widget>::Data,
id: Id,
disabled: bool,
event: Event,
) -> IsUsed {
let mut is_used = Unused;
let do_handle_event;

if id == widget.id_ref() {
if disabled {
if cx.target_is_disabled {
return is_used;
}

match &event {
Event::MouseHover(state) => {
is_used |= widget.handle_hover(cx, *state);
widget.handle_hover(cx, *state);
return Used;
}
Event::NavFocus(FocusSource::Key) => {
cx.set_scroll(Scroll::Rect(widget.rect()));
Expand All @@ -49,7 +49,7 @@ pub fn _send<W: Events>(
let translation = widget.translation();
let mut _found = false;
widget.as_node(data).for_child(index, |mut node| {
is_used = node._send(cx, id.clone(), disabled, event.clone() + translation);
is_used = node._send(cx, id.clone(), event.clone() + translation);
_found = true;
});

Expand Down Expand Up @@ -84,17 +84,11 @@ pub fn _send<W: Events>(
}

/// Generic implementation of [`Widget::_replay`]
pub fn _replay<W: Events>(
widget: &mut W,
cx: &mut EventCx,
data: &<W as Widget>::Data,
id: Id,
msg: Erased,
) {
pub fn _replay<W: Events>(widget: &mut W, cx: &mut EventCx, data: &<W as Widget>::Data, id: Id) {
if let Some(index) = widget.find_child_index(&id) {
let mut _found = false;
widget.as_node(data).for_child(index, |mut node| {
node._replay(cx, id.clone(), msg);
node._replay(cx, id.clone());
_found = true;
});

Expand All @@ -116,7 +110,6 @@ pub fn _replay<W: Events>(
widget.handle_messages(cx, data);
}
} else if id == widget.id_ref() {
cx.push_erased(msg);
widget.handle_messages(cx, data);
} else {
// This implies use of push_async / push_spawn from a widget which was
Expand Down
32 changes: 13 additions & 19 deletions crates/kas-core/src/core/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::event::{ConfigCx, Event, EventCx, IsUsed};
use crate::geom::{Coord, Rect};
use crate::layout::{AxisInfo, SizeRules};
use crate::theme::{DrawCx, SizeCx};
use crate::{messages::Erased, Id, Layout, NavAdvance};
use crate::{Id, Layout, NavAdvance};

#[cfg(not(feature = "unsafe_node"))]
trait NodeT {
Expand All @@ -34,8 +34,8 @@ trait NodeT {
fn _configure(&mut self, cx: &mut ConfigCx, id: Id);
fn _update(&mut self, cx: &mut ConfigCx);

fn _send(&mut self, cx: &mut EventCx, id: Id, disabled: bool, event: Event) -> IsUsed;
fn _replay(&mut self, cx: &mut EventCx, id: Id, msg: Erased);
fn _send(&mut self, cx: &mut EventCx, id: Id, event: Event) -> IsUsed;
fn _replay(&mut self, cx: &mut EventCx, id: Id);
fn _nav_next(
&mut self,
cx: &mut ConfigCx,
Expand Down Expand Up @@ -94,11 +94,11 @@ impl<'a, T> NodeT for (&'a mut dyn Widget<Data = T>, &'a T) {
self.0._update(cx, self.1);
}

fn _send(&mut self, cx: &mut EventCx, id: Id, disabled: bool, event: Event) -> IsUsed {
self.0._send(cx, self.1, id, disabled, event)
fn _send(&mut self, cx: &mut EventCx, id: Id, event: Event) -> IsUsed {
self.0._send(cx, self.1, id, event)
}
fn _replay(&mut self, cx: &mut EventCx, id: Id, msg: Erased) {
self.0._replay(cx, self.1, id, msg);
fn _replay(&mut self, cx: &mut EventCx, id: Id) {
self.0._replay(cx, self.1, id);
}
fn _nav_next(
&mut self,
Expand Down Expand Up @@ -344,29 +344,23 @@ impl<'a> Node<'a> {
}

/// Internal method: send recursively
pub(crate) fn _send(
&mut self,
cx: &mut EventCx,
id: Id,
disabled: bool,
event: Event,
) -> IsUsed {
pub(crate) fn _send(&mut self, cx: &mut EventCx, id: Id, event: Event) -> IsUsed {
cfg_if::cfg_if! {
if #[cfg(feature = "unsafe_node")] {
self.0._send(cx, self.1, id, disabled, event)
self.0._send(cx, self.1, id, event)
} else {
self.0._send(cx, id, disabled, event)
self.0._send(cx, id, event)
}
}
}

/// Internal method: replay recursively
pub(crate) fn _replay(&mut self, cx: &mut EventCx, id: Id, msg: Erased) {
pub(crate) fn _replay(&mut self, cx: &mut EventCx, id: Id) {
cfg_if::cfg_if! {
if #[cfg(feature = "unsafe_node")] {
self.0._replay(cx, self.1, id, msg);
self.0._replay(cx, self.1, id);
} else {
self.0._replay(cx, id, msg);
self.0._replay(cx, id);
}
}
}
Expand Down
30 changes: 8 additions & 22 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use super::{Layout, Node};
#[allow(unused)] use crate::event::Used;
use crate::event::{ConfigCx, Event, EventCx, IsUsed, Scroll, Unused};
use crate::{messages::Erased, Id};
use crate::Id;
use kas_macros::autoimpl;

#[allow(unused)] use kas_macros as macros;
Expand Down Expand Up @@ -140,23 +140,20 @@ pub trait Events: Widget + Sized {

/// Mouse focus handler
///
/// Called on [`Event::MouseHover`] before [`Self::handle_event`].
/// `state` is true when hovered.
/// Called when mouse hover state changes.
///
/// When the [`#widget`] macro properties `hover_highlight` or `cursor_icon`
/// are used, an instance of this method is generated. Otherwise, the
/// default implementation of this method does nothing and equivalent
/// functionality could be implemented in [`Events::handle_event`] instead.
/// default implementation of this method does nothing.
///
/// Note: to implement `hover_highlight`, simply request a redraw on
/// focus gain and loss. To implement `cursor_icon`, call
/// `cx.set_hover_cursor(EXPR);` on focus gain.
///
/// [`#widget`]: macros::widget
#[inline]
fn handle_hover(&mut self, cx: &mut EventCx, state: bool) -> IsUsed {
fn handle_hover(&mut self, cx: &mut EventCx, state: bool) {
let _ = (cx, state);
Unused
}

/// Handle an [`Event`]
Expand Down Expand Up @@ -398,31 +395,20 @@ pub trait Widget: Layout {

/// Internal method: send recursively
///
/// If `disabled`, widget `id` does not receive the `event`. Widget `id` is
/// the first disabled widget (may be an ancestor of the original target);
/// ancestors of `id` are not disabled.
///
/// Do not implement this method directly!
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn _send(
&mut self,
cx: &mut EventCx,
data: &Self::Data,
id: Id,
disabled: bool,
event: Event,
) -> IsUsed;
fn _send(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id, event: Event) -> IsUsed;

/// Internal method: replay recursively
///
/// Behaves as if an event had been sent to `id`, then the widget had pushed
/// `msg` to the message stack. Widget `id` or any ancestor may handle.
/// Traverses the widget tree to `id`, then unwinds.
/// It is expected that some message is available on the stack.
///
/// Do not implement this method directly!
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn _replay(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id, msg: Erased);
fn _replay(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id);

/// Internal method: search for the previous/next navigation target
///
Expand Down
24 changes: 24 additions & 0 deletions crates/kas-core/src/event/cx/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ use super::PendingNavFocus;
use crate::event::{EventState, FocusSource};
use crate::geom::{Rect, Size};
use crate::layout::AlignPair;
use crate::messages::Erased;
use crate::text::TextApi;
use crate::theme::{Feature, SizeCx, TextClass, ThemeSize};
use crate::{Id, Node};
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};

#[allow(unused)] use crate::{event::Event, Events, Layout};
Expand Down Expand Up @@ -88,6 +90,28 @@ impl<'a> ConfigCx<'a> {
widget._update(self);
}

/// Push a message (replay)
///
/// Unlike [`EventCx::push`], this is not handled while unwinding
/// from event sending, but via a fresh traversal of the widget tree.
///
/// TODO: `id` should not be part of the function signature?
pub fn push<M: Debug + 'static>(&mut self, id: Id, msg: M) {
self.send(id, msg);
}

/// Push a type-erased message (replay)
///
/// Unlike [`EventCx::push_erased`], this is not handled while unwinding
/// from event sending, but via a fresh traversal of the widget tree.
///
/// The message may be [popped](EventCx::try_pop) or
/// [observed](EventCx::try_observe) from [`Events::handle_messages`]
/// by the widget itself, its parent, or any ancestor.
pub fn push_erased(&mut self, id: Id, msg: Erased) {
self.send_erased(id, msg);
}

/// Align a feature's rect
///
/// In case the input `rect` is larger than desired on either axis, it is
Expand Down
33 changes: 28 additions & 5 deletions crates/kas-core/src/event/cx/cx_pub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,26 @@ impl EventState {
/// Note that keyboard shortcuts and mnemonics should usually match against
/// the "logical key". [`PhysicalKey`] is used here since the the logical key
/// may be changed by modifier keys.
pub fn depress_with_key(&mut self, id: Id, code: PhysicalKey) {
if self.key_depress.values().any(|v| *v == id) {
return;
///
/// Does nothing when `code` is `None`.
pub fn depress_with_key(&mut self, id: Id, code: impl Into<Option<PhysicalKey>>) {
fn inner(state: &mut EventState, id: Id, code: PhysicalKey) {
if state
.key_depress
.get(&code)
.map(|target| *target == id)
.unwrap_or(false)
{
return;
}

state.key_depress.insert(code, id.clone());
state.action(id, Action::REDRAW);
}

self.key_depress.insert(code, id.clone());
self.action(id, Action::REDRAW);
if let Some(code) = code.into() {
inner(self, id, code);
}
}

/// Request keyboard input focus
Expand Down Expand Up @@ -561,6 +574,16 @@ impl EventState {
self.hover_icon = icon;
}

/// Send a message to `id`
pub fn send<M: Debug + 'static>(&mut self, id: Id, msg: M) {
self.send_erased(id, Erased::new(msg));
}

/// Send an erased message to `id`
pub fn send_erased(&mut self, id: Id, msg: Erased) {
self.send_queue.push_back((id, msg));
}

/// Asynchronously push a message to the stack via a [`Future`]
///
/// The future is polled after event handling and after drawing and is able
Expand Down
10 changes: 8 additions & 2 deletions crates/kas-core/src/event/cx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ pub struct EventState {
popups: SmallVec<[(WindowId, crate::PopupDescriptor, Option<Id>); 16]>,
popup_removed: SmallVec<[(Id, WindowId); 16]>,
time_updates: Vec<(Instant, Id, u64)>,
// Set of messages awaiting sending
send_queue: VecDeque<(Id, Erased)>,
// Set of futures of messages together with id of sending widget
fut_messages: Vec<(Id, Pin<Box<dyn Future<Output = Erased>>>)>,
// Widget requiring update (and optionally configure)
Expand Down Expand Up @@ -369,6 +371,7 @@ pub struct EventCx<'a> {
shared: &'a mut dyn AppShared,
window: &'a dyn WindowDataErased,
messages: &'a mut MessageStack,
pub(crate) target_is_disabled: bool,
last_child: Option<usize>,
scroll: Scroll,
}
Expand Down Expand Up @@ -529,7 +532,9 @@ impl<'a> EventCx<'a> {
self.messages.set_base();
log::trace!(target: "kas_core::event", "replay: id={id}: {msg:?}");

widget._replay(self, id, msg);
self.target_is_disabled = false;
self.push_erased(msg);
widget._replay(self, id);
self.last_child = None;
self.scroll = Scroll::None;
}
Expand All @@ -554,8 +559,9 @@ impl<'a> EventCx<'a> {
log::trace!(target: "kas_core::event", "target is disabled; sending to ancestor {id}");
}
}
self.target_is_disabled = disabled;

let used = widget._send(self, id, disabled, event) == Used;
let used = widget._send(self, id, event) == Used;

self.last_child = None;
self.scroll = Scroll::None;
Expand Down
7 changes: 7 additions & 0 deletions crates/kas-core/src/event/cx/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl EventState {
popups: Default::default(),
popup_removed: Default::default(),
time_updates: vec![],
send_queue: Default::default(),
fut_messages: vec![],
pending_update: None,
pending_sel_focus: None,
Expand Down Expand Up @@ -118,6 +119,7 @@ impl EventState {
shared,
window,
messages,
target_is_disabled: false,
last_child: None,
scroll: Scroll::None,
};
Expand Down Expand Up @@ -222,6 +224,11 @@ impl EventState {
cx.send_event(win.as_node(data), id, Event::Command(cmd, None));
}

while let Some((id, msg)) = cx.send_queue.pop_front() {
log::trace!(target: "kas_core::event", "sending message {msg:?} to {id}");
cx.replay(win.as_node(data), id, msg);
}

// Poll futures almost last. This means that any newly pushed future
// should get polled from the same update() call.
cx.poll_futures(win.as_node(data));
Expand Down
Loading

0 comments on commit 8961548

Please sign in to comment.