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 return values on some closures; replace some custom widgets #432

Merged
merged 5 commits into from
Dec 23, 2023
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
12 changes: 3 additions & 9 deletions crates/kas-core/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ mod common;
#[cfg(winit)] mod shared;
#[cfg(winit)] mod window;

use crate::messages::MessageStack;
#[cfg(winit)] use crate::WindowId;
use crate::{messages::MessageStack, Action};
#[cfg(winit)] use app::PlatformWrapper;
#[cfg(winit)] use event_loop::Loop as EventLoop;
#[cfg(winit)] pub(crate) use shared::{AppShared, AppState};
Expand Down Expand Up @@ -41,17 +41,11 @@ pub trait AppData: 'static {
/// This is the last message handler: it is called when, after traversing
/// the widget tree (see [kas::event] module doc), a message is left on the
/// stack. Unhandled messages will result in warnings in the log.
///
/// The method returns an [`Action`], usually either [`Action::empty`]
/// (nothing to do) or [`Action::UPDATE`] (to update widgets).
/// This action affects all windows.
fn handle_messages(&mut self, messages: &mut MessageStack) -> Action;
fn handle_messages(&mut self, messages: &mut MessageStack);
}

impl AppData for () {
fn handle_messages(&mut self, _: &mut MessageStack) -> Action {
Action::empty()
}
fn handle_messages(&mut self, _: &mut MessageStack) {}
}

#[crate::autoimpl(Debug)]
Expand Down
9 changes: 7 additions & 2 deletions crates/kas-core/src/app/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,13 @@ where
#[inline]
pub(crate) fn handle_messages(&mut self, messages: &mut MessageStack) {
if messages.reset_and_has_any() {
let action = self.data.handle_messages(messages);
self.shared.pending.push_back(Pending::Action(action));
let count = messages.get_op_count();
self.data.handle_messages(messages);
if messages.get_op_count() != count {
self.shared
.pending
.push_back(Pending::Action(Action::UPDATE));
}
}
}

Expand Down
9 changes: 9 additions & 0 deletions crates/kas-core/src/event/cx/cx_pub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,15 @@ impl<'a> EventCx<'a> {
self.messages.try_debug()
}

/// Get the message stack operation count
///
/// This is incremented every time the message stack is changed, thus can be
/// used to test whether a message handler did anything.
#[inline]
pub fn msg_op_count(&self) -> usize {
self.messages.get_op_count()
}

/// Set a scroll action
///
/// When setting [`Scroll::Rect`], use the widget's own coordinate space.
Expand Down
11 changes: 11 additions & 0 deletions crates/kas-core/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ impl SendErased {
#[derive(Debug, Default)]
pub struct MessageStack {
base: usize,
count: usize,
stack: Vec<Erased>,
}

Expand All @@ -142,6 +143,14 @@ impl MessageStack {
self.base = self.stack.len();
}

/// Get the current operation count
///
/// This is incremented every time the message stack is changed.
#[inline]
pub(crate) fn get_op_count(&self) -> usize {
self.count
}

/// Reset the base; return true if messages are available after reset
#[inline]
pub(crate) fn reset_and_has_any(&mut self) -> bool {
Expand All @@ -158,6 +167,7 @@ impl MessageStack {
/// Push a type-erased message to the stack
#[inline]
pub(crate) fn push_erased(&mut self, msg: Erased) {
self.count = self.count.wrapping_add(1);
self.stack.push(msg);
}

Expand All @@ -166,6 +176,7 @@ impl MessageStack {
/// This method may be called from [`Events::handle_messages`].
pub fn try_pop<M: Debug + 'static>(&mut self) -> Option<M> {
if self.has_any() && self.stack.last().map(|m| m.is::<M>()).unwrap_or(false) {
self.count = self.count.wrapping_add(1);
self.stack.pop().unwrap().downcast::<M>().ok().map(|m| *m)
} else {
None
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/theme/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ mod defaults {
}

pub fn font_size() -> f32 {
14.0
16.0
}

pub fn color_schemes() -> BTreeMap<String, ColorsSrgb> {
Expand Down
28 changes: 12 additions & 16 deletions crates/kas-widgets/src/adapt/adapt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ impl_scope! {
inner: W,
configure_handler: Option<Box<dyn Fn(&mut AdaptConfigCx, &mut S)>>,
update_handler: Option<Box<dyn Fn(&mut AdaptConfigCx, &mut S, &A)>>,
timer_handlers: LinearMap<u64, Box<dyn Fn(&mut AdaptEventCx, &mut S, &A) -> bool>>,
message_handlers: Vec<Box<dyn Fn(&mut AdaptEventCx, &mut S, &A) -> bool>>,
timer_handlers: LinearMap<u64, Box<dyn Fn(&mut AdaptEventCx, &mut S, &A)>>,
message_handlers: Vec<Box<dyn Fn(&mut AdaptEventCx, &mut S, &A)>>,
}

impl Self {
Expand Down Expand Up @@ -72,10 +72,12 @@ impl_scope! {

/// Set a timer handler
///
/// The closure should return `true` if state was updated.
/// It is assumed that state is modified by this timer. Frequent usage
/// of timers which don't do anything may be inefficient; prefer usage
/// of [`EventState::push_async`](kas::event::EventState::push_async).
pub fn on_timer<H>(mut self, timer_id: u64, handler: H) -> Self
where
H: Fn(&mut AdaptEventCx, &mut S, &A) -> bool + 'static,
H: Fn(&mut AdaptEventCx, &mut S, &A) + 'static,
{
debug_assert!(self.timer_handlers.get(&timer_id).is_none());
self.timer_handlers.insert(timer_id, Box::new(handler));
Expand All @@ -96,19 +98,14 @@ impl_scope! {
self.on_messages(move |cx, state, _data| {
if let Some(m) = cx.try_pop() {
handler(cx, state, m);
true
} else {
false
}
})
}

/// Add a generic message handler
///
/// The closure should return `true` if state was updated.
pub fn on_messages<H>(mut self, handler: H) -> Self
where
H: Fn(&mut AdaptEventCx, &mut S, &A) -> bool + 'static,
H: Fn(&mut AdaptEventCx, &mut S, &A) + 'static,
{
self.message_handlers.push(Box::new(handler));
self
Expand Down Expand Up @@ -137,9 +134,8 @@ impl_scope! {
Event::Timer(timer_id) => {
if let Some(handler) = self.timer_handlers.get(&timer_id) {
let mut cx = AdaptEventCx::new(cx, self.id());
if handler(&mut cx, &mut self.state, data) {
cx.update(self.as_node(data));
}
handler(&mut cx, &mut self.state, data);
cx.update(self.as_node(data));
Used
} else {
Unused
Expand All @@ -150,12 +146,12 @@ impl_scope! {
}

fn handle_messages(&mut self, cx: &mut EventCx, data: &A) {
let mut update = false;
let count = cx.msg_op_count();
let mut cx = AdaptEventCx::new(cx, self.id());
for handler in self.message_handlers.iter() {
update |= handler(&mut cx, &mut self.state, data);
handler(&mut cx, &mut self.state, data);
}
if update {
if cx.msg_op_count() != count {
cx.update(self.as_node(data));
}
}
Expand Down
71 changes: 27 additions & 44 deletions examples/gallery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,52 +582,35 @@ fn main() -> kas::app::Result<()> {
})
.build();

let ui = impl_anon! {
#[widget{
layout = column! [
self.menubar,
Separator::new(),
self.stack,
];
}]
struct {
core: widget_core!(),
state: AppData,
#[widget(&self.state)] menubar: menu::MenuBar::<AppData, Right> = menubar,
#[widget(&self.state)] stack: TabStack<Box<dyn Widget<Data = AppData>>> = TabStack::from([
("&Widgets", widgets()), //TODO: use img_gallery as logo
("Te&xt editor", editor()),
("&List", filter_list()),
("Can&vas", canvas()),
("Confi&g", config()),
]).with_msg(|_, title| WindowCommand::SetTitle(format!("Gallery — {}", title))),
let ui = kas::column![
menubar,
Separator::new(),
TabStack::from([
("&Widgets", widgets()), //TODO: use img_gallery as logo
("Te&xt editor", editor()),
("&List", filter_list()),
("Can&vas", canvas()),
("Confi&g", config()),
])
.with_msg(|_, title| WindowCommand::SetTitle(format!("Gallery — {}", title))),
];

let ui = Adapt::new(ui, AppData::default()).on_message(|cx, state, msg| match msg {
Menu::Theme(name) => {
println!("Theme: {name:?}");
cx.adjust_theme(|theme| theme.set_theme(name));
}
impl Events for Self {
type Data = ();

fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
if let Some(msg) = cx.try_pop::<Menu>() {
match msg {
Menu::Theme(name) => {
println!("Theme: {name:?}");
cx.adjust_theme(|theme| theme.set_theme(name));
}
Menu::Colour(name) => {
println!("Colour scheme: {name:?}");
cx.adjust_theme(|theme| theme.set_scheme(&name));
}
Menu::Disabled(state) => {
self.state.disabled = state;
cx.update(self.as_node(&()));
}
Menu::Quit => {
cx.exit();
}
}
}
}
Menu::Colour(name) => {
println!("Colour scheme: {name:?}");
cx.adjust_theme(|theme| theme.set_scheme(&name));
}
};
Menu::Disabled(disabled) => {
state.disabled = disabled;
}
Menu::Quit => {
cx.exit();
}
});

app.add(Window::new(ui, "Gallery — Widgets"));
app.run()
Expand Down
5 changes: 1 addition & 4 deletions examples/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,9 @@ struct AppData {
}

impl kas::app::AppData for AppData {
fn handle_messages(&mut self, messages: &mut kas::messages::MessageStack) -> Action {
fn handle_messages(&mut self, messages: &mut kas::messages::MessageStack) {
if let Some(SetColor(color)) = messages.try_pop() {
self.color = Some(color);
Action::UPDATE
} else {
Action::empty()
}
}
}
Expand Down
89 changes: 34 additions & 55 deletions examples/stopwatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,75 +7,54 @@

use std::time::{Duration, Instant};

use kas::event::{ConfigCx, Event, EventCx, IsUsed, Unused, Used};
use kas::widgets::{format_data, Button};
use kas::{Decorations, Events, Layout, LayoutExt, Widget, Window};
use kas::prelude::*;
use kas::widgets::{format_data, Adapt, Button};
use kas::Decorations;

#[derive(Clone, Debug)]
struct MsgReset;
#[derive(Clone, Debug)]
struct MsgStart;

fn make_window() -> Box<dyn kas::Widget<Data = ()>> {
Box::new(kas::impl_anon! {
#[widget{
layout = row! [
self.display,
Button::label_msg("&reset", MsgReset),
Button::label_msg("&start / &stop", MsgStart),
];
}]
struct {
core: widget_core!(),
#[widget(&self.elapsed)] display: impl Widget<Data = Duration> =
format_data!(dur: &Duration, "{}.{:03}", dur.as_secs(), dur.subsec_millis()),
last: Option<Instant>,
elapsed: Duration,
}
impl Events for Self {
type Data = ();
#[derive(Debug, Default)]
struct Timer {
elapsed: Duration,
last: Option<Instant>,
}

fn configure(&mut self, cx: &mut ConfigCx) {
cx.enable_alt_bypass(self.id_ref(), true);
}
fn handle_event(&mut self, cx: &mut EventCx, data: &(), event: Event) -> IsUsed {
match event {
Event::Timer(0) => {
if let Some(last) = self.last {
let now = Instant::now();
self.elapsed += now - last;
self.last = Some(now);
cx.update(self.as_node(data));
cx.request_timer(self.id(), 0, Duration::new(0, 1));
}
Used
}
_ => Unused,
}
fn make_window() -> impl Widget<Data = ()> {
let ui = kas::row![
format_data!(timer: &Timer, "{}.{:03}", timer.elapsed.as_secs(), timer.elapsed.subsec_millis()),
Button::label_msg("&reset", MsgReset).map_any(),
Button::label_msg("&start / &stop", MsgStart).map_any(),
];

Adapt::new(ui, Timer::default())
.on_configure(|cx, _| cx.enable_alt_bypass(true))
.on_message(|_, timer, MsgReset| *timer = Timer::default())
.on_message(|cx, timer, MsgStart| {
let now = Instant::now();
if let Some(last) = timer.last.take() {
timer.elapsed += now - last;
} else {
timer.last = Some(now);
cx.request_timer(0, Duration::new(0, 0));
}
fn handle_messages(&mut self, cx: &mut EventCx, data: &()) {
if let Some(MsgReset) = cx.try_pop() {
self.elapsed = Duration::default();
self.last = None;
cx.update(self.as_node(data));
} else if let Some(MsgStart) = cx.try_pop() {
let now = Instant::now();
if let Some(last) = self.last.take() {
self.elapsed += now - last;
} else {
self.last = Some(now);
cx.request_timer(self.id(), 0, Duration::new(0, 0));
}
}
})
.on_timer(0, |cx, timer, _| {
if let Some(last) = timer.last {
let now = Instant::now();
timer.elapsed += now - last;
timer.last = Some(now);
cx.request_timer(0, Duration::new(0, 1));
}
}
})
})
}

fn main() -> kas::app::Result<()> {
env_logger::init();

let window = Window::new_boxed(make_window(), "Stopwatch")
let window = Window::new(make_window(), "Stopwatch")
.with_decorations(Decorations::Border)
.with_transparent(true)
.with_restrictions(true, true);
Expand Down
Loading