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

OnMessage trait #257

Closed
wants to merge 5 commits into from
Closed
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
2 changes: 1 addition & 1 deletion crates/kas-core/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub use enums::{CursorIcon, ModifiersState, MouseButton, VirtualKeyCode};
pub use events::*;
pub use handler::{Handler, SendEvent};
pub use manager::{ConfigureManager, GrabMode, Manager, ManagerState};
pub use response::Response;
pub use response::{OnMessage, Response};
pub use update::UpdateHandle;

/// A type supporting a small number of key bindings
Expand Down
43 changes: 42 additions & 1 deletion crates/kas-core/src/event/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

//! Event handling: Response type

use super::VoidResponse;
use super::{Manager, VoidResponse};
use crate::geom::{Offset, Rect};
use crate::Widget;

/// Response type from [`Handler::handle`].
///
Expand Down Expand Up @@ -149,3 +150,43 @@ impl<M> From<M> for Response<M> {
Response::Msg(msg)
}
}

/// Configurable message converter / handler
///
/// Parent widgets are expected to implement this to handle or convert messages
/// from child widgets, excepting where the parent and child message types are
/// equal (which is implemented as pass-through).
pub trait OnMessage<M>: Widget {
/// Called on a widget:
///
/// - `mgr`: the event manager
/// - `index`: index of child widget
/// - `msg`: message from child
fn on_msg(&mut self, mgr: &mut Manager, index: usize, msg: M) -> Response<Self::Msg>;
}

// TODO: This impl is required, yet falsely reported to cause conflicts
// Bug report: https://github.com/rust-lang/rust/issues/90587
impl<W: Widget, M: Into<W::Msg>> OnMessage<M> for W {
#[inline]
fn on_msg(&mut self, _mgr: &mut Manager, _index: usize, msg: M) -> Response<W::Msg> {
Response::Msg(msg.into())
}
}

// Below alternatives are not viable: some widgets like CheckBox are generic
// over M and required implementations hit the same conflict.

// impl<W: Widget> OnMessage<VoidMsg> for W {
// #[inline]
// fn on_msg(&mut self, _: &mut Manager, _: usize, _: VoidMsg) -> Response<W::Msg> {
// Response::None
// }
// }

// impl<W: Widget> OnMessage<W::Msg> for W {
// #[inline]
// fn on_msg(&mut self, _: &mut Manager, _: usize, msg: W::Msg) -> Response<W::Msg> {
// Response::Msg(msg)
// }
// }
2 changes: 1 addition & 1 deletion crates/kas-core/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub use crate::draw::{
};
#[doc(no_inline)]
pub use crate::event::{
Event, Handler, Manager, ManagerState, Response, SendEvent, UpdateHandle, VoidMsg,
Event, Handler, Manager, ManagerState, OnMessage, Response, SendEvent, UpdateHandle, VoidMsg,
};
#[doc(no_inline)]
pub use crate::geom::{Coord, Offset, Rect, Size};
Expand Down
63 changes: 1 addition & 62 deletions crates/kas-macros/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ mod kw {
custom_keyword!(map_msg);
custom_keyword!(use_msg);
custom_keyword!(discard_msg);
custom_keyword!(update);
custom_keyword!(msg);
custom_keyword!(generics);
custom_keyword!(single);
Expand Down Expand Up @@ -312,26 +311,6 @@ impl Parse for WidgetDerive {
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Handler {
None,
Use(Ident),
Map(Ident),
FlatMap(Ident),
Discard,
}
impl Handler {
pub fn is_none(&self) -> bool {
*self == Handler::None
}
pub fn any_ref(&self) -> Option<&Ident> {
match self {
Handler::None | Handler::Discard => None,
Handler::Use(n) | Handler::Map(n) | Handler::FlatMap(n) => Some(n),
}
}
}

#[derive(Debug)]
pub struct WidgetAttrArgs {
pub col: Option<Lit>,
Expand All @@ -340,8 +319,6 @@ pub struct WidgetAttrArgs {
pub rspan: Option<Lit>,
pub halign: Option<Ident>,
pub valign: Option<Ident>,
pub update: Option<Ident>,
pub handler: Handler,
}

#[derive(Debug)]
Expand Down Expand Up @@ -409,8 +386,6 @@ impl Parse for WidgetAttrArgs {
rspan: None,
halign: None,
valign: None,
update: None,
handler: Handler::None,
};
if input.is_empty() {
return Ok(args);
Expand Down Expand Up @@ -459,30 +434,11 @@ impl Parse for WidgetAttrArgs {
let _: kw::valign = content.parse()?;
let _: Eq = content.parse()?;
args.valign = Some(content.parse()?);
} else if args.update.is_none() && lookahead.peek(kw::update) {
let _: kw::update = content.parse()?;
let _: Eq = content.parse()?;
args.update = Some(content.parse()?);
} else if args.handler.is_none() && lookahead.peek(kw::flatmap_msg) {
let _: kw::flatmap_msg = content.parse()?;
let _: Eq = content.parse()?;
args.handler = Handler::FlatMap(content.parse()?);
} else if args.handler.is_none() && lookahead.peek(kw::map_msg) {
let _: kw::map_msg = content.parse()?;
let _: Eq = content.parse()?;
args.handler = Handler::Map(content.parse()?);
} else if args.handler.is_none() && lookahead.peek(kw::use_msg) {
let _: kw::use_msg = content.parse()?;
let _: Eq = content.parse()?;
args.handler = Handler::Use(content.parse()?);
} else if args.handler.is_none() && lookahead.peek(kw::discard_msg) {
let _: kw::discard_msg = content.parse()?;
args.handler = Handler::Discard;
} else if lookahead.peek(kw::handler) {
let tok: Ident = content.parse()?;
return Err(Error::new(
tok.span(),
"handler is obsolete; replace with flatmap_msg, map_msg, use_msg or discard_msg",
"handler is obsolete; replace with OnMessage",
));
} else {
return Err(lookahead.error());
Expand All @@ -506,7 +462,6 @@ impl ToTokens for WidgetAttrArgs {
|| self.rspan.is_some()
|| self.halign.is_some()
|| self.valign.is_some()
|| !self.handler.is_none()
{
let comma = TokenTree::from(Punct::new(',', Spacing::Alone));
let mut args = TokenStream::new();
Expand Down Expand Up @@ -543,22 +498,6 @@ impl ToTokens for WidgetAttrArgs {
}
args.append_all(quote! { valign = #ident });
}
if let Some(ref ident) = self.update {
if !args.is_empty() {
args.append(comma.clone());
}
args.append_all(quote! { update = #ident });
}
if !self.handler.is_none() && !args.is_empty() {
args.append(comma);
}
match &self.handler {
Handler::None => (),
Handler::Use(f) => args.append_all(quote! { use_msg = #f }),
Handler::Map(f) => args.append_all(quote! { map_msg = #f }),
Handler::FlatMap(f) => args.append_all(quote! { flatmap_msg = #f }),
Handler::Discard => args.append_all(quote! { discard_msg }),
}
tokens.append_all(quote! { ( #args ) });
}
}
Expand Down
129 changes: 17 additions & 112 deletions crates/kas-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@

extern crate proc_macro;

use self::args::{ChildType, Handler, HandlerArgs};
use self::args::{ChildType, HandlerArgs};
use proc_macro2::{Span, TokenStream};
use proc_macro_error::abort;
use quote::{quote, ToTokens, TokenStreamExt};
use std::collections::HashMap;
use std::fmt::Write;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::Token;
use syn::{parse_macro_input, parse_quote};
use syn::{GenericParam, Ident, Type, TypeParam, TypePath, WhereClause, WherePredicate};
Expand Down Expand Up @@ -380,70 +379,37 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
quote! { self.#inner.send(mgr, id, event) }
} else {
let mut ev_to_num = TokenStream::new();
for child in args.children.iter() {
for (index, child) in args.children.iter().enumerate() {
#[cfg(feature = "log")]
let log_msg = quote! {
log::trace!(
"Received by {} from {}: {:?}",
"Received by {} from #{}: {:?}",
self.id(),
id,
#index,
::kas::util::TryFormat(&msg)
);
};
#[cfg(not(feature = "log"))]
let log_msg = quote! {};

let ident = &child.ident;
let update = if let Some(f) = child.args.update.as_ref() {
quote! {
if matches!(r, Response::Update) {
self.#f(mgr);
}
}
} else {
quote! {}
};
let handler = match &child.args.handler {
Handler::Use(f) => quote! {
r.try_into().unwrap_or_else(|msg| {
#log_msg
let _: () = self.#f(mgr, msg);
Response::None
})
},
Handler::Map(f) => quote! {
r.try_into().unwrap_or_else(|msg| {
#log_msg
Response::Msg(self.#f(mgr, msg))
})
},
Handler::FlatMap(f) => quote! {
r.try_into().unwrap_or_else(|msg| {
#log_msg
self.#f(mgr, msg)
})
},
Handler::Discard => quote! {
r.try_into().unwrap_or_else(|msg| {
#log_msg
let _ = msg;
Response::None
})
},
Handler::None => quote! { r.into() },
};

ev_to_num.append_all(quote! {
if id <= self.#ident.id() {
let r = self.#ident.send(mgr, id, event);
#update
#handler
match self.#ident.send(mgr, id, event).try_into() {
Ok(r) => r,
Err(msg) => {
#log_msg
self.on_msg(mgr, #index, msg)
}
}
} else
});
}

quote! {
use ::kas::{WidgetCore, event::Response};
use ::kas::WidgetCore;
use ::kas::event::{OnMessage, Response};
if self.is_disabled() {
return Response::Unhandled;
}
Expand Down Expand Up @@ -589,58 +555,6 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// See documentation [in the `kas::macros` module](https://docs.rs/kas/latest/kas/macros#the-make_widget-macro).
#[proc_macro]
pub fn make_widget(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut find_handler_ty_buf: Vec<(Ident, Type)> = vec![];
// find type of handler's message; return None on error
let mut find_handler_ty = |handler: &Ident,
impls: &Vec<(Option<TypePath>, Vec<syn::ImplItem>)>|
-> Option<Type> {
// check the buffer in case we did this already
for (ident, ty) in &find_handler_ty_buf {
if ident == handler {
return Some(ty.clone());
}
}

let mut x: Option<(Ident, Type)> = None;

for impl_block in impls {
for f in &impl_block.1 {
match f {
syn::ImplItem::Method(syn::ImplItemMethod { sig, .. })
if sig.ident == *handler =>
{
if let Some(_x) = x {
abort!(
handler.span(), "multiple methods with this name";
help = _x.0.span() => "first method with this name";
help = sig.ident.span() => "second method with this name";
);
}
if sig.inputs.len() != 3 {
abort!(
sig.span(),
"handler functions must have signature: fn handler(&mut self, mgr: &mut Manager, msg: T)"
);
}
let arg = sig.inputs.last().unwrap();
let ty = match arg {
syn::FnArg::Typed(arg) => (*arg.ty).clone(),
_ => panic!("expected typed argument"), // nothing else is possible here?
};
x = Some((sig.ident.clone(), ty));
}
_ => (),
}
}
}
if let Some(x) = x {
find_handler_ty_buf.push((handler.clone(), x.1.clone()));
Some(x.1)
} else {
abort!(handler.span(), "no methods with this name found");
}
};

let mut args = parse_macro_input!(input as args::MakeWidget);

// Used to make fresh identifiers for generic types
Expand Down Expand Up @@ -714,6 +628,7 @@ pub fn make_widget(input: proc_macro::TokenStream) -> proc_macro::TokenStream {

for (index, field) in args.fields.drain(..).enumerate() {
let attr = field.widget_attr;
let is_widget = attr.is_some();

let ident = match &field.ident {
Some(ref ident) => ident.clone(),
Expand All @@ -737,29 +652,19 @@ pub fn make_widget(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
name_buf.write_fmt(format_args!("MWAnon{}", index)).unwrap();
let ty = Ident::new(&name_buf, Span::call_site());

if let Some(ref wattr) = attr {
if is_widget {
if let Some(tyr) = gen_msg {
handler_clauses.push(parse_quote! { #ty: ::kas::Widget<Msg = #tyr> });
} else if let Some(handler) = wattr.args.handler.any_ref() {
// Message passed to a method; exact type required
if let Some(ty_bound) = find_handler_ty(handler, &args.impls) {
handler_clauses
.push(parse_quote! { #ty: ::kas::Widget<Msg = #ty_bound> });
} else {
return quote! {}.into(); // exit after emitting error
}
} else if wattr.args.handler == Handler::Discard {
// No type bound on discarded message
} else {
// Message converted via Into
// Message converted via OnMessage
name_buf.push('R');
let tyr = Ident::new(&name_buf, Span::call_site());
handler
.generics
.params
.push(syn::GenericParam::Type(tyr.clone().into()));
handler_clauses.push(parse_quote! { #ty: ::kas::Widget<Msg = #tyr> });
handler_clauses.push(parse_quote! { #msg: From<#tyr> });
handler_clauses.push(parse_quote! { Self: OnMessage<#tyr, #msg> });
}

if let Some(mut bound) = gen_bound {
Expand Down
1 change: 1 addition & 0 deletions crates/kas-widgets/src/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use kas::prelude::*;
#[handler(noauto)]
#[widget(config=noauto)]
#[widget_derive(class_traits)]
// TODO: less restriction on label
pub struct Button<L: Widget<Msg = VoidMsg>, M: 'static> {
#[widget_core]
core: kas::CoreData,
Expand Down
Loading