diff --git a/Cargo.toml b/Cargo.toml
index 30bd82efd..6d53672a0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,7 +35,7 @@ stable = ["default", "x11", "serde", "toml", "yaml", "json", "ron", "macros_log"
# Enables all "recommended" features for nightly rustc
nightly = ["stable", "nightly-diagnostics", "kas-core/nightly"]
# Additional, less recommendation-worthy features
-experimental = ["dark-light", "recursive-layout-widgets", "unsafe_node"]
+experimental = ["dark-light", "unsafe_node"]
# Enable dynamic linking (faster linking via an extra run-time dependency):
dynamic = ["dep:kas-dylib"]
@@ -114,21 +114,6 @@ wayland = ["kas-core/wayland"]
# Support X11
x11 = ["kas-core/x11"]
-# Optimize generated layout widgets
-#
-# Recursive layout macros allow the generation of complex layout widgets; for
-# example `row!["a", column!["b", "c"]]` yields a single layout widget (over
-# three `StrLabel` widgets) instead of two separate layout widgets.
-# (Note that this happens anyway in custom widget layout syntax where it is
-# requried to support reference to widget fields.)
-#
-# A limited number of method calls such as `.align(AlignHints::LEFT)` and
-# `.map_any()` are supported but are not linked correctly via rust-analyzer.
-#
-# Often results in unused import warnings, hence you may want to use
-# RUSTFLAGS="-A unused_imports".
-recursive-layout-widgets = ["kas-core/recursive-layout-widgets"]
-
# Optimize Node using unsafe code
unsafe_node = ["kas-core/unsafe_node"]
diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml
index dadbcb2cb..baf0f4ca2 100644
--- a/crates/kas-core/Cargo.toml
+++ b/crates/kas-core/Cargo.toml
@@ -25,7 +25,7 @@ stable = ["minimal", "clipboard", "markdown", "shaping", "spawn", "x11", "serde"
# Enables all "recommended" features for nightly rustc
nightly = ["stable", "nightly-diagnostics"]
# Additional, less recommendation-worthy features
-experimental = ["dark-light", "recursive-layout-widgets", "unsafe_node"]
+experimental = ["dark-light", "unsafe_node"]
# Enables better proc-macro diagnostics (including warnings); nightly only.
nightly-diagnostics = ["kas-macros/nightly"]
@@ -85,9 +85,6 @@ dark-light = ["dep:dark-light"]
# Support spawning async tasks
spawn = ["dep:async-global-executor"]
-# Optimize generated layout widgets
-recursive-layout-widgets = ["kas-macros/recursive-layout-widgets"]
-
# Optimize Node using unsafe code
unsafe_node = []
diff --git a/crates/kas-core/src/core/layout.rs b/crates/kas-core/src/core/layout.rs
index 349370c6b..91991edd8 100644
--- a/crates/kas-core/src/core/layout.rs
+++ b/crates/kas-core/src/core/layout.rs
@@ -264,10 +264,9 @@ pub trait Tile: Layout {
/// Get the widget's region, relative to its parent.
///
- /// This method is implemented by the `#[widget]` macro.
- fn rect(&self) -> Rect {
- unimplemented!() // make rustdoc show that this is a provided method
- }
+ /// This method is usually implemented by the `#[widget]` macro.
+ /// See also [`kas::widget_set_rect`].
+ fn rect(&self) -> Rect;
/// Get the name of the widget struct
///
@@ -291,6 +290,8 @@ pub trait Tile: Layout {
/// Access a child as a `dyn Tile`
///
+ /// This method returns `None` exactly when `index >= self.num_children()`.
+ ///
/// This method is usually implemented automatically by the `#[widget]`
/// macro.
fn get_child(&self, index: usize) -> Option<&dyn Tile> {
@@ -316,16 +317,21 @@ pub trait Tile: Layout {
/// Controls Tab navigation order of children.
/// This method should:
///
- /// - Return `None` if there is no next child
- /// - Determine the next child after `from` (if provided) or the whole
- /// range, optionally in `reverse` order
- /// - Ensure that the selected widget is addressable through
- /// [`Tile::get_child`]
+ /// - Return `None` if there is no (next) navigable child
+ /// - In the case there are navigable children and `from == None`, return
+ /// the index of the first (or last if `reverse`) navigable child
+ /// - In the case there are navigable children and `from == Some(index)`,
+ /// it may be expected that `from` is the output of a previous call to
+ /// this method; the method should return the next (or previous if
+ /// `reverse`) navigable child (if any)
+ ///
+ /// The return value mut be `None` or `Some(index)` where
+ /// `self.get_child(index).is_some()` (see [`Tile::get_child`]).
///
- /// Both `from` and the return value use the widget index, as used by
- /// [`Tile::get_child`].
+ /// It is not required that all children (all indices `i` for
+ /// `i < self.num_children()`) are returnable from this method.
///
- /// Default implementation:
+ /// Default (macro generated) implementation:
///
/// - Generated from `#[widget]`'s layout property, if used (not always possible!)
/// - Otherwise, iterate through children in order of definition
diff --git a/crates/kas-core/src/draw/color.rs b/crates/kas-core/src/draw/color.rs
index 22feeb40f..c1026a1a3 100644
--- a/crates/kas-core/src/draw/color.rs
+++ b/crates/kas-core/src/draw/color.rs
@@ -329,7 +329,7 @@ impl From<[u8; 4]> for Rgba8Srgb {
#[derive(Copy, Clone, Debug, Error)]
pub enum ParseError {
/// Incorrect input length
- #[error("invalid length (expected 6 or 8 bytes")]
+ #[error("invalid length (expected 6 or 8 bytes)")]
Length,
/// Invalid hex byte
#[error("invalid hex byte (expected 0-9, a-f or A-F)")]
diff --git a/crates/kas-core/src/hidden.rs b/crates/kas-core/src/hidden.rs
index 6def0ea81..1b839c4c5 100644
--- a/crates/kas-core/src/hidden.rs
+++ b/crates/kas-core/src/hidden.rs
@@ -10,9 +10,10 @@
//! not supported (i.e. **changes are not considered breaking**).
use crate::event::ConfigCx;
-use crate::geom::Rect;
-use crate::layout::AlignHints;
-use crate::theme::{Text, TextClass};
+use crate::geom::{Rect, Size};
+use crate::layout::{AlignHints, AxisInfo, SizeRules};
+use crate::theme::{SizeCx, Text, TextClass};
+#[allow(unused)] use crate::Action;
use crate::{Events, Layout, Widget};
use kas_macros::{autoimpl, impl_scope, widget_set_rect};
@@ -94,3 +95,76 @@ impl_scope! {
}
}
}
+
+impl_scope! {
+ /// Apply an alignment hint
+ ///
+ /// The inner widget chooses how to apply (or ignore) this hint.
+ ///
+ /// Usually, this type will be constructed through one of the methods on
+ /// [`AdaptWidget`](https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidget.html).
+ #[widget{ derive = self.inner; }]
+ pub struct Align {
+ pub inner: W,
+ /// Hints may be modified directly.
+ ///
+ /// Use [`Action::RESIZE`] to apply changes.
+ pub hints: AlignHints,
+ }
+
+ impl Self {
+ /// Construct
+ #[inline]
+ pub fn new(inner: W, hints: AlignHints) -> Self {
+ Align { inner, hints }
+ }
+ }
+
+ impl Layout for Self {
+ fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
+ self.inner.set_rect(cx, rect, self.hints.combine(hints));
+ }
+ }
+}
+
+impl_scope! {
+ /// Apply an alignment hint, squash and align the result
+ ///
+ /// The inner widget chooses how to apply (or ignore) this hint.
+ /// The widget is then prevented from stretching beyond its ideal size,
+ /// aligning within the available rect.
+ ///
+ /// Usually, this type will be constructed through one of the methods on
+ /// [`AdaptWidget`](https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidget.html).
+ #[widget{ derive = self.inner; }]
+ pub struct Pack {
+ pub inner: W,
+ /// Hints may be modified directly.
+ ///
+ /// Use [`Action::RESIZE`] to apply changes.
+ pub hints: AlignHints,
+ size: Size,
+ }
+
+ impl Self {
+ /// Construct
+ #[inline]
+ pub fn new(inner: W, hints: AlignHints) -> Self {
+ Pack { inner, hints, size: Size::ZERO }
+ }
+ }
+
+ impl Layout for Self {
+ fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
+ let rules = self.inner.size_rules(sizer, axis);
+ self.size.set_component(axis, rules.ideal_size());
+ rules
+ }
+
+ fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
+ let align = self.hints.combine(hints).complete_default();
+ let rect = align.aligned_rect(self.size, rect);
+ self.inner.set_rect(cx, rect, hints);
+ }
+ }
+}
diff --git a/crates/kas-core/src/layout/align.rs b/crates/kas-core/src/layout/align.rs
index de46dd6d0..3dc4aba7e 100644
--- a/crates/kas-core/src/layout/align.rs
+++ b/crates/kas-core/src/layout/align.rs
@@ -51,9 +51,9 @@ impl AlignHints {
/// Top, left
pub const TOP_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::TL));
/// Top, right
- pub const TOP_RIGHT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::BR));
+ pub const TOP_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::TL));
/// Bottom, left
- pub const BOTTOM_LEFT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::TL));
+ pub const BOTTOM_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::BR));
/// Bottom, right
pub const BOTTOM_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::BR));
diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs
index 59c266000..8947a8700 100644
--- a/crates/kas-core/src/layout/visitor.rs
+++ b/crates/kas-core/src/layout/visitor.rs
@@ -78,11 +78,13 @@ impl<'a> Visitor> {
storage: &'a mut FrameStorage,
child: C,
style: FrameStyle,
+ bg: Background,
) -> Visitor {
Visitor(Frame {
child,
storage,
style,
+ bg,
})
}
@@ -340,6 +342,7 @@ struct Frame<'a, C: Layout> {
child: C,
storage: &'a mut FrameStorage,
style: FrameStyle,
+ bg: Background,
}
impl<'a, C: Layout> Layout for Frame<'a, C> {
@@ -365,7 +368,7 @@ impl<'a, C: Layout> Layout for Frame<'a, C> {
}
fn draw(&mut self, mut draw: DrawCx) {
- draw.frame(self.storage.rect, self.style, Background::Default);
+ draw.frame(self.storage.rect, self.style, self.bg);
self.child.draw(draw);
}
}
diff --git a/crates/kas-core/src/popup.rs b/crates/kas-core/src/popup.rs
index 71c579eac..c9089592b 100644
--- a/crates/kas-core/src/popup.rs
+++ b/crates/kas-core/src/popup.rs
@@ -35,7 +35,7 @@ impl_scope! {
///
/// A popup receives input data from its parent like any other widget.
#[widget {
- layout = frame!(self.inner, style = kas::theme::FrameStyle::Popup);
+ layout = frame!(self.inner).with_style(kas::theme::FrameStyle::Popup);
}]
pub struct Popup {
core: widget_core!(),
diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs
index 3ecb509ac..fcd670063 100644
--- a/crates/kas-core/src/theme/draw.rs
+++ b/crates/kas-core/src/theme/draw.rs
@@ -7,7 +7,7 @@
use super::{FrameStyle, MarkStyle, SelectionStyle, SizeCx, Text, TextClass, ThemeSize};
use crate::dir::Direction;
-use crate::draw::color::Rgb;
+use crate::draw::color::{ParseError, Rgb};
use crate::draw::{Draw, DrawIface, DrawShared, DrawSharedImpl, ImageId, PassType};
use crate::event::{ConfigCx, EventState};
use crate::geom::{Offset, Rect};
@@ -29,6 +29,39 @@ pub enum Background {
Rgb(Rgb),
}
+impl From for Background {
+ #[inline]
+ fn from(color: Rgb) -> Self {
+ Background::Rgb(color)
+ }
+}
+
+#[derive(Copy, Clone, Debug, thiserror::Error)]
+pub enum BackgroundParseError {
+ /// No `#` prefix
+ ///
+ /// NOTE: this exists to allow the possibility of supporting new exprs like
+ /// "Default" or "Error".
+ #[error("Unknown: no `#` prefix")]
+ Unknown,
+ /// Invalid hex
+ #[error("invalid hex sequence")]
+ InvalidRgb(#[from] ParseError),
+}
+
+impl std::str::FromStr for Background {
+ type Err = BackgroundParseError;
+
+ #[inline]
+ fn from_str(s: &str) -> Result {
+ if s.starts_with("#") {
+ Rgb::from_str(s).map(|c| c.into()).map_err(|e| e.into())
+ } else {
+ Err(BackgroundParseError::Unknown)
+ }
+ }
+}
+
/// Draw interface
///
/// This interface is provided to widgets in [`crate::Layout::draw`].
diff --git a/crates/kas-core/src/theme/text.rs b/crates/kas-core/src/theme/text.rs
index 88110d91a..1495cbb61 100644
--- a/crates/kas-core/src/theme/text.rs
+++ b/crates/kas-core/src/theme/text.rs
@@ -84,7 +84,7 @@ impl Layout for Text {
}
fn draw(&mut self, mut draw: DrawCx) {
- draw.text(self.rect, &self);
+ draw.text(self.rect, self);
}
}
diff --git a/crates/kas-macros/Cargo.toml b/crates/kas-macros/Cargo.toml
index 3a2a6e79e..e1c21df81 100644
--- a/crates/kas-macros/Cargo.toml
+++ b/crates/kas-macros/Cargo.toml
@@ -19,9 +19,6 @@ proc-macro = true
# Requires that all crates using these macros depend on the log crate.
log = []
-# Optimize generated layout widgets
-recursive-layout-widgets = []
-
# Enable reporting of warnings from proc-macros
nightly = ["proc-macro-error2/nightly"]
diff --git a/crates/kas-macros/src/collection.rs b/crates/kas-macros/src/collection.rs
index 92d5e3580..f7571513c 100644
--- a/crates/kas-macros/src/collection.rs
+++ b/crates/kas-macros/src/collection.rs
@@ -11,31 +11,17 @@ use syn::parse::{Error, Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
-use syn::{braced, parenthesized};
-use syn::{Expr, Ident, Lifetime, LitInt, LitStr, Token};
-
-#[derive(Debug)]
-pub enum StorIdent {
- Named(Ident),
- Generated(Ident),
-}
-impl From for StorIdent {
- fn from(mut lt: Lifetime) -> StorIdent {
- lt.ident.set_span(lt.span());
- StorIdent::Named(lt.ident)
- }
-}
-impl From for StorIdent {
- fn from(ident: Ident) -> StorIdent {
- StorIdent::Generated(ident)
- }
-}
-impl ToTokens for StorIdent {
- fn to_tokens(&self, toks: &mut Toks) {
- match self {
- StorIdent::Named(ident) | StorIdent::Generated(ident) => ident.to_tokens(toks),
- }
- }
+use syn::{braced, bracketed, parenthesized};
+use syn::{Expr, Ident, LitInt, LitStr, Token};
+
+#[allow(non_camel_case_types)]
+mod kw {
+ syn::custom_keyword!(align);
+ syn::custom_keyword!(pack);
+ syn::custom_keyword!(aligned_column);
+ syn::custom_keyword!(aligned_row);
+ syn::custom_keyword!(column);
+ syn::custom_keyword!(row);
}
#[derive(Default)]
@@ -47,14 +33,6 @@ impl NameGenerator {
let span = Span::call_site();
Ident::new(&name, span)
}
-
- pub fn parse_or_next(&mut self, input: ParseStream) -> Result {
- if input.peek(Lifetime) {
- Ok(input.parse::()?.into())
- } else {
- Ok(self.next().into())
- }
- }
}
#[derive(Copy, Clone, Debug)]
@@ -174,14 +152,41 @@ impl ToTokens for GridDimensions {
}
pub enum Item {
- Label(Ident, LitStr),
+ Label(Ident, Toks, Toks),
Widget(Ident, Expr),
}
impl Item {
fn parse(input: ParseStream, gen: &mut NameGenerator) -> Result {
if input.peek(LitStr) {
- Ok(Item::Label(gen.next(), input.parse()?))
+ let text: LitStr = input.parse()?;
+ let span = text.span();
+ let mut ty = quote! { ::kas::hidden::StrLabel };
+ let mut def = quote_spanned! {span=> ::kas::hidden::StrLabel::new(#text) };
+
+ if input.peek(Token![.]) && input.peek2(kw::align) {
+ let _: Token![.] = input.parse()?;
+ let _: kw::align = input.parse()?;
+
+ let inner;
+ let _ = parenthesized!(inner in input);
+ let hints: Expr = inner.parse()?;
+
+ ty = quote! { ::kas::hidden::Align<#ty> };
+ def = quote! { ::kas::hidden::Align::new(#def, #hints) };
+ } else if input.peek(Token![.]) && input.peek2(kw::pack) {
+ let _: Token![.] = input.parse()?;
+ let _: kw::pack = input.parse()?;
+
+ let inner;
+ let _ = parenthesized!(inner in input);
+ let hints: Expr = inner.parse()?;
+
+ ty = quote! { ::kas::hidden::Pack<#ty> };
+ def = quote! { ::kas::hidden::Pack::new(#def, #hints) };
+ }
+
+ Ok(Item::Label(gen.next(), ty, def))
} else {
Ok(Item::Widget(gen.next(), input.parse()?))
}
@@ -211,33 +216,41 @@ impl Parse for Collection {
}
impl Parse for CellCollection {
- fn parse(inner: ParseStream) -> Result {
+ fn parse(input: ParseStream) -> Result {
+ if input.peek(kw::aligned_column) {
+ let _: kw::aligned_column = input.parse()?;
+ return Self::parse_aligned::(input, false);
+ } else if input.peek(kw::aligned_row) {
+ let _: kw::aligned_row = input.parse()?;
+ return Self::parse_aligned::(input, true);
+ }
+
let mut gen = NameGenerator::default();
let mut cells = vec![];
let mut items = vec![];
- while !inner.is_empty() {
- cells.push(inner.parse()?);
- let _: Token![=>] = inner.parse()?;
+ while !input.is_empty() {
+ cells.push(input.parse()?);
+ let _: Token![=>] = input.parse()?;
let item;
let require_comma;
- if inner.peek(syn::token::Brace) {
- let inner2;
- let _ = braced!(inner2 in inner);
- item = Item::parse(&inner2, &mut gen)?;
+ if input.peek(syn::token::Brace) {
+ let inner;
+ let _ = braced!(inner in input);
+ item = Item::parse(&inner, &mut gen)?;
require_comma = false;
} else {
- item = Item::parse(inner, &mut gen)?;
+ item = Item::parse(input, &mut gen)?;
require_comma = true;
}
items.push(item);
- if inner.is_empty() {
+ if input.is_empty() {
break;
}
- if let Err(e) = inner.parse::() {
+ if let Err(e) = input.parse::() {
if require_comma {
return Err(e);
}
@@ -248,6 +261,46 @@ impl Parse for CellCollection {
}
}
+impl CellCollection {
+ fn parse_aligned(input: ParseStream, transmute: bool) -> Result {
+ let mut gen = NameGenerator::default();
+ let mut cells = vec![];
+ let mut items = vec![];
+
+ let mut row = 0;
+ while !input.is_empty() {
+ let _: Kw = input.parse()?;
+ let _: Token![!] = input.parse()?;
+
+ let inner;
+ let _ = bracketed!(inner in input);
+ let mut col = 0;
+ while !inner.is_empty() {
+ let (mut a, mut b) = (col, row);
+ if transmute {
+ (a, b) = (b, a);
+ }
+ cells.push(CellInfo::new(a, b));
+ items.push(Item::parse(&inner, &mut gen)?);
+
+ if inner.is_empty() {
+ break;
+ }
+ let _: Token![,] = inner.parse()?;
+ col += 1;
+ }
+
+ if input.is_empty() {
+ break;
+ }
+ let _: Token![,] = input.parse()?;
+ row += 1;
+ }
+
+ Ok(CellCollection(cells, Collection(items)))
+ }
+}
+
impl Collection {
pub fn impl_parts(&self) -> (Toks, Toks, Toks, Toks, Toks) {
let mut data_ty = None;
@@ -275,20 +328,13 @@ impl Collection {
for (index, item) in self.0.iter().enumerate() {
let path = match item {
- Item::Label(stor, text) => {
- let span = text.span();
+ Item::Label(stor, ty, def) => {
if let Some(ref data_ty) = data_ty {
- stor_ty.append_all(
- quote! { #stor: ::kas::hidden::MapAny<#data_ty, ::kas::hidden::StrLabel>, },
- );
- stor_def.append_all(
- quote_spanned! {span=> #stor: ::kas::hidden::MapAny::new(::kas::hidden::StrLabel::new(#text)), },
- );
+ stor_ty.append_all(quote! { #stor: ::kas::hidden::MapAny<#data_ty, #ty>, });
+ stor_def.append_all(quote! { #stor: ::kas::hidden::MapAny::new(#def), });
} else {
- stor_ty.append_all(quote! { #stor: ::kas::hidden::StrLabel, });
- stor_def.append_all(
- quote_spanned! {span=> #stor: ::kas::hidden::StrLabel::new(#text), },
- );
+ stor_ty.append_all(quote! { #stor: #ty, });
+ stor_def.append_all(quote! { #stor: #def, });
}
stor.to_token_stream()
}
diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs
index 68fd30882..c7cf93ae2 100644
--- a/crates/kas-macros/src/lib.rs
+++ b/crates/kas-macros/src/lib.rs
@@ -19,10 +19,10 @@ mod collection;
mod extends;
mod make_layout;
mod scroll_traits;
+mod visitors;
mod widget;
mod widget_args;
mod widget_derive;
-mod widget_index;
/// Implement `Default`
///
@@ -208,23 +208,13 @@ 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_](macro@column), [_Row_](macro@row), [_List_](macro@list), [_AlignedColumn_](macro@aligned_column), [_AlignedRow_](macro@aligned_row), [_Grid_](macro@grid), [_Float_](macro@float) :\
+/// > [_Column_], [_Row_], [_List_] [_AlignedColumn_], [_AlignedRow_], [_Grid_], [_Float_], [_Frame_] :\
/// > These stand-alone macros are explicitly supported in this position.\
-/// > Optionally, a _Storage_ specifier is supported immediately after the macro name, e.g.\
-/// > `column! 'storage_name ["one", "two"]`
///
/// > _Single_ :\
/// > `self` `.` _Member_\
/// > A named child: `self.foo` (more precisely, this matches any expression starting `self`, and uses `&mut (#expr)`)
/// >
-/// > _Frame_ :\
-/// > `frame!` _Storage_? `(` _Layout_ ( `,` `style` `=` _Expr_ )? `)`\
-/// > Adds a frame of type _Expr_ around content, defaulting to `FrameStyle::Frame`.
-/// >
-/// > _Button_ :\
-/// > `button!` _Storage_? `(` _Layout_ ( `,` `color` `=` _Expr_ )? `)`\
-/// > Adds a button frame (optionally with color _Expr_) around content.
-/// >
/// > _WidgetConstructor_ :\
/// > _Expr_\
/// > An expression yielding a widget, e.g. `Label::new("Hello world")`. The result must be an object of some type `W: Widget`.
@@ -233,24 +223,12 @@ pub fn impl_scope(input: TokenStream) -> TokenStream {
/// > _StrLit_\
/// > A string literal generates a label widget, e.g. "Hello world". This is an internal type without text wrapping.
/// >
-/// > _NonNavigable_ :\
-/// > `non_navigable!` `(` _Layout_ `)` \
-/// > Does not affect layout. Specifies that the content is excluded from tab-navigation order.
-///
/// Additional syntax rules (not layout items):
///
/// > _Member_ :\
/// > _Ident_ | _Index_\
/// > The name of a struct field or an index into a tuple struct.
/// >
-/// > _Direction_ :\
-/// > `left` | `right` | `up` | `down` | _Expr_:\
-/// > Note that an _Expr_ must start with `self`
-/// >
-/// > _Storage_ :\
-/// > `'` _Ident_\
-/// > Used to explicitly name the storage used by a generated widget or layout; for example `row 'x: ["A", "B", "C"]` will add a field `x: R` where `R: RowStorage` within the generated `widget_core!()`. If omitted, the field name will be anonymous (generated).
-///
/// ## Examples
///
/// A simple example is the
@@ -261,7 +239,7 @@ pub fn impl_scope(input: TokenStream) -> TokenStream {
/// /// A frame around content
/// #[derive(Clone, Default)]
/// #[widget{
-/// layout = frame!(self.inner, style = kas::theme::FrameStyle::Frame);
+/// layout = frame!(self.inner);
/// }]
/// pub struct Frame {
/// core: widget_core!(),
@@ -339,6 +317,14 @@ pub fn impl_scope(input: TokenStream) -> TokenStream {
/// [`CursorIcon`]: https://docs.rs/kas/latest/kas/event/enum.CursorIcon.html
/// [`IsUsed`]: https://docs.rs/kas/latest/kas/event/enum.IsUsed.html
/// [`Deref`]: std::ops::Deref
+/// [_Column_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.column.html
+/// [_Row_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.row.html
+/// [_List_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.list.html
+/// [_Float_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.float.html
+/// [_Frame_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.frame.html
+/// [_Grid_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.grid.html
+/// [_AlignedColumn_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.aligned_column.html
+/// [_AlignedRow_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.aligned_row.html
#[proc_macro_attribute]
#[proc_macro_error]
pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream {
@@ -420,7 +406,7 @@ pub fn impl_anon(input: TokenStream) -> TokenStream {
#[proc_macro_error]
#[proc_macro]
pub fn widget_index(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input as widget_index::UnscopedInput);
+ let input = parse_macro_input!(input as visitors::UnscopedInput);
input.into_token_stream().into()
}
@@ -429,8 +415,11 @@ pub fn widget_index(input: TokenStream) -> TokenStream {
/// Widgets have a hidden field of type [`Rect`] in their `widget_core!()`, used
/// to implement method [`Tile::rect`]. This macro assigns to that field.
///
-/// This macro is usable only within an [`impl_scope!`] macro using the
-/// [`widget`](macro@widget) attribute.
+/// This macro is usable only within the definition of `Layout::set_rect` within
+/// an [`impl_scope!`] macro using the [`widget`](macro@widget) attribute.
+///
+/// The method `Tile::rect` will be generated if this macro is used by the
+/// widget, otherwise a definition of the method must be provided.
///
/// Example usage:
/// ```ignore
@@ -444,223 +433,79 @@ pub fn widget_index(input: TokenStream) -> TokenStream {
#[proc_macro_error]
#[proc_macro]
pub fn widget_set_rect(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input as widget_index::UnscopedInput);
+ let input = parse_macro_input!(input as visitors::UnscopedInput);
input.into_token_stream().into()
}
-trait ExpandLayout {
- fn expand_layout(self, name: &str) -> TokenStream;
-}
-impl ExpandLayout for make_layout::Tree {
- fn expand_layout(self, name: &str) -> TokenStream {
- match self.expand_as_widget(name) {
- Ok(toks) => toks.into(),
- Err(err) => {
- emit_call_site_error!(err);
- TokenStream::default()
- }
- }
- }
-}
-
-/// Make a column widget
-///
-/// Items support [widget layout syntax](macro@widget#layout-1).
-///
-/// # Example
-///
-/// ```ignore
-/// let my_widget = kas::column! [
-/// "one",
-/// "two",
-/// ];
-/// ```
-#[proc_macro_error]
-#[proc_macro]
-pub fn column(input: TokenStream) -> TokenStream {
- parse_macro_input!(input with make_layout::Tree::column).expand_layout("_Column")
-}
-
-/// Make a row widget
-///
-/// Items support [widget layout syntax](macro@widget#layout-1).
-///
-/// # Example
-///
-/// ```ignore
-/// let my_widget = kas::row! ["one", "two"];
-/// ```
-#[proc_macro_error]
-#[proc_macro]
-pub fn row(input: TokenStream) -> TokenStream {
- parse_macro_input!(input with make_layout::Tree::row).expand_layout("_Row")
-}
-
-/// Make a list widget
-///
-/// This is a more generic variant of [`column!`] and [`row!`].
-///
-/// Children are navigated in visual order.
-///
-/// Items support [widget layout syntax](macro@widget#layout-1).
-///
-/// # Example
-///
-/// ```ignore
-/// let my_widget = kas::list!(left, ["one", "two"]);
-/// ```
+/// Generate an anonymous struct which implements [`kas::Collection`]
///
/// # Syntax
///
-/// > _List_ :\
-/// > `list!` `(` _Direction_ `,` `[` ( _Layout_ `,` )* ( _Layout_ `,`? )? `]` `}`
+/// > _Collection_ :\
+/// > `collection!` `[` _Items_\? `]`
/// >
-/// > _Direction_ :\
-/// > `left` | `right` | `up` | `down`
-
-#[proc_macro_error]
-#[proc_macro]
-pub fn list(input: TokenStream) -> TokenStream {
- parse_macro_input!(input with make_layout::Tree::list).expand_layout("_List")
-}
-
-/// Make a float widget
-///
-/// All children occupy the same space with the first child on top.
-///
-/// Size is determined as the maximum required by any child for each axis.
-/// All children are assigned this size. It is usually necessary to use [`pack`]
-/// or a similar mechanism to constrain a child to avoid it hiding the content
-/// underneath (note that even if an unconstrained child does not *visually*
-/// hide everything beneath, it may still "occupy" the assigned area, preventing
-/// mouse clicks from reaching the widget beneath).
+/// > _Items_ :\
+/// > (_Item_ `,`)\* _Item_ `,`\?
///
-/// Children are navigated in order of declaration.
+/// In this case, _Item_ may be:
///
-/// Items support [widget layout syntax](macro@widget#layout-1).
+/// - A string literal (interpreted as a label widget), optionally followed by
+/// an [`align`] or [`pack`] method call
+/// - An expression yielding an object implementing `Widget`
///
-/// # Example
+/// In case all _Item_ instances are a string literal, the data type of the
+/// `collection!` widget will be `()`; otherwise the data type of the widget is `_A`
+/// where `_A` is a generic type parameter of the widget.
///
-/// ```ignore
-/// let my_widget = kas::float! [
-/// "one".pack(AlignHints::TOP_LEFT),
-/// "two".pack(AlignHints::BOTTOM_RIGHT),
-/// "some text\nin the\nbackground"
-/// ];
-/// ```
+/// For example usage, see [`List`](https://docs.rs/kas/latest/kas/widgets/struct.List.html).
///
-/// [`pack`]: https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidget.html#method.pack
+/// [`kas::Collection`]: https://docs.rs/kas/latest/kas/trait.Collection.html
+/// [`align`]: https://docs.rs/kas/latest/kas/widgets/adapt/trait.AdaptWidget.html#method.align
+/// [`pack`]: https://docs.rs/kas/latest/kas/widgets/adapt/trait.AdaptWidget.html#method.pack
#[proc_macro_error]
#[proc_macro]
-pub fn float(input: TokenStream) -> TokenStream {
- parse_macro_input!(input with make_layout::Tree::float).expand_layout("_Float")
+pub fn collection(input: TokenStream) -> TokenStream {
+ parse_macro_input!(input as collection::Collection)
+ .expand()
+ .into()
}
-/// Make a grid widget
-///
-/// Constructs a table with auto-determined number of rows and columns.
-/// Each child is assigned a cell using match-like syntax.
-///
-/// A child may be stretched across multiple cells using range-like syntax:
-/// `3..5`, `3..=4` and `3..+2` are all equivalent.
-///
-/// Behaviour of overlapping widgets is identical to [`float!`]: the first
-/// declared item is on top.
-///
-/// Children are navigated in order of declaration.
-///
-/// Items support [widget layout syntax](macro@widget#layout-1).
-///
-/// # Example
-///
-/// ```ignore
-/// let my_widget = kas::grid! {
-/// (0, 0) => "top left",
-/// (1, 0) => "top right",
-/// (0..2, 1) => "bottom row (merged)",
-/// };
-/// ```
+/// Generate an anonymous struct which implements [`kas::CellCollection`]
///
/// # Syntax
///
-/// > _Grid_ :\
-/// > `grid!` `{` _GridCell_* `}`
+/// > _Collection_ :\
+/// > `collection!` `[` _ItemArms_\? `]`
/// >
-/// > _GridCell_ :\
-/// > `(` _CellRange_ `,` _CellRange_ `)` `=>` ( _Layout_ | `{` _Layout_ `}` )
+/// > _ItemArms_ :\
+/// > (_ItemArm_ `,`)\* _ItemArm_ `,`\?
/// >
-/// > _CellRange_ :\
-/// > _LitInt_ ( `..` `+`? _LitInt_ )?
-///
-/// Cells are specified using `match`-like syntax from `(col_spec, row_spec)` to
-/// a layout, e.g.: `(1, 0) => self.foo`. Spans are specified via range syntax,
-/// e.g. `(0..2, 1) => self.bar`.
-#[proc_macro_error]
-#[proc_macro]
-pub fn grid(input: TokenStream) -> TokenStream {
- parse_macro_input!(input with make_layout::Tree::grid).expand_layout("_Grid")
-}
-
-/// Make an aligned column widget
-///
-/// Items support [widget layout syntax](macro@widget#layout-1).
-///
-/// # Example
-///
-/// ```ignore
-/// let my_widget = kas::aligned_column! [
-/// row!["one", "two"],
-/// row!["three", "four"],
-/// ];
-/// ```
-#[proc_macro_error]
-#[proc_macro]
-pub fn aligned_column(input: TokenStream) -> TokenStream {
- parse_macro_input!(input with make_layout::Tree::aligned_column).expand_layout("_AlignedColumn")
-}
-
-/// Make an aligned row widget
-///
-/// Items support [widget layout syntax](macro@widget#layout-1).
-///
-/// # Example
-///
-/// ```ignore
-/// let my_widget = kas::aligned_row! [
-/// column!["one", "two"],
-/// column!["three", "four"],
-/// ];
-/// ```
-#[proc_macro_error]
-#[proc_macro]
-pub fn aligned_row(input: TokenStream) -> TokenStream {
- parse_macro_input!(input with make_layout::Tree::aligned_row).expand_layout("_AlignedRow")
-}
-
-/// Generate an anonymous struct which implements [`kas::Collection`]
+/// > _ItemArm_ :\
+/// > `(` _Column_ `,` _Row_ `)` `=>` _Item_
+/// >
+/// > _Column_, _Row_ :\
+/// > _LitInt_ | ( _LitInt_ `..` `+` _LitInt_ ) | ( _LitInt_ `..`
+/// > _LitInt_ ) | ( _LitInt_ `..=` _LitInt_ )
///
-/// Each item must be either a string literal (inferred as a static label) or a
-/// widget (implements [`kas::Widget`](https://docs.rs/kas/latest/kas/trait.Widget.html)).
+/// Here, _Column_ and _Row_ are selected via an index (from 0), a range of
+/// indices, or a start + increment. For example, `2` = `2..+1` = `2..3` =
+/// `2..=2` while `5..+2` = `5..7` = `5..=6`.
///
-/// For example usage, see [`List`](https://docs.rs/kas/latest/kas/widgets/struct.List.html).
+/// _Item_ may be:
///
-/// [`kas::Collection`]: https://docs.rs/kas/latest/kas/trait.Collection.html
-#[proc_macro_error]
-#[proc_macro]
-pub fn collection(input: TokenStream) -> TokenStream {
- parse_macro_input!(input as collection::Collection)
- .expand()
- .into()
-}
-
-/// Generate an anonymous struct which implements [`kas::CellCollection`]
+/// - A string literal (interpreted as a label widget), optionally followed by
+/// an [`align`] or [`pack`] method call
+/// - An expression yielding an object implementing `Widget`
///
-/// Each item must be either a string literal (inferred as a static label) or a
-/// widget (implements [`kas::Widget`](https://docs.rs/kas/latest/kas/trait.Widget.html)).
+/// In case all _Item_ instances are a string literal, the data type of the
+/// `collection!` widget will be `()`; otherwise the data type of the widget is `_A`
+/// where `_A` is a generic type parameter of the widget.
///
/// For example usage, see [`Grid`](https://docs.rs/kas/latest/kas/widgets/struct.Grid.html).
///
/// [`kas::CellCollection`]: https://docs.rs/kas/latest/kas/trait.CellCollection.html
+/// [`align`]: https://docs.rs/kas/latest/kas/widgets/adapt/trait.AdaptWidget.html#method.align
+/// [`pack`]: https://docs.rs/kas/latest/kas/widgets/adapt/trait.AdaptWidget.html#method.pack
#[proc_macro_error]
#[proc_macro]
pub fn cell_collection(input: TokenStream) -> TokenStream {
diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs
index 9a8a6198f..a098bbf57 100644
--- a/crates/kas-macros/src/make_layout.rs
+++ b/crates/kas-macros/src/make_layout.rs
@@ -3,8 +3,7 @@
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
// https://www.apache.org/licenses/LICENSE-2.0
-use crate::collection::{CellInfo, GridDimensions, NameGenerator, StorIdent};
-use crate::widget;
+use crate::collection::{CellInfo, GridDimensions, NameGenerator};
use crate::widget_args::{Child, ChildIdent};
use proc_macro2::{Span, TokenStream as Toks};
use proc_macro_error2::emit_error;
@@ -29,7 +28,6 @@ mod kw {
custom_keyword!(center);
custom_keyword!(stretch);
custom_keyword!(frame);
- custom_keyword!(button);
custom_keyword!(list);
custom_keyword!(grid);
custom_keyword!(default);
@@ -38,12 +36,12 @@ mod kw {
custom_keyword!(aligned_column);
custom_keyword!(aligned_row);
custom_keyword!(float);
- custom_keyword!(non_navigable);
custom_keyword!(px);
custom_keyword!(em);
- custom_keyword!(style);
- custom_keyword!(color);
custom_keyword!(map_any);
+ custom_keyword!(with_direction);
+ custom_keyword!(with_style);
+ custom_keyword!(with_background);
}
#[derive(Default)]
@@ -67,7 +65,7 @@ impl Tree {
self.0.generate(core_path)
}
- /// Generate implementation of nav_next (excludes fn signature)
+ /// Generate implementation of nav_next
pub fn nav_next<'a, I: Clone + Iterator- >(
&self,
children: I,
@@ -75,242 +73,24 @@ impl Tree {
let mut v = Vec::new();
self.0.nav_next(children, &mut v).map(|()| {
quote! {
- let mut iter = [#(#v),*].into_iter();
- if !reverse {
- if let Some(wi) = from {
- let _ = iter.find(|x| *x == wi);
- }
- iter.next()
- } else {
- let mut iter = iter.rev();
- if let Some(wi) = from {
- let _ = iter.find(|x| *x == wi);
+ fn nav_next(&self, reverse: bool, from: Option) -> Option {
+ let mut iter = [#(#v),*].into_iter();
+ if !reverse {
+ if let Some(wi) = from {
+ let _ = iter.find(|x| *x == wi);
+ }
+ iter.next()
+ } else {
+ let mut iter = iter.rev();
+ if let Some(wi) = from {
+ let _ = iter.find(|x| *x == wi);
+ }
+ iter.next()
}
- iter.next()
}
}
})
}
-
- /// Synthesize an entire widget from the layout
- pub fn expand_as_widget(self, widget_name: &str) -> Result {
- let mut children = Vec::new();
- let data_ty: syn::Type = syn::parse_quote! { _Data };
- let stor_defs = self.storage_fields(&mut children, &data_ty);
- let stor_ty = &stor_defs.ty_toks;
- let stor_def = &stor_defs.def_toks;
-
- let name = Ident::new(widget_name, Span::call_site());
- let core_path = quote! { self };
- let (impl_generics, impl_target) = if stor_defs.used_data_ty {
- (quote! { <_Data> }, quote! { #name <_Data> })
- } else {
- (quote! {}, quote! { #name })
- };
-
- let count = children.len();
- let num_children = quote! {
- fn num_children(&self) -> usize {
- #count
- }
- };
-
- let mut get_rules = quote! {};
- for (index, child) in children.iter().enumerate() {
- get_rules.append_all(child.ident.get_rule(&core_path, index));
- }
-
- let core_impl = widget::impl_core_methods(widget_name, &core_path);
- let widget_impl = widget::impl_widget(
- &impl_generics,
- &impl_target,
- &data_ty,
- &core_path,
- &children,
- true,
- );
-
- let layout_visitor = self.layout_visitor(&core_path)?;
- let nav_next = match self.nav_next(children.iter()) {
- Ok(result) => Some(result),
- Err((span, msg)) => {
- emit_error!(span, "unable to generate `fn Tile::nav_next`: {}", msg);
- None
- }
- };
-
- let toks = quote! {{
- struct #name #impl_generics {
- _rect: ::kas::geom::Rect,
- _id: ::kas::Id,
- #[cfg(debug_assertions)]
- status: ::kas::WidgetStatus,
- #stor_ty
- }
-
- impl #impl_generics ::kas::layout::LayoutVisitor for #impl_target {
- fn layout_visitor(&mut self) -> ::kas::layout::Visitor {
- use ::kas::layout;
- #layout_visitor
- }
- }
-
- impl #impl_generics ::kas::Layout for #impl_target {
- fn size_rules(
- &mut self,
- sizer: ::kas::theme::SizeCx,
- axis: ::kas::layout::AxisInfo,
- ) -> ::kas::layout::SizeRules {
- #[cfg(debug_assertions)]
- #core_path.status.size_rules(core_path._id, axis);
- ::kas::layout::LayoutVisitor::layout_visitor(self).size_rules(sizer, axis)
- }
-
- fn set_rect(
- &mut self,
- cx: &mut ::kas::event::ConfigCx,
- rect: ::kas::geom::Rect,
- hints: ::kas::layout::AlignHints,
- ) {
- #[cfg(debug_assertions)]
- #core_path.status.set_rect(core_path._id);
-
- #core_path._rect = rect;
- ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect, hints);
- }
-
- fn try_probe(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> {
- ::kas::Tile::rect(self).contains(coord).then(|| ::kas::Tile::probe(self, coord))
- }
-
- fn draw(&mut self, mut draw: ::kas::theme::DrawCx) {
- #[cfg(debug_assertions)]
- #core_path.status.require_rect(core_path._id);
-
- draw.set_id(::kas::Tile::id(self));
-
- ::kas::layout::LayoutVisitor::layout_visitor(self).draw(draw);
- }
- }
-
- impl #impl_generics ::kas::Tile for #impl_target {
- #core_impl
- #num_children
- fn get_child(&self, index: usize) -> Option<&dyn ::kas::Tile> {
- match index {
- #get_rules
- _ => None,
- }
- }
-
- fn nav_next(&self, reverse: bool, from: Option) -> Option {
- #nav_next
- }
-
- #[inline]
- fn probe(&mut self, coord: ::kas::geom::Coord) -> ::kas::Id {
- #[cfg(debug_assertions)]
- #core_path.status.require_rect(core_path._id);
-
- let coord = coord + ::kas::Tile::translation(self);
- ::kas::layout::LayoutVisitor::layout_visitor(self)
- .try_probe(coord)
- .unwrap_or_else(|| ::kas::Tile::id(self))
- }
- }
-
- impl #impl_generics ::kas::Events for #impl_target {
- fn handle_event(
- &mut self,
- _: &mut ::kas::event::EventCx,
- _: &Self::Data,
- _: ::kas::event::Event,
- ) -> ::kas::event::IsUsed {
- #[cfg(debug_assertions)]
- #core_path.status.require_rect(core_path._id);
- ::kas::event::Unused
- }
- }
-
- #widget_impl
-
- #name {
- _rect: Default::default(),
- _id: Default::default(),
- #[cfg(debug_assertions)]
- status: Default::default(),
- #stor_def
- }
- }};
- // println!("{}", toks);
- Ok(toks)
- }
-
- /// Parse a column (contents only)
- pub fn column(inner: ParseStream) -> Result {
- let mut core_gen = NameGenerator::default();
- let stor = core_gen.next();
- let list = parse_layout_items(inner, &mut core_gen, false)?;
- Ok(Tree(Layout::List(stor.into(), Direction::Down, list)))
- }
-
- /// Parse a row (contents only)
- pub fn row(inner: ParseStream) -> Result {
- let mut core_gen = NameGenerator::default();
- let stor = core_gen.next();
- let list = parse_layout_items(inner, &mut core_gen, false)?;
- Ok(Tree(Layout::List(stor.into(), Direction::Right, list)))
- }
-
- /// Parse an aligned column (contents only)
- pub fn aligned_column(inner: ParseStream) -> Result {
- let mut core_gen = NameGenerator::default();
- let stor = core_gen.next();
- Ok(Tree(parse_grid_as_list_of_lists::(
- stor.into(),
- inner,
- &mut core_gen,
- true,
- false,
- )?))
- }
-
- /// Parse an aligned row (contents only)
- pub fn aligned_row(inner: ParseStream) -> Result {
- let mut core_gen = NameGenerator::default();
- let stor = core_gen.next();
- Ok(Tree(parse_grid_as_list_of_lists::(
- stor.into(),
- inner,
- &mut core_gen,
- false,
- false,
- )?))
- }
-
- /// Parse direction, list
- pub fn list(inner: ParseStream) -> Result {
- let mut core_gen = NameGenerator::default();
- let stor = core_gen.next();
- let dir: Direction = inner.parse()?;
- let _: Token![,] = inner.parse()?;
- let list = parse_layout_list(inner, &mut core_gen, false)?;
- Ok(Tree(Layout::List(stor.into(), dir, list)))
- }
-
- /// Parse a float (contents only)
- pub fn float(inner: ParseStream) -> Result {
- let mut core_gen = NameGenerator::default();
- let list = parse_layout_items(inner, &mut core_gen, false)?;
- Ok(Tree(Layout::Float(list)))
- }
-
- /// Parse a grid (contents only)
- pub fn grid(inner: ParseStream) -> Result {
- let mut core_gen = NameGenerator::default();
- let stor = core_gen.next();
- Ok(Tree(parse_grid(stor.into(), inner, &mut core_gen, false)?))
- }
}
#[derive(Debug)]
@@ -353,13 +133,11 @@ enum Layout {
Pack(Box, Pack),
Single(ExprMember),
Widget(Ident, Expr),
- Frame(StorIdent, Box, Expr),
- Button(StorIdent, Box, Expr),
- List(StorIdent, Direction, LayoutList<()>),
+ Frame(Ident, Box, Expr, Expr),
+ List(Ident, Direction, LayoutList<()>),
Float(LayoutList<()>),
- Grid(StorIdent, GridDimensions, LayoutList),
+ Grid(Ident, GridDimensions, LayoutList),
Label(Ident, LitStr),
- NonNavigable(Box),
MapAny(Box, MapAny),
}
@@ -393,45 +171,13 @@ bitflags::bitflags! {
impl Parse for Tree {
fn parse(input: ParseStream) -> Result {
let mut core_gen = NameGenerator::default();
- Ok(Tree(Layout::parse(input, &mut core_gen, true)?))
+ Ok(Tree(Layout::parse(input, &mut core_gen)?))
}
}
impl Layout {
- fn parse(input: ParseStream, core_gen: &mut NameGenerator, _recurse: bool) -> Result {
- #[cfg(feature = "recursive-layout-widgets")]
- let _recurse = true;
-
- #[cfg(not(feature = "recursive-layout-widgets"))]
- if input.peek2(Token![!]) {
- let input2 = input.fork();
- let mut temp_gen = NameGenerator::default();
- if Self::parse_macro_like(&input2, &mut temp_gen).is_ok() {
- loop {
- if let Ok(dot_token) = input2.parse::() {
- if input2.peek(kw::map_any) {
- let _ = MapAny::parse(dot_token, &input2)?;
- continue;
- } else if input2.peek(kw::align) {
- let _ = Align::parse(dot_token, &input2)?;
- continue;
- } else if input2.peek(kw::pack) {
- let _ = Pack::parse(dot_token, &input2, &mut temp_gen)?;
- continue;
- } else if let Ok(ident) = input2.parse::() {
- proc_macro_error2::emit_warning!(
- ident, "this method call is incompatible with feature `recursive-layout-widgets`";
- note = "extract operand from layout expression or wrap with braces",
- );
- }
- }
-
- break;
- }
- }
- }
-
- let mut layout = if _recurse && input.peek2(Token![!]) {
+ fn parse(input: ParseStream, core_gen: &mut NameGenerator) -> Result {
+ let mut layout = if input.peek2(Token![!]) {
Self::parse_macro_like(input, core_gen)?
} else if input.peek(Token![self]) {
Layout::Single(input.parse()?)
@@ -456,19 +202,28 @@ impl Layout {
let pack = Pack::parse(dot_token, input, core_gen)?;
layout = Layout::Pack(Box::new(layout), pack);
} else if let Ok(ident) = input.parse::() {
+ let note_msg = if matches!(&layout, &Layout::Frame(_, _, _, _)) {
+ "supported methods on layout objects: `map_any`, `align`, `pack`, `with_style`, `with_background`"
+ } else {
+ "supported methods on layout objects: `map_any`, `align`, `pack`"
+ };
emit_error!(
ident, "method not supported here";
- note = "supported methods on layout objects: `map_any`, `align`, `pack`",
+ note = note_msg,
);
// Clear remainder of input stream to avoid a redundant error
- input.step(|cursor| {
- let mut rest = *cursor;
- while let Some((_, next)) = rest.token_tree() {
- rest = next;
- }
- Ok(((), rest))
- })?;
+ let turbofish = if input.peek(Token![::]) {
+ Some(syn::AngleBracketedGenericArguments::parse_turbofish(input)?)
+ } else {
+ None
+ };
+
+ if turbofish.is_some() || input.peek(syn::token::Paren) {
+ let inner;
+ let _ = parenthesized!(inner in input);
+ let _ = inner.parse_terminated(Expr::parse, Token![,])?;
+ }
// Continue with macro expansion to minimise secondary errors
return Ok(layout);
@@ -489,104 +244,95 @@ impl Layout {
if lookahead.peek(kw::frame) {
let _: kw::frame = input.parse()?;
let _: Token![!] = input.parse()?;
- let stor = core_gen.parse_or_next(input)?;
+ let stor = core_gen.next();
let inner;
let _ = parenthesized!(inner in input);
- let layout = Layout::parse(&inner, core_gen, true)?;
-
- let style: Expr = if !inner.is_empty() {
- let _: Token![,] = inner.parse()?;
- let _: kw::style = inner.parse()?;
- let _: Token![=] = inner.parse()?;
- inner.parse()?
- } else {
- syn::parse_quote! { ::kas::theme::FrameStyle::Frame }
- };
-
- Ok(Layout::Frame(stor, Box::new(layout), style))
- } else if lookahead.peek(kw::button) {
- let _: kw::button = input.parse()?;
- let _: Token![!] = input.parse()?;
- let stor = core_gen.parse_or_next(input)?;
-
- let inner;
- let _ = parenthesized!(inner in input);
- let layout = Layout::parse(&inner, core_gen, true)?;
+ let layout = Layout::parse(&inner, core_gen)?;
+
+ let mut style = None;
+ let mut bg = None;
+ while input.peek(Token![.]) {
+ if style.is_none() && input.peek2(kw::with_style) {
+ let _: Token![.] = input.parse()?;
+ let _: kw::with_style = input.parse()?;
+
+ let inner;
+ let _ = parenthesized!(inner in input);
+ style = Some(inner.parse()?);
+ } else if bg.is_none() && input.peek2(kw::with_background) {
+ let _: Token![.] = input.parse()?;
+ let _: kw::with_background = input.parse()?;
+
+ let inner;
+ let _ = parenthesized!(inner in input);
+ bg = Some(inner.parse()?);
+ } else {
+ break;
+ }
+ }
- let color: Expr = if !inner.is_empty() {
- let _: Token![,] = inner.parse()?;
- let _: kw::color = inner.parse()?;
- let _: Token![=] = inner.parse()?;
- inner.parse()?
- } else {
- syn::parse_quote! { None }
- };
+ let style =
+ style.unwrap_or_else(|| syn::parse_quote! { ::kas::theme::FrameStyle::Frame });
+ let bg = bg.unwrap_or_else(|| syn::parse_quote! { ::kas::theme::Background::Default });
- Ok(Layout::Button(stor, Box::new(layout), color))
+ Ok(Layout::Frame(stor, Box::new(layout), style, bg))
} else if lookahead.peek(kw::column) {
let _: kw::column = input.parse()?;
let _: Token![!] = input.parse()?;
- let stor = core_gen.parse_or_next(input)?;
- let list = parse_layout_list(input, core_gen, true)?;
+ let stor = core_gen.next();
+ let list = parse_layout_list(input, core_gen)?;
Ok(Layout::List(stor, Direction::Down, list))
} else if lookahead.peek(kw::row) {
let _: kw::row = input.parse()?;
let _: Token![!] = input.parse()?;
- let stor = core_gen.parse_or_next(input)?;
- let list = parse_layout_list(input, core_gen, true)?;
+ let stor = core_gen.next();
+ let list = parse_layout_list(input, core_gen)?;
Ok(Layout::List(stor, Direction::Right, list))
} else if lookahead.peek(kw::list) {
let _: kw::list = input.parse()?;
let _: Token![!] = input.parse()?;
- let stor = core_gen.parse_or_next(input)?;
- let inner;
- let _ = parenthesized!(inner in input);
- let dir: Direction = inner.parse()?;
- let _: Token![,] = inner.parse()?;
- let list = parse_layout_list(&inner, core_gen, true)?;
+ let stor = core_gen.next();
+ let list = parse_layout_list(input, core_gen)?;
+ let _: Token![.] = input.parse()?;
+ let _: kw::with_direction = input.parse()?;
+ let args;
+ let _ = parenthesized!(args in input);
+ let dir: Direction = args.parse()?;
Ok(Layout::List(stor, dir, list))
} else if lookahead.peek(kw::float) {
let _: kw::float = input.parse()?;
let _: Token![!] = input.parse()?;
- let list = parse_layout_list(input, core_gen, true)?;
+ let list = parse_layout_list(input, core_gen)?;
Ok(Layout::Float(list))
} else if lookahead.peek(kw::aligned_column) {
let _: kw::aligned_column = input.parse()?;
let _: Token![!] = input.parse()?;
- let stor = core_gen.parse_or_next(input)?;
+ let stor = core_gen.next();
let inner;
let _ = bracketed!(inner in input);
Ok(parse_grid_as_list_of_lists::(
- stor, &inner, core_gen, true, true,
+ stor, &inner, core_gen, true,
)?)
} else if lookahead.peek(kw::aligned_row) {
let _: kw::aligned_row = input.parse()?;
let _: Token![!] = input.parse()?;
- let stor = core_gen.parse_or_next(input)?;
+ let stor = core_gen.next();
let inner;
let _ = bracketed!(inner in input);
Ok(parse_grid_as_list_of_lists::(
- stor, &inner, core_gen, false, true,
+ stor, &inner, core_gen, false,
)?)
} else if lookahead.peek(kw::grid) {
let _: kw::grid = input.parse()?;
let _: Token![!] = input.parse()?;
- let stor = core_gen.parse_or_next(input)?;
+ let stor = core_gen.next();
let inner;
let _ = braced!(inner in input);
- Ok(parse_grid(stor, &inner, core_gen, true)?)
- } else if lookahead.peek(kw::non_navigable) {
- let _: kw::non_navigable = input.parse()?;
- let _: Token![!] = input.parse()?;
-
- let inner;
- let _ = parenthesized!(inner in input);
- let layout = Layout::parse(&inner, core_gen, true)?;
- Ok(Layout::NonNavigable(Box::new(layout)))
+ Ok(parse_grid(stor, &inner, core_gen)?)
} else {
let ident = core_gen.next();
let expr = input.parse()?;
@@ -595,28 +341,20 @@ impl Layout {
}
}
-fn parse_layout_list(
- input: ParseStream,
- core_gen: &mut NameGenerator,
- recurse: bool,
-) -> Result> {
+fn parse_layout_list(input: ParseStream, core_gen: &mut NameGenerator) -> Result> {
let inner;
let _ = bracketed!(inner in input);
- parse_layout_items(&inner, core_gen, recurse)
+ parse_layout_items(&inner, core_gen)
}
-fn parse_layout_items(
- inner: ParseStream,
- core_gen: &mut NameGenerator,
- recurse: bool,
-) -> Result> {
+fn parse_layout_items(inner: ParseStream, core_gen: &mut NameGenerator) -> Result> {
let mut list = vec![];
let mut gen2 = NameGenerator::default();
while !inner.is_empty() {
list.push(ListItem {
cell: (),
stor: gen2.next(),
- layout: Layout::parse(inner, core_gen, recurse)?,
+ layout: Layout::parse(inner, core_gen)?,
});
if inner.is_empty() {
@@ -630,11 +368,10 @@ fn parse_layout_items(
}
fn parse_grid_as_list_of_lists(
- stor: StorIdent,
+ stor: Ident,
inner: ParseStream,
core_gen: &mut NameGenerator,
row_major: bool,
- recurse: bool,
) -> Result {
let (mut col, mut row) = (0, 0);
let mut dim = GridDimensions::default();
@@ -651,7 +388,7 @@ fn parse_grid_as_list_of_lists(
while !inner2.is_empty() {
let cell = CellInfo::new(col, row);
dim.update(&cell);
- let layout = Layout::parse(&inner2, core_gen, recurse)?;
+ let layout = Layout::parse(&inner2, core_gen)?;
cells.push(ListItem {
cell,
stor: gen2.next(),
@@ -687,12 +424,7 @@ fn parse_grid_as_list_of_lists(
Ok(Layout::Grid(stor, dim, LayoutList(cells)))
}
-fn parse_grid(
- stor: StorIdent,
- inner: ParseStream,
- core_gen: &mut NameGenerator,
- recurse: bool,
-) -> Result {
+fn parse_grid(stor: Ident, inner: ParseStream, core_gen: &mut NameGenerator) -> Result {
let mut dim = GridDimensions::default();
let mut gen2 = NameGenerator::default();
let mut cells = vec![];
@@ -706,10 +438,10 @@ fn parse_grid(
if inner.peek(syn::token::Brace) {
let inner2;
let _ = braced!(inner2 in inner);
- layout = Layout::parse(&inner2, core_gen, recurse)?;
+ layout = Layout::parse(&inner2, core_gen)?;
require_comma = false;
} else {
- layout = Layout::parse(inner, core_gen, recurse)?;
+ layout = Layout::parse(inner, core_gen)?;
require_comma = true;
}
cells.push(ListItem {
@@ -848,7 +580,7 @@ struct Pack {
pub kw: kw::pack,
pub paren_token: token::Paren,
pub hints: Expr,
- pub stor: StorIdent,
+ pub stor: Ident,
}
impl Pack {
fn parse(
@@ -864,7 +596,7 @@ impl Pack {
kw,
paren_token,
hints: content.parse()?,
- stor: core_gen.next().into(),
+ stor: core_gen.next(),
})
}
@@ -882,7 +614,7 @@ impl Pack {
impl Layout {
fn append_fields(&self, fields: &mut StorageFields, children: &mut Vec, data_ty: &Type) {
match self {
- Layout::Align(layout, _) | Layout::NonNavigable(layout) => {
+ Layout::Align(layout, _) => {
layout.append_fields(fields, children, data_ty);
}
Layout::Single(_) => (),
@@ -907,7 +639,7 @@ impl Layout {
.append_all(quote_spanned! {span=> #ident: Box::new(#expr), });
fields.used_data_ty = true;
}
- Layout::Frame(stor, layout, _) | Layout::Button(stor, layout, _) => {
+ Layout::Frame(stor, layout, _, _) => {
fields
.ty_toks
.append_all(quote! { #stor: ::kas::layout::FrameStorage, });
@@ -1008,16 +740,10 @@ impl Layout {
Layout::Widget(ident, _) => quote! {
layout::Visitor::single(&mut #core_path.#ident)
},
- Layout::Frame(stor, layout, style) => {
+ Layout::Frame(stor, layout, style, bg) => {
let inner = layout.generate(core_path)?;
quote! {
- layout::Visitor::frame(&mut #core_path.#stor, #inner, #style)
- }
- }
- Layout::Button(stor, layout, color) => {
- let inner = layout.generate(core_path)?;
- quote! {
- layout::Visitor::button(&mut #core_path.#stor, #inner, #color)
+ layout::Visitor::frame(&mut #core_path.#stor, #inner, #style, #bg)
}
}
Layout::List(stor, dir, list) => {
@@ -1038,16 +764,13 @@ impl Layout {
Layout::Label(stor, _) => {
quote! { layout::Visitor::single(&mut #core_path.#stor) }
}
- Layout::NonNavigable(layout) | Layout::MapAny(layout, _) => {
- return layout.generate(core_path)
- }
+ Layout::MapAny(layout, _) => return layout.generate(core_path),
})
}
/// Create a Vec enumerating all children in navigation order
///
/// - `output`: the result
- /// - `index`: the next widget's index
fn nav_next<'a, I: Clone + Iterator
- >(
&self,
children: I,
@@ -1056,12 +779,8 @@ impl Layout {
match self {
Layout::Align(layout, _)
| Layout::Pack(layout, _)
- | Layout::Frame(_, layout, _)
+ | Layout::Frame(_, layout, _, _)
| Layout::MapAny(layout, _) => layout.nav_next(children, output),
- Layout::Button(_, _, _) | Layout::NonNavigable(_) => {
- // Internals of a button are not navigable
- Ok(())
- }
Layout::Single(m) => {
for (i, child) in children.enumerate() {
if let ChildIdent::Field(ref ident) = child.ident {
diff --git a/crates/kas-macros/src/widget_index.rs b/crates/kas-macros/src/visitors.rs
similarity index 75%
rename from crates/kas-macros/src/widget_index.rs
rename to crates/kas-macros/src/visitors.rs
index e80e6a21b..16be61c26 100644
--- a/crates/kas-macros/src/widget_index.rs
+++ b/crates/kas-macros/src/visitors.rs
@@ -53,11 +53,10 @@ impl Parse for WidgetInput {
}
}
-struct Visitor<'a, I: Clone + Iterator
- > {
+struct WidgetIndexVisitor<'a, I: Clone + Iterator
- > {
children: I,
- path_rect: TokenStream,
}
-impl<'a, I: Clone + Iterator
- > VisitMut for Visitor<'a, I> {
+impl<'a, I: Clone + Iterator
- > VisitMut for WidgetIndexVisitor<'a, I> {
fn visit_macro_mut(&mut self, node: &mut syn::Macro) {
// HACK: we cannot expand the macro here since we do not have an Expr
// to replace. Instead we can only modify the macro's tokens.
@@ -84,7 +83,39 @@ impl<'a, I: Clone + Iterator
- > VisitMut for Visitor<'
emit_error!(args.ident.span(), "does not match any child widget");
node.tokens = parse_quote! { error_emitted 0 };
return;
- } else if node.path == parse_quote! { widget_set_rect } {
+ }
+
+ visit_mut::visit_macro_mut(self, node);
+ }
+}
+
+pub fn widget_index<'a, I: Clone + Iterator
- >(
+ children: I,
+ impls: &mut [syn::ItemImpl],
+) {
+ let mut obj = WidgetIndexVisitor { children };
+
+ for impl_ in impls {
+ obj.visit_item_impl_mut(impl_);
+ }
+}
+
+struct SetRectVisitor {
+ path_rect: TokenStream,
+ first_usage: Option,
+}
+impl VisitMut for SetRectVisitor {
+ fn visit_macro_mut(&mut self, node: &mut syn::Macro) {
+ // HACK: we cannot expand the macro here since we do not have an Expr
+ // to replace. Instead we can only modify the macro's tokens.
+ // WARNING: if the macro's tokens are modified before printing an error
+ // message is emitted then the span of that error message is incorrect.
+
+ if node.path == parse_quote! { widget_set_rect } {
+ if self.first_usage.is_none() {
+ self.first_usage = Some(node.span());
+ }
+
let expr = match syn::parse2::(node.tokens.clone()) {
Ok(expr) => expr,
Err(err) => {
@@ -103,17 +134,14 @@ impl<'a, I: Clone + Iterator
- > VisitMut for Visitor<'
}
}
-pub fn visit_impls<'a, I: Clone + Iterator
- >(
- children: I,
- path_rect: TokenStream,
- impls: &mut [syn::ItemImpl],
-) {
- let mut obj = Visitor {
- children,
+/// Returns first span of widget_set_rect usage, if any
+pub fn widget_set_rect(path_rect: TokenStream, block: &mut syn::Block) -> Option {
+ let mut obj = SetRectVisitor {
path_rect,
+ first_usage: None,
};
- for impl_ in impls {
- obj.visit_item_impl_mut(impl_);
- }
+ obj.visit_block_mut(block);
+
+ obj.first_usage
}
diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs
index 7d509930b..b88fe1517 100644
--- a/crates/kas-macros/src/widget.rs
+++ b/crates/kas-macros/src/widget.rs
@@ -287,8 +287,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul
ChildIdent::Field(ref member) => Some((i, member)),
ChildIdent::CoreField(_) => None,
});
- let path_rect = quote! { #core_path._rect };
- crate::widget_index::visit_impls(named_child_iter, path_rect, &mut scope.impls);
+ crate::visitors::widget_index(named_child_iter, &mut scope.impls);
if let Some(ref span) = num_children {
if get_child.is_none() {
@@ -322,17 +321,15 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul
#core_path.status.require_rect(core_path._id);
};
- let mut required_tile_methods = impl_core_methods(&name.to_string(), &core_path);
-
let do_impl_widget_children = get_child.is_none() && for_child_node.is_none();
- if do_impl_widget_children {
+ let fns_get_child = if do_impl_widget_children {
let mut get_rules = quote! {};
for (index, child) in children.iter().enumerate() {
get_rules.append_all(child.ident.get_rule(&core_path, index));
}
let count = children.len();
- required_tile_methods.append_all(quote! {
+ Some(quote! {
fn num_children(&self) -> usize {
#count
}
@@ -342,8 +339,10 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul
_ => None,
}
}
- });
- }
+ })
+ } else {
+ None
+ };
if let Some(index) = widget_impl {
let widget_impl = &mut scope.impls[index];
@@ -383,13 +382,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul
};
let mut fn_draw = None;
if let Some(Layout { tree, .. }) = args.layout.take() {
- fn_nav_next = tree.nav_next(children.iter()).map(|toks| {
- quote! {
- fn nav_next(&self, reverse: bool, from: Option) -> Option {
- #toks
- }
- }
- });
+ fn_nav_next = tree.nav_next(children.iter());
let layout_visitor = tree.layout_visitor(&core_path)?;
scope.generated.push(quote! {
@@ -548,6 +541,9 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul
}
};
+ let core_rect_is_set;
+ let mut widget_set_rect_span = None;
+ let mut fn_set_rect_span = None;
if let Some(index) = layout_impl {
let layout_impl = &mut scope.impls[index];
let item_idents = collect_idents(layout_impl);
@@ -573,16 +569,25 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul
}
if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "set_rect") {
- if let Some(ref core) = core_data {
- if let ImplItem::Fn(f) = &mut layout_impl.items[*index] {
+ if let ImplItem::Fn(f) = &mut layout_impl.items[*index] {
+ fn_set_rect_span = Some(f.span());
+
+ let path_rect = quote! { #core_path._rect };
+ widget_set_rect_span = crate::visitors::widget_set_rect(path_rect, &mut f.block);
+ core_rect_is_set = widget_set_rect_span.is_some();
+
+ if let Some(ref core) = core_data {
f.block.stmts.insert(0, parse_quote! {
#[cfg(debug_assertions)]
self.#core.status.set_rect(&self.#core._id);
});
}
+ } else {
+ core_rect_is_set = false;
}
} else {
layout_impl.items.push(Verbatim(fn_set_rect));
+ core_rect_is_set = true;
}
if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "try_probe") {
@@ -631,8 +636,19 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul
#fn_draw
}
});
+ core_rect_is_set = true;
+ } else {
+ core_rect_is_set = false;
}
+ let mut have_fn_rect = core_rect_is_set;
+ let required_tile_methods = required_tile_methods(&name.to_string(), &core_path);
+ let fn_rect = if core_rect_is_set {
+ Some(fn_rect(&core_path))
+ } else {
+ None
+ };
+
if let Some(index) = tile_impl {
let tile_impl = &mut scope.impls[index];
let item_idents = collect_idents(tile_impl);
@@ -640,6 +656,37 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul
tile_impl.items.push(Verbatim(required_tile_methods));
+ if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "rect") {
+ have_fn_rect = true;
+ if let Some(span) = widget_set_rect_span {
+ let fn_rect_span = tile_impl.items[*index].span();
+ emit_warning!(
+ span, "assignment `widget_set_rect!` has no effect when `fn rect` is defined";
+ note = fn_rect_span => "this `fn rect`";
+ );
+ }
+ if core_rect_is_set {
+ let fn_rect_span = tile_impl.items[*index].span();
+ if let Some(span) = widget_set_rect_span {
+ emit_warning!(
+ span, "assignment `widget_set_rect!` has no effect when `fn rect` is defined";
+ note = fn_rect_span => "this `fn rect`";
+ );
+ } else {
+ emit_warning!(
+ fn_rect_span,
+ "definition of `Layout::set_rect` is expected when `fn rect` is defined"
+ );
+ }
+ }
+ } else if let Some(method) = fn_rect {
+ tile_impl.items.push(Verbatim(method));
+ }
+
+ if let Some(methods) = fns_get_child {
+ tile_impl.items.push(Verbatim(methods));
+ }
+
if !has_item("nav_next") {
match fn_nav_next {
Ok(method) => tile_impl.items.push(Verbatim(method)),
@@ -665,12 +712,25 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul
scope.generated.push(quote! {
impl #impl_generics ::kas::Tile for #impl_target {
#required_tile_methods
+ #fn_rect
+ #fns_get_child
#fn_nav_next
#fn_probe
}
});
}
+ // TODO(opt): omit field widget.core._rect if !core_rect_is_set
+
+ if !have_fn_rect {
+ if let Some(span) = fn_set_rect_span {
+ emit_warning!(
+ span, "`widget_set_rect!(/* rect */)` not found";
+ note = "`fn Tile::rect()` implementation cannot be generated without `widget_set_rect!`";
+ );
+ }
+ }
+
if let Ok(val) = std::env::var("KAS_DEBUG_WIDGET") {
if name == val.as_str() {
println!("{}", scope.to_token_stream());
@@ -692,7 +752,7 @@ pub fn collect_idents(item_impl: &ItemImpl) -> Vec<(usize, Ident)> {
.collect()
}
-pub fn impl_core_methods(name: &str, core_path: &Toks) -> Toks {
+pub fn required_tile_methods(name: &str, core_path: &Toks) -> Toks {
quote! {
#[inline]
fn as_tile(&self) -> &dyn ::kas::Tile {
@@ -702,10 +762,6 @@ pub fn impl_core_methods(name: &str, core_path: &Toks) -> Toks {
fn id_ref(&self) -> &::kas::Id {
core_path._id
}
- #[inline]
- fn rect(&self) -> ::kas::geom::Rect {
- #core_path._rect
- }
#[inline]
fn widget_name(&self) -> &'static str {
@@ -714,6 +770,15 @@ pub fn impl_core_methods(name: &str, core_path: &Toks) -> Toks {
}
}
+pub fn fn_rect(core_path: &Toks) -> Toks {
+ quote! {
+ #[inline]
+ fn rect(&self) -> ::kas::geom::Rect {
+ #core_path._rect
+ }
+ }
+}
+
pub fn impl_widget(
impl_generics: &Toks,
impl_target: &Toks,
diff --git a/crates/kas-widgets/src/adapt/adapt.rs b/crates/kas-widgets/src/adapt/adapt.rs
index 12008a468..6022ece4a 100644
--- a/crates/kas-widgets/src/adapt/adapt.rs
+++ b/crates/kas-widgets/src/adapt/adapt.rs
@@ -17,25 +17,44 @@ impl_scope! {
/// Where [`Map`] allows mapping to a sub-set of input data, `Adapt` allows
/// mapping to a super-set (including internal storage). Further, `Adapt`
/// supports message handlers which mutate internal storage.
+ ///
+ /// # Inner data type
+ ///
+ /// Note that, at least for now, the type of state stored by `Adapt` must
+ /// equal the data type of the inner widget: `state: ::Data`.
+ /// Since `W::Data` must outlive `W` (for our purposes this is not much
+ /// different than if `Widget::Data: 'static`), we cannot support `W::Data`
+ /// like `(&A, &S)` where `state: S`, so we might as well simply pass `&S`
+ /// to the inner widget `W`. This implies that any state from `A` which
+ /// needs to be passed into `W` must be *copied* into `state: W::Data` by
+ /// [`Adapt::on_update`].
+ ///
+ /// (It is possible that the above restrictions will change in the future,
+ /// but they would require Rust to support generic associated types in
+ /// dyn-safe traits (also known as object safe GATs), at least for lifetime
+ /// parameters. There *was* an unstable feature for this,
+ /// `generic_associated_types_extended`, but it was removed due to being
+ /// stale, experimental and unsound. But even if Rust did gain this feature,
+ /// it is not clear that [`Widget::Data`] should be generic.)
#[autoimpl(Scrollable using self.inner where W: trait)]
#[widget {
layout = self.inner;
}]
- pub struct Adapt, S: Debug> {
+ pub struct Adapt {
core: widget_core!(),
- state: S,
+ state: W::Data,
#[widget(&self.state)]
inner: W,
- configure_handler: Option>,
- update_handler: Option>,
- timer_handlers: LinearMap>,
- message_handlers: Vec>,
+ configure_handler: Option>,
+ update_handler: Option>,
+ timer_handlers: LinearMap>,
+ message_handlers: Vec>,
}
impl Self {
/// Construct over `inner` with additional `state`
#[inline]
- pub fn new(inner: W, state: S) -> Self {
+ pub fn new(inner: W, state: W::Data) -> Self {
Adapt {
core: Default::default(),
state,
@@ -50,7 +69,7 @@ impl_scope! {
/// Add a handler to be called on configuration
pub fn on_configure(mut self, handler: F) -> Self
where
- F: Fn(&mut AdaptConfigCx, &mut S) + 'static,
+ F: Fn(&mut AdaptConfigCx, &mut W::Data) + 'static,
{
debug_assert!(self.configure_handler.is_none());
self.configure_handler = Some(Box::new(handler));
@@ -62,7 +81,7 @@ impl_scope! {
/// Children will be updated after the handler is called.
pub fn on_update(mut self, handler: F) -> Self
where
- F: Fn(&mut AdaptConfigCx, &mut S, &A) + 'static,
+ F: Fn(&mut AdaptConfigCx, &mut W::Data, &A) + 'static,
{
debug_assert!(self.update_handler.is_none());
self.update_handler = Some(Box::new(handler));
@@ -76,7 +95,7 @@ impl_scope! {
/// of [`EventState::push_async`](kas::event::EventState::push_async).
pub fn on_timer(mut self, timer_id: u64, handler: H) -> Self
where
- H: Fn(&mut AdaptEventCx, &mut S, &A) + 'static,
+ H: Fn(&mut AdaptEventCx, &mut W::Data, &A) + 'static,
{
debug_assert!(self.timer_handlers.get(&timer_id).is_none());
self.timer_handlers.insert(timer_id, Box::new(handler));
@@ -92,7 +111,7 @@ impl_scope! {
pub fn on_message(self, handler: H) -> Self
where
M: Debug + 'static,
- H: Fn(&mut AdaptEventCx, &mut S, M) + 'static,
+ H: Fn(&mut AdaptEventCx, &mut W::Data, M) + 'static,
{
self.on_messages(move |cx, state, _data| {
if let Some(m) = cx.try_pop() {
@@ -104,7 +123,7 @@ impl_scope! {
/// Add a generic message handler
pub fn on_messages(mut self, handler: H) -> Self
where
- H: Fn(&mut AdaptEventCx, &mut S, &A) + 'static,
+ H: Fn(&mut AdaptEventCx, &mut W::Data, &A) + 'static,
{
self.message_handlers.push(Box::new(handler));
self
diff --git a/crates/kas-widgets/src/adapt/adapt_widget.rs b/crates/kas-widgets/src/adapt/adapt_widget.rs
index 313b7f0cd..fb5ea87e2 100644
--- a/crates/kas-widgets/src/adapt/adapt_widget.rs
+++ b/crates/kas-widgets/src/adapt/adapt_widget.rs
@@ -185,5 +185,12 @@ pub trait AdaptWidget: Widget + Sized {
{
WithLabel::new_dir(self, direction, label)
}
+
+ /// Construct an [`Adapt`] widget over input
+ #[inline]
+ #[must_use]
+ fn with_state(self, state: Self::Data) -> Adapt {
+ Adapt::new(self, state)
+ }
}
impl AdaptWidget for W {}
diff --git a/crates/kas-widgets/src/adapt/align.rs b/crates/kas-widgets/src/adapt/align.rs
deleted file mode 100644
index a089e4e6d..000000000
--- a/crates/kas-widgets/src/adapt/align.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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
-
-//! Alignment
-
-use kas::prelude::*;
-
-impl_scope! {
- /// Apply an alignment hint
- ///
- /// The inner widget chooses how to apply (or ignore) this hint.
- ///
- /// Usually, this type will be constructed through one of the methods on
- /// [`AdaptWidget`](crate::adapt::AdaptWidget).
- #[widget{ derive = self.inner; }]
- pub struct Align {
- pub inner: W,
- /// Hints may be modified directly.
- ///
- /// Use [`Action::RESIZE`] to apply changes.
- pub hints: AlignHints,
- }
-
- impl Self {
- /// Construct
- #[inline]
- pub fn new(inner: W, hints: AlignHints) -> Self {
- Align { inner, hints }
- }
- }
-
- impl Layout for Self {
- fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
- self.inner.set_rect(cx, rect, self.hints.combine(hints));
- }
- }
-}
-
-impl_scope! {
- /// Apply an alignment hint, squash and align the result
- ///
- /// The inner widget chooses how to apply (or ignore) this hint.
- /// The widget is then prevented from stretching beyond its ideal size,
- /// aligning within the available rect.
- ///
- /// Usually, this type will be constructed through one of the methods on
- /// [`AdaptWidget`](crate::adapt::AdaptWidget).
- #[widget{ derive = self.inner; }]
- pub struct Pack {
- pub inner: W,
- /// Hints may be modified directly.
- ///
- /// Use [`Action::RESIZE`] to apply changes.
- pub hints: AlignHints,
- size: Size,
- }
-
- impl Self {
- /// Construct
- #[inline]
- pub fn new(inner: W, hints: AlignHints) -> Self {
- Pack { inner, hints, size: Size::ZERO }
- }
- }
-
- impl Layout for Self {
- fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
- let rules = self.inner.size_rules(sizer, axis);
- self.size.set_component(axis, rules.ideal_size());
- rules
- }
-
- fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
- let align = self.hints.combine(hints).complete_default();
- let rect = align.aligned_rect(self.size, rect);
- self.inner.set_rect(cx, rect, hints);
- }
- }
-}
diff --git a/crates/kas-widgets/src/adapt/mod.rs b/crates/kas-widgets/src/adapt/mod.rs
index 6c22abfd5..c2b084b0a 100644
--- a/crates/kas-widgets/src/adapt/mod.rs
+++ b/crates/kas-widgets/src/adapt/mod.rs
@@ -9,7 +9,6 @@ mod adapt;
mod adapt_cx;
mod adapt_events;
mod adapt_widget;
-mod align;
mod reserve;
mod with_label;
@@ -17,7 +16,7 @@ pub use adapt::{Adapt, Map};
pub use adapt_cx::{AdaptConfigCx, AdaptEventCx};
pub use adapt_events::AdaptEvents;
pub use adapt_widget::*;
-pub use align::{Align, Pack};
#[doc(inline)] pub use kas::hidden::MapAny;
+pub use kas::hidden::{Align, Pack};
pub use reserve::{Margins, Reserve};
pub use with_label::WithLabel;
diff --git a/crates/kas-widgets/src/adapt/with_label.rs b/crates/kas-widgets/src/adapt/with_label.rs
index da6b1c938..8cec44b2d 100644
--- a/crates/kas-widgets/src/adapt/with_label.rs
+++ b/crates/kas-widgets/src/adapt/with_label.rs
@@ -6,7 +6,7 @@
//! Wrapper adding a label
use crate::AccessLabel;
-use kas::{layout, prelude::*};
+use kas::prelude::*;
impl_scope! {
/// A wrapper widget with a label
@@ -18,7 +18,7 @@ impl_scope! {
#[derive(Clone, Default)]
#[widget {
Data = W::Data;
- layout = list! 'row (self.dir, [self.inner, non_navigable!(self.label)]);
+ layout = list![self.inner, self.label].with_direction(self.dir);
}]
pub struct WithLabel {
core: widget_core!(),
@@ -72,15 +72,6 @@ impl_scope! {
self.inner
}
- /// Access layout storage
- ///
- /// The number of columns/rows is fixed at two: the `inner` widget, and
- /// the `label` (in this order, regardless of direction).
- #[inline]
- pub fn layout_storage(&mut self) -> &mut impl layout::RowStorage {
- &mut self.core.row
- }
-
/// Get whether line-wrapping is enabled
#[inline]
pub fn wrap(&self) -> bool {
@@ -112,6 +103,10 @@ impl_scope! {
}
impl Tile for Self {
+ fn nav_next(&self, _: bool, from: Option) -> Option {
+ from.xor(Some(widget_index!(self.inner)))
+ }
+
fn probe(&mut self, _: Coord) -> Id {
self.inner.id()
}
diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs
index 15d6118a1..51c4bd0a5 100644
--- a/crates/kas-widgets/src/button.rs
+++ b/crates/kas-widgets/src/button.rs
@@ -6,9 +6,9 @@
//! Push-buttons
use super::AccessLabel;
-use kas::draw::color::Rgb;
use kas::event::Key;
use kas::prelude::*;
+use kas::theme::{Background, FrameStyle};
use std::fmt::Debug;
impl_scope! {
@@ -17,14 +17,17 @@ impl_scope! {
/// Default alignment of content is centered.
#[widget {
Data = W::Data;
- layout = button!(self.inner, color = self.color);
+ layout = frame!(self.inner)
+ .with_style(FrameStyle::Button)
+ .with_background(self.bg)
+ .align(AlignHints::CENTER);
navigable = true;
hover_highlight = true;
}]
pub struct Button {
core: widget_core!(),
key: Option,
- color: Option,
+ bg: Background,
#[widget]
pub inner: W,
on_press: Option>,
@@ -37,7 +40,7 @@ impl_scope! {
Button {
core: Default::default(),
key: Default::default(),
- color: None,
+ bg: Background::Default,
inner,
on_press: None,
}
@@ -80,19 +83,23 @@ impl_scope! {
self
}
- /// Set button color
- pub fn set_color(&mut self, color: Option) {
- self.color = color;
- }
-
- /// Set button color (chain style)
+ /// Set the frame background color (inline)
+ ///
+ /// The default background is [`Background::Default`].
+ #[inline]
#[must_use]
- pub fn with_color(mut self, color: Rgb) -> Self {
- self.color = Some(color);
+ pub fn with_background(mut self, bg: Background) -> Self {
+ self.bg = bg;
self
}
}
+ impl Tile for Self {
+ fn probe(&mut self, _: Coord) -> Id {
+ self.id()
+ }
+ }
+
impl Events for Self {
fn configure(&mut self, cx: &mut ConfigCx) {
if let Some(key) = self.key.clone() {
diff --git a/crates/kas-widgets/src/check_box.rs b/crates/kas-widgets/src/check_box.rs
index 57331d06f..25d0cad15 100644
--- a/crates/kas-widgets/src/check_box.rs
+++ b/crates/kas-widgets/src/check_box.rs
@@ -174,7 +174,7 @@ impl_scope! {
///
/// This is a [`CheckBox`] with a label.
#[widget{
- layout = list!(self.direction(), [self.inner, non_navigable!(self.label)]);
+ layout = list![self.inner, self.label].with_direction(self.direction());
}]
pub struct CheckButton {
core: widget_core!(),
@@ -194,6 +194,10 @@ impl_scope! {
}
impl Tile for Self {
+ fn nav_next(&self, _: bool, from: Option) -> Option {
+ from.xor(Some(widget_index!(self.inner)))
+ }
+
fn probe(&mut self, _: Coord) -> Id {
self.inner.id()
}
diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs
index 7caf1c7a7..b7a2f49d9 100644
--- a/crates/kas-widgets/src/combobox.rs
+++ b/crates/kas-widgets/src/combobox.rs
@@ -9,6 +9,7 @@ use crate::adapt::AdaptEvents;
use crate::{menu::MenuEntry, Column, Label, Mark};
use kas::event::{Command, FocusSource, ScrollDelta};
use kas::prelude::*;
+use kas::theme::FrameStyle;
use kas::theme::{MarkStyle, TextClass};
use kas::Popup;
use std::fmt::Debug;
@@ -28,7 +29,9 @@ 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 = button! 'frame(row! [self.label, self.mark]);
+ layout = frame!(row! [self.label, self.mark])
+ .with_style(FrameStyle::Button)
+ .align(AlignHints::CENTER);
navigable = true;
hover_highlight = true;
}]
@@ -51,6 +54,10 @@ impl_scope! {
// We have no child within our rect
None
}
+
+ fn probe(&mut self, _: Coord) -> Id {
+ self.id()
+ }
}
impl Events for Self {
diff --git a/crates/kas-widgets/src/float.rs b/crates/kas-widgets/src/float.rs
new file mode 100644
index 000000000..7bbd698bd
--- /dev/null
+++ b/crates/kas-widgets/src/float.rs
@@ -0,0 +1,174 @@
+// 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
+
+//! A pile of widgets "floating" over the top of each other
+
+use kas::prelude::*;
+use kas::Collection;
+
+/// Make a [`Float`] widget
+///
+/// # Syntax
+///
+/// > _Collection_ :\
+/// > `float!` `[` _Items_\? `]`
+/// >
+/// > _Items_ :\
+/// > (_Item_ `,`)\* _Item_ `,`\?
+///
+/// ## Stand-alone usage
+///
+/// When used as a stand-alone macro, `float! [/* ... */]` is just syntactic sugar
+/// for `Float::new(kas::collection! [/* ... */])`.
+///
+/// In this case, _Item_ may be:
+///
+/// - A string literal (interpreted as a label widget), optionally followed by
+/// an [`align`] or [`pack`] method call
+/// - An expression yielding an object implementing `Widget`
+///
+/// In case all _Item_ instances are a string literal, the data type of the
+/// `float!` widget will be `()`; otherwise the data type of the widget is `_A`
+/// where `_A` is a generic type parameter of the widget.
+///
+/// ## Usage within widget layout syntax
+///
+/// When called within [widget layout syntax], `float!` may be evaluated as a
+/// recursive macro and the result does not have a specified type, except that
+/// methods [`map_any`], [`align`] and [`pack`] are supported via emulation.
+///
+/// In this case, _Item_ is evaluated using [widget layout syntax]. This is
+/// broadly similar to the above with a couple of exceptions:
+///
+/// - Supported layout macros do not need to be imported to the module scope
+/// - An _Item_ may be a `#[widget]` field of the widget
+///
+/// # Example
+///
+/// ```
+/// # use kas::prelude::*;
+/// let my_widget = kas_widgets::float! [
+/// "one".pack(AlignHints::TOP_LEFT),
+/// "two".pack(AlignHints::BOTTOM_RIGHT),
+/// "some text\nin the\nbackground"
+/// ];
+/// ```
+///
+/// [widget layout syntax]: macro@widget#layout-1
+/// [`map_any`]: crate::AdaptWidgetAny::map_any
+/// [`align`]: crate::AdaptWidget::align
+/// [`pack`]: crate::AdaptWidget::pack
+#[macro_export]
+macro_rules! float {
+ ( $( $ee:expr ),* ) => {
+ $crate::Float::new( ::kas::collection! [ $( $ee ),* ] )
+ };
+ ( $( $ee:expr ),+ , ) => {
+ $crate::Float::new( ::kas::collection! [ $( $ee ),+ ] )
+ };
+}
+
+impl_scope! {
+ /// A float widget
+ ///
+ /// All widgets occupy the same space with the first child on top.
+ ///
+ /// Size is determined as the maximum required by any child for each axis.
+ /// All widgets are assigned this size. It is usually necessary to use
+ /// [`pack`] or a similar mechanism to constrain a child to avoid it hiding
+ /// the content underneath (note that even if an unconstrained child does
+ /// not *visually* hide everything beneath, it may still "occupy" the
+ /// assigned area, preventing mouse clicks from reaching the widget
+ /// beneath).
+ ///
+ /// [`pack`]: crate::AdaptWidget::pack
+ #[derive(Clone, Default)]
+ #[widget]
+ pub struct Float {
+ core: widget_core!(),
+ widgets: C,
+ }
+
+ impl Self {
+ /// Construct a float
+ #[inline]
+ pub fn new(widgets: C) -> Self {
+ Float {
+ core: Default::default(),
+ widgets,
+ }
+ }
+ }
+
+ impl Layout for Self {
+ fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
+ let mut rules = SizeRules::EMPTY;
+ for i in 0..self.widgets.len() {
+ if let Some(child) = self.widgets.get_mut_tile(i) {
+ rules = rules.max(child.size_rules(sizer.re(), axis));
+ }
+ }
+ rules
+ }
+
+ fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
+ widget_set_rect!(rect);
+ for i in 0..self.widgets.len() {
+ if let Some(child) = self.widgets.get_mut_tile(i) {
+ child.set_rect(cx, rect, hints);
+ }
+ }
+ }
+
+ fn draw(&mut self, mut draw: DrawCx) {
+ let mut iter = (0..self.widgets.len()).rev();
+ if let Some(first) = iter.next() {
+ if let Some(child) = self.widgets.get_mut_tile(first) {
+ child.draw(draw.re());
+ }
+ }
+ for i in iter {
+ if let Some(child) = self.widgets.get_mut_tile(i) {
+ draw.with_pass(|draw| child.draw(draw));
+ }
+ }
+ }
+ }
+
+ impl Tile for Self {
+ #[inline]
+ fn num_children(&self) -> usize {
+ self.widgets.len()
+ }
+
+ fn get_child(&self, index: usize) -> Option<&dyn Tile> {
+ self.widgets.get_tile(index)
+ }
+
+ fn probe(&mut self, coord: Coord) -> Id {
+ for i in 0..self.widgets.len() {
+ if let Some(child) = self.widgets.get_mut_tile(i) {
+ if let Some(id) = child.try_probe(coord) {
+ return id;
+ }
+ }
+ }
+ self.id()
+ }
+ }
+
+ impl Widget for Self {
+ type Data = C::Data;
+
+ fn for_child_node(
+ &mut self,
+ data: &C::Data,
+ index: usize,
+ closure: Box) + '_>,
+ ) {
+ self.widgets.for_node(data, index, closure);
+ }
+ }
+}
diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs
index 44eb6a456..74ae5b236 100644
--- a/crates/kas-widgets/src/frame.rs
+++ b/crates/kas-widgets/src/frame.rs
@@ -6,6 +6,41 @@
//! A simple frame
use kas::prelude::*;
+use kas::theme::{Background, FrameStyle};
+
+/// Make a [`Frame`] widget
+///
+/// # Syntax
+///
+/// ## Stand-alone usage
+///
+/// When called as a stand-alone macro, `frame!(inner)` is just syntactic sugar
+/// for `Frame::new(inner)`, and yes, this makes the macro pointless.
+///
+/// ## Usage within widget layout syntax
+///
+/// When called within [widget layout syntax], `frame!` may be evaluated as a
+/// recursive macro and the result does not have a specified type, except that
+/// methods [`map_any`], [`align`], [`pack`] and [`with_style`] are supported
+/// via emulation.
+///
+/// # Example
+///
+/// ```
+/// let my_widget = kas_widgets::frame!(kas_widgets::Label::new("content"));
+/// ```
+///
+/// [widget layout syntax]: macro@widget#layout-1
+/// [`map_any`]: crate::AdaptWidgetAny::map_any
+/// [`align`]: crate::AdaptWidget::align
+/// [`pack`]: crate::AdaptWidget::pack
+/// [`with_style`]: Frame::with_style
+#[macro_export]
+macro_rules! frame {
+ ( $e:expr ) => {
+ $crate::Frame::new($e)
+ };
+}
impl_scope! {
/// A frame around content
@@ -18,10 +53,12 @@ impl_scope! {
#[derive(Clone, Default)]
#[widget{
Data = W::Data;
- layout = frame!(self.inner);
+ layout = frame!(self.inner).with_style(self.style);
}]
pub struct Frame {
core: widget_core!(),
+ style: FrameStyle,
+ bg: Background,
/// The inner widget
#[widget]
pub inner: W,
@@ -33,8 +70,33 @@ impl_scope! {
pub fn new(inner: W) -> Self {
Frame {
core: Default::default(),
+ style: FrameStyle::Frame,
+ bg: Background::default(),
inner,
}
}
+
+ /// Set the frame style (inline)
+ ///
+ /// The default style is [`FrameStyle::Frame`].
+ ///
+ /// Note: using [`FrameStyle::NavFocus`] does not automatically make
+ /// this widget interactive. Use [`NavFrame`](crate::NavFrame) for that.
+ #[inline]
+ #[must_use]
+ pub fn with_style(mut self, style: FrameStyle) -> Self {
+ self.style = style;
+ self
+ }
+
+ /// Set the frame background color (inline)
+ ///
+ /// The default background is [`Background::Default`].
+ #[inline]
+ #[must_use]
+ pub fn with_background(mut self, bg: Background) -> Self {
+ self.bg = bg;
+ self
+ }
}
}
diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs
index 027a18084..54ce2a6da 100644
--- a/crates/kas-widgets/src/grid.rs
+++ b/crates/kas-widgets/src/grid.rs
@@ -10,6 +10,124 @@ use kas::layout::{GridSetter, GridSolver, RulesSetter, RulesSolver};
use kas::{layout, prelude::*, CellCollection};
use std::ops::{Index, IndexMut};
+/// Make a [`Grid`] widget
+///
+/// Constructs a table with auto-determined number of rows and columns.
+/// Cells may overlap, in which case behaviour is identical to [`float!`]: the
+/// first declared item is on top.
+///
+/// # Syntax
+///
+/// > _Collection_ :\
+/// > `collection!` `[` _ItemArms_\? `]`
+/// >
+/// > _ItemArms_ :\
+/// > (_ItemArm_ `,`)\* _ItemArm_ `,`\?
+/// >
+/// > _ItemArm_ :\
+/// > `(` _Column_ `,` _Row_ `)` `=>` _Item_
+/// >
+/// > _Column_, _Row_ :\
+/// > _LitInt_ | ( _LitInt_ `..` `+` _LitInt_ ) | ( _LitInt_ `..`
+/// > _LitInt_ ) | ( _LitInt_ `..=` _LitInt_ )
+///
+/// Here, _Column_ and _Row_ are selected via an index (from 0), a range of
+/// indices, or a start + increment. For example, `2` = `2..+1` = `2..3` =
+/// `2..=2` while `5..+2` = `5..7` = `5..=6`.
+///
+/// ## Stand-alone usage
+///
+/// When used as a stand-alone macro, `grid! [/* ... */]` is just syntactic
+/// sugar for `Grid::new(kas::cell_collection! [/* ... */])`.
+///
+/// In this case, _Item_ may be:
+///
+/// - A string literal (interpreted as a label widget), optionally followed by
+/// an [`align`] or [`pack`] method call
+/// - An expression yielding an object implementing `Widget`
+///
+/// In case all _Item_ instances are a string literal, the data type of the
+/// `grid!` widget will be `()`; otherwise the data type of the widget is `_A`
+/// where `_A` is a generic type parameter of the widget.
+///
+/// ## Usage within widget layout syntax
+///
+/// In this case, _Item_ uses [widget layout syntax]. This is broadly similar to
+/// the above with a couple of exceptions:
+///
+/// - Supported layout macros do not need to be imported to the module scope
+/// - An _Item_ may be a `#[widget]` field of the widget
+///
+/// # Example
+///
+/// ```
+/// let my_widget = kas_widgets::grid! {
+/// (0, 0) => "one",
+/// (1, 0) => "two",
+/// (0..2, 1) => "three",
+/// };
+/// ```
+///
+/// [widget layout syntax]: macro@widget#layout-1
+/// [`align`]: crate::AdaptWidget::align
+/// [`pack`]: crate::AdaptWidget::pack
+/// [`float!`]: crate::float
+#[macro_export]
+macro_rules! grid {
+ ( $( ($cc:expr, $rr:expr) => $ee:expr ),* ) => {
+ $crate::Grid::new( ::kas::cell_collection! [ $( ($cc, $rr) => $ee ),* ] )
+ };
+ ( $( ($cc:expr, $rr:expr) => $ee:expr ),+ , ) => {
+ $crate::Grid::new( ::kas::cell_collection! [ $( ($cc, $rr) => $ee ),+ ] )
+ };
+}
+
+/// Define a [`Grid`] as a sequence of rows
+///
+/// This is just special convenience syntax for defining a [`Grid`]. See also
+/// [`grid!`] documentation.
+///
+/// # Example
+///
+/// ```
+/// let my_widget = kas_widgets::aligned_column! [
+/// row!["one", "two"],
+/// row!["three", "four"],
+/// ];
+/// ```
+#[macro_export]
+macro_rules! aligned_column {
+ () => {
+ $crate::Grid::new(::kas::cell_collection! [])
+ };
+ ($(row![$($ee:expr),* $(,)?]),+ $(,)?) => {
+ $crate::Grid::new(::kas::cell_collection![aligned_column $(row![$($ee),*]),+])
+ };
+}
+
+/// Define a [`Grid`] as a sequence of columns
+///
+/// This is just special convenience syntax for defining a [`Grid`]. See also
+/// [`grid!`] documentation.
+///
+/// # Example
+///
+/// ```
+/// let my_widget = kas_widgets::aligned_row! [
+/// column!["one", "two"],
+/// column!["three", "four"],
+/// ];
+/// ```
+#[macro_export]
+macro_rules! aligned_row {
+ () => {
+ $crate::Grid::new(::kas::cell_collection! [])
+ };
+ ($(column![$($ee:expr),* $(,)?]),+ $(,)?) => {
+ $crate::Grid::new(::kas::cell_collection![aligned_row $(column![$($ee),*]),+])
+ };
+}
+
impl_scope! {
/// A generic grid widget
///
diff --git a/crates/kas-widgets/src/grip.rs b/crates/kas-widgets/src/grip.rs
index 461361189..c74736e47 100644
--- a/crates/kas-widgets/src/grip.rs
+++ b/crates/kas-widgets/src/grip.rs
@@ -73,6 +73,8 @@ impl_scope! {
core: widget_core!(),
// The track is the area within which this GripPart may move
track: Rect,
+ // The position of the grip handle
+ rect: Rect,
press_coord: Coord,
}
@@ -82,9 +84,19 @@ impl_scope! {
SizeRules::EMPTY
}
+ fn set_rect(&mut self, _: &mut ConfigCx, rect: Rect, _: AlignHints) {
+ self.rect = rect;
+ }
+
fn draw(&mut self, _: DrawCx) {}
}
+ impl Tile for Self {
+ fn rect(&self) -> Rect {
+ self.rect
+ }
+ }
+
impl Events for GripPart {
type Data = ();
@@ -121,6 +133,7 @@ impl_scope! {
GripPart {
core: Default::default(),
track: Default::default(),
+ rect: Default::default(),
press_coord: Coord::ZERO,
}
}
@@ -153,7 +166,7 @@ impl_scope! {
///
/// This size may be read via `self.rect().size`.
pub fn set_size(&mut self, size: Size) {
- widget_set_rect!(Rect { pos: self.rect().pos, size, });
+ self.rect.size = size;
}
/// Get the current grip position
@@ -187,7 +200,7 @@ impl_scope! {
let offset = offset.min(self.max_offset()).max(Offset::ZERO);
let grip_pos = self.track.pos + offset;
if grip_pos != self.rect().pos {
- widget_set_rect!(Rect { pos: grip_pos, size: self.rect().size });
+ self.rect.pos = grip_pos;
cx.redraw(self);
}
offset
diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs
index 7d7b9899f..734626ba1 100644
--- a/crates/kas-widgets/src/lib.rs
+++ b/crates/kas-widgets/src/lib.rs
@@ -71,6 +71,7 @@ pub mod dialog;
pub mod edit;
mod event_config;
mod filler;
+mod float;
mod frame;
mod grid;
mod grip;
@@ -94,8 +95,6 @@ mod stack;
mod tab_stack;
mod text;
-pub use kas_macros::{aligned_column, aligned_row, column, float, grid, list, row};
-
pub use crate::image::Image;
#[cfg(feature = "image")] pub use crate::image::ImageError;
pub use button::Button;
@@ -104,6 +103,7 @@ pub use combobox::ComboBox;
pub use edit::{EditBox, EditField, EditGuard};
pub use event_config::EventConfig;
pub use filler::Filler;
+pub use float::Float;
pub use frame::Frame;
pub use grid::Grid;
pub use grip::{GripMsg, GripPart};
diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs
index acbd25aab..b0e7ad3e0 100644
--- a/crates/kas-widgets/src/list.rs
+++ b/crates/kas-widgets/src/list.rs
@@ -14,6 +14,186 @@ use kas::Collection;
use std::collections::hash_map::{Entry, HashMap};
use std::ops::{Index, IndexMut};
+/// Make a [`Row`] widget
+///
+/// # Syntax
+///
+/// > _Collection_ :\
+/// > `row!` `[` _Items_\? `]`
+/// >
+/// > _Items_ :\
+/// > (_Item_ `,`)\* _Item_ `,`\?
+///
+/// ## Stand-alone usage
+///
+/// When used as a stand-alone macro, `row! [/* ... */]` is just syntactic sugar
+/// for `Row::new(kas::collection! [/* ... */])`.
+///
+/// In this case, _Item_ may be:
+///
+/// - A string literal (interpreted as a label widget), optionally followed by
+/// an [`align`] or [`pack`] method call
+/// - An expression yielding an object implementing `Widget`
+///
+/// In case all _Item_ instances are a string literal, the data type of the
+/// `row!` widget will be `()`; otherwise the data type of the widget is `_A`
+/// where `_A` is a generic type parameter of the widget.
+///
+/// ## Usage within widget layout syntax
+///
+/// When called within [widget layout syntax], `row!` may be evaluated as a
+/// recursive macro and the result does not have a specified type, except that
+/// methods [`map_any`], [`align`] and [`pack`] are supported via emulation.
+///
+/// In this case, _Item_ is evaluated using [widget layout syntax]. This is
+/// broadly similar to the above with a couple of exceptions:
+///
+/// - Supported layout macros do not need to be imported to the module scope
+/// - An _Item_ may be a `#[widget]` field of the widget
+///
+/// # Example
+///
+/// ```
+/// let my_widget = kas_widgets::row!["one", "two"];
+/// ```
+///
+/// [widget layout syntax]: macro@widget#layout-1
+/// [`map_any`]: crate::AdaptWidgetAny::map_any
+/// [`align`]: crate::AdaptWidget::align
+/// [`pack`]: crate::AdaptWidget::pack
+#[macro_export]
+macro_rules! row {
+ ( $( $ee:expr ),* ) => {
+ $crate::Row::new( ::kas::collection! [ $( $ee ),* ] )
+ };
+ ( $( $ee:expr ),+ , ) => {
+ $crate::Row::new( ::kas::collection! [ $( $ee ),+ ] )
+ };
+}
+
+/// Make a [`Column`] widget
+///
+/// # Syntax
+///
+/// > _Collection_ :\
+/// > `column!` `[` _Items_\? `]`
+/// >
+/// > _Items_ :\
+/// > (_Item_ `,`)\* _Item_ `,`\?
+///
+/// ## Stand-alone usage
+///
+/// When used as a stand-alone macro, `column! [/* ... */]` is just syntactic sugar
+/// for `Column::new(kas::collection! [/* ... */])`.
+///
+/// In this case, _Item_ may be:
+///
+/// - A string literal (interpreted as a label widget), optionally followed by
+/// an [`align`] or [`pack`] method call
+/// - An expression yielding an object implementing `Widget`
+///
+/// In case all _Item_ instances are a string literal, the data type of the
+/// `column!` widget will be `()`; otherwise the data type of the widget is `_A`
+/// where `_A` is a generic type parameter of the widget.
+///
+/// ## Usage within widget layout syntax
+///
+/// When called within [widget layout syntax], `column!` may be evaluated as a
+/// recursive macro and the result does not have a specified type, except that
+/// methods [`map_any`], [`align`] and [`pack`] are supported via emulation.
+///
+/// In this case, _Item_ is evaluated using [widget layout syntax]. This is
+/// broadly similar to the above with a couple of exceptions:
+///
+/// - Supported layout macros do not need to be imported to the module scope
+/// - An _Item_ may be a `#[widget]` field of the widget
+///
+/// # Example
+///
+/// ```
+/// let my_widget = kas_widgets::column! [
+/// "one",
+/// "two",
+/// ];
+/// ```
+///
+/// [widget layout syntax]: macro@widget#layout-1
+/// [`map_any`]: crate::AdaptWidgetAny::map_any
+/// [`align`]: crate::AdaptWidget::align
+/// [`pack`]: crate::AdaptWidget::pack
+#[macro_export]
+macro_rules! column {
+ ( $( $ee:expr ),* ) => {
+ $crate::Column::new( ::kas::collection! [ $( $ee ),* ] )
+ };
+ ( $( $ee:expr ),+ , ) => {
+ $crate::Column::new( ::kas::collection! [ $( $ee ),+ ] )
+ };
+}
+
+/// Make a [`List`] widget
+///
+/// # Syntax
+///
+/// > _Collection_ :\
+/// > `list!` `[` _Items_\? `]`
+/// >
+/// > _Items_ :\
+/// > (_Item_ `,`)\* _Item_ `,`\?
+///
+/// ## Stand-alone usage
+///
+/// When used as a stand-alone macro, `list! [/* ... */]` is just syntactic sugar
+/// for `List::new(kas::collection! [/* ... */])`.
+///
+/// In this case, _Item_ may be:
+///
+/// - A string literal (interpreted as a label widget), optionally followed by
+/// an [`align`] or [`pack`] method call
+/// - An expression yielding an object implementing `Widget`
+///
+/// In case all _Item_ instances are a string literal, the data type of the
+/// `list!` widget will be `()`; otherwise the data type of the widget is `_A`
+/// where `_A` is a generic type parameter of the widget.
+///
+/// ## Usage within widget layout syntax
+///
+/// When called within [widget layout syntax], `list!` may be evaluated as a
+/// recursive macro and the result does not have a specified type, except that
+/// methods [`map_any`], [`align`], [`pack`] and [`with_direction`] are
+/// supported via emulation. In this case, calling [`with_direction`] is
+/// required. Note that the argument passed to [`with_direction`] is expanded
+/// at the use site, so for example `.with_direction(self.dir)` will read
+/// `self.dir` whenever layout is computed.
+///
+/// In this case, _Item_ is evaluated using [widget layout syntax]. This is
+/// broadly similar to the above with a couple of exceptions:
+///
+/// - Supported layout macros do not need to be imported to the module scope
+/// - An _Item_ may be a `#[widget]` field of the widget
+///
+/// # Example
+///
+/// ```
+/// let my_widget = kas_widgets::list! ["one", "two"]
+/// .with_direction(kas::dir::Left);
+/// ```
+///
+/// [widget layout syntax]: macro@widget#layout-1
+/// [`map_any`]: crate::AdaptWidgetAny::map_any
+/// [`align`]: crate::AdaptWidget::align
+/// [`pack`]: crate::AdaptWidget::pack
+/// [`with_direction`]: List::with_direction
+#[macro_export]
+macro_rules! list {
+ ( $( $ee:expr ),* ) => {
+ $crate::List::new( ::kas::collection! [ $( $ee ),* ] )
+ };
+ ( $( $ee:expr ),+ , ) => {
+ $crate::List::new( ::kas::collection! [ $( $ee ),+ ] )
+ };
+}
+
/// A generic row widget
///
/// See documentation of [`List`] type.
@@ -261,9 +441,9 @@ impl_scope! {
}
}
- impl List {
+ impl List {
/// Set the direction of contents
- pub fn set_direction(&mut self, cx: &mut EventState, direction: Direction) {
+ pub fn set_direction(&mut self, cx: &mut EventState, direction: D) {
if direction == self.direction {
return;
}
@@ -293,6 +473,13 @@ impl_scope! {
self.direction.as_direction()
}
+ /// Set the direction of contents (inline)
+ #[inline]
+ pub fn with_direction(mut self, direction: D) -> Self {
+ self.direction = direction;
+ self
+ }
+
/// Access layout storage
///
/// The number of columns/rows is [`Self.len`].
diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs
index f9833eb3f..2b063cffe 100644
--- a/crates/kas-widgets/src/nav_frame.rs
+++ b/crates/kas-widgets/src/nav_frame.rs
@@ -22,7 +22,7 @@ impl_scope! {
#[widget{
Data = W::Data;
navigable = true;
- layout = frame!(self.inner, style = kas::theme::FrameStyle::NavFocus);
+ layout = frame!(self.inner).with_style(kas::theme::FrameStyle::NavFocus);
}]
pub struct NavFrame {
core: widget_core!(),
diff --git a/crates/kas-widgets/src/radio_box.rs b/crates/kas-widgets/src/radio_box.rs
index 6f732fbde..124f82c95 100644
--- a/crates/kas-widgets/src/radio_box.rs
+++ b/crates/kas-widgets/src/radio_box.rs
@@ -135,7 +135,7 @@ impl_scope! {
///
/// See also [`RadioBox`] which excludes the label.
#[widget{
- layout = list!(self.direction(), [self.inner, non_navigable!(self.label)]);
+ layout = list![self.inner, self.label].with_direction(self.direction());
}]
pub struct RadioButton {
core: widget_core!(),
@@ -155,6 +155,10 @@ impl_scope! {
}
impl Tile for Self {
+ fn nav_next(&self, _: bool, from: Option) -> Option {
+ from.xor(Some(widget_index!(self.inner)))
+ }
+
fn probe(&mut self, _: Coord) -> Id {
self.inner.id()
}
diff --git a/crates/kas-widgets/src/spinner.rs b/crates/kas-widgets/src/spinner.rs
index 65158c8f0..5e99cb982 100644
--- a/crates/kas-widgets/src/spinner.rs
+++ b/crates/kas-widgets/src/spinner.rs
@@ -163,10 +163,8 @@ impl_scope! {
/// - With floating-point types, ensure that `step` is exactly
/// representable, e.g. an integer or a power of 2.
#[widget {
- layout = frame!(row! [
- self.edit,
- column! [self.b_up, self.b_down],
- ], style = FrameStyle::EditBox);
+ layout = frame!(row![self.edit, column! [self.b_up, self.b_down]])
+ .with_style(FrameStyle::EditBox);
}]
pub struct Spinner {
core: widget_core!(),
diff --git a/crates/kas-widgets/src/tab_stack.rs b/crates/kas-widgets/src/tab_stack.rs
index 4be72db1c..b2e6027cc 100644
--- a/crates/kas-widgets/src/tab_stack.rs
+++ b/crates/kas-widgets/src/tab_stack.rs
@@ -21,7 +21,7 @@ impl_scope! {
/// This is a special variant of `Button` which sends a [`Select`] on press.
#[widget {
Data = ();
- layout = frame!(self.label, style = FrameStyle::Tab);
+ layout = frame!(self.label).with_style(FrameStyle::Tab);
navigable = true;
hover_highlight = true;
}]
@@ -99,10 +99,7 @@ impl_scope! {
/// See also the main implementing widget: [`Stack`].
#[impl_default(Self::new())]
#[widget {
- layout = list!(self.direction, [
- self.stack,
- self.tabs,
- ]);
+ layout = list![self.stack, self.tabs].with_direction(self.direction);
}]
pub struct TabStack {
core: widget_core!(),
diff --git a/examples/counter.rs b/examples/counter.rs
index bb9390af5..a9cc5024b 100644
--- a/examples/counter.rs
+++ b/examples/counter.rs
@@ -6,7 +6,7 @@
//! Counter example (simple button)
use kas::prelude::*;
-use kas::widgets::{column, format_value, row, Adapt, Button};
+use kas::widgets::{column, format_value, row, Button};
#[derive(Clone, Debug)]
struct Increment(i32);
@@ -21,7 +21,8 @@ fn counter() -> impl Widget {
.map_any(),
];
- Adapt::new(tree, 0).on_message(|_, count, Increment(add)| *count += add)
+ tree.with_state(0)
+ .on_message(|_, count, Increment(add)| *count += add)
}
fn main() -> kas::runner::Result<()> {
diff --git a/examples/data-list-view.rs b/examples/data-list-view.rs
index 67f2247fc..41fd2e29b 100644
--- a/examples/data-list-view.rs
+++ b/examples/data-list-view.rs
@@ -212,7 +212,9 @@ fn main() -> kas::runner::Result<()> {
ScrollBars::new(list).with_fixed_bars(false, true),
];
- let ui = Adapt::new(tree, data).on_message(|_, data, control| data.handle(control));
+ let ui = tree
+ .with_state(data)
+ .on_message(|_, data, control| data.handle(control));
let window = Window::new(ui, "Dynamic widget demo");
diff --git a/examples/data-list.rs b/examples/data-list.rs
index 1ef20f9b8..ff9139337 100644
--- a/examples/data-list.rs
+++ b/examples/data-list.rs
@@ -22,7 +22,7 @@
use kas::prelude::*;
use kas::widgets::edit::{EditBox, EditField, EditGuard};
use kas::widgets::{column, row};
-use kas::widgets::{Adapt, Button, Label, List, RadioButton, ScrollBarRegion, Separator, Text};
+use kas::widgets::{Button, Label, List, RadioButton, ScrollBarRegion, Separator, Text};
#[derive(Debug)]
struct SelectEntry(usize);
@@ -178,7 +178,9 @@ fn main() -> kas::runner::Result<()> {
ScrollBarRegion::new(list).with_fixed_bars(false, true),
];
- let ui = Adapt::new(tree, data).on_message(|_, data, control| data.handle(control));
+ let ui = tree
+ .with_state(data)
+ .on_message(|_, data, control| data.handle(control));
let window = Window::new(ui, "Dynamic widget demo");
diff --git a/examples/gallery.rs b/examples/gallery.rs
index d01403b8e..b1b00c5f8 100644
--- a/examples/gallery.rs
+++ b/examples/gallery.rs
@@ -163,19 +163,19 @@ fn widgets() -> Box> {
img_light.clone(),
ConfigMsg::Theme(ThemeConfigMsg::SetActiveScheme("light".to_string()))
)
- .with_color("#B38DF9".parse().unwrap())
+ .with_background("#B38DF9".parse().unwrap())
.with_access_key(Key::Character("h".into())),
Button::new_msg(
img_light,
ConfigMsg::Theme(ThemeConfigMsg::SetActiveScheme("blue".to_string()))
)
- .with_color("#7CDAFF".parse().unwrap())
+ .with_background("#7CDAFF".parse().unwrap())
.with_access_key(Key::Character("b".into())),
Button::new_msg(
img_dark,
ConfigMsg::Theme(ThemeConfigMsg::SetActiveScheme("dark".to_string()))
)
- .with_color("#E77346".parse().unwrap())
+ .with_background("#E77346".parse().unwrap())
.with_access_key(Key::Character("k".into())),
]
.map_any()
@@ -234,7 +234,8 @@ fn widgets() -> Box> {
row!["Child window", popup_edit_box],
];
- let ui = Adapt::new(widgets, data)
+ let ui = widgets
+ .with_state(data)
.on_message(|_, data, ScrollMsg(value)| {
println!("ScrollMsg({value})");
data.ratio = value as f32 / 100.0;
@@ -340,7 +341,8 @@ Demonstration of *as-you-type* formatting from **Markdown**.
}),
];
- let ui = Adapt::new(ui, Data::default())
+ let ui = ui
+ .with_state(Data::default())
.on_update(|_, data, app_data: &AppData| data.disabled = app_data.disabled)
.on_message(|_, data, MsgDirection| {
data.dir = match data.dir {
@@ -409,7 +411,8 @@ fn filter_list() -> Box> {
sel_buttons.map(|data: &Data| &data.mode),
ScrollBars::new(list_view),
];
- let ui = Adapt::new(ui, data)
+ let ui = ui
+ .with_state(data)
.on_message(|_, data, mode| data.mode = mode)
.on_message(|_, data, selection: SelectionMsg| match selection {
SelectionMsg::Select(i) => println!("Selected: {}", &data.list[i]),
@@ -601,28 +604,30 @@ fn main() -> kas::runner::Result<()> {
.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:?}");
- let act = cx
- .config()
- .update_theme(|theme| theme.set_active_theme(name));
- cx.window_action(act);
- }
- Menu::Colour(name) => {
- println!("Colour scheme: {name:?}");
- let act = cx
- .config()
- .update_theme(|theme| theme.set_active_scheme(name));
- cx.window_action(act);
- }
- Menu::Disabled(disabled) => {
- state.disabled = disabled;
- }
- Menu::Quit => {
- cx.exit();
- }
- });
+ let ui = ui
+ .with_state(AppData::default())
+ .on_message(|cx, state, msg| match msg {
+ Menu::Theme(name) => {
+ println!("Theme: {name:?}");
+ let act = cx
+ .config()
+ .update_theme(|theme| theme.set_active_theme(name));
+ cx.window_action(act);
+ }
+ Menu::Colour(name) => {
+ println!("Colour scheme: {name:?}");
+ let act = cx
+ .config()
+ .update_theme(|theme| theme.set_active_scheme(name));
+ cx.window_action(act);
+ }
+ Menu::Disabled(disabled) => {
+ state.disabled = disabled;
+ }
+ Menu::Quit => {
+ cx.exit();
+ }
+ });
runner.add(Window::new(ui, "Gallery — Widgets"));
runner.run()
diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs
index b42b02a54..8036e83ce 100644
--- a/examples/stopwatch.rs
+++ b/examples/stopwatch.rs
@@ -9,7 +9,7 @@ use std::time::{Duration, Instant};
use kas::decorations::Decorations;
use kas::prelude::*;
-use kas::widgets::{format_data, row, Adapt, Button};
+use kas::widgets::{format_data, row, Button};
#[derive(Clone, Debug)]
struct MsgReset;
@@ -29,7 +29,7 @@ fn make_window() -> impl Widget {
Button::label_msg("&start / &stop", MsgStart).map_any(),
];
- Adapt::new(ui, Timer::default())
+ ui.with_state(Timer::default())
.on_configure(|cx, _| cx.enable_alt_bypass(true))
.on_message(|_, timer, MsgReset| *timer = Timer::default())
.on_message(|cx, timer, MsgStart| {
diff --git a/examples/sync-counter.rs b/examples/sync-counter.rs
index 7e787254e..062ade4e8 100644
--- a/examples/sync-counter.rs
+++ b/examples/sync-counter.rs
@@ -7,7 +7,7 @@
//!
//! Each window shares the counter, but has its own increment step.
-use kas::widgets::{column, format_data, row, Adapt, Button, Label, Slider};
+use kas::widgets::{column, format_data, row, AdaptWidget, Button, Label, Slider};
use kas::{messages::MessageStack, Window};
#[derive(Clone, Debug)]
@@ -46,7 +46,8 @@ fn counter(title: &str) -> Window {
],
];
- let ui = Adapt::new(ui, initial)
+ let ui = ui
+ .with_state(initial)
.on_update(|_, state, count| state.0 = *count)
.on_message(|_, state, SetValue(v)| state.1 = v);
Window::new(ui, title)
diff --git a/examples/times-tables.rs b/examples/times-tables.rs
index 1bfbfd7c2..f85f283e0 100644
--- a/examples/times-tables.rs
+++ b/examples/times-tables.rs
@@ -2,7 +2,7 @@
use kas::prelude::*;
use kas::view::{driver, MatrixData, MatrixView, SelectionMode, SelectionMsg, SharedData};
-use kas::widgets::{column, row, Adapt, EditBox, ScrollBars};
+use kas::widgets::{column, row, EditBox, ScrollBars};
#[derive(Debug)]
struct TableSize(usize);
@@ -63,7 +63,8 @@ fn main() -> kas::runner::Result<()> {
],
table.align(AlignHints::RIGHT),
];
- let ui = Adapt::new(ui, TableSize(12))
+ let ui = ui
+ .with_state(TableSize(12))
.on_message(|_, data, SetLen(len)| data.0 = len)
.on_message(|_, _, selection| match selection {
SelectionMsg::<(usize, usize)>::Select((col, row)) => {
diff --git a/tests/layout_macros.rs b/tests/layout_macros.rs
index 7378c69df..a5b931768 100644
--- a/tests/layout_macros.rs
+++ b/tests/layout_macros.rs
@@ -16,7 +16,7 @@ fn row() {
#[test]
fn list() {
- use_widget(list!(left, ["one", "two"]));
+ use_widget(list!["one", "two"].with_direction(kas::dir::Left));
}
#[test]