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

Support sending async messages #376

Merged
merged 33 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c23aa93
Remove EventManager::try_pop_boxed_msg
dhardy Jan 10, 2023
e292d26
Add type kas_core::event::ErasedMessage
dhardy Jan 10, 2023
5ab1401
Move EventMgr::set_hover to EventState; remove param shell from Event…
dhardy Jan 10, 2023
d127654
Replace EventMgr::send_all with send_update
dhardy Jan 10, 2023
ab193d7
Add EventMgr::push_msg_async and push_erased_msg_async
dhardy Jan 19, 2023
c63de4f
Rename EventMgr::push_msg -> push, etc.
dhardy Jan 19, 2023
0752c57
Clippy
dhardy Jan 19, 2023
984c68a
kas_wgpu: replace dep futures with futures-lite
dhardy Jan 20, 2023
362dd30
Add spawn feature, EventMgr::spawn
dhardy Jan 20, 2023
2a4e9ea
Doc fixes
dhardy Jan 20, 2023
2d4cf0a
Move kas_core::event::ErasedMessage -> kas_core::Erased
dhardy Jan 20, 2023
f671ba2
Add EventState::is_hovered_recursive; improve invisible scrollbars
dhardy Jan 21, 2023
97ec733
Do not call steal_event on the target widget
dhardy Jan 21, 2023
33ab136
EventMgr: check/clean state when sending messages
dhardy Jan 22, 2023
dcbb445
Add EventMgr::last_child
dhardy Jan 22, 2023
ac007b8
Call handle_message after steal_event, handle_event and handle_unused
dhardy Jan 22, 2023
856749a
Update event-handling documentation
dhardy Jan 22, 2023
1ebc2f4
Rename EventMgr::try_pop_msg -> try_pop
dhardy Jan 22, 2023
c1f8a42
Tidy up message types of examples/data-list[-view]
dhardy Jan 22, 2023
4fb0bce
Revise Svg code
dhardy Jan 22, 2023
76073de
Futures return a non-optional message
dhardy Jan 23, 2023
e5eb0de
Move push_async etc from EventMgr to EventState
dhardy Jan 23, 2023
e94da0a
Canvas: inline redraw code
dhardy Jan 23, 2023
02cc76c
Fix EventMgr::send (affects mouse selection of menus)
dhardy Jan 23, 2023
ba7336a
Update gallery's canvas text
dhardy Jan 23, 2023
9002547
Canvas: fix resizing
dhardy Jan 23, 2023
928bb59
kas-resvg requires feature "kas-core/spawn"
dhardy Jan 23, 2023
67d66ff
Clippy fix
dhardy Jan 23, 2023
04fd954
Fix: poll new futures before the event loop sleeps
dhardy Jan 23, 2023
901b91d
Add a frame counter
dhardy Jan 23, 2023
c29cb1c
EventMgr: remove redundant action field
dhardy Jan 23, 2023
3952a03
Add EventState::post_draw to poll futures
dhardy Jan 23, 2023
b08a530
Canvas: call CanvasProgram::need_redraw from Widget::draw
dhardy Jan 23, 2023
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
6 changes: 2 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@ jobs:
run: cargo test --manifest-path examples/mandlebrot/Cargo.toml --all-features
- name: Clippy
run: |
cargo clippy --all --features nightly -- -W clippy::all \
cargo clippy --all --features nightly -- \
-A clippy::collapsible-if \
-A clippy::collapsible_else_if \
-A clippy::module-inception \
-A clippy::single_match \
-A clippy::too-many-arguments \
-A clippy::comparison_chain \
-A clippy::unit_arg
Expand Down Expand Up @@ -100,11 +99,10 @@ jobs:
- name: Clippy (stable)
if: matrix.os == 'macos-latest'
run: |
cargo clippy --all -- -D warnings -W clippy::all \
cargo clippy --all -- -D warnings \
-A clippy::collapsible-if \
-A clippy::collapsible_else_if \
-A clippy::module-inception \
-A clippy::single_match \
-A clippy::too-many-arguments \
-A clippy::comparison_chain \
-A clippy::unit_arg
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ rustdoc-args = ["--cfg", "doc_cfg"]
######### meta / build features #########

# All recommended features for optimal experience
default = ["minimal", "view", "yaml", "image", "resvg", "clipboard", "markdown", "shaping", "dark-light"]
default = ["minimal", "view", "yaml", "image", "resvg", "clipboard", "markdown", "shaping", "dark-light", "spawn"]

# Include only "required" features.
#
Expand Down Expand Up @@ -93,6 +93,9 @@ tiny-skia = ["dep:kas-resvg"]
# Automatically detect usage of dark theme
dark-light = ["kas-core/dark-light"]

# Support spawning async tasks
spawn = ["kas-core/spawn"]

# Support SVG images

# Inject logging into macro-generated code.
Expand Down
4 changes: 4 additions & 0 deletions crates/kas-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ serde = ["dep:serde", "kas-text/serde", "winit?/serde"]
# Automatically detect usage of dark theme
dark-light = ["dep:dark-light"]

# Support spawning async tasks
spawn = ["dep:async-global-executor"]

[dependencies]
log = "0.4"
smallvec = "1.6.1"
Expand All @@ -76,6 +79,7 @@ num_enum = "0.5.6"
dark-light = { version = "0.2.2", optional = true }
raw-window-handle = "0.5.0"
window_clipboard = { version = "0.2.0", optional = true }
async-global-executor = { version = "2.3.1", optional = true }

[dependencies.kas-macros]
version = "0.12.0"
Expand Down
60 changes: 33 additions & 27 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ pub trait Layout {
///
/// fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response {
/// event.on_activate(mgr, self.id(), |mgr| {
/// mgr.push_msg(self.message.clone());
/// mgr.push(self.message.clone());
/// Response::Used
/// })
/// }
Expand Down Expand Up @@ -491,22 +491,12 @@ pub trait Widget: WidgetChildren + Layout {
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn pre_handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response;

/// Handle an event sent to this widget
/// Handle an [`Event`] sent to this widget
///
/// An [`Event`] is some form of user input, timer or notification.
/// This is the primary event handler (see [documentation](crate::event)).
///
/// This is the primary event handler for a widget. Secondary handlers are:
///
/// - If this method returns [`Response::Unused`], then
/// [`Widget::handle_unused`] is called on each parent until the event
/// is used (or the root widget is reached)
/// - If a message is left on the stack by [`EventMgr::push_msg`], then
/// [`Widget::handle_message`] is called on each parent until the stack is
/// empty (failing to empty the stack results in a warning in the log).
/// - If any scroll state is set by [`EventMgr::set_scroll`], then
/// [`Widget::handle_scroll`] is called for each parent
///
/// Default implementation: do nothing; return [`Response::Unused`].
/// Default implementation of `handle_event`: do nothing; return
/// [`Response::Unused`].
///
/// # Calling `handle_event`
///
Expand All @@ -522,9 +512,12 @@ pub trait Widget: WidgetChildren + Layout {

/// Potentially steal an event before it reaches a child
///
/// This is called on each widget while sending an event, including when the
/// target is self.
/// If this returns [`Response::Used`], the event is not sent further.
/// This is an optional event handler (see [documentation](crate::event)).
///
/// May cause a panic if this method returns [`Response::Unused`] but does
/// affect `mgr` (e.g. by calling [`EventMgr::set_scroll`] or leaving a
/// message on the stack, possibly from [`EventMgr::send`]).
/// This is considered a corner-case and not currently supported.
///
/// Default implementation: return [`Response::Unused`].
#[inline]
Expand All @@ -535,29 +528,39 @@ pub trait Widget: WidgetChildren + Layout {

/// Handle an event sent to child `index` but left unhandled
///
/// This is an optional event handler (see [documentation](crate::event)).
///
/// [`EventMgr::last_child`] may be called to find the original target,
/// and should never return [`None`] (when called from this method).
///
/// Default implementation: call [`Self::handle_event`] with `event`.
#[inline]
fn handle_unused(&mut self, mgr: &mut EventMgr, index: usize, event: Event) -> Response {
let _ = index;
fn handle_unused(&mut self, mgr: &mut EventMgr, event: Event) -> Response {
self.handle_event(mgr, event)
}

/// Handler for messages from children/descendants
///
/// This method is called when a child leaves a message on the stack. *Some*
/// parent or ancestor widget should read this message.
/// This is the secondary event handler (see [documentation](crate::event)).
///
/// It is implied that the stack contains at least one message.
/// Use [`EventMgr::try_pop`] and/or [`EventMgr::try_observe`].
///
/// [`EventMgr::last_child`] may be called to find the message's sender.
/// This may return [`None`] (if no child was visited, which implies that
/// the message was sent by `self`).
///
/// The default implementation does nothing.
#[inline]
fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) {
let _ = (mgr, index);
fn handle_message(&mut self, mgr: &mut EventMgr) {
let _ = mgr;
}

/// Handler for scrolling
///
/// When a child calls [`EventMgr::set_scroll`] with a value other than
/// [`Scroll::None`], this method is called. (This method is not called
/// after [`Self::handle_event`] or other handlers called on self.)
/// When, during [event handling](crate::event), a widget which is a strict
/// descendant of `self` (i.e. not `self`) calls [`EventMgr::set_scroll`]
/// with a value other than [`Scroll::None`], this method is called.
///
/// Note that [`Scroll::Rect`] values are in the child's coordinate space,
/// and must be translated to the widget's own coordinate space by this
Expand All @@ -569,6 +572,9 @@ pub trait Widget: WidgetChildren + Layout {
/// should call `mgr.set_scroll(Scroll::None)` to avoid any reactions to
/// child's scroll requests.
///
/// [`EventMgr::last_child`] may be called to find the child responsible,
/// and should never return [`None`] (when called from this method).
///
/// The default implementation does nothing.
#[inline]
fn handle_scroll(&mut self, mgr: &mut EventMgr, scroll: Scroll) {
Expand Down
63 changes: 63 additions & 0 deletions crates/kas-core/src/erased.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
// https://www.apache.org/licenses/LICENSE-2.0

//! Erased type

use std::any::Any;
use std::fmt::Debug;

/// A type-erased value
///
/// This is vaguely a wrapper over `Box<dyn (Any + Debug)>`, except that Rust
/// doesn't (yet) support multi-trait objects.
pub struct Erased {
// TODO: use trait_upcasting feature when stable: Box<dyn AnyDebug>
// where trait AnyDebug: Any + Debug {}. This replaces the fmt field.
any: Box<dyn Any>,
#[cfg(debug_assertions)]
fmt: String,
}

impl Erased {
/// Construct
pub fn new<V: Any + Debug>(v: V) -> Self {
#[cfg(debug_assertions)]
let fmt = format!("{}::{:?}", std::any::type_name::<V>(), &v);
let any = Box::new(v);
Erased {
#[cfg(debug_assertions)]
fmt,
any,
}
}

/// Returns `true` if the inner type is the same as `T`.
pub fn is<T: 'static>(&self) -> bool {
self.any.is::<T>()
}

/// Attempt to downcast self to a concrete type.
pub fn downcast<T: 'static>(self) -> Result<Box<T>, Box<dyn Any>> {
self.any.downcast::<T>()
}

/// Returns some reference to the inner value if it is of type `T`, or `None` if it isn’t.
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.any.downcast_ref::<T>()
}
}

/// Support debug formatting
///
/// Debug builds only. On release builds, a placeholder message is printed.
impl std::fmt::Debug for Erased {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
#[cfg(debug_assertions)]
let r = f.write_str(&self.fmt);
#[cfg(not(debug_assertions))]
let r = f.write_str("[use debug build to see value]");
r
}
}
12 changes: 5 additions & 7 deletions crates/kas-core/src/event/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,11 @@ pub enum Event {
TimerUpdate(u64),
/// Update triggerred via an [`UpdateId`]
///
/// This event is received by all widgets when [`EventMgr::update_all`]
/// is called.
///
/// Note that this event is only received by [`Widget::handle_event`] and
/// not by [`Widget::steal_event`] or [`Widget::handle_unused`].
/// Messages and scroll actions will *not* be handled by parent's
/// [`Widget::handle_message`] or [`Widget::handle_scroll`] methods.
/// When [`EventMgr::update_all`] is called, this event is broadcast to all
/// widgets via depth-first traversal of the widget tree. As such,
/// [`Widget::steal_event`] and [`Widget::handle_unused`] are not called
/// with this `Event`, nor are [`Widget::handle_message`] or
/// [`Widget::handle_scroll`] called after a widget receives this `Event`.
Update { id: UpdateId, payload: u64 },
/// Notification that a popup has been destroyed
///
Expand Down
Loading