Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move layout macros to kas-widgets; do not recurse internally #440

Merged
merged 7 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,22 @@ wayland = ["kas-core/wayland"]
# Support X11
x11 = ["kas-core/x11"]

# Optimise Node using unsafe code
# 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"]

[dependencies]
Expand Down
5 changes: 4 additions & 1 deletion crates/kas-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ dark-light = ["dep:dark-light"]
# Support spawning async tasks
spawn = ["dep:async-global-executor"]

# Optimise Node using unsafe code
# Optimize generated layout widgets
recursive-layout-widgets = ["kas-macros/recursive-layout-widgets"]

# Optimize Node using unsafe code
unsafe_node = []

[build-dependencies]
Expand Down
6 changes: 3 additions & 3 deletions crates/kas-core/src/geom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,19 +636,19 @@ impl std::ops::SubAssign<Offset> for Rect {
#[cfg_attr(doc_cfg, doc(cfg(feature = "winit")))]
mod winit_impls {
use super::{Coord, Size};
use crate::cast::{Cast, CastApprox, Conv, ConvApprox};
use crate::cast::{Cast, CastApprox, Conv, ConvApprox, Result};
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};

impl<X: CastApprox<i32>> ConvApprox<PhysicalPosition<X>> for Coord {
#[inline]
fn try_conv_approx(pos: PhysicalPosition<X>) -> cast::Result<Self> {
fn try_conv_approx(pos: PhysicalPosition<X>) -> Result<Self> {
Ok(Coord(pos.x.cast_approx(), pos.y.cast_approx()))
}
}

impl<X: Cast<i32>> Conv<PhysicalSize<X>> for Size {
#[inline]
fn try_conv(size: PhysicalSize<X>) -> cast::Result<Self> {
fn try_conv(size: PhysicalSize<X>) -> Result<Self> {
Ok(Size(size.width.cast(), size.height.cast()))
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/kas-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ mod root;
pub use crate::core::*;
pub use action::Action;
pub use decorations::Decorations;
pub use kas_macros::*;
pub use kas_macros::{autoimpl, extends, impl_default, widget};
pub use kas_macros::{collection, impl_anon, impl_scope, widget_index};
#[doc(inline)] pub use popup::Popup;
#[doc(inline)] pub(crate) use popup::PopupDescriptor;
#[doc(inline)]
Expand Down
3 changes: 3 additions & 0 deletions crates/kas-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ proc-macro = true
# Requires that all crates using these macros depend on the log crate.
log = []

# Optimize generated layout widgets
recursive-layout-widgets = []

[dependencies]
quote = "1.0"
proc-macro2 = { version = "1.0" }
Expand Down
6 changes: 4 additions & 2 deletions crates/kas-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,9 +617,11 @@ pub fn aligned_row(input: TokenStream) -> TokenStream {
/// # Example
///
/// ```ignore
/// let list = kas::widgets::List::right(kas::collection![
/// use kas::collection;
/// use kas::widgets::{CheckBox, List};
/// let list = List::right(collection![
/// "A checkbox",
/// kas::widgets::CheckBox::new(|_, _| false),
/// CheckBox::new(|_, _| false),
/// ]);
/// ```
///
Expand Down
100 changes: 74 additions & 26 deletions crates/kas-macros/src/make_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,15 @@ impl Tree {
pub fn column(inner: ParseStream) -> Result<Self> {
let mut gen = NameGenerator::default();
let stor = gen.next();
let list = parse_layout_items(inner, &mut gen)?;
let list = parse_layout_items(inner, &mut gen, false)?;
Ok(Tree(Layout::List(stor.into(), Direction::Down, list)))
}

/// Parse a row (contents only)
pub fn row(inner: ParseStream) -> Result<Self> {
let mut gen = NameGenerator::default();
let stor = gen.next();
let list = parse_layout_items(inner, &mut gen)?;
let list = parse_layout_items(inner, &mut gen, false)?;
Ok(Tree(Layout::List(stor.into(), Direction::Right, list)))
}

Expand All @@ -287,6 +287,7 @@ impl Tree {
inner,
&mut gen,
true,
false,
)?))
}

Expand All @@ -299,6 +300,7 @@ impl Tree {
inner,
&mut gen,
false,
false,
)?))
}

Expand All @@ -308,22 +310,22 @@ impl Tree {
let stor = gen.next();
let dir: Direction = inner.parse()?;
let _: Token![,] = inner.parse()?;
let list = parse_layout_list(inner, &mut gen)?;
let list = parse_layout_list(inner, &mut gen, false)?;
Ok(Tree(Layout::List(stor.into(), dir, list)))
}

/// Parse a float (contents only)
pub fn float(inner: ParseStream) -> Result<Self> {
let mut gen = NameGenerator::default();
let list = parse_layout_items(inner, &mut gen)?;
let list = parse_layout_items(inner, &mut gen, false)?;
Ok(Tree(Layout::Float(list)))
}

/// Parse a grid (contents only)
pub fn grid(inner: ParseStream) -> Result<Self> {
let mut gen = NameGenerator::default();
let stor = gen.next();
Ok(Tree(parse_grid(stor.into(), inner, &mut gen)?))
Ok(Tree(parse_grid(stor.into(), inner, &mut gen, false)?))
}
}

Expand Down Expand Up @@ -504,13 +506,45 @@ impl GridDimensions {
impl Parse for Tree {
fn parse(input: ParseStream) -> Result<Self> {
let mut gen = NameGenerator::default();
Ok(Tree(Layout::parse(input, &mut gen)?))
Ok(Tree(Layout::parse(input, &mut gen, true)?))
}
}

impl Layout {
fn parse(input: ParseStream, gen: &mut NameGenerator) -> Result<Self> {
let mut layout = if input.peek2(Token![!]) {
fn parse(input: ParseStream, gen: &mut NameGenerator, _recurse: bool) -> Result<Self> {
#[cfg(feature = "recursive-layout-widgets")]
let _recurse = true;

#[cfg(not(feature = "recursive-layout-widgets"))]
if input.peek2(Token![!]) {
let input2 = input.fork();
let mut gen2 = NameGenerator::default();
if Self::parse_macro_like(&input2, &mut gen2).is_ok() {
loop {
if let Ok(dot_token) = input2.parse::<Token![.]>() {
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 gen2)?;
continue;
} else if let Ok(ident) = input2.parse::<Ident>() {
proc_macro_error::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![!]) {
Self::parse_macro_like(input, gen)?
} else if input.peek(Token![self]) {
Layout::Single(input.parse()?)
Expand Down Expand Up @@ -572,7 +606,7 @@ impl Layout {

let inner;
let _ = parenthesized!(inner in input);
let layout = Layout::parse(&inner, gen)?;
let layout = Layout::parse(&inner, gen, true)?;

let style: Expr = if !inner.is_empty() {
let _: Token![,] = inner.parse()?;
Expand All @@ -591,7 +625,7 @@ impl Layout {

let inner;
let _ = parenthesized!(inner in input);
let layout = Layout::parse(&inner, gen)?;
let layout = Layout::parse(&inner, gen, true)?;

let color: Expr = if !inner.is_empty() {
let _: Token![,] = inner.parse()?;
Expand All @@ -607,13 +641,13 @@ impl Layout {
let _: kw::column = input.parse()?;
let _: Token![!] = input.parse()?;
let stor = gen.parse_or_next(input)?;
let list = parse_layout_list(input, gen)?;
let list = parse_layout_list(input, gen, true)?;
Ok(Layout::List(stor, Direction::Down, list))
} else if lookahead.peek(kw::row) {
let _: kw::row = input.parse()?;
let _: Token![!] = input.parse()?;
let stor = gen.parse_or_next(input)?;
let list = parse_layout_list(input, gen)?;
let list = parse_layout_list(input, gen, true)?;
Ok(Layout::List(stor, Direction::Right, list))
} else if lookahead.peek(kw::list) {
let _: kw::list = input.parse()?;
Expand All @@ -623,12 +657,12 @@ impl Layout {
let _ = parenthesized!(inner in input);
let dir: Direction = inner.parse()?;
let _: Token![,] = inner.parse()?;
let list = parse_layout_list(&inner, gen)?;
let list = parse_layout_list(&inner, gen, true)?;
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, gen)?;
let list = parse_layout_list(input, gen, true)?;
Ok(Layout::Float(list))
} else if lookahead.peek(kw::aligned_column) {
let _: kw::aligned_column = input.parse()?;
Expand All @@ -638,7 +672,7 @@ impl Layout {
let inner;
let _ = bracketed!(inner in input);
Ok(parse_grid_as_list_of_lists::<kw::row>(
stor, &inner, gen, true,
stor, &inner, gen, true, true,
)?)
} else if lookahead.peek(kw::aligned_row) {
let _: kw::aligned_row = input.parse()?;
Expand All @@ -648,7 +682,7 @@ impl Layout {
let inner;
let _ = bracketed!(inner in input);
Ok(parse_grid_as_list_of_lists::<kw::column>(
stor, &inner, gen, false,
stor, &inner, gen, false, true,
)?)
} else if lookahead.peek(kw::grid) {
let _: kw::grid = input.parse()?;
Expand All @@ -657,14 +691,14 @@ impl Layout {

let inner;
let _ = braced!(inner in input);
Ok(parse_grid(stor, &inner, gen)?)
Ok(parse_grid(stor, &inner, 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, gen)?;
let layout = Layout::parse(&inner, gen, true)?;
Ok(Layout::NonNavigable(Box::new(layout)))
} else {
let ident = gen.next();
Expand All @@ -674,20 +708,28 @@ impl Layout {
}
}

fn parse_layout_list(input: ParseStream, gen: &mut NameGenerator) -> Result<VisitableList<()>> {
fn parse_layout_list(
input: ParseStream,
gen: &mut NameGenerator,
recurse: bool,
) -> Result<VisitableList<()>> {
let inner;
let _ = bracketed!(inner in input);
parse_layout_items(&inner, gen)
parse_layout_items(&inner, gen, recurse)
}

fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result<VisitableList<()>> {
fn parse_layout_items(
inner: ParseStream,
gen: &mut NameGenerator,
recurse: bool,
) -> Result<VisitableList<()>> {
let mut list = vec![];
let mut gen2 = NameGenerator::default();
while !inner.is_empty() {
list.push(ListItem {
cell: (),
stor: gen2.next(),
layout: Layout::parse(inner, gen)?,
layout: Layout::parse(inner, gen, recurse)?,
});

if inner.is_empty() {
Expand All @@ -705,6 +747,7 @@ fn parse_grid_as_list_of_lists<KW: Parse>(
inner: ParseStream,
gen: &mut NameGenerator,
row_major: bool,
recurse: bool,
) -> Result<Layout> {
let (mut col, mut row) = (0, 0);
let mut dim = GridDimensions::default();
Expand All @@ -721,7 +764,7 @@ fn parse_grid_as_list_of_lists<KW: Parse>(
while !inner2.is_empty() {
let cell = CellInfo::new(col, row);
dim.update(&cell);
let layout = Layout::parse(&inner2, gen)?;
let layout = Layout::parse(&inner2, gen, recurse)?;
cells.push(ListItem {
cell,
stor: gen2.next(),
Expand Down Expand Up @@ -757,7 +800,12 @@ fn parse_grid_as_list_of_lists<KW: Parse>(
Ok(Layout::Grid(stor, dim, VisitableList(cells)))
}

fn parse_grid(stor: StorIdent, inner: ParseStream, gen: &mut NameGenerator) -> Result<Layout> {
fn parse_grid(
stor: StorIdent,
inner: ParseStream,
gen: &mut NameGenerator,
recurse: bool,
) -> Result<Layout> {
let mut dim = GridDimensions::default();
let mut gen2 = NameGenerator::default();
let mut cells = vec![];
Expand All @@ -771,10 +819,10 @@ fn parse_grid(stor: StorIdent, inner: ParseStream, gen: &mut NameGenerator) -> R
if inner.peek(syn::token::Brace) {
let inner2;
let _ = braced!(inner2 in inner);
layout = Layout::parse(&inner2, gen)?;
layout = Layout::parse(&inner2, gen, recurse)?;
require_comma = false;
} else {
layout = Layout::parse(inner, gen)?;
layout = Layout::parse(inner, gen, recurse)?;
require_comma = true;
}
cells.push(ListItem {
Expand Down
2 changes: 2 additions & 0 deletions crates/kas-widgets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ 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;
Expand Down
4 changes: 3 additions & 1 deletion crates/kas-widgets/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl_scope! {
///
/// [`Row`] and [`Column`] are type-defs to `List` which fix the direction `D`.
///
/// The macros [`kas::row`] and [`kas::column`] also create row/column
/// The macros [`row!`] and [`column!`] also create row/column
/// layouts, but are not fully equivalent:
///
/// - `row!` and `column!` generate anonymous layout widgets (or objects).
Expand All @@ -55,6 +55,8 @@ impl_scope! {
/// Drawing and event handling is O(log n) in the number of children (assuming
/// only a small number are visible at any one time).
///
/// [`row!`]: crate::row
/// [`column!`]: crate::column
/// [`set_direction`]: List::set_direction
#[autoimpl(Default where C: Default, D: Default)]
#[widget]
Expand Down
Loading
Loading