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 input data from widget layout syntax items #478

Merged
merged 6 commits into from
Feb 17, 2025
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
20 changes: 13 additions & 7 deletions crates/kas-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,27 +208,33 @@ pub fn impl_scope(input: TokenStream) -> TokenStream {
/// implementation of `Tile::nav_next`, with a couple of exceptions
/// (where macro-time analysis is insufficient to implement this method).
///
/// > [_Column_], [_Row_], [_List_] [_AlignedColumn_], [_AlignedRow_], [_Grid_], [_Float_], [_Frame_] :\
/// > [_Column_], [_Row_], [_List_] [_AlignedColumn_], [_AlignedRow_], [_Grid_],
/// > [_Float_], [_Frame_] :\
/// >    These stand-alone macros are explicitly supported in this position.\
///
/// >
/// > _Single_ :\
/// >    `self` `.` _Member_\
/// >    A named child: `self.foo` (more precisely, this matches any expression starting `self`, and uses `&mut (#expr)`)
/// >    A named child: `self.foo` (more precisely, this matches any
/// > expression starting `self`, and uses `&mut (#expr)`).
/// >
/// > _WidgetConstructor_ :\
/// >    _Expr_\
/// >    An expression yielding a widget, e.g. `Label::new("Hello world")`. The result must be an object of some type `W: Widget`.
/// >    An expression yielding a widget, e.g.
/// > `Label::new("Hello world")`. The result must be an object of some type
/// > `W: Widget<Data = ()>`. This widget will be stored in a hidden field and
/// > is accessible through `Tile::get_child` but does not receive input data.
/// >
/// > _LabelLit_ :\
/// > &nbsp;&nbsp; _StrLit_\
/// > &nbsp;&nbsp; A string literal generates a label widget, e.g. "Hello world". This is an internal type without text wrapping.
/// >
/// > &nbsp;&nbsp; A string literal generates a label widget, e.g. "Hello
/// > world". This is an internal type without text wrapping.
///
/// Additional syntax rules (not layout items):
///
/// > _Member_ :\
/// > &nbsp;&nbsp; _Ident_ | _Index_\
/// > &nbsp;&nbsp; The name of a struct field or an index into a tuple struct.
/// >
///
/// ## Examples
///
/// A simple example is the
Expand Down
130 changes: 26 additions & 104 deletions crates/kas-macros/src/make_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use proc_macro_error2::emit_error;
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
use syn::parse::{Parse, ParseStream, Result};
use syn::spanned::Spanned;
use syn::{braced, bracketed, parenthesized, parse_quote, token};
use syn::{Expr, Ident, LitStr, Member, Token, Type};
use syn::{braced, bracketed, parenthesized, token};
use syn::{Expr, Ident, LitStr, Member, Token};

#[allow(non_camel_case_types)]
mod kw {
Expand All @@ -21,24 +21,12 @@ mod kw {
custom_keyword!(pack);
custom_keyword!(column);
custom_keyword!(row);
custom_keyword!(right);
custom_keyword!(left);
custom_keyword!(down);
custom_keyword!(up);
custom_keyword!(center);
custom_keyword!(stretch);
custom_keyword!(frame);
custom_keyword!(list);
custom_keyword!(grid);
custom_keyword!(default);
custom_keyword!(top);
custom_keyword!(bottom);
custom_keyword!(aligned_column);
custom_keyword!(aligned_row);
custom_keyword!(float);
custom_keyword!(px);
custom_keyword!(em);
custom_keyword!(map_any);
custom_keyword!(with_direction);
custom_keyword!(with_style);
custom_keyword!(with_background);
Expand All @@ -48,15 +36,14 @@ mod kw {
pub struct StorageFields {
pub ty_toks: Toks,
pub def_toks: Toks,
pub used_data_ty: bool,
}

#[derive(Debug)]
pub struct Tree(Layout);
impl Tree {
pub fn storage_fields(&self, children: &mut Vec<Child>, data_ty: &Type) -> StorageFields {
pub fn storage_fields(&self, children: &mut Vec<Child>) -> StorageFields {
let mut fields = StorageFields::default();
self.0.append_fields(&mut fields, children, data_ty);
self.0.append_fields(&mut fields, children);
fields
}

Expand Down Expand Up @@ -138,7 +125,6 @@ enum Layout {
Float(LayoutList<()>),
Grid(Ident, GridDimensions, LayoutList<CellInfo>),
Label(Ident, LitStr),
MapAny(Box<Layout>, MapAny),
}

#[derive(Debug)]
Expand All @@ -148,6 +134,7 @@ struct ExprMember {
member: Member,
}

#[allow(unused)]
#[derive(Debug)]
enum Direction {
Left,
Expand Down Expand Up @@ -192,20 +179,17 @@ impl Layout {

loop {
if let Ok(dot_token) = input.parse::<Token![.]>() {
if input.peek(kw::map_any) {
let map_any = MapAny::parse(dot_token, input)?;
layout = Layout::MapAny(Box::new(layout), map_any);
} else if input.peek(kw::align) {
if input.peek(kw::align) {
let align = Align::parse(dot_token, input)?;
layout = Layout::Align(Box::new(layout), align);
} else if input.peek(kw::pack) {
let pack = Pack::parse(dot_token, input, core_gen)?;
layout = Layout::Pack(Box::new(layout), pack);
} else if let Ok(ident) = input.parse::<Ident>() {
let note_msg = if matches!(&layout, &Layout::Frame(_, _, _, _)) {
"supported methods on layout objects: `map_any`, `align`, `pack`, `with_style`, `with_background`"
"supported methods on layout objects: `align`, `pack`, `with_style`, `with_background`"
} else {
"supported methods on layout objects: `map_any`, `align`, `pack`"
"supported methods on layout objects: `align`, `pack`"
};
emit_error!(
ident, "method not supported here";
Expand Down Expand Up @@ -484,24 +468,7 @@ impl ToTokens for ExprMember {

impl Parse for Direction {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::right) {
let _: kw::right = input.parse()?;
Ok(Direction::Right)
} else if lookahead.peek(kw::down) {
let _: kw::down = input.parse()?;
Ok(Direction::Down)
} else if lookahead.peek(kw::left) {
let _: kw::left = input.parse()?;
Ok(Direction::Left)
} else if lookahead.peek(kw::up) {
let _: kw::up = input.parse()?;
Ok(Direction::Up)
} else if lookahead.peek(Token![self]) {
Ok(Direction::Expr(input.parse()?))
} else {
Err(lookahead.error())
}
Ok(Direction::Expr(input.parse()?))
}
}

Expand All @@ -524,24 +491,6 @@ impl ToTokens for Direction {
}
}

#[derive(Debug)]
#[allow(unused)]
struct MapAny {
pub dot_token: Token![.],
pub kw: kw::map_any,
pub paren_token: token::Paren,
}
impl MapAny {
fn parse(dot_token: Token![.], input: ParseStream) -> Result<Self> {
let _content;
Ok(MapAny {
dot_token,
kw: input.parse()?,
paren_token: parenthesized!(_content in input),
})
}
}

#[derive(Debug)]
#[allow(unused)]
struct Align {
Expand Down Expand Up @@ -612,10 +561,10 @@ impl Pack {
}

impl Layout {
fn append_fields(&self, fields: &mut StorageFields, children: &mut Vec<Child>, data_ty: &Type) {
fn append_fields(&self, fields: &mut StorageFields, children: &mut Vec<Child>) {
match self {
Layout::Align(layout, _) => {
layout.append_fields(fields, children, data_ty);
layout.append_fields(fields, children);
}
Layout::Single(_) => (),
Layout::Pack(layout, pack) => {
Expand All @@ -626,18 +575,17 @@ impl Layout {
fields
.def_toks
.append_all(quote! { #stor: Default::default(), });
layout.append_fields(fields, children, data_ty);
layout.append_fields(fields, children);
}
Layout::Widget(ident, expr) => {
children.push(Child::new_core(ident.clone().into()));
fields
.ty_toks
.append_all(quote! { #ident: Box<dyn ::kas::Widget<Data = #data_ty>>, });
.append_all(quote! { #ident: Box<dyn ::kas::Widget<Data = ()>>, });
let span = expr.span();
fields
.def_toks
.append_all(quote_spanned! {span=> #ident: Box::new(#expr), });
fields.used_data_ty = true;
}
Layout::Frame(stor, layout, _, _) => {
fields
Expand All @@ -646,7 +594,7 @@ impl Layout {
fields
.def_toks
.append_all(quote! { #stor: Default::default(), });
layout.append_fields(fields, children, data_ty);
layout.append_fields(fields, children);
}
Layout::List(stor, _, LayoutList(list)) => {
fields
Expand All @@ -660,12 +608,12 @@ impl Layout {
quote! { #stor: ::kas::layout::FixedRowStorage<#len>, }
});
for item in list {
item.layout.append_fields(fields, children, data_ty);
item.layout.append_fields(fields, children);
}
}
Layout::Float(LayoutList(list)) => {
for item in list {
item.layout.append_fields(fields, children, data_ty);
item.layout.append_fields(fields, children);
}
}
Layout::Grid(stor, dim, LayoutList(list)) => {
Expand All @@ -678,42 +626,18 @@ impl Layout {
.append_all(quote! { #stor: Default::default(), });

for item in list {
item.layout.append_fields(fields, children, data_ty);
item.layout.append_fields(fields, children);
}
}
Layout::Label(ident, text) => {
children.push(Child::new_core(ident.clone().into()));
let span = text.span();
if *data_ty == syn::parse_quote! { () } {
fields
.ty_toks
.append_all(quote! { #ident: ::kas::hidden::StrLabel, });
fields.def_toks.append_all(
quote_spanned! {span=> #ident: ::kas::hidden::StrLabel::new(#text), },
);
} else {
fields.ty_toks.append_all(
quote! { #ident: ::kas::hidden::MapAny<#data_ty, ::kas::hidden::StrLabel>, },
);
fields.def_toks.append_all(
quote_spanned! {span=> #ident: ::kas::hidden::MapAny::new(::kas::hidden::StrLabel::new(#text)), },
);
fields.used_data_ty = true;
}
}
Layout::MapAny(layout, map_any) => {
let start = children.len();
layout.append_fields(fields, children, &parse_quote! { () });
let map_expr: Expr = parse_quote! { &() };
for child in &mut children[start..] {
if let Some(ref expr) = child.data_binding {
if *expr != map_expr {
emit_error!(map_any.kw, "invalid data type mapping")
}
} else {
child.data_binding = Some(map_expr.clone());
}
}
fields
.ty_toks
.append_all(quote! { #ident: ::kas::hidden::StrLabel, });
fields.def_toks.append_all(
quote_spanned! {span=> #ident: ::kas::hidden::StrLabel::new(#text), },
);
}
}
}
Expand Down Expand Up @@ -764,7 +688,6 @@ impl Layout {
Layout::Label(stor, _) => {
quote! { layout::Visitor::single(&mut #core_path.#stor) }
}
Layout::MapAny(layout, _) => return layout.generate(core_path),
})
}

Expand All @@ -777,10 +700,9 @@ impl Layout {
output: &mut Vec<usize>,
) -> std::result::Result<(), (Span, &'static str)> {
match self {
Layout::Align(layout, _)
| Layout::Pack(layout, _)
| Layout::Frame(_, layout, _, _)
| Layout::MapAny(layout, _) => layout.nav_next(children, output),
Layout::Align(layout, _) | Layout::Pack(layout, _) | Layout::Frame(_, layout, _, _) => {
layout.nav_next(children, output)
}
Layout::Single(m) => {
for (i, child) in children.enumerate() {
if let ChildIdent::Field(ref ident) = child.ident {
Expand Down
7 changes: 1 addition & 6 deletions crates/kas-macros/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul

let mut stor_defs = Default::default();
if let Some(Layout { ref tree, .. }) = args.layout {
stor_defs = tree.storage_fields(&mut children, &data_ty);
stor_defs = tree.storage_fields(&mut children);
}
if !stor_defs.ty_toks.is_empty() {
let name = format!("_{name}CoreTy");
Expand Down Expand Up @@ -796,11 +796,6 @@ pub fn impl_widget(
ChildIdent::Field(ident) => quote! { self.#ident },
ChildIdent::CoreField(ident) => quote! { #core_path.#ident },
};
// TODO: incorrect or unconstrained data type of child causes a poor error
// message here. Add a constaint like this (assuming no mapping fn):
// <#ty as WidgetNode::Data> == Self::Data
// But this is unsupported: rust#20041
// predicates.push(..);

get_mut_rules.append_all(if let Some(ref data) = child.data_binding {
quote! { #i => closure(#path.as_node(#data)), }
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-macros/src/widget_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ impl Child {
Child {
ident: ChildIdent::CoreField(ident),
attr_span: None,
data_binding: None,
data_binding: Some(syn::parse_quote! { &() }),
}
}
}
5 changes: 1 addition & 4 deletions crates/kas-widgets/src/combobox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl_scope! {
/// when selected. If a handler is specified via [`Self::with`] or
/// [`Self::with_msg`] then this message is passed to the handler and not emitted.
#[widget {
layout = frame!(row! [self.label, self.mark])
layout = frame!(row! [self.label, Mark::new(MarkStyle::Point(Direction::Down))])
.with_style(FrameStyle::Button)
.align(AlignHints::CENTER);
navigable = true;
Expand All @@ -40,8 +40,6 @@ impl_scope! {
#[widget(&())]
label: Label<String>,
#[widget(&())]
mark: Mark,
#[widget(&())]
popup: Popup<AdaptEvents<Column<Vec<MenuEntry<V>>>>>,
active: usize,
opening: bool,
Expand Down Expand Up @@ -233,7 +231,6 @@ impl<A, V: Clone + Debug + Eq + 'static> ComboBox<A, V> {
ComboBox {
core: Default::default(),
label,
mark: Mark::new(MarkStyle::Point(Direction::Down)),
popup: Popup::new(
AdaptEvents::new(Column::new(entries)).on_messages(|cx, _, _| {
if let Some(_) = cx.try_peek::<V>() {
Expand Down
5 changes: 3 additions & 2 deletions examples/gallery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,13 @@ fn widgets() -> Box<dyn Widget<Data = AppData>> {
let popup_edit_box = impl_anon! {
#[widget{
layout = row! [
format_data!(data: &Data, "{}", &data.text),
Button::label_msg("&Edit", MsgEdit).map_any(),
self.text,
Button::label_msg("&Edit", MsgEdit),
];
}]
struct {
core: widget_core!(),
#[widget] text: Text<Data, String> = format_data!(data: &Data, "{}", &data.text),
#[widget(&())] popup: Popup<TextEdit> = Popup::new(TextEdit::new("", true), Direction::Down),
}
impl Events for Self {
Expand Down