From 6bf8e4a8c1226e6b19c2c94cd7af1fd9a735a916 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 29 Nov 2021 16:11:15 +0000 Subject: [PATCH 01/53] examples/data-list: remove unwanted filler widget --- examples/data-list.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/data-list.rs b/examples/data-list.rs index 7de1198a9..5dea192fe 100644 --- a/examples/data-list.rs +++ b/examples/data-list.rs @@ -151,7 +151,6 @@ fn main() -> Result<(), kas::shell::Error> { #[widget] _ = Separator::new(), #[widget(use_msg = set_radio)] list: ScrollBarRegion> = ScrollBarRegion::new(list).with_bars(false, true), - #[widget] _ = Filler::maximize(), active: usize = 0, } impl Self { From b6ebff0e86565bbe03b4ebf5b63659bf972fee39 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 29 Nov 2021 18:04:32 +0000 Subject: [PATCH 02/53] New layout::Visitor trait and layout::List impl --- crates/kas-core/src/layout/mod.rs | 2 + crates/kas-core/src/layout/visitor.rs | 69 +++++++++++++++++++++++++++ crates/kas-widgets/src/list.rs | 25 ++++------ 3 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 crates/kas-core/src/layout/visitor.rs diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index d56caf917..3ddee510a 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -43,6 +43,7 @@ mod size_rules; mod size_types; mod sizer; mod storage; +mod visitor; use crate::dir::{Direction, Directional}; @@ -59,6 +60,7 @@ pub use storage::{ DynGridStorage, DynRowStorage, FixedGridStorage, FixedRowStorage, GridStorage, RowStorage, RowTemp, Storage, }; +pub use visitor::{List, Visitor}; /// Information on which axis is being resized /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs new file mode 100644 index 000000000..3ef5d2a64 --- /dev/null +++ b/crates/kas-core/src/layout/visitor.rs @@ -0,0 +1,69 @@ +// 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 + +//! Layout visitor + +use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules}; +use super::{RowSetter, RowSolver, RowStorage}; +use crate::draw::SizeHandle; +use crate::event::Manager; +use crate::geom::Rect; +use crate::{dir::Directional, WidgetConfig}; +use std::iter::ExactSizeIterator; + +/// Implementation helper for layout of children +pub trait Visitor { + /// Get size rules for the given axis + fn size_rules(self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules; + + /// Apply a given `rect` to self + fn set_rect(self, mgr: &mut Manager, rect: Rect, align: AlignHints); +} + +/// Implement row/column layout for children +pub struct List<'a, L: RowStorage, D: Directional, I> +where + I: ExactSizeIterator, +{ + data: &'a mut L, + direction: D, + children: I, +} + +impl<'a, L: RowStorage, D: Directional, I> List<'a, L, D, I> +where + I: ExactSizeIterator, +{ + pub fn new(data: &'a mut L, direction: D, children: I) -> Self { + List { + data, + direction, + children, + } + } +} + +impl<'a, L: RowStorage, D: Directional, I> Visitor for List<'a, L, D, I> +where + I: ExactSizeIterator, +{ + fn size_rules(self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + let dim = (self.direction, self.children.len()); + let mut solver = RowSolver::new(axis, dim, self.data); + for (n, child) in self.children { + solver.for_child(self.data, n, |axis| child.size_rules(sh, axis)); + } + solver.finish(self.data) + } + + fn set_rect(self, mgr: &mut Manager, rect: Rect, align: AlignHints) { + let dim = (self.direction, self.children.len()); + let mut setter = RowSetter::, _>::new(rect, dim, align, self.data); + + for (n, child) in self.children { + child.set_rect(mgr, setter.child_rect(self.data, n), align); + } + } +} diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 0b4274371..b8b6bd440 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -6,7 +6,7 @@ //! A row or column with run-time adjustable contents use kas::dir::{Down, Right}; -use kas::layout::{self, RulesSetter, RulesSolver}; +use kas::layout::{self, Visitor as _}; use kas::{event, prelude::*}; use std::ops::{Index, IndexMut}; @@ -204,26 +204,21 @@ widget! { } } + impl Self { + fn layout<'a>(&'a mut self) -> impl layout::Visitor + 'a { + let iter = self.widgets.iter_mut().map(|w| w.as_widget_mut()).enumerate(); + layout::List::new(&mut self.data, self.direction, iter) + } + } + impl Layout for Self { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let dim = (self.direction, self.widgets.len()); - let mut solver = layout::RowSolver::new(axis, dim, &mut self.data); - for (n, child) in self.widgets.iter_mut().enumerate() { - solver.for_child(&mut self.data, n, |axis| { - child.size_rules(size_handle, axis) - }); - } - solver.finish(&mut self.data) + self.layout().size_rules(size_handle, axis) } fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { self.core.rect = rect; - let dim = (self.direction, self.widgets.len()); - let mut setter = layout::RowSetter::, _>::new(rect, dim, align, &mut self.data); - - for (n, child) in self.widgets.iter_mut().enumerate() { - child.set_rect(mgr, setter.child_rect(&mut self.data, n), align); - } + self.layout().set_rect(mgr, rect, align); } fn spatial_nav( From f3f3d682443012861cccd39f6e89f63fff38405e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 30 Nov 2021 11:48:39 +0000 Subject: [PATCH 03/53] layout::Visitor: support embedded layouts Test in examples/counter; this is currently quite hacky --- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 42 +++++++++++++----- crates/kas-widgets/src/list.rs | 2 +- examples/counter.rs | 62 +++++++++++++++++++++++---- 4 files changed, 87 insertions(+), 21 deletions(-) diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 3ddee510a..ed8d225a2 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -60,7 +60,7 @@ pub use storage::{ DynGridStorage, DynRowStorage, FixedGridStorage, FixedRowStorage, GridStorage, RowStorage, RowTemp, Storage, }; -pub use visitor::{List, Visitor}; +pub use visitor::{Item, List, Visitor}; /// Information on which axis is being resized /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 3ef5d2a64..e86d1a619 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -16,16 +16,24 @@ use std::iter::ExactSizeIterator; /// Implementation helper for layout of children pub trait Visitor { /// Get size rules for the given axis - fn size_rules(self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules; + fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules; /// Apply a given `rect` to self - fn set_rect(self, mgr: &mut Manager, rect: Rect, align: AlignHints); + fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints); +} + +/// Items which can be placed in a layout +pub enum Item<'a> { + /// A widget + Widget(&'a mut dyn WidgetConfig), + /// An embedded layout + Layout(Box), // TODO: inline storage? } /// Implement row/column layout for children pub struct List<'a, L: RowStorage, D: Directional, I> where - I: ExactSizeIterator, + I: ExactSizeIterator)>, { data: &'a mut L, direction: D, @@ -34,7 +42,7 @@ where impl<'a, L: RowStorage, D: Directional, I> List<'a, L, D, I> where - I: ExactSizeIterator, + I: ExactSizeIterator)>, { pub fn new(data: &'a mut L, direction: D, children: I) -> Self { List { @@ -47,23 +55,35 @@ where impl<'a, L: RowStorage, D: Directional, I> Visitor for List<'a, L, D, I> where - I: ExactSizeIterator, + I: ExactSizeIterator)>, { - fn size_rules(self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + fn size_rules(&mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let dim = (self.direction, self.children.len()); let mut solver = RowSolver::new(axis, dim, self.data); - for (n, child) in self.children { - solver.for_child(self.data, n, |axis| child.size_rules(sh, axis)); + for (n, child) in &mut self.children { + match child { + Item::Widget(child) => { + solver.for_child(self.data, n, |axis| child.size_rules(sh, axis)) + } + Item::Layout(mut layout) => { + solver.for_child(self.data, n, |axis| layout.size_rules(sh, axis)) + } + } } solver.finish(self.data) } - fn set_rect(self, mgr: &mut Manager, rect: Rect, align: AlignHints) { + fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { let dim = (self.direction, self.children.len()); let mut setter = RowSetter::, _>::new(rect, dim, align, self.data); - for (n, child) in self.children { - child.set_rect(mgr, setter.child_rect(self.data, n), align); + for (n, child) in &mut self.children { + match child { + Item::Widget(child) => child.set_rect(mgr, setter.child_rect(self.data, n), align), + Item::Layout(mut layout) => { + layout.set_rect(mgr, setter.child_rect(self.data, n), align) + } + } } } } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index b8b6bd440..49099bd68 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -206,7 +206,7 @@ widget! { impl Self { fn layout<'a>(&'a mut self) -> impl layout::Visitor + 'a { - let iter = self.widgets.iter_mut().map(|w| w.as_widget_mut()).enumerate(); + let iter = self.widgets.iter_mut().map(|w| layout::Item::Widget(w.as_widget_mut())).enumerate(); layout::List::new(&mut self.data, self.direction, iter) } } diff --git a/examples/counter.rs b/examples/counter.rs index b0df43568..251d75149 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -5,31 +5,77 @@ //! Counter example (simple button) +use kas::layout::{self, Visitor as _}; use kas::macros::make_widget; use kas::prelude::*; -use kas::widgets::{row, Label, TextButton, Window}; +use kas::widgets::{Label, TextButton, Window}; fn main() -> Result<(), kas::shell::Error> { env_logger::init(); let counter = make_widget! { - #[layout(column)] #[handler(msg = VoidMsg)] struct { + layout_data2: layout::FixedRowStorage<2> = Default::default(), #[widget(halign=centre)] display: Label = Label::from("0"), - #[widget(use_msg = handle_button)] - buttons = row![ - TextButton::new_msg("−", -1), - TextButton::new_msg("+", 1), - ], + #[widget(use_msg = update)] + b_decr = TextButton::new_msg("−", -1), + #[widget(use_msg = update)] + b_incr = TextButton::new_msg("+", 1), count: i32 = 0, } impl Self { - fn handle_button(&mut self, mgr: &mut Manager, incr: i32) { + fn update(&mut self, mgr: &mut Manager, incr: i32) { self.count += incr; *mgr |= self.display.set_string(self.count.to_string()); } + fn layout<'a>(&'a mut self) -> impl layout::Visitor + 'a { + let arr2 = [ + (0, layout::Item::Widget(self.b_decr.as_widget_mut())), + (1, layout::Item::Widget(self.b_incr.as_widget_mut())), + ]; + let arr = [ + (0, layout::Item::Widget(self.display.as_widget_mut())), + (1, layout::Item::Layout(Box::new(layout::List::new(&mut self.layout_data2, kas::dir::Right, arr2.into_iter())))), + ]; + layout::List::new(&mut self.layout_data, kas::dir::Down, arr.into_iter()) + } + } + impl Layout for Self { + fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + self.layout().size_rules(size_handle, axis) + } + + fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { + self.core.rect = rect; + self.layout().set_rect(mgr, rect, align); + } + + fn find_id(&self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + None + } else if let Some(id) = self.display.find_id(coord) { + Some(id) + } else if let Some(id) = self.b_decr.find_id(coord) { + Some(id) + } else if let Some(id) = self.b_incr.find_id(coord) { + Some(id) + } else { + Some(self.id()) + } + } + + fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + self.display.draw(draw_handle, mgr, disabled); + self.b_decr.draw(draw_handle, mgr, disabled); + self.b_incr.draw(draw_handle, mgr, disabled); + } + } + impl LayoutData for Self { + type Data = layout::FixedRowStorage<2>; + type Solver = layout::RowSolver; + type Setter = layout::RowSetter; } }; From ab00b9b1c15b21edbe782afa6ffaa0d16afa6f8e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 30 Nov 2021 12:14:09 +0000 Subject: [PATCH 04/53] layout::Visitor: support alignment --- crates/kas-core/src/layout/align.rs | 14 ++++++++++++++ crates/kas-core/src/layout/visitor.rs | 21 +++++++++++++-------- crates/kas-widgets/src/list.rs | 4 +++- examples/counter.rs | 11 ++++++----- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/crates/kas-core/src/layout/align.rs b/crates/kas-core/src/layout/align.rs index 7a5ff7c7c..af52ac71d 100644 --- a/crates/kas-core/src/layout/align.rs +++ b/crates/kas-core/src/layout/align.rs @@ -39,11 +39,25 @@ impl AlignHints { /// No hints pub const NONE: AlignHints = AlignHints::new(None, None); + /// Center on both axes + pub const CENTER: AlignHints = AlignHints::new(Some(Align::Centre), Some(Align::Centre)); + + /// Stretch on both axes + pub const STRETCH: AlignHints = AlignHints::new(Some(Align::Stretch), Some(Align::Stretch)); + /// Construct with optional horiz. and vert. alignment pub const fn new(horiz: Option, vert: Option) -> Self { Self { horiz, vert } } + /// Combine two hints (first takes priority) + pub fn combine(self, rhs: AlignHints) -> Self { + Self { + horiz: self.horiz.or(rhs.horiz), + vert: self.vert.or(rhs.vert), + } + } + /// Unwrap type's alignments or substitute parameters pub fn unwrap_or(self, horiz: Align, vert: Align) -> (Align, Align) { (self.horiz.unwrap_or(horiz), self.vert.unwrap_or(vert)) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index e86d1a619..1ad8eceb4 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -31,10 +31,7 @@ pub enum Item<'a> { } /// Implement row/column layout for children -pub struct List<'a, L: RowStorage, D: Directional, I> -where - I: ExactSizeIterator)>, -{ +pub struct List<'a, L, D, I> { data: &'a mut L, direction: D, children: I, @@ -42,8 +39,15 @@ where impl<'a, L: RowStorage, D: Directional, I> List<'a, L, D, I> where - I: ExactSizeIterator)>, + I: ExactSizeIterator, AlignHints)>, { + /// Construct + /// + /// - `data`: associated storage type + /// - `direction`: row/column direction + /// - `children`: iterator over `(hints, item)` tuples where + /// `hints` is optional alignment hints and + /// `item` is a layout [`Item`]. pub fn new(data: &'a mut L, direction: D, children: I) -> Self { List { data, @@ -55,12 +59,12 @@ where impl<'a, L: RowStorage, D: Directional, I> Visitor for List<'a, L, D, I> where - I: ExactSizeIterator)>, + I: ExactSizeIterator, AlignHints)>, { fn size_rules(&mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let dim = (self.direction, self.children.len()); let mut solver = RowSolver::new(axis, dim, self.data); - for (n, child) in &mut self.children { + for (n, (child, _)) in (&mut self.children).enumerate() { match child { Item::Widget(child) => { solver.for_child(self.data, n, |axis| child.size_rules(sh, axis)) @@ -77,7 +81,8 @@ where let dim = (self.direction, self.children.len()); let mut setter = RowSetter::, _>::new(rect, dim, align, self.data); - for (n, child) in &mut self.children { + for (n, (child, hints)) in (&mut self.children).enumerate() { + let align = hints.combine(align); match child { Item::Widget(child) => child.set_rect(mgr, setter.child_rect(self.data, n), align), Item::Layout(mut layout) => { diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 49099bd68..4abec76a0 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -206,7 +206,9 @@ widget! { impl Self { fn layout<'a>(&'a mut self) -> impl layout::Visitor + 'a { - let iter = self.widgets.iter_mut().map(|w| layout::Item::Widget(w.as_widget_mut())).enumerate(); + let iter = self.widgets.iter_mut().map(|w| { + (layout::Item::Widget(w.as_widget_mut()), AlignHints::default()) + }); layout::List::new(&mut self.data, self.direction, iter) } } diff --git a/examples/counter.rs b/examples/counter.rs index 251d75149..5ae8a8f2c 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -17,7 +17,7 @@ fn main() -> Result<(), kas::shell::Error> { #[handler(msg = VoidMsg)] struct { layout_data2: layout::FixedRowStorage<2> = Default::default(), - #[widget(halign=centre)] + #[widget] display: Label = Label::from("0"), #[widget(use_msg = update)] b_decr = TextButton::new_msg("−", -1), @@ -32,12 +32,13 @@ fn main() -> Result<(), kas::shell::Error> { } fn layout<'a>(&'a mut self) -> impl layout::Visitor + 'a { let arr2 = [ - (0, layout::Item::Widget(self.b_decr.as_widget_mut())), - (1, layout::Item::Widget(self.b_incr.as_widget_mut())), + (layout::Item::Widget(self.b_decr.as_widget_mut()), AlignHints::NONE), + (layout::Item::Widget(self.b_incr.as_widget_mut()), AlignHints::NONE), ]; + let b_layout = Box::new(layout::List::new(&mut self.layout_data2, kas::dir::Right, arr2.into_iter())); let arr = [ - (0, layout::Item::Widget(self.display.as_widget_mut())), - (1, layout::Item::Layout(Box::new(layout::List::new(&mut self.layout_data2, kas::dir::Right, arr2.into_iter())))), + (layout::Item::Widget(self.display.as_widget_mut()), AlignHints::CENTER), + (layout::Item::Layout(b_layout), AlignHints::NONE), ]; layout::List::new(&mut self.layout_data, kas::dir::Down, arr.into_iter()) } From 93c0c5099c92a1ec335fdb1943178928a1711d4f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 30 Nov 2021 14:32:52 +0000 Subject: [PATCH 05/53] layout::sizer: add perf metrics --- crates/kas-core/src/layout/sizer.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/kas-core/src/layout/sizer.rs b/crates/kas-core/src/layout/sizer.rs index 50bd87594..1d4f10a56 100644 --- a/crates/kas-core/src/layout/sizer.rs +++ b/crates/kas-core/src/layout/sizer.rs @@ -138,12 +138,16 @@ impl SolveCache { widget: &mut dyn WidgetConfig, size_handle: &mut dyn SizeHandle, ) -> Self { + let start = std::time::Instant::now(); + let w = widget.size_rules(size_handle, AxisInfo::new(false, None)); let h = widget.size_rules(size_handle, AxisInfo::new(true, Some(w.ideal_size()))); let min = Size(w.min_size(), h.min_size()); let ideal = Size(w.ideal_size(), h.ideal_size()); let margins = Margins::hv(w.margins(), h.margins()); + + trace!(target: "kas_perf", "layout::find_constraints: {}ms", start.elapsed().as_millis()); trace!( "layout::solve: min={:?}, ideal={:?}, margins={:?}", min, @@ -187,6 +191,8 @@ impl SolveCache { mut rect: Rect, inner_margin: bool, ) { + let start = std::time::Instant::now(); + let mut width = rect.size.0; if inner_margin { width -= self.margins.sum_horiz(); @@ -218,8 +224,9 @@ impl SolveCache { } widget.set_rect(mgr, rect, AlignHints::NONE); + trace!(target: "kas_perf", "layout::apply_rect: {}ms", start.elapsed().as_millis()); trace!( - "layout::solve_and_set for size={:?} has hierarchy:{}", + "layout::apply_rect: size={:?}, hierarchy:{}", rect.size, WidgetHeirarchy(widget, 0), ); From e4b82de7160682b132a9abed7074a43e1b04b2a6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 30 Nov 2021 16:36:06 +0000 Subject: [PATCH 06/53] Push layout data into CoreData struct as Option> Rationale 1: one less field needed in custom structs. Rationale 2: allows simpler layout visitor construction? Benchmarks: run examples/data-list with various numbers of entries, timing layout::sizer::apply_rect. Debug results: negligible change. Release results: 2-5% increase in time. Release after removing alignment tokens: <2% increase. --- crates/kas-core/src/core/data.rs | 54 +++++++---- crates/kas-core/src/layout/storage.rs | 45 ++++++++- crates/kas-core/src/prelude.rs | 4 +- crates/kas-macros/src/args.rs | 23 +---- crates/kas-macros/src/layout.rs | 112 +++++++++------------- crates/kas-macros/src/make_widget.rs | 2 - crates/kas-macros/src/widget.rs | 15 +-- crates/kas-widgets/src/adapter/label.rs | 21 ++-- crates/kas-widgets/src/checkbox.rs | 4 - crates/kas-widgets/src/dialog.rs | 3 - crates/kas-widgets/src/menu/menu_entry.rs | 4 - crates/kas-widgets/src/radiobox.rs | 4 - examples/counter.rs | 6 +- examples/data-list-view.rs | 3 - examples/data-list.rs | 3 - examples/gallery.rs | 3 - examples/mandlebrot/mandlebrot.rs | 3 - src/lib.rs | 2 +- src/macros.rs | 2 +- 19 files changed, 141 insertions(+), 172 deletions(-) diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index cb88d07c7..e25944758 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -14,7 +14,8 @@ use super::Layout; use super::Widget; use crate::event::{self, Manager}; use crate::geom::Rect; -use crate::{dir::Direction, layout, WindowId}; +use crate::layout::Storage; +use crate::{dir::Direction, WindowId}; #[cfg(feature = "winit")] pub use winit::window::Icon; @@ -115,31 +116,44 @@ fn size_of_option_widget_id() { /// Common widget data /// /// All widgets should embed a `#[widget_core] core: CoreData` field. -#[derive(Clone, Default, Debug)] +#[derive(Default, Debug)] pub struct CoreData { + layout: Option>, pub rect: Rect, pub id: WidgetId, pub disabled: bool, } -/// Trait to describe the type needed by the layout implementation. -/// -/// The (non-trivial) [`layout`] engines require a storage field within their -/// widget. For manual [`Layout`] implementations this may be specified -/// directly, but to allow the `derive(Widget)` macro to specify the appropriate -/// data type, a widget should include a field of the following form: -/// ```none -/// #[layout_data] layout_data: ::Data, -/// ``` -/// -/// Ideally we would use an inherent associated type on the struct in question, -/// but until rust-lang#8995 is implemented that is not possible. We also cannot -/// place this associated type on the [`Widget`] trait itself, since then uses -/// of the trait would require parameterisation. Thus, this trait. -pub trait LayoutData { - type Data: Clone + fmt::Debug + Default; - type Solver: layout::RulesSolver; - type Setter: layout::RulesSetter; +/// Note: the clone has default-initialised layout storage and identifier. +/// Configuration and layout solving is required as for any other widget. +impl Clone for CoreData { + fn clone(&self) -> Self { + CoreData { + layout: None, + rect: self.rect, + id: WidgetId::default(), + disabled: self.disabled, + } + } +} + +impl CoreData { + /// Access layout storage + /// + /// This storage is allocated and initialised on first access. + /// + /// Panics if the type `T` differs from the initial usage. + #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] + #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] + pub fn layout_storage(&mut self) -> &mut T { + if let Some(ref mut l) = self.layout { + return l + .downcast_mut() + .unwrap_or_else(|| panic!("CoreData::layout_storage::(): incorrect type T")); + } + self.layout = Some(Box::new(T::default())); + self.layout.as_mut().unwrap().downcast_mut::().unwrap() + } } /// A widget which escapes its parent's rect diff --git a/crates/kas-core/src/layout/storage.rs b/crates/kas-core/src/layout/storage.rs index fedbe78f2..e284bc8c5 100644 --- a/crates/kas-core/src/layout/storage.rs +++ b/crates/kas-core/src/layout/storage.rs @@ -6,9 +6,28 @@ //! Layout solver — storage use super::SizeRules; +use std::any::Any; /// Master trait over storage types -pub trait Storage {} +pub trait Storage: Any + std::fmt::Debug { + /// Get self as type `Any` (mutable) + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +impl dyn Storage { + /// Forwards to the method defined on the type `Any`. + #[inline] + pub fn downcast_mut(&mut self) -> Option<&mut T> { + ::downcast_mut::(self.as_any_mut()) + } +} + +/// Empty storage type +impl Storage for () { + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} /// Requirements of row solver storage type /// @@ -54,7 +73,11 @@ impl Default for FixedRowStorage { } } -impl Storage for FixedRowStorage {} +impl Storage for FixedRowStorage { + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} impl RowStorage for FixedRowStorage { fn set_dim(&mut self, cols: usize) { @@ -79,7 +102,11 @@ pub struct DynRowStorage { widths: Vec, } -impl Storage for DynRowStorage {} +impl Storage for DynRowStorage { + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} impl RowStorage for DynRowStorage { fn set_dim(&mut self, cols: usize) { @@ -182,7 +209,11 @@ impl Default for FixedGridStorage { } } -impl Storage for FixedGridStorage {} +impl Storage for FixedGridStorage { + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} impl GridStorage for FixedGridStorage { fn set_dims(&mut self, cols: usize, rows: usize) { @@ -230,7 +261,11 @@ pub struct DynGridStorage { heights: Vec, } -impl Storage for DynGridStorage {} +impl Storage for DynGridStorage { + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} impl GridStorage for DynGridStorage { fn set_dims(&mut self, cols: usize, rows: usize) { diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index 1cdd5298a..0f3388017 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -33,10 +33,10 @@ pub use crate::text::AccelString; #[doc(no_inline)] pub use crate::text::{EditableTextApi, Text, TextApi, TextApiExt}; #[doc(no_inline)] +pub use crate::CoreData; +#[doc(no_inline)] pub use crate::WidgetId; #[doc(no_inline)] pub use crate::{Boxed, TkAction}; #[doc(no_inline)] -pub use crate::{CoreData, LayoutData}; -#[doc(no_inline)] pub use crate::{Layout, Widget, WidgetChildren, WidgetConfig, WidgetCore}; diff --git a/crates/kas-macros/src/args.rs b/crates/kas-macros/src/args.rs index ffc3c397f..2b994895f 100644 --- a/crates/kas-macros/src/args.rs +++ b/crates/kas-macros/src/args.rs @@ -4,7 +4,7 @@ // https://www.apache.org/licenses/LICENSE-2.0 use proc_macro2::{Punct, Spacing, Span, TokenStream, TokenTree}; -use proc_macro_error::{abort, emit_error, emit_warning}; +use proc_macro_error::{abort, emit_error}; use quote::{quote, ToTokens, TokenStreamExt}; use syn::parse::{Error, Parse, ParseStream, Result}; use syn::punctuated::Punctuated; @@ -38,7 +38,6 @@ pub struct Widget { pub semi_token: Option, pub core_data: Option, - pub layout_data: Option, pub children: Vec, pub extra_impls: Vec, @@ -122,7 +121,6 @@ impl Parse for Widget { } let mut core_data = None; - let mut layout_data = None; let mut children = Vec::new(); for (i, field) in fields.iter_mut().enumerate() { @@ -134,20 +132,6 @@ impl Parse for Widget { } else { emit_error!(attr.span(), "multiple fields marked with #[widget_core]"); } - } else if attr.path == parse_quote! { layout_data } { - if layout_data.is_some() { - emit_error!(attr.span(), "multiple fields marked with #[layout_data]"); - } else if field.ty != parse_quote! { ::Data } - && field.ty != parse_quote! { ::Data } - && field.ty != parse_quote! { ::Data } - { - emit_warning!( - field.ty.span(), - "expected type `::Data`" - ); - } else { - layout_data = Some(member(i, field.ident.clone())); - } } else if attr.path == parse_quote! { widget } { let ident = member(i, field.ident.clone()); let args = syn::parse2(attr.tokens)?; @@ -168,10 +152,10 @@ impl Parse for Widget { "require a field with #[widget_core] or #[widget(derive = FIELD)]", ); } - if layout_data.is_some() || !children.is_empty() { + if !children.is_empty() { emit_error!( fields.span(), - "require a field with #[widget_core] when using #[layout_data] or #[widget]", + "require a field with #[widget_core] when using #[widget]", ); } } @@ -195,7 +179,6 @@ impl Parse for Widget { fields, semi_token, core_data, - layout_data, children, extra_impls, }) diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs index 466b2ce78..6edb82de0 100644 --- a/crates/kas-macros/src/layout.rs +++ b/crates/kas-macros/src/layout.rs @@ -4,12 +4,12 @@ // https://www.apache.org/licenses/LICENSE-2.0 use crate::args::{Child, LayoutArgs, LayoutType}; -use proc_macro2::TokenStream; +use proc_macro2::TokenStream as Toks; use quote::{quote, TokenStreamExt}; use syn::parse::{Error, Result}; use syn::Member; -pub(crate) fn data_type(children: &[Child], layout: &LayoutArgs) -> Result { +pub(crate) fn data_type(children: &[Child], layout: &LayoutArgs) -> Result<(Toks, Toks, Toks)> { if layout.layout == LayoutType::Single && children.len() != 1 { return Err(Error::new( layout.span, @@ -66,71 +66,47 @@ pub(crate) fn data_type(children: &[Child], layout: &LayoutArgs) -> Result quote! { - type Data = (); - type Solver = ::kas::layout::SingleSolver; - type Setter = ::kas::layout::SingleSetter; - }, - l @ LayoutType::Right | l @ LayoutType::Left => quote! { - type Data = ::kas::layout::FixedRowStorage::<#cols>; - type Solver = ::kas::layout::RowSolver::; - type Setter = ::kas::layout::RowSetter::< - #l, - #col_temp, - Self::Data, - >; - }, - l @ LayoutType::Down | l @ LayoutType::Up => quote! { - type Data = ::kas::layout::FixedRowStorage::<#rows>; - type Solver = ::kas::layout::RowSolver::; - type Setter = ::kas::layout::RowSetter::< - #l, - #row_temp, - Self::Data, - >; - }, - LayoutType::Grid => quote! { - type Data = ::kas::layout::FixedGridStorage::< - #cols, - #rows, - >; - type Solver = ::kas::layout::GridSolver::< - [(::kas::layout::SizeRules, u32, u32); #col_spans], - [(::kas::layout::SizeRules, u32, u32); #row_spans], - Self::Data, - >; - type Setter = ::kas::layout::GridSetter::< - #col_temp, - #row_temp, - Self::Data, - >; - }, + LayoutType::Single => ( + quote! { () }, + quote! { ::kas::layout::SingleSolver }, + quote! { ::kas::layout::SingleSetter }, + ), + l @ LayoutType::Right | l @ LayoutType::Left => { + let dt = quote! { ::kas::layout::FixedRowStorage::<#cols> }; + let solver = quote! { ::kas::layout::RowSolver::<#dt> }; + let setter = quote! { ::kas::layout::RowSetter::<#l, #col_temp, #dt> }; + (dt, solver, setter) + } + l @ LayoutType::Down | l @ LayoutType::Up => { + let dt = quote! { ::kas::layout::FixedRowStorage::<#rows> }; + let solver = quote! { ::kas::layout::RowSolver::<#dt> }; + let setter = quote! { ::kas::layout::RowSetter::<#l, #row_temp, #dt> }; + (dt, solver, setter) + } + LayoutType::Grid => { + let dt = quote! { ::kas::layout::FixedGridStorage::<#cols, #rows> }; + let solver = quote! { + ::kas::layout::GridSolver::< + [(::kas::layout::SizeRules, u32, u32); #col_spans], + [(::kas::layout::SizeRules, u32, u32); #row_spans], + #dt, + > + }; + let setter = quote! { ::kas::layout::GridSetter::<#col_temp, #row_temp, #dt> }; + (dt, solver, setter) + } }) } -pub(crate) fn derive( - children: &[Child], - layout: &LayoutArgs, - data_field: &Option, -) -> Result { - let data = if let Some(ref field) = data_field { - quote! { self.#field } - } else { - if layout.layout != LayoutType::Single { - return Err(Error::new( - layout.span, - "data field marked with #[layout_data] required when deriving Widget", - )); - } - quote! { () } - }; +pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> Result { + let (storage_type, solver_type, setter_type) = data_type(children, layout)?; let mut cols = 0u32; let mut rows = 0u32; let mut col_spans = 0u32; let mut row_spans = 0u32; - let mut size = TokenStream::new(); - let mut set_rect = TokenStream::new(); + let mut size = Toks::new(); + let mut set_rect = Toks::new(); let mut draw = quote! { use ::kas::{geom::Coord, WidgetCore}; let rect = draw_handle.get_clip_rect(); @@ -138,7 +114,7 @@ pub(crate) fn derive( let pos2 = rect.pos2(); let disabled = disabled || self.is_disabled(); }; - let mut find_id_child = TokenStream::new(); + let mut find_id_child = Toks::new(); for child in children.iter() { let ident = &child.ident; @@ -187,7 +163,7 @@ pub(crate) fn derive( size.append_all(quote! { let child = &mut self.#ident; solver.for_child( - &mut #data, + data, #child_info, |axis| child.size_rules(sh, axis) ); @@ -201,7 +177,7 @@ pub(crate) fn derive( set_rect.append_all(quote! { align2.vert = Some(#toks); }); } set_rect.append_all(quote! { - self.#ident.set_rect(_mgr, setter.child_rect(&mut #data, #child_info), align2); + self.#ident.set_rect(_mgr, setter.child_rect(data, #child_info), align2); }); draw.append_all(quote! { @@ -255,13 +231,14 @@ pub(crate) fn derive( use ::kas::WidgetCore; use ::kas::layout::RulesSolver; - let mut solver = ::Solver::new( + let data = self.#core.layout_storage::<#storage_type>(); + let mut solver = #solver_type::new( axis, #dim, - &mut #data, + data, ); #size - solver.finish(&mut #data) + solver.finish(data) } fn set_rect( @@ -274,11 +251,12 @@ pub(crate) fn derive( use ::kas::layout::{RulesSetter}; self.core.rect = rect; - let mut setter = ::Setter::new( + let data = self.#core.layout_storage::<#storage_type>(); + let mut setter = #setter_type::new( rect, #dim, align, - &mut #data, + data, ); #set_rect } diff --git a/crates/kas-macros/src/make_widget.rs b/crates/kas-macros/src/make_widget.rs index ae51f0d01..c82bb8bc1 100644 --- a/crates/kas-macros/src/make_widget.rs +++ b/crates/kas-macros/src/make_widget.rs @@ -73,12 +73,10 @@ pub(crate) fn make_widget(mut args: MakeWidget) -> TokenStream { // fields of anonymous struct: let mut field_toks = quote! { #[widget_core] core: ::kas::CoreData, - #[layout_data] layout_data: ::Data, }; // initialisers for these fields: let mut field_val_toks = quote! { core: Default::default(), - layout_data: Default::default(), }; // debug impl let mut debug_fields = TokenStream::new(); diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index adf59603a..385a579fe 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -88,6 +88,7 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { let (core_data, core_data_mut) = args .core_data + .as_ref() .map(|cd| (quote! { &self.#cd }, quote! { &mut self.#cd })) .unwrap_or_else(|| { let inner = opt_derive.as_ref().unwrap(); @@ -260,18 +261,8 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { } }); } else if let Some(ref layout) = args.attr_layout { - match layout::data_type(&args.children, layout) { - Ok(dt) => toks.append_all(quote! { - impl #impl_generics ::kas::LayoutData - for #name #ty_generics #where_clause - { - #dt - } - }), - Err(err) => return err.to_compile_error(), - } - - match layout::derive(&args.children, layout, &args.layout_data) { + let core = args.core_data.as_ref().unwrap(); + match layout::derive(core, &args.children, layout) { Ok(fns) => toks.append_all(quote! { impl #impl_generics ::kas::Layout for #name #ty_generics #where_clause diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index 828294959..f9b2b75ab 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -18,7 +18,6 @@ widget! { pub struct WithLabel { #[widget_core] core: CoreData, - layout_data: layout::FixedRowStorage<2>, dir: D, #[widget] inner: W, @@ -40,7 +39,6 @@ widget! { pub fn new_with_direction>(direction: D, inner: W, label: T) -> Self { WithLabel { core: Default::default(), - layout_data: Default::default(), dir: direction, inner, label_pos: Default::default(), @@ -64,29 +62,32 @@ widget! { impl Layout for Self { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let mut solver = layout::RowSolver::new(axis, (self.dir, 2), &mut self.layout_data); + let data = self.core.layout_storage::>(); + let mut solver = layout::RowSolver::new(axis, (self.dir, 2), data); let child = &mut self.inner; - solver.for_child(&mut self.layout_data, 0usize, |axis| { + solver.for_child(data, 0usize, |axis| { child.size_rules(size_handle, axis) }); let label = &mut self.label; - solver.for_child(&mut self.layout_data, 1usize, |axis| { + solver.for_child(data, 1usize, |axis| { size_handle.text_bound(label, TextClass::Label, axis) }); - solver.finish(&mut self.layout_data) + solver.finish(data) } fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { self.core.rect = rect; + let dim = (self.dir, 2); + let data = self.core.layout_storage::>(); let mut setter = layout::RowSetter::<_, [i32; 2], _>::new( rect, - (self.dir, 2), + dim, align, - &mut self.layout_data, + data, ); - let rect = setter.child_rect(&mut self.layout_data, 0); + let rect = setter.child_rect(data, 0); self.inner.set_rect(mgr, rect, align); - let rect = setter.child_rect(&mut self.layout_data, 1); + let rect = setter.child_rect(data, 1); self.label_pos = rect.pos; self.label.update_env(|env| { env.set_bounds(rect.size.into()); diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index b0b07fb62..b6def1c2f 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -135,8 +135,6 @@ widget! { pub struct CheckBox { #[widget_core] core: CoreData, - #[layout_data] - layout_data: ::Data, #[widget] checkbox: CheckBoxBare, #[widget] @@ -162,7 +160,6 @@ widget! { pub fn new>(label: T) -> Self { CheckBox { core: Default::default(), - layout_data: Default::default(), checkbox: CheckBoxBare::new(), label: AccelLabel::new(label.into()), } @@ -180,7 +177,6 @@ widget! { { CheckBox { core: self.core, - layout_data: self.layout_data, checkbox: self.checkbox.on_toggle(f), label: self.label, } diff --git a/crates/kas-widgets/src/dialog.rs b/crates/kas-widgets/src/dialog.rs index 2985a6c90..5d9aa073c 100644 --- a/crates/kas-widgets/src/dialog.rs +++ b/crates/kas-widgets/src/dialog.rs @@ -21,8 +21,6 @@ widget! { pub struct MessageBox { #[widget_core] core: CoreData, - #[layout_data] - layout_data: ::Data, title: String, #[widget] label: Label, @@ -34,7 +32,6 @@ widget! { pub fn new(title: A, message: T) -> Self { MessageBox { core: Default::default(), - layout_data: Default::default(), title: title.to_string(), label: Label::new(message), button: TextButton::new_msg("Ok", ()).with_keys(&[ diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 6371cf943..e52a1394a 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -127,8 +127,6 @@ widget! { pub struct MenuToggle { #[widget_core] core: CoreData, - #[layout_data] - layout_data: ::Data, #[widget] checkbox: CheckBoxBare, // TODO: label should use TextClass::MenuLabel @@ -154,7 +152,6 @@ widget! { pub fn new>(label: T) -> Self { MenuToggle { core: Default::default(), - layout_data: Default::default(), checkbox: CheckBoxBare::new(), label: AccelLabel::new(label.into()), } @@ -172,7 +169,6 @@ widget! { { MenuToggle { core: self.core, - layout_data: self.layout_data, checkbox: self.checkbox.on_toggle(f), label: self.label, } diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index 7d6c04bbc..67eaa80d4 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -177,8 +177,6 @@ widget! { pub struct RadioBox { #[widget_core] core: CoreData, - #[layout_data] - layout_data: ::Data, #[widget] radiobox: RadioBoxBare, #[widget] @@ -207,7 +205,6 @@ widget! { pub fn new>(label: T, handle: UpdateHandle) -> Self { RadioBox { core: Default::default(), - layout_data: Default::default(), radiobox: RadioBoxBare::new(handle), label: AccelLabel::new(label.into()), } @@ -227,7 +224,6 @@ widget! { { RadioBox { core: self.core, - layout_data: self.layout_data, radiobox: self.radiobox.on_select(f), label: self.label, } diff --git a/examples/counter.rs b/examples/counter.rs index 5ae8a8f2c..4f7b66fc6 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -16,6 +16,7 @@ fn main() -> Result<(), kas::shell::Error> { let counter = make_widget! { #[handler(msg = VoidMsg)] struct { + layout_data: layout::FixedRowStorage<2> = Default::default(), layout_data2: layout::FixedRowStorage<2> = Default::default(), #[widget] display: Label = Label::from("0"), @@ -73,11 +74,6 @@ fn main() -> Result<(), kas::shell::Error> { self.b_incr.draw(draw_handle, mgr, disabled); } } - impl LayoutData for Self { - type Data = layout::FixedRowStorage<2>; - type Solver = layout::RowSolver; - type Setter = layout::RowSetter; - } }; let window = Window::new("Counter", counter); diff --git a/examples/data-list-view.rs b/examples/data-list-view.rs index a1bc0c8d1..e49267e1d 100644 --- a/examples/data-list-view.rs +++ b/examples/data-list-view.rs @@ -157,8 +157,6 @@ widget! { struct ListEntry { #[widget_core] core: CoreData, - #[layout_data] - layout_data: ::Data, #[widget] label: StringLabel, #[widget] @@ -180,7 +178,6 @@ impl Driver<(usize, bool, String)> for MyDriver { // Default instances are not shown, so the data is unimportant ListEntry { core: Default::default(), - layout_data: Default::default(), label: Label::new(String::default()), radio: RadioBox::new("display this entry", self.radio_group) .on_select(move |_| Some(EntryMsg::Select)), diff --git a/examples/data-list.rs b/examples/data-list.rs index 5dea192fe..0288f3e9b 100644 --- a/examples/data-list.rs +++ b/examples/data-list.rs @@ -72,8 +72,6 @@ widget! { struct ListEntry { #[widget_core] core: CoreData, - #[layout_data] - layout_data: ::Data, #[widget] label: StringLabel, #[widget] @@ -87,7 +85,6 @@ impl ListEntry { fn new(n: usize, active: bool) -> Self { ListEntry { core: Default::default(), - layout_data: Default::default(), label: Label::new(format!("Entry number {}", n + 1)), radio: RadioBox::new("display this entry", RADIO.with(|h| *h)) .with_state(active) diff --git a/examples/gallery.rs b/examples/gallery.rs index 911c12b1d..bcab063a2 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -51,8 +51,6 @@ widget! { struct TextEditPopup { #[widget_core] core: CoreData, - #[layout_data] - layout_data: ::Data, #[widget(cspan = 3)] edit: EditBox, #[widget(row = 1, col = 0)] @@ -67,7 +65,6 @@ widget! { fn new(text: S) -> Self { TextEditPopup { core: Default::default(), - layout_data: Default::default(), edit: EditBox::new(text).multi_line(true), fill: Filler::maximize(), cancel: TextButton::new_msg("&Cancel", false), diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index 9bf31ccb0..f4d223b69 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -423,8 +423,6 @@ widget! { struct MandlebrotWindow { #[widget_core] core: CoreData, - #[layout_data] - layout_data: ::Data, #[widget(cspan = 2)] label: Label, #[widget(row=1, halign=centre)] @@ -442,7 +440,6 @@ widget! { let mbrot = Mandlebrot::new(); let w = MandlebrotWindow { core: Default::default(), - layout_data: Default::default(), label: Label::new(mbrot.loc()), iters: ReserveP::new(Label::from("64"), |size_handle, axis| { Label::new("000").size_rules(size_handle, axis) diff --git a/src/lib.rs b/src/lib.rs index 369b04ce9..83c8d8da7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ pub use kas_core::config; #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] pub use kas_core::ShellWindow; pub use kas_core::{cast, class, dir, draw, event, geom, layout, text, updatable, util}; -pub use kas_core::{Boxed, Layout, LayoutData, Window}; +pub use kas_core::{Boxed, Layout, Window}; pub use kas_core::{CoreData, Future, Popup, TkAction, WidgetId, WindowId}; pub use kas_core::{Widget, WidgetChildren, WidgetConfig, WidgetCore}; diff --git a/src/macros.rs b/src/macros.rs index 8e93d9649..b47ccaa26 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -465,7 +465,7 @@ use crate::{ event::{Handler, Response, SendEvent}, layout::AlignHints, - CoreData, Layout, LayoutData, Widget, WidgetChildren, WidgetConfig, WidgetCore, WidgetId, + CoreData, Layout, Widget, WidgetChildren, WidgetConfig, WidgetCore, WidgetId, }; pub use kas_core::macros::*; From 1676592d9c64bcadb8adfa867c44bc6685c620a1 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 09:14:23 +0000 Subject: [PATCH 07/53] Add layout::StorageChain to support multiple layouts --- crates/kas-core/src/core/data.rs | 25 ++---------- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 52 ++++++++++++++++++++++--- crates/kas-macros/src/layout.rs | 4 +- crates/kas-widgets/src/adapter/label.rs | 4 +- examples/counter.rs | 30 ++++++++------ 6 files changed, 72 insertions(+), 45 deletions(-) diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index e25944758..4cf5b7702 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -14,7 +14,7 @@ use super::Layout; use super::Widget; use crate::event::{self, Manager}; use crate::geom::Rect; -use crate::layout::Storage; +use crate::layout::StorageChain; use crate::{dir::Direction, WindowId}; #[cfg(feature = "winit")] @@ -118,7 +118,7 @@ fn size_of_option_widget_id() { /// All widgets should embed a `#[widget_core] core: CoreData` field. #[derive(Default, Debug)] pub struct CoreData { - layout: Option>, + pub layout: StorageChain, pub rect: Rect, pub id: WidgetId, pub disabled: bool, @@ -129,7 +129,7 @@ pub struct CoreData { impl Clone for CoreData { fn clone(&self) -> Self { CoreData { - layout: None, + layout: StorageChain::default(), rect: self.rect, id: WidgetId::default(), disabled: self.disabled, @@ -137,25 +137,6 @@ impl Clone for CoreData { } } -impl CoreData { - /// Access layout storage - /// - /// This storage is allocated and initialised on first access. - /// - /// Panics if the type `T` differs from the initial usage. - #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] - #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] - pub fn layout_storage(&mut self) -> &mut T { - if let Some(ref mut l) = self.layout { - return l - .downcast_mut() - .unwrap_or_else(|| panic!("CoreData::layout_storage::(): incorrect type T")); - } - self.layout = Some(Box::new(T::default())); - self.layout.as_mut().unwrap().downcast_mut::().unwrap() - } -} - /// A widget which escapes its parent's rect /// /// A pop-up is a special widget drawn either as a layer over the existing diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index ed8d225a2..cba827b67 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -60,7 +60,7 @@ pub use storage::{ DynGridStorage, DynRowStorage, FixedGridStorage, FixedRowStorage, GridStorage, RowStorage, RowTemp, Storage, }; -pub use visitor::{Item, List, Visitor}; +pub use visitor::{Item, List, StorageChain, Visitor}; /// Information on which axis is being resized /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 1ad8eceb4..7269439fa 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -5,7 +5,7 @@ //! Layout visitor -use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules}; +use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules, Storage}; use super::{RowSetter, RowSolver, RowStorage}; use crate::draw::SizeHandle; use crate::event::Manager; @@ -13,6 +13,46 @@ use crate::geom::Rect; use crate::{dir::Directional, WidgetConfig}; use std::iter::ExactSizeIterator; +/// Chaining layout storage +/// +/// We support embedded layouts within a single widget which means that we must +/// support storage for multiple layouts, though commonly zero or one layout is +/// used. We therefore use a simple linked list. +#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] +#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] +#[derive(Debug)] +pub struct StorageChain(Option<(Box, Box)>); + +impl Default for StorageChain { + fn default() -> Self { + StorageChain(None) + } +} + +impl StorageChain { + /// Access layout storage + /// + /// This storage is allocated and initialised on first access. + /// + /// Panics if the type `T` differs from the initial usage. + pub fn storage(&mut self) -> (&mut T, &mut StorageChain) { + if let StorageChain(Some(ref mut b)) = self { + let storage = + b.1.downcast_mut() + .unwrap_or_else(|| panic!("StorageChain::storage::(): incorrect type T")); + return (storage, &mut b.0); + } + // TODO(rust#42877): store (StorageChain, dyn Storage) tuple in a single box + let s = Box::new(StorageChain(None)); + let t: Box = Box::new(T::default()); + *self = StorageChain(Some((s, t))); + match self { + StorageChain(Some(b)) => (b.1.downcast_mut::().unwrap(), &mut b.0), + _ => unreachable!(), + } + } +} + /// Implementation helper for layout of children pub trait Visitor { /// Get size rules for the given axis @@ -31,13 +71,13 @@ pub enum Item<'a> { } /// Implement row/column layout for children -pub struct List<'a, L, D, I> { - data: &'a mut L, +pub struct List<'a, S, D, I> { + data: &'a mut S, direction: D, children: I, } -impl<'a, L: RowStorage, D: Directional, I> List<'a, L, D, I> +impl<'a, S: RowStorage, D: Directional, I> List<'a, S, D, I> where I: ExactSizeIterator, AlignHints)>, { @@ -48,7 +88,7 @@ where /// - `children`: iterator over `(hints, item)` tuples where /// `hints` is optional alignment hints and /// `item` is a layout [`Item`]. - pub fn new(data: &'a mut L, direction: D, children: I) -> Self { + pub fn new(data: &'a mut S, direction: D, children: I) -> Self { List { data, direction, @@ -57,7 +97,7 @@ where } } -impl<'a, L: RowStorage, D: Directional, I> Visitor for List<'a, L, D, I> +impl<'a, S: RowStorage, D: Directional, I> Visitor for List<'a, S, D, I> where I: ExactSizeIterator, AlignHints)>, { diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs index 6edb82de0..d45016c26 100644 --- a/crates/kas-macros/src/layout.rs +++ b/crates/kas-macros/src/layout.rs @@ -231,7 +231,7 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> use ::kas::WidgetCore; use ::kas::layout::RulesSolver; - let data = self.#core.layout_storage::<#storage_type>(); + let (data, _) = self.#core.layout.storage::<#storage_type>(); let mut solver = #solver_type::new( axis, #dim, @@ -251,7 +251,7 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> use ::kas::layout::{RulesSetter}; self.core.rect = rect; - let data = self.#core.layout_storage::<#storage_type>(); + let (data, _) = self.#core.layout.storage::<#storage_type>(); let mut setter = #setter_type::new( rect, #dim, diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index f9b2b75ab..c12340399 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -62,7 +62,7 @@ widget! { impl Layout for Self { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let data = self.core.layout_storage::>(); + let (data, _) = self.core.layout.storage::>(); let mut solver = layout::RowSolver::new(axis, (self.dir, 2), data); let child = &mut self.inner; solver.for_child(data, 0usize, |axis| { @@ -78,7 +78,7 @@ widget! { fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { self.core.rect = rect; let dim = (self.dir, 2); - let data = self.core.layout_storage::>(); + let (data, _) = self.core.layout.storage::>(); let mut setter = layout::RowSetter::<_, [i32; 2], _>::new( rect, dim, diff --git a/examples/counter.rs b/examples/counter.rs index 4f7b66fc6..8f8b1db7a 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -16,8 +16,6 @@ fn main() -> Result<(), kas::shell::Error> { let counter = make_widget! { #[handler(msg = VoidMsg)] struct { - layout_data: layout::FixedRowStorage<2> = Default::default(), - layout_data2: layout::FixedRowStorage<2> = Default::default(), #[widget] display: Label = Label::from("0"), #[widget(use_msg = update)] @@ -32,16 +30,24 @@ fn main() -> Result<(), kas::shell::Error> { *mgr |= self.display.set_string(self.count.to_string()); } fn layout<'a>(&'a mut self) -> impl layout::Visitor + 'a { - let arr2 = [ - (layout::Item::Widget(self.b_decr.as_widget_mut()), AlignHints::NONE), - (layout::Item::Widget(self.b_incr.as_widget_mut()), AlignHints::NONE), - ]; - let b_layout = Box::new(layout::List::new(&mut self.layout_data2, kas::dir::Right, arr2.into_iter())); - let arr = [ - (layout::Item::Widget(self.display.as_widget_mut()), AlignHints::CENTER), - (layout::Item::Layout(b_layout), AlignHints::NONE), - ]; - layout::List::new(&mut self.layout_data, kas::dir::Down, arr.into_iter()) + let (data, next) = self.core.layout.storage::>(); + let (data2, _) = next.storage::>(); + + layout::List::new(data, kas::dir::Down, { + let arr = [ + (layout::Item::Widget(self.display.as_widget_mut()), AlignHints::CENTER), + (layout::Item::Layout(Box::new(layout::List::new( + data2, + kas::dir::Right, + { + let arr2 = [ + (layout::Item::Widget(self.b_decr.as_widget_mut()), AlignHints::NONE), + (layout::Item::Widget(self.b_incr.as_widget_mut()), AlignHints::NONE), + ]; arr2.into_iter() + }, + ))), AlignHints::NONE), + ]; arr.into_iter() + }) } } impl Layout for Self { From 242a0024f4482e1ecf2864443ddb7ff16e3c6950 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 11:01:31 +0000 Subject: [PATCH 08/53] Rename layout::Item -> layout::Layout and hide layout::Visitor --- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 89 +++++++++++++++------------ crates/kas-widgets/src/list.rs | 9 ++- examples/counter.rs | 40 +++++++----- 4 files changed, 79 insertions(+), 61 deletions(-) diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index cba827b67..7e8bdf2b3 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -60,7 +60,7 @@ pub use storage::{ DynGridStorage, DynRowStorage, FixedGridStorage, FixedRowStorage, GridStorage, RowStorage, RowTemp, Storage, }; -pub use visitor::{Item, List, StorageChain, Visitor}; +pub use visitor::{Layout, StorageChain}; /// Information on which axis is being resized /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 7269439fa..983a8a673 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -54,7 +54,7 @@ impl StorageChain { } /// Implementation helper for layout of children -pub trait Visitor { +trait Visitor { /// Get size rules for the given axis fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules; @@ -62,57 +62,76 @@ pub trait Visitor { fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints); } +pub struct Layout<'a> { + layout: LayoutType<'a>, + hints: AlignHints, +} + /// Items which can be placed in a layout -pub enum Item<'a> { - /// A widget - Widget(&'a mut dyn WidgetConfig), +enum LayoutType<'a> { + /// A single child widget + Single(&'a mut dyn WidgetConfig), /// An embedded layout - Layout(Box), // TODO: inline storage? + // TODO: why use a trait instead of enumerating all options? + Visitor(Box), // TODO: inline storage? } /// Implement row/column layout for children -pub struct List<'a, S, D, I> { +struct List<'a, S, D, I> { data: &'a mut S, direction: D, children: I, } -impl<'a, S: RowStorage, D: Directional, I> List<'a, S, D, I> -where - I: ExactSizeIterator, AlignHints)>, -{ - /// Construct - /// - /// - `data`: associated storage type - /// - `direction`: row/column direction - /// - `children`: iterator over `(hints, item)` tuples where - /// `hints` is optional alignment hints and - /// `item` is a layout [`Item`]. - pub fn new(data: &'a mut S, direction: D, children: I) -> Self { - List { +impl<'a> Layout<'a> { + /// Construct a single-item layout + pub fn single(widget: &'a mut dyn WidgetConfig, hints: AlignHints) -> Self { + let layout = LayoutType::Single(widget); + Layout { layout, hints } + } + + /// Construct a list layout using an iterator over sub-layouts + pub fn list(list: I, direction: D, data: &'a mut S, hints: AlignHints) -> Self + where + I: ExactSizeIterator> + 'a, + D: Directional, + S: RowStorage, + { + let layout = LayoutType::Visitor(Box::new(List { data, direction, - children, + children: list, + })); + Layout { layout, hints } + } + + /// Get size rules for the given axis + pub fn size_rules(mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + match &mut self.layout { + LayoutType::Single(child) => child.size_rules(sh, axis), + LayoutType::Visitor(visitor) => visitor.size_rules(sh, axis), + } + } + + /// Apply a given `rect` to self + pub fn set_rect(mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { + let align = self.hints.combine(align); + match &mut self.layout { + LayoutType::Single(child) => child.set_rect(mgr, rect, align), + LayoutType::Visitor(layout) => layout.set_rect(mgr, rect, align), } } } impl<'a, S: RowStorage, D: Directional, I> Visitor for List<'a, S, D, I> where - I: ExactSizeIterator, AlignHints)>, + I: ExactSizeIterator>, { fn size_rules(&mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let dim = (self.direction, self.children.len()); let mut solver = RowSolver::new(axis, dim, self.data); - for (n, (child, _)) in (&mut self.children).enumerate() { - match child { - Item::Widget(child) => { - solver.for_child(self.data, n, |axis| child.size_rules(sh, axis)) - } - Item::Layout(mut layout) => { - solver.for_child(self.data, n, |axis| layout.size_rules(sh, axis)) - } - } + for (n, child) in (&mut self.children).enumerate() { + solver.for_child(self.data, n, |axis| child.size_rules(sh, axis)); } solver.finish(self.data) } @@ -121,14 +140,8 @@ where let dim = (self.direction, self.children.len()); let mut setter = RowSetter::, _>::new(rect, dim, align, self.data); - for (n, (child, hints)) in (&mut self.children).enumerate() { - let align = hints.combine(align); - match child { - Item::Widget(child) => child.set_rect(mgr, setter.child_rect(self.data, n), align), - Item::Layout(mut layout) => { - layout.set_rect(mgr, setter.child_rect(self.data, n), align) - } - } + for (n, child) in (&mut self.children).enumerate() { + child.set_rect(mgr, setter.child_rect(self.data, n), align); } } } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 4abec76a0..9ac7ae9e4 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -6,8 +6,7 @@ //! A row or column with run-time adjustable contents use kas::dir::{Down, Right}; -use kas::layout::{self, Visitor as _}; -use kas::{event, prelude::*}; +use kas::{event, layout, prelude::*}; use std::ops::{Index, IndexMut}; /// Support for optionally-indexed messages @@ -205,11 +204,11 @@ widget! { } impl Self { - fn layout<'a>(&'a mut self) -> impl layout::Visitor + 'a { + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { let iter = self.widgets.iter_mut().map(|w| { - (layout::Item::Widget(w.as_widget_mut()), AlignHints::default()) + layout::Layout::single(w.as_widget_mut(), AlignHints::NONE) }); - layout::List::new(&mut self.data, self.direction, iter) + layout::Layout::list(iter, self.direction, &mut self.data, AlignHints::NONE) } } diff --git a/examples/counter.rs b/examples/counter.rs index 8f8b1db7a..d3663f22a 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -5,7 +5,7 @@ //! Counter example (simple button) -use kas::layout::{self, Visitor as _}; +use kas::layout; use kas::macros::make_widget; use kas::prelude::*; use kas::widgets::{Label, TextButton, Window}; @@ -29,25 +29,31 @@ fn main() -> Result<(), kas::shell::Error> { self.count += incr; *mgr |= self.display.set_string(self.count.to_string()); } - fn layout<'a>(&'a mut self) -> impl layout::Visitor + 'a { + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { let (data, next) = self.core.layout.storage::>(); let (data2, _) = next.storage::>(); - layout::List::new(data, kas::dir::Down, { - let arr = [ - (layout::Item::Widget(self.display.as_widget_mut()), AlignHints::CENTER), - (layout::Item::Layout(Box::new(layout::List::new( - data2, - kas::dir::Right, - { - let arr2 = [ - (layout::Item::Widget(self.b_decr.as_widget_mut()), AlignHints::NONE), - (layout::Item::Widget(self.b_incr.as_widget_mut()), AlignHints::NONE), - ]; arr2.into_iter() - }, - ))), AlignHints::NONE), - ]; arr.into_iter() - }) + layout::Layout::list( + { + let arr = [ + layout::Layout::single(self.display.as_widget_mut(), AlignHints::CENTER), + layout::Layout::list( + { + let arr = [ + layout::Layout::single(self.b_decr.as_widget_mut(), AlignHints::NONE), + layout::Layout::single(self.b_incr.as_widget_mut(), AlignHints::NONE), + ]; arr.into_iter() + }, + kas::dir::Right, + data2, + AlignHints::NONE, + ), + ]; arr.into_iter() + }, + kas::dir::Down, + data, + AlignHints::NONE, + ) } } impl Layout for Self { From 37e7d3e522600fc5e09df466b41ad62328ecaac8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 14:16:18 +0000 Subject: [PATCH 09/53] New make_layout macro --- crates/kas-macros/src/lib.rs | 38 ++++++ crates/kas-macros/src/make_layout.rs | 173 +++++++++++++++++++++++++++ examples/counter.rs | 30 +---- 3 files changed, 217 insertions(+), 24 deletions(-) create mode 100644 crates/kas-macros/src/make_layout.rs diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 3459b5564..c2289be6f 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -17,6 +17,7 @@ use syn::{GenericParam, Generics, ItemStruct}; mod args; mod autoimpl; mod layout; +mod make_layout; mod make_widget; pub(crate) mod where_clause; mod widget; @@ -185,6 +186,43 @@ pub fn make_widget(input: TokenStream) -> TokenStream { make_widget::make_widget(args).into() } +/// Macro to make a `kas::layout::Layout` +/// +/// # Syntax +/// +/// > _Align_ :\ +/// >    `center` | `stretch` +/// > +/// > _AlignInfo_ :\ +/// >    `align` `(` _Align_ `)` +/// > +/// > _List_ :\ +/// >    `[` _Layout_ `]` +/// > +/// > _LayoutType_ :\ +/// >       `self` `.` _Member_ +/// > | `column` _List_ +/// > | `row` _List_ +/// > +/// > _Layout_ :\ +/// >    _AlignInfo_? _LayoutType_ +/// > +/// > _MakeLayout_:\ +/// >    `(` _CoreData_ `;` _Layout_ +/// +/// # Example +/// +/// ```none +/// make_layout!(self.core; row[self.a, self.b]) +/// ``` + +#[proc_macro_error] +#[proc_macro] +pub fn make_layout(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as make_layout::Input); + make_layout::make_layout(input).into() +} + /// Macro to derive `From` /// /// See documentation [ in the `kas::macros` module](https://docs.rs/kas/latest/kas/macros#the-derivevoidmsg-macro). diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs new file mode 100644 index 000000000..2bd7ced1c --- /dev/null +++ b/crates/kas-macros/src/make_layout.rs @@ -0,0 +1,173 @@ +// 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 + +use proc_macro2::TokenStream as Toks; +use quote::{quote, TokenStreamExt}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::{bracketed, parenthesized, Token}; + +#[allow(non_camel_case_types)] +mod kw { + use syn::custom_keyword; + + custom_keyword!(align); + custom_keyword!(col); + custom_keyword!(column); + custom_keyword!(row); + custom_keyword!(right); + custom_keyword!(left); + custom_keyword!(down); + custom_keyword!(up); + custom_keyword!(center); + custom_keyword!(stretch); +} + +pub struct Input { + core: syn::Expr, + layout: Layout, +} + +enum Layout { + Single(syn::Expr, Align), + List(List, Align), +} + +struct List { + dir: Toks, + list: Vec, +} + +enum Align { + None, + Center, + Stretch, +} + +impl Parse for Input { + fn parse(input: ParseStream) -> Result { + let core = input.parse()?; + let _: Token![;] = input.parse()?; + let layout = input.parse()?; + + Ok(Input { core, layout }) + } +} + +impl Parse for Layout { + fn parse(input: ParseStream) -> Result { + let mut align = Align::None; + + let mut lookahead = input.lookahead1(); + if lookahead.peek(kw::align) { + let _: kw::align = input.parse()?; + align = parse_align(input)?; + + let _: Token![:] = input.parse()?; + lookahead = input.lookahead1(); + } + + if lookahead.peek(Token![self]) { + Ok(Layout::Single(input.parse()?, align)) + } else if lookahead.peek(kw::column) { + let _: kw::column = input.parse()?; + let dir = quote! { ::kas::dir::Down }; + let list = parse_layout_list(input)?; + Ok(Layout::List(List { dir, list }, align)) + } else if lookahead.peek(kw::row) { + let _: kw::row = input.parse()?; + let dir = quote! { ::kas::dir::Right }; + let list = parse_layout_list(input)?; + Ok(Layout::List(List { dir, list }, align)) + } else { + Err(lookahead.error()) + } + } +} + +fn parse_align(input: ParseStream) -> Result { + let inner; + let _ = parenthesized!(inner in input); + + let lookahead = inner.lookahead1(); + if lookahead.peek(kw::center) { + let _: kw::center = inner.parse()?; + Ok(Align::Center) + } else if lookahead.peek(kw::stretch) { + let _: kw::stretch = inner.parse()?; + Ok(Align::Stretch) + } else { + Err(lookahead.error()) + } +} + +fn parse_layout_list(input: ParseStream) -> Result> { + let inner; + let _ = bracketed!(inner in input); + + let mut list = vec![]; + while !inner.is_empty() { + list.push(inner.parse::()?); + + if inner.is_empty() { + break; + } + + let _: Token![,] = inner.parse()?; + } + + Ok(list) +} + +impl quote::ToTokens for Align { + fn to_tokens(&self, toks: &mut Toks) { + toks.append_all(match self { + Align::None => quote! { ::kas::layout::AlignHints::NONE }, + Align::Center => quote! { ::kas::layout::AlignHints::CENTER }, + Align::Stretch => quote! { ::kas::layout::AlignHints::STRETCH }, + }); + } +} + +impl Layout { + fn generate(&self) -> Toks { + match self { + Layout::Single(expr, align) => quote! { + layout::Layout::single(#expr.as_widget_mut(), #align) + }, + Layout::List(List { dir, list }, align) => { + let len = list.len(); + let storage = if len > 16 { + quote! { ::kas::layout::DynRowStorage } + } else { + quote! { ::kas::layout::FixedRowStorage<#len> } + }; + // Get a storage slot from the chain. Order doesn't matter. + let data = quote! { { + let (data, next) = _chain.storage::<#storage>(); + _chain = next; + data + } }; + + let mut items = Toks::new(); + for item in list { + let item = item.generate(); + items.append_all(quote! { #item, }); + } + let iter = quote! { { let arr = [#items]; arr.into_iter() } }; + + quote! { ::kas::layout::Layout::list(#iter, #dir, #data, #align) } + } + } + } +} + +pub fn make_layout(input: Input) -> Toks { + let core = &input.core; + let layout = input.layout.generate(); + quote! { { + let mut _chain = &mut #core.layout; + #layout + } } +} diff --git a/examples/counter.rs b/examples/counter.rs index d3663f22a..d20ed4490 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -6,7 +6,7 @@ //! Counter example (simple button) use kas::layout; -use kas::macros::make_widget; +use kas::macros::{make_layout, make_widget}; use kas::prelude::*; use kas::widgets::{Label, TextButton, Window}; @@ -30,29 +30,11 @@ fn main() -> Result<(), kas::shell::Error> { *mgr |= self.display.set_string(self.count.to_string()); } fn layout<'a>(&'a mut self) -> layout::Layout<'a> { - let (data, next) = self.core.layout.storage::>(); - let (data2, _) = next.storage::>(); - - layout::Layout::list( - { - let arr = [ - layout::Layout::single(self.display.as_widget_mut(), AlignHints::CENTER), - layout::Layout::list( - { - let arr = [ - layout::Layout::single(self.b_decr.as_widget_mut(), AlignHints::NONE), - layout::Layout::single(self.b_incr.as_widget_mut(), AlignHints::NONE), - ]; arr.into_iter() - }, - kas::dir::Right, - data2, - AlignHints::NONE, - ), - ]; arr.into_iter() - }, - kas::dir::Down, - data, - AlignHints::NONE, + make_layout!(self.core; + column[ + align(center): self.display, + row[self.b_decr, self.b_incr], + ] ) } } From 54c42a86caa15ec3ab87e4f675ec65c5924a6890 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 14:34:45 +0000 Subject: [PATCH 10/53] Fix: location of popup inside scroll region --- crates/kas-widgets/src/scroll.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index d67c5672e..027d31b7d 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -342,11 +342,8 @@ widget! { } #[inline] - fn translation(&self, child_index: usize) -> Offset { - match child_index { - 2 => self.scroll_offset(), - _ => Offset::ZERO, - } + fn translation(&self, _: usize) -> Offset { + self.scroll_offset() } fn find_id(&self, coord: Coord) -> Option { From 76243d9055752f430656c264432d5c23310adb5c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 17:34:29 +0000 Subject: [PATCH 11/53] Fix "translation" for other widgets; remove child_index param Tested on ListView --- crates/kas-core/src/core/widget.rs | 8 ++++---- crates/kas-macros/src/widget.rs | 4 ++-- crates/kas-widgets/src/editbox.rs | 5 +++++ crates/kas-widgets/src/scroll.rs | 2 +- crates/kas-widgets/src/scroll_label.rs | 5 +++++ crates/kas-widgets/src/view/list_view.rs | 5 +++++ crates/kas-widgets/src/view/matrix_view.rs | 5 +++++ crates/kas-widgets/src/window.rs | 2 +- 8 files changed, 28 insertions(+), 8 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index db744f540..90185bc6a 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -441,17 +441,17 @@ pub trait Layout: WidgetChildren { self.core_data_mut().rect = rect; } - /// Get translation of a child + /// Get translation of children /// /// Children may live in a translated coordinate space relative to their /// parent. This method returns an offset which should be *added* to a /// coordinate to translate *into* the child's coordinate space or /// subtracted to translate out. /// - /// In most cases, the translation will be zero. Widgets should return - /// [`Offset::ZERO`] for non-existant children. + /// Note that this should only be non-zero for widgets themselves + /// implementing scrolling (and thin wrappers around these). #[inline] - fn translation(&self, _child_index: usize) -> Offset { + fn translation(&self) -> Offset { Offset::ZERO } diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 385a579fe..8256e8c25 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -233,8 +233,8 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { self.#inner.set_rect(mgr, rect, align); } #[inline] - fn translation(&self, child_index: usize) -> ::kas::geom::Offset { - self.#inner.translation(child_index) + fn translation(&self) -> ::kas::geom::Offset { + self.#inner.translation() } #[inline] fn spatial_nav( diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index 44d355fc1..45ac9e789 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -430,6 +430,11 @@ widget! { self.set_view_offset_from_edit_pos(); } + #[inline] + fn translation(&self) -> Offset { + self.scroll_offset() + } + fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let class = if self.multi_line { TextClass::EditMulti diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index 027d31b7d..3ebf7eb4c 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -342,7 +342,7 @@ widget! { } #[inline] - fn translation(&self, _: usize) -> Offset { + fn translation(&self) -> Offset { self.scroll_offset() } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 8bb073a81..eaa6ff8fc 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -46,6 +46,11 @@ widget! { self.set_view_offset_from_edit_pos(); } + #[inline] + fn translation(&self) -> Offset { + self.scroll_offset() + } + fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let class = TextClass::LabelScroll; let state = self.input_state(mgr, disabled); diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index d950686b8..7dd1d00b7 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -517,6 +517,11 @@ widget! { Some(data % usize::conv(self.cur_len)) } + #[inline] + fn translation(&self) -> Offset { + self.scroll_offset() + } + fn find_id(&self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index d9b76eaba..746b00852 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -469,6 +469,11 @@ widget! { } } + #[inline] + fn translation(&self) -> Offset { + self.scroll_offset() + } + fn find_id(&self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index bd7ddc93d..0d26b0889 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -217,7 +217,7 @@ fn find_rect(widget: &dyn WidgetConfig, id: WidgetId) -> Option { if id > w.id() { continue; } - return find_rect(w, id).map(|rect| rect - widget.translation(i)); + return find_rect(w, id).map(|rect| rect - widget.translation()); } break; } From 719b8ca0e6a3da85bffe01b041fb683dc4baa282 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 17:37:53 +0000 Subject: [PATCH 12/53] Make Layout::find_id default impl sufficient for most cases --- crates/kas-core/src/core/widget.rs | 31 ++++++++++++++--------- crates/kas-macros/src/layout.rs | 29 --------------------- crates/kas-widgets/src/adapter/label.rs | 7 ----- crates/kas-widgets/src/adapter/reserve.rs | 8 ------ crates/kas-widgets/src/editbox.rs | 8 ------ crates/kas-widgets/src/frame.rs | 8 ------ crates/kas-widgets/src/grid.rs | 16 +----------- crates/kas-widgets/src/menu/menubar.rs | 11 -------- crates/kas-widgets/src/nav_frame.rs | 8 ------ crates/kas-widgets/src/scroll.rs | 10 -------- crates/kas-widgets/src/scrollbar.rs | 19 -------------- crates/kas-widgets/src/slider.rs | 7 ----- examples/counter.rs | 14 ---------- 13 files changed, 20 insertions(+), 156 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 90185bc6a..e1ca6c0e5 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -494,21 +494,22 @@ pub trait Layout: WidgetChildren { /// Find a widget by coordinate /// - /// Used to find the widget responsible for handling events at this `coord` - /// — usually the leaf-most widget containing the coordinate. + /// This method translates from coordinates (usually from mouse input) to a + /// [`WidgetId`]. It is expected to do the following: /// - /// The default implementation suffices for widgets without children; - /// otherwise this is usually implemented as follows: + /// - Return `None` if `coord` is not within `self.rect()` + /// - Find the child which should respond to input at `coord`, if any, and + /// call `find_id` recursively on this child + /// - Otherwise return `self.id()` /// - /// 1. return `None` if `!self.rect().contains(coord)` - /// 2. for each `child`, check whether `child.find_id(coord)` returns - /// `Some(id)`, and if so return this result (parents with many children - /// might use a faster search strategy here) - /// 3. otherwise, return `Some(self.id())` + /// The default implementation suffices so long as none of the following + /// are true: /// - /// Exceptionally, a widget may deviate from this behaviour, but only when - /// the coord is within the widget's own rect (example: `CheckBox` contains - /// an embedded `CheckBoxBare` and always forwards this child's id). + /// - Child widgets should not be matched even though their area covers + /// `coord` + /// - Child widgets have offset not equal to [`Layout::translation`] + /// - This widget has very large numbers of children such that iterating + /// through all children in order is too slow. /// /// This must not be called before [`Layout::set_rect`]. #[inline] @@ -516,6 +517,12 @@ pub trait Layout: WidgetChildren { if !self.rect().contains(coord) { return None; } + let coord = coord + self.translation(); + for n in 0..self.num_children() { + if let Some(id) = self.get_child(n).and_then(|w| w.find_id(coord)) { + return Some(id); + } + } Some(self.id()) } diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs index d45016c26..aab7b5b8a 100644 --- a/crates/kas-macros/src/layout.rs +++ b/crates/kas-macros/src/layout.rs @@ -114,7 +114,6 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> let pos2 = rect.pos2(); let disabled = disabled || self.is_disabled(); }; - let mut find_id_child = Toks::new(); for child in children.iter() { let ident = &child.ident; @@ -187,13 +186,6 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> self.#ident.draw(draw_handle, mgr, disabled); } }); - - // TODO: more efficient search strategy? - find_id_child.append_all(quote! { - if let Some(id) = self.#ident.find_id(coord) { - return Some(id); - } - }); } let (ucols, urows) = (cols as usize, rows as usize); @@ -206,18 +198,6 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> LayoutType::Grid => quote! { (#cols, #rows, #col_spans, #row_spans) }, }; - let find_id_area = layout.area.as_ref().map(|area_widget| { - quote! { - Some(self.#area_widget.id()) - } - }); - let find_id_body = find_id_area.unwrap_or_else(|| { - quote! { - #find_id_child - Some(self.id()) - } - }); - if let Some(ref method) = layout.draw { draw = quote! { self.#method(draw_handle, mgr, disabled); @@ -261,15 +241,6 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> #set_rect } - fn find_id(&self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { - use ::kas::WidgetCore; - if !self.rect().contains(coord) { - return None; - } - - #find_id_body - } - fn draw( &self, draw_handle: &mut dyn ::kas::draw::DrawHandle, diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index c12340399..8a3350e70 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -95,13 +95,6 @@ widget! { }); } - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.inner.find_id(coord).or(Some(self.id())) - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); self.inner.draw(draw_handle, mgr, disabled); diff --git a/crates/kas-widgets/src/adapter/reserve.rs b/crates/kas-widgets/src/adapter/reserve.rs index 3ba0fb44a..1f92fa832 100644 --- a/crates/kas-widgets/src/adapter/reserve.rs +++ b/crates/kas-widgets/src/adapter/reserve.rs @@ -84,14 +84,6 @@ widget! { self.inner.set_rect(mgr, rect, align); } - #[inline] - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.inner.find_id(coord).or(Some(self.id())) - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); self.inner.draw(draw_handle, mgr, disabled); diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index 45ac9e789..08984f8c1 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -199,14 +199,6 @@ widget! { self.inner.set_rect(mgr, rect, align); } - #[inline] - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - Some(self.inner.id()) - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { // We draw highlights for input state of inner: let disabled = disabled || self.is_disabled() || self.inner.is_disabled(); diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index ecbf9d705..1de7c13eb 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -55,14 +55,6 @@ widget! { self.inner.set_rect(mgr, rect, align); } - #[inline] - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.inner.find_id(coord).or(Some(self.id())) - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { draw_handle.outer_frame(self.core_data().rect); let disabled = disabled || self.is_disabled(); diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 7c490a7c5..70c963777 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -107,21 +107,7 @@ widget! { // macro-generated grid widgets). // fn spatial_nav(&self, reverse: bool, from: Option) -> Option { .. } - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - - // TODO(opt): more efficient position solver (also for drawing)? - // Reverse iteration since the last valid candidate should be "on top" - for child in self.widgets.iter().rev() { - if let Some(id) = child.1.find_id(coord) { - return Some(id); - } - } - - Some(self.id()) - } + // TODO: more efficient find_id and draw? fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 328b54639..0ee58d965 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -71,17 +71,6 @@ widget! { self.bar.set_rect(mgr, rect, align); } - #[inline] - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - if let Some(id) = self.bar.find_id(coord) { - return Some(id); - } - Some(self.id()) - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { self.bar.draw(draw_handle, mgr, disabled); } diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs index 03eea6d04..e8da7626a 100644 --- a/crates/kas-widgets/src/nav_frame.rs +++ b/crates/kas-widgets/src/nav_frame.rs @@ -55,14 +55,6 @@ widget! { self.inner.set_rect(mgr, rect, align); } - #[inline] - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.inner.find_id(coord).or(Some(self.id())) - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let input_state = self.input_state(mgr, disabled); draw_handle.nav_frame(self.rect(), input_state); diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index 3ebf7eb4c..bb1717f26 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -346,16 +346,6 @@ widget! { self.scroll_offset() } - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - - self.inner - .find_id(coord + self.scroll_offset()) - .or(Some(self.id())) - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); draw_handle.with_clip_region(self.core.rect, self.scroll_offset(), &mut |handle| { diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index 3cc557832..a3ac273aa 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -230,13 +230,6 @@ widget! { None // handle is not navigable } - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.handle.find_id(coord).or(Some(self.id())) - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.handle.input_state(mgr, disabled); @@ -595,18 +588,6 @@ widget! { } } - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - - self.horiz_bar - .find_id(coord) - .or_else(|| self.vert_bar.find_id(coord)) - .or_else(|| self.inner.find_id(coord)) - .or(Some(self.id())) - } - #[cfg(feature = "min_spec")] default fn draw( &self, diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 0ebbc546c..57435bdce 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -245,13 +245,6 @@ widget! { None // handle is not navigable } - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.handle.find_id(coord).or(Some(self.id())) - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.input_state(mgr, disabled) | self.handle.input_state(mgr, disabled); diff --git a/examples/counter.rs b/examples/counter.rs index d20ed4490..0ab8b4aa7 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -48,20 +48,6 @@ fn main() -> Result<(), kas::shell::Error> { self.layout().set_rect(mgr, rect, align); } - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - None - } else if let Some(id) = self.display.find_id(coord) { - Some(id) - } else if let Some(id) = self.b_decr.find_id(coord) { - Some(id) - } else if let Some(id) = self.b_incr.find_id(coord) { - Some(id) - } else { - Some(self.id()) - } - } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.display.draw(draw_handle, mgr, disabled); self.b_decr.draw(draw_handle, mgr, disabled); From 128db3e32a25fb937bdc56c538f7e7146667437e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 17:57:22 +0000 Subject: [PATCH 13/53] layout::Layout: add None variant --- crates/kas-core/src/layout/visitor.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 983a8a673..5172b8048 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -69,6 +69,8 @@ pub struct Layout<'a> { /// Items which can be placed in a layout enum LayoutType<'a> { + /// No layout + None, /// A single child widget Single(&'a mut dyn WidgetConfig), /// An embedded layout @@ -84,6 +86,13 @@ struct List<'a, S, D, I> { } impl<'a> Layout<'a> { + /// Construct an empty layout + pub fn none() -> Self { + let layout = LayoutType::None; + let hints = AlignHints::NONE; + Layout { layout, hints } + } + /// Construct a single-item layout pub fn single(widget: &'a mut dyn WidgetConfig, hints: AlignHints) -> Self { let layout = LayoutType::Single(widget); @@ -108,6 +117,7 @@ impl<'a> Layout<'a> { /// Get size rules for the given axis pub fn size_rules(mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { match &mut self.layout { + LayoutType::None => SizeRules::EMPTY, LayoutType::Single(child) => child.size_rules(sh, axis), LayoutType::Visitor(visitor) => visitor.size_rules(sh, axis), } @@ -117,6 +127,7 @@ impl<'a> Layout<'a> { pub fn set_rect(mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { let align = self.hints.combine(align); match &mut self.layout { + LayoutType::None => (), LayoutType::Single(child) => child.set_rect(mgr, rect, align), LayoutType::Visitor(layout) => layout.set_rect(mgr, rect, align), } From 6ed2e1068b1ca866904a5cfb27942efb7f6dccb1 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 18:12:06 +0000 Subject: [PATCH 14/53] Remove walk_children* methods Unused and confusing (also call f on self) --- crates/kas-core/src/core/impls.rs | 7 ----- crates/kas-core/src/core/widget.rs | 42 ------------------------------ crates/kas-widgets/src/menu.rs | 7 ----- 3 files changed, 56 deletions(-) diff --git a/crates/kas-core/src/core/impls.rs b/crates/kas-core/src/core/impls.rs index 6f841974b..e12376136 100644 --- a/crates/kas-core/src/core/impls.rs +++ b/crates/kas-core/src/core/impls.rs @@ -67,13 +67,6 @@ impl WidgetChildren for Box> { fn find_leaf_mut(&mut self, id: WidgetId) -> Option<&mut dyn WidgetConfig> { self.as_mut().find_leaf_mut(id) } - - fn walk_children_dyn(&self, f: &mut dyn FnMut(&dyn WidgetConfig)) { - self.as_ref().walk_children_dyn(f); - } - fn walk_children_mut_dyn(&mut self, f: &mut dyn FnMut(&mut dyn WidgetConfig)) { - self.as_mut().walk_children_mut_dyn(f); - } } impl WidgetConfig for Box> { diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index e1ca6c0e5..7f1835001 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -266,48 +266,6 @@ pub trait WidgetChildren: WidgetCore { None } } - - /// Walk through all widgets, calling `f` once on each. - /// - /// This walk is iterative (nonconcurrent), depth-first, and always calls - /// `f` on self *after* walking through all children. - fn walk_children(&self, mut f: F) - where - Self: Sized, - { - self.walk_children_dyn(&mut f) - } - - #[doc(hidden)] - fn walk_children_dyn(&self, f: &mut dyn FnMut(&dyn WidgetConfig)) { - for i in 0..self.num_children() { - if let Some(w) = self.get_child(i) { - w.walk_children_dyn(f); - } - } - f(self.as_widget()); - } - - /// Walk through all widgets, calling `f` once on each. - /// - /// This walk is iterative (nonconcurrent), depth-first, and always calls - /// `f` on self *after* walking through all children. - fn walk_children_mut(&mut self, mut f: F) - where - Self: Sized, - { - self.walk_children_mut_dyn(&mut f) - } - - #[doc(hidden)] - fn walk_children_mut_dyn(&mut self, f: &mut dyn FnMut(&mut dyn WidgetConfig)) { - for i in 0..self.num_children() { - if let Some(w) = self.get_child_mut(i) { - w.walk_children_mut_dyn(f); - } - } - f(self.as_widget_mut()); - } } /// Widget configuration diff --git a/crates/kas-widgets/src/menu.rs b/crates/kas-widgets/src/menu.rs index d81a109c6..80c91f33c 100644 --- a/crates/kas-widgets/src/menu.rs +++ b/crates/kas-widgets/src/menu.rs @@ -95,13 +95,6 @@ impl WidgetChildren for Box> { fn find_leaf_mut(&mut self, id: WidgetId) -> Option<&mut dyn WidgetConfig> { self.as_mut().find_leaf_mut(id) } - - fn walk_children_dyn(&self, f: &mut dyn FnMut(&dyn WidgetConfig)) { - self.as_ref().walk_children_dyn(f); - } - fn walk_children_mut_dyn(&mut self, f: &mut dyn FnMut(&mut dyn WidgetConfig)) { - self.as_mut().walk_children_mut_dyn(f); - } } impl WidgetConfig for Box> { From f295bdb66d777f91a542d2ac7a219438ace8899d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 18:14:37 +0000 Subject: [PATCH 15/53] Add Layout::layout method; default impls for size_rules, set_rect and draw --- crates/kas-core/src/core/widget.rs | 28 ++++++++++++++++++++++----- crates/kas-core/src/layout/visitor.rs | 6 ++++++ crates/kas-widgets/src/list.rs | 13 +------------ examples/counter.rs | 18 ++--------------- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 7f1835001..f8edcd8e4 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -11,7 +11,7 @@ use std::fmt; use crate::draw::{DrawHandle, InputState, SizeHandle}; use crate::event::{self, ConfigureManager, Manager, ManagerState}; use crate::geom::{Coord, Offset, Rect}; -use crate::layout::{AlignHints, AxisInfo, SizeRules}; +use crate::layout::{self, AlignHints, AxisInfo, SizeRules}; use crate::{CoreData, TkAction, WidgetId}; impl dyn WidgetCore { @@ -361,6 +361,14 @@ pub trait WidgetConfig: Layout { /// /// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro pub trait Layout: WidgetChildren { + /// Make a layout + /// + /// If used, this allows automatic implementation of `size_rules` and + /// `set_rect` methods. The default case is the empty layout. + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + Default::default() // TODO: remove default impl + } + /// Get size rules for the given axis /// /// This method takes `&mut self` to allow local caching of child widget @@ -374,7 +382,9 @@ pub trait Layout: WidgetChildren { /// /// For widgets with children, a [`crate::layout::RulesSolver`] engine may be /// useful to calculate requirements of complex layouts. - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules; + fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + self.layout().size_rules(size_handle, axis) + } /// Apply a given `rect` to self /// @@ -393,10 +403,9 @@ pub trait Layout: WidgetChildren { /// One may assume that `size_rules` has been called at least once for each /// axis with current size information before this method, however /// `size_rules` might not be re-called before calling `set_rect` again. - #[inline] fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - let _ = (mgr, align); self.core_data_mut().rect = rect; + self.layout().set_rect(mgr, rect, align); } /// Get translation of children @@ -495,7 +504,16 @@ pub trait Layout: WidgetChildren { /// /// [`WidgetCore::input_state`] may be used to obtain an [`InputState`] to /// determine active visual effects. - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool); + /// + /// The default impl draws all children. TODO: have default? + fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + let disabled = disabled || self.is_disabled(); + for i in 0..self.num_children() { + if let Some(child) = self.get_child(i) { + child.draw(draw_handle, mgr, disabled); + } + } + } } /// Widget trait diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 5172b8048..2b37b410a 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -85,6 +85,12 @@ struct List<'a, S, D, I> { children: I, } +impl<'a> Default for Layout<'a> { + fn default() -> Self { + Layout::none() + } +} + impl<'a> Layout<'a> { /// Construct an empty layout pub fn none() -> Self { diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 9ac7ae9e4..61860b558 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -203,24 +203,13 @@ widget! { } } - impl Self { + impl Layout for Self { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { let iter = self.widgets.iter_mut().map(|w| { layout::Layout::single(w.as_widget_mut(), AlignHints::NONE) }); layout::Layout::list(iter, self.direction, &mut self.data, AlignHints::NONE) } - } - - impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - self.layout().size_rules(size_handle, axis) - } - - fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - self.layout().set_rect(mgr, rect, align); - } fn spatial_nav( &mut self, diff --git a/examples/counter.rs b/examples/counter.rs index 0ab8b4aa7..0de8e82a2 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -29,6 +29,8 @@ fn main() -> Result<(), kas::shell::Error> { self.count += incr; *mgr |= self.display.set_string(self.count.to_string()); } + } + impl Layout for Self { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { make_layout!(self.core; column[ @@ -38,22 +40,6 @@ fn main() -> Result<(), kas::shell::Error> { ) } } - impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - self.layout().size_rules(size_handle, axis) - } - - fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - self.layout().set_rect(mgr, rect, align); - } - - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - self.display.draw(draw_handle, mgr, disabled); - self.b_decr.draw(draw_handle, mgr, disabled); - self.b_incr.draw(draw_handle, mgr, disabled); - } - } }; let window = Window::new("Counter", counter); From 078ea93416650f352101c0e58ddef52ca4cb8551 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 1 Dec 2021 22:26:16 +0000 Subject: [PATCH 16/53] Add layout::Layout::is_reversed to improve default Layout::spatial_nav impl --- crates/kas-core/src/core/widget.rs | 2 ++ crates/kas-core/src/layout/visitor.rs | 17 +++++++++++++++++ crates/kas-widgets/src/list.rs | 27 --------------------------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index f8edcd8e4..569ecaa4a 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -445,6 +445,8 @@ pub trait Layout: WidgetChildren { return None; } + let reverse = reverse ^ self.layout().is_reversed(); + if let Some(index) = from { match reverse { false if index < last => Some(index + 1), diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 2b37b410a..651dcb3fe 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -60,6 +60,8 @@ trait Visitor { /// Apply a given `rect` to self fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints); + + fn is_reversed(&mut self) -> bool; } pub struct Layout<'a> { @@ -138,6 +140,17 @@ impl<'a> Layout<'a> { LayoutType::Visitor(layout) => layout.set_rect(mgr, rect, align), } } + + /// Return true if layout is up/left + /// + /// This is a lazy method of implementing tab order for reversible layouts. + pub fn is_reversed(mut self) -> bool { + match &mut self.layout { + LayoutType::None => false, + LayoutType::Single(_) => false, + LayoutType::Visitor(layout) => layout.is_reversed(), + } + } } impl<'a, S: RowStorage, D: Directional, I> Visitor for List<'a, S, D, I> @@ -161,4 +174,8 @@ where child.set_rect(mgr, setter.child_rect(self.data, n), align); } } + + fn is_reversed(&mut self) -> bool { + self.direction.is_reversed() + } } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 61860b558..5660555d1 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -211,33 +211,6 @@ widget! { layout::Layout::list(iter, self.direction, &mut self.data, AlignHints::NONE) } - fn spatial_nav( - &mut self, - _: &mut Manager, - reverse: bool, - from: Option, - ) -> Option { - if self.num_children() == 0 { - return None; - } - - let last = self.num_children() - 1; - let reverse = reverse ^ self.direction.is_reversed(); - - if let Some(index) = from { - match reverse { - false if index < last => Some(index + 1), - true if 0 < index => Some(index - 1), - _ => None, - } - } else { - match reverse { - false => Some(0), - true => Some(last), - } - } - } - fn find_id(&self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; From 90acbe783710af7baca3f111806de085b933b0d1 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 2 Dec 2021 11:42:57 +0000 Subject: [PATCH 17/53] temp --- crates/kas-core/src/layout/visitor.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 651dcb3fe..18fa875c3 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -64,6 +64,10 @@ trait Visitor { fn is_reversed(&mut self) -> bool; } +/// A layout visitor +/// +/// This constitutes a "visitor" which iterates over each child widget. Layout +/// algorithm details are implemented over this visitor. pub struct Layout<'a> { layout: LayoutType<'a>, hints: AlignHints, From b85f3715f5bb0b08f2ec4d2f08639769fcedf33d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 2 Dec 2021 11:51:00 +0000 Subject: [PATCH 18/53] Add layout::Layout::frame --- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 66 +++++++++++++++++++++++---- crates/kas-widgets/src/frame.rs | 23 ++-------- 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 7e8bdf2b3..32c219cac 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -60,7 +60,7 @@ pub use storage::{ DynGridStorage, DynRowStorage, FixedGridStorage, FixedRowStorage, GridStorage, RowStorage, RowTemp, Storage, }; -pub use visitor::{Layout, StorageChain}; +pub use visitor::{FrameStorage, Layout, StorageChain}; /// Information on which axis is being resized /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 18fa875c3..a126127f2 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -9,9 +9,11 @@ use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules, Storage}; use super::{RowSetter, RowSolver, RowStorage}; use crate::draw::SizeHandle; use crate::event::Manager; -use crate::geom::Rect; +use crate::geom::{Offset, Rect, Size}; use crate::{dir::Directional, WidgetConfig}; +use std::any::Any; use std::iter::ExactSizeIterator; +use std::mem::replace; /// Chaining layout storage /// @@ -80,17 +82,9 @@ enum LayoutType<'a> { /// A single child widget Single(&'a mut dyn WidgetConfig), /// An embedded layout - // TODO: why use a trait instead of enumerating all options? Visitor(Box), // TODO: inline storage? } -/// Implement row/column layout for children -struct List<'a, S, D, I> { - data: &'a mut S, - direction: D, - children: I, -} - impl<'a> Default for Layout<'a> { fn default() -> Self { Layout::none() @@ -111,6 +105,14 @@ impl<'a> Layout<'a> { Layout { layout, hints } } + /// Construct a frame around a sub-layout + /// + /// This frame has dimensions according to [`SizeHandle::frame`]. + pub fn frame(data: &'a mut FrameStorage, child: Self, hints: AlignHints) -> Self { + let layout = LayoutType::Visitor(Box::new(Frame { data, child })); + Layout { layout, hints } + } + /// Construct a list layout using an iterator over sub-layouts pub fn list(list: I, direction: D, data: &'a mut S, hints: AlignHints) -> Self where @@ -157,6 +159,13 @@ impl<'a> Layout<'a> { } } +/// Implement row/column layout for children +struct List<'a, S, D, I> { + data: &'a mut S, + direction: D, + children: I, +} + impl<'a, S: RowStorage, D: Directional, I> Visitor for List<'a, S, D, I> where I: ExactSizeIterator>, @@ -183,3 +192,42 @@ where self.direction.is_reversed() } } + +/// Layout storage for frame layout +#[derive(Default, Debug)] +pub struct FrameStorage { + offset: Offset, + size: Size, +} +impl Storage for FrameStorage { + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +/// A frame around other content +struct Frame<'a> { + data: &'a mut FrameStorage, + child: Layout<'a>, +} + +impl<'a> Visitor for Frame<'a> { + fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + let frame_rules = size_handle.frame(axis.is_vertical()); + let child_rules = replace(&mut self.child, Layout::default()).size_rules(size_handle, axis); + let (rules, offset, size) = frame_rules.surround_as_margin(child_rules); + self.data.offset.set_component(axis, offset); + self.data.size.set_component(axis, size); + rules + } + + fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { + rect.pos += self.data.offset; + rect.size -= self.data.size; + replace(&mut self.child, Layout::default()).set_rect(mgr, rect, align); + } + + fn is_reversed(&mut self) -> bool { + replace(&mut self.child, Layout::default()).is_reversed() + } +} diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index 1de7c13eb..7ce7b1a74 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -5,6 +5,7 @@ //! A simple frame +use kas::layout::{self, FrameStorage}; use kas::{event, prelude::*}; widget! { @@ -21,8 +22,6 @@ widget! { core: CoreData, #[widget] pub inner: W, - offset: Offset, - size: Size, } impl Self { @@ -32,27 +31,15 @@ widget! { Frame { core: Default::default(), inner, - offset: Offset::ZERO, - size: Size::ZERO, } } } impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let frame_rules = size_handle.frame(axis.is_vertical()); - let child_rules = self.inner.size_rules(size_handle, axis); - let (rules, offset, size) = frame_rules.surround_as_margin(child_rules); - self.offset.set_component(axis, offset); - self.size.set_component(axis, size); - rules - } - - fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { - self.core.rect = rect; - rect.pos += self.offset; - rect.size -= self.size; - self.inner.set_rect(mgr, rect, align); + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + let (data, _) = self.core.layout.storage::(); + let inner = layout::Layout::single(&mut self.inner, AlignHints::default()); + layout::Layout::frame(data, inner, AlignHints::default()) } fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { From 05979329e064825f6e14fab99de5b3525bb07e78 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 2 Dec 2021 12:12:59 +0000 Subject: [PATCH 19/53] make_layout: support frame layout --- crates/kas-macros/src/make_layout.rs | 16 ++++++++++++++++ crates/kas-widgets/src/frame.rs | 8 +++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 2bd7ced1c..ed1db37d8 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -22,6 +22,7 @@ mod kw { custom_keyword!(up); custom_keyword!(center); custom_keyword!(stretch); + custom_keyword!(frame); } pub struct Input { @@ -31,6 +32,7 @@ pub struct Input { enum Layout { Single(syn::Expr, Align), + Frame(Box, Align), List(List, Align), } @@ -70,6 +72,12 @@ impl Parse for Layout { if lookahead.peek(Token![self]) { Ok(Layout::Single(input.parse()?, align)) + } else if lookahead.peek(kw::frame) { + let _: kw::frame = input.parse()?; + let inner; + let _ = parenthesized!(inner in input); + let layout: Layout = inner.parse()?; + Ok(Layout::Frame(Box::new(layout), align)) } else if lookahead.peek(kw::column) { let _: kw::column = input.parse()?; let dir = quote! { ::kas::dir::Down }; @@ -136,6 +144,14 @@ impl Layout { Layout::Single(expr, align) => quote! { layout::Layout::single(#expr.as_widget_mut(), #align) }, + Layout::Frame(layout, align) => { + let inner = layout.generate(); + quote! { + let (data, next) = _chain.storage::<::kas::layout::FrameStorage>(); + _chain = next; + layout::Layout::frame(data, #inner, #align) + } + } Layout::List(List { dir, list }, align) => { let len = list.len(); let storage = if len > 16 { diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index 7ce7b1a74..7ded4c21a 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -5,8 +5,8 @@ //! A simple frame -use kas::layout::{self, FrameStorage}; -use kas::{event, prelude::*}; +use kas::macros::make_layout; +use kas::{event, layout, prelude::*}; widget! { /// A frame around content @@ -37,9 +37,7 @@ widget! { impl Layout for Self { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { - let (data, _) = self.core.layout.storage::(); - let inner = layout::Layout::single(&mut self.inner, AlignHints::default()); - layout::Layout::frame(data, inner, AlignHints::default()) + make_layout!(self.core; frame(self.inner)) } fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { From 2af8d21c1ee13726b5a61e1109ca5509f4edfa46 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 2 Dec 2021 15:17:17 +0000 Subject: [PATCH 20/53] Make Layout::find_id take &mut self This allows using the layout visitor --- crates/kas-core/src/core/impls.rs | 4 +- crates/kas-core/src/core/widget.rs | 4 +- crates/kas-core/src/layout/row_solver.rs | 47 +++++++++++++++------- crates/kas-macros/src/widget.rs | 2 +- crates/kas-widgets/src/list.rs | 4 +- crates/kas-widgets/src/menu.rs | 4 +- crates/kas-widgets/src/splitter.rs | 6 +-- crates/kas-widgets/src/stack.rs | 2 +- crates/kas-widgets/src/view/list_view.rs | 4 +- crates/kas-widgets/src/view/matrix_view.rs | 4 +- crates/kas-widgets/src/window.rs | 6 +-- 11 files changed, 53 insertions(+), 34 deletions(-) diff --git a/crates/kas-core/src/core/impls.rs b/crates/kas-core/src/core/impls.rs index e12376136..6b5fd0169 100644 --- a/crates/kas-core/src/core/impls.rs +++ b/crates/kas-core/src/core/impls.rs @@ -94,8 +94,8 @@ impl Layout for Box> { self.as_mut().set_rect(mgr, rect, align); } - fn find_id(&self, coord: Coord) -> Option { - self.as_ref().find_id(coord) + fn find_id(&mut self, coord: Coord) -> Option { + self.as_mut().find_id(coord) } fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 569ecaa4a..cc1db0d78 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -482,13 +482,13 @@ pub trait Layout: WidgetChildren { /// /// This must not be called before [`Layout::set_rect`]. #[inline] - fn find_id(&self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } let coord = coord + self.translation(); for n in 0..self.num_children() { - if let Some(id) = self.get_child(n).and_then(|w| w.find_id(coord)) { + if let Some(id) = self.get_child_mut(n).and_then(|w| w.find_id(coord)) { return Some(id); } } diff --git a/crates/kas-core/src/layout/row_solver.rs b/crates/kas-core/src/layout/row_solver.rs index 6ef44bbcb..484705906 100644 --- a/crates/kas-core/src/layout/row_solver.rs +++ b/crates/kas-core/src/layout/row_solver.rs @@ -297,24 +297,43 @@ impl RowPositionSolver { /// /// Returns `None` when the coordinates lie within the margin area or /// outside of the parent widget. - pub fn find_child(self, widgets: &[W], coord: Coord) -> Option<&W> { - let index = match self.binary_search(widgets, coord) { - Ok(i) => i, + pub fn find_child_index(self, widgets: &[W], coord: Coord) -> Option { + match self.binary_search(widgets, coord) { + Ok(i) => Some(i), + Err(i) if self.direction.is_reversed() => { + if i == widgets.len() || !widgets[i].rect().contains(coord) { + None + } else { + Some(i) + } + } Err(i) => { - if self.direction.is_reversed() { - if i == widgets.len() || !widgets[i].rect().contains(coord) { - return None; - } - i + if i == 0 || !widgets[i - 1].rect().contains(coord) { + None } else { - if i == 0 || !widgets[i - 1].rect().contains(coord) { - return None; - } - i - 1 + Some(i - 1) } } - }; - Some(&widgets[index]) + } + } + + /// Find the child containing the given coordinates + /// + /// Returns `None` when the coordinates lie within the margin area or + /// outside of the parent widget. + #[inline] + pub fn find_child(self, widgets: &[W], coord: Coord) -> Option<&W> { + self.find_child_index(widgets, coord).map(|i| &widgets[i]) + } + + /// Find the child containing the given coordinates + /// + /// Returns `None` when the coordinates lie within the margin area or + /// outside of the parent widget. + #[inline] + pub fn find_child_mut(self, widgets: &mut [W], coord: Coord) -> Option<&mut W> { + self.find_child_index(widgets, coord) + .map(|i| &mut widgets[i]) } /// Call `f` on each child intersecting the given `rect` diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 8256e8c25..545313e9d 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -246,7 +246,7 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { self.#inner.spatial_nav(mgr, reverse, from) } #[inline] - fn find_id(&self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { self.#inner.find_id(coord) } #[inline] diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 5660555d1..d0f74f0b8 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -211,13 +211,13 @@ widget! { layout::Layout::list(iter, self.direction, &mut self.data, AlignHints::NONE) } - fn find_id(&self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } let solver = layout::RowPositionSolver::new(self.direction); - if let Some(child) = solver.find_child(&self.widgets, coord) { + if let Some(child) = solver.find_child_mut(&mut self.widgets, coord) { return child.find_id(coord); } diff --git a/crates/kas-widgets/src/menu.rs b/crates/kas-widgets/src/menu.rs index 80c91f33c..3e1890d74 100644 --- a/crates/kas-widgets/src/menu.rs +++ b/crates/kas-widgets/src/menu.rs @@ -122,8 +122,8 @@ impl Layout for Box> { self.as_mut().set_rect(mgr, rect, align); } - fn find_id(&self, coord: Coord) -> Option { - self.as_ref().find_id(coord) + fn find_id(&mut self, coord: Coord) -> Option { + self.as_mut().find_id(coord) } fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index f2e372363..dbe848d55 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -184,7 +184,7 @@ widget! { None // handles are not navigable } - fn find_id(&self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } @@ -194,12 +194,12 @@ widget! { // calling it twice. let solver = layout::RowPositionSolver::new(self.direction); - if let Some(child) = solver.find_child(&self.widgets, coord) { + if let Some(child) = solver.find_child_mut(&mut self.widgets, coord) { return child.find_id(coord).or(Some(self.id())); } let solver = layout::RowPositionSolver::new(self.direction); - if let Some(child) = solver.find_child(&self.handles, coord) { + if let Some(child) = solver.find_child_mut(&mut self.handles, coord) { return child.find_id(coord).or(Some(self.id())); } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index c833887bb..724a42519 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -79,7 +79,7 @@ widget! { } } - fn find_id(&self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if self.active < self.widgets.len() { return self.widgets[self.active].find_id(coord); } diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index 7dd1d00b7..39d41f4d6 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -522,13 +522,13 @@ widget! { self.scroll_offset() } - fn find_id(&self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } let coord = coord + self.scroll.offset(); - for child in &self.widgets[..self.cur_len.cast()] { + for child in &mut self.widgets[..self.cur_len.cast()] { if let Some(id) = child.widget.find_id(coord) { return Some(id); } diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 746b00852..f87d5fd64 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -474,14 +474,14 @@ widget! { self.scroll_offset() } - fn find_id(&self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } let coord = coord + self.scroll.offset(); let num = usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1); - for child in &self.widgets[..num] { + for child in &mut self.widgets[..num] { if child.key.is_some() { if let Some(id) = child.widget.find_id(coord) { return Some(id); diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index 0d26b0889..dd95b0f38 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -43,12 +43,12 @@ widget! { } #[inline] - fn find_id(&self, coord: Coord) -> Option { + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } - for popup in self.popups.iter().rev() { - if let Some(id) = self.w.find_leaf(popup.1.id).and_then(|w| w.find_id(coord)) { + for popup in self.popups.iter_mut().rev() { + if let Some(id) = self.w.find_leaf_mut(popup.1.id).and_then(|w| w.find_id(coord)) { return Some(id); } } From 06d5a2e75fa1619a832212930a3dbe1139d88002 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 2 Dec 2021 15:46:20 +0000 Subject: [PATCH 21/53] Rename draw_handle var to draw Also some tweaks to examples/clock --- crates/kas-core/src/core/impls.rs | 4 +-- crates/kas-core/src/core/widget.rs | 4 +-- crates/kas-core/src/draw/draw.rs | 4 +-- crates/kas-core/src/draw/handle.rs | 6 ++--- crates/kas-macros/src/layout.rs | 8 +++--- crates/kas-macros/src/widget.rs | 4 +-- crates/kas-widgets/src/adapter/label.rs | 6 ++--- crates/kas-widgets/src/adapter/reserve.rs | 4 +-- crates/kas-widgets/src/button.rs | 12 ++++----- crates/kas-widgets/src/checkbox.rs | 4 +-- crates/kas-widgets/src/combobox.rs | 6 ++--- crates/kas-widgets/src/editbox.rs | 16 ++++++------ crates/kas-widgets/src/frame.rs | 6 ++--- crates/kas-widgets/src/grid.rs | 4 +-- crates/kas-widgets/src/label.rs | 20 +++++++-------- crates/kas-widgets/src/list.rs | 6 ++--- crates/kas-widgets/src/menu.rs | 4 +-- crates/kas-widgets/src/menu/menu_entry.rs | 14 +++++----- crates/kas-widgets/src/menu/menubar.rs | 4 +-- crates/kas-widgets/src/menu/submenu.rs | 6 ++--- crates/kas-widgets/src/nav_frame.rs | 6 ++--- crates/kas-widgets/src/progress.rs | 4 +-- crates/kas-widgets/src/radiobox.rs | 4 +-- crates/kas-widgets/src/scroll.rs | 4 +-- crates/kas-widgets/src/scroll_label.rs | 8 +++--- crates/kas-widgets/src/scrollbar.rs | 30 +++++++++++----------- crates/kas-widgets/src/separator.rs | 4 +-- crates/kas-widgets/src/slider.rs | 4 +-- crates/kas-widgets/src/splitter.rs | 10 ++++---- crates/kas-widgets/src/stack.rs | 4 +-- crates/kas-widgets/src/view/list_view.rs | 8 +++--- crates/kas-widgets/src/view/matrix_view.rs | 8 +++--- crates/kas-widgets/src/window.rs | 8 +++--- examples/async-event.rs | 4 +-- examples/clock.rs | 19 +++++++------- examples/mandlebrot/mandlebrot.rs | 4 +-- 36 files changed, 136 insertions(+), 135 deletions(-) diff --git a/crates/kas-core/src/core/impls.rs b/crates/kas-core/src/core/impls.rs index 6b5fd0169..00e9a4849 100644 --- a/crates/kas-core/src/core/impls.rs +++ b/crates/kas-core/src/core/impls.rs @@ -98,8 +98,8 @@ impl Layout for Box> { self.as_mut().find_id(coord) } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { - self.as_ref().draw(draw_handle, mgr, disabled); + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + self.as_ref().draw(draw, mgr, disabled); } } diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index cc1db0d78..d52bc2e66 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -508,11 +508,11 @@ pub trait Layout: WidgetChildren { /// determine active visual effects. /// /// The default impl draws all children. TODO: have default? - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); for i in 0..self.num_children() { if let Some(child) = self.get_child(i) { - child.draw(draw_handle, mgr, disabled); + child.draw(draw, mgr, disabled); } } } diff --git a/crates/kas-core/src/draw/draw.rs b/crates/kas-core/src/draw/draw.rs index f4e363706..9ec257521 100644 --- a/crates/kas-core/src/draw/draw.rs +++ b/crates/kas-core/src/draw/draw.rs @@ -29,10 +29,10 @@ use std::any::Any; /// # _pd: std::marker::PhantomData, /// # } /// impl CircleWidget { -/// fn draw(&self, draw_handle: &mut dyn DrawHandle) { +/// fn draw(&self, draw: &mut dyn DrawHandle) { /// // This type assumes usage of kas_wgpu without a custom draw pipe: /// type DrawIface = DrawIface>; -/// if let Some(mut draw) = DrawIface::downcast_from(draw_handle.draw_device()) { +/// if let Some(mut draw) = DrawIface::downcast_from(draw.draw_device()) { /// draw.circle(self.rect.into(), 0.9, Rgba::BLACK); /// } /// } diff --git a/crates/kas-core/src/draw/handle.rs b/crates/kas-core/src/draw/handle.rs index 6b7a2ae04..cd1dd0e40 100644 --- a/crates/kas-core/src/draw/handle.rs +++ b/crates/kas-core/src/draw/handle.rs @@ -750,15 +750,15 @@ where mod test { use super::*; - fn _draw_handle_ext(draw_handle: &mut dyn DrawHandle) { + fn _draw_handle_ext(draw: &mut dyn DrawHandle) { // We can't call this method without constructing an actual DrawHandle. // But we don't need to: we just want to test that methods are callable. - let _scale = draw_handle.size_handle().scale_factor(); + let _scale = draw.size_handle().scale_factor(); let text = crate::text::Text::new_single("sample"); let class = TextClass::Label; let state = InputState::empty(); - draw_handle.text_selected(Coord::ZERO, &text, .., class, state) + draw.text_selected(Coord::ZERO, &text, .., class, state) } } diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs index aab7b5b8a..178ceb579 100644 --- a/crates/kas-macros/src/layout.rs +++ b/crates/kas-macros/src/layout.rs @@ -109,7 +109,7 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> let mut set_rect = Toks::new(); let mut draw = quote! { use ::kas::{geom::Coord, WidgetCore}; - let rect = draw_handle.get_clip_rect(); + let rect = draw.get_clip_rect(); let pos1 = rect.pos; let pos2 = rect.pos2(); let disabled = disabled || self.is_disabled(); @@ -183,7 +183,7 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> let c1 = self.#ident.rect().pos; let c2 = self.#ident.rect().pos2(); if c1.0 <= pos2.0 && c2.0 >= pos1.0 && c1.1 <= pos2.1 && c2.1 >= pos1.1 { - self.#ident.draw(draw_handle, mgr, disabled); + self.#ident.draw(draw, mgr, disabled); } }); } @@ -200,7 +200,7 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> if let Some(ref method) = layout.draw { draw = quote! { - self.#method(draw_handle, mgr, disabled); + self.#method(draw, mgr, disabled); } }; @@ -243,7 +243,7 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> fn draw( &self, - draw_handle: &mut dyn ::kas::draw::DrawHandle, + draw: &mut dyn ::kas::draw::DrawHandle, mgr: &::kas::event::ManagerState, disabled: bool, ) { diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 545313e9d..1211842b7 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -252,11 +252,11 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { #[inline] fn draw( &self, - draw_handle: &mut dyn ::kas::draw::DrawHandle, + draw: &mut dyn ::kas::draw::DrawHandle, mgr: &::kas::event::ManagerState, disabled: bool, ) { - self.#inner.draw(draw_handle, mgr, disabled); + self.#inner.draw(draw, mgr, disabled); } } }); diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index 8a3350e70..63019c23a 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -95,12 +95,12 @@ widget! { }); } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); - self.inner.draw(draw_handle, mgr, disabled); + self.inner.draw(theme, mgr, disabled); let accel = mgr.show_accel_labels(); let state = self.input_state(mgr, disabled); - draw_handle.text_accel(self.label_pos, &self.label, accel, TextClass::Label, state); + theme.text_accel(self.label_pos, &self.label, accel, TextClass::Label, state); } } diff --git a/crates/kas-widgets/src/adapter/reserve.rs b/crates/kas-widgets/src/adapter/reserve.rs index 1f92fa832..11bc575b3 100644 --- a/crates/kas-widgets/src/adapter/reserve.rs +++ b/crates/kas-widgets/src/adapter/reserve.rs @@ -84,9 +84,9 @@ widget! { self.inner.set_rect(mgr, rect, align); } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); - self.inner.draw(draw_handle, mgr, disabled); + self.inner.draw(theme, mgr, disabled); } } } diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index 5534f00a1..0f03be991 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -66,9 +66,9 @@ widget! { self.label.set_rect(mgr, rect, align); } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { - draw_handle.button(self.core.rect, self.color, self.input_state(mgr, disabled)); - self.label.draw(draw_handle, mgr, disabled); + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + theme.button(self.core.rect, self.color, self.input_state(mgr, disabled)); + self.label.draw(theme, mgr, disabled); } } @@ -248,12 +248,12 @@ widget! { }); } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { - draw_handle.button(self.core.rect, self.color, self.input_state(mgr, disabled)); + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + theme.button(self.core.rect, self.color, self.input_state(mgr, disabled)); let pos = self.core.rect.pos + self.frame_offset; let accel = mgr.show_accel_labels(); let state = self.input_state(mgr, disabled); - draw_handle.text_accel(pos, &self.label, accel, TextClass::Button, state); + theme.text_accel(pos, &self.label, accel, TextClass::Button, state); } } diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index b6def1c2f..3071d86b3 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -36,8 +36,8 @@ widget! { self.core.rect = rect; } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { - draw_handle.checkbox(self.core.rect, self.state, self.input_state(mgr, disabled)); + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + theme.checkbox(self.core.rect, self.state, self.input_state(mgr, disabled)); } } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 1c1194610..b5a30c1a3 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -55,13 +55,13 @@ widget! { None } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let mut state = self.input_state(mgr, disabled); if self.popup_id.is_some() { state.insert(InputState::DEPRESS); } - draw_handle.button(self.core.rect, None, state); - draw_handle.text( + theme.button(self.core.rect, None, state); + theme.text( self.core.rect.pos, self.label.as_ref(), TextClass::Button, diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index 08984f8c1..a06a3a1f7 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -199,15 +199,15 @@ widget! { self.inner.set_rect(mgr, rect, align); } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { // We draw highlights for input state of inner: let disabled = disabled || self.is_disabled() || self.inner.is_disabled(); let mut input_state = self.inner.input_state(mgr, disabled); if self.inner.has_error() { input_state.insert(InputState::ERROR); } - draw_handle.edit_box(self.core.rect, input_state); - self.inner.draw(draw_handle, mgr, disabled); + draw.edit_box(self.core.rect, input_state); + self.inner.draw(draw, mgr, disabled); } } } @@ -427,21 +427,21 @@ widget! { self.scroll_offset() } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let class = if self.multi_line { TextClass::EditMulti } else { TextClass::Edit }; let state = self.input_state(mgr, disabled); - draw_handle.with_clip_region(self.rect(), self.view_offset, &mut |draw_handle| { + draw.with_clip_region(self.rect(), self.view_offset, &mut |draw| { if self.selection.is_empty() { - draw_handle.text(self.rect().pos, self.text.as_ref(), class, state); + draw.text(self.rect().pos, self.text.as_ref(), class, state); } else { // TODO(opt): we could cache the selection rectangles here to make // drawing more efficient (self.text.highlight_lines(range) output). // The same applies to the edit marker below. - draw_handle.text_selected( + draw.text_selected( self.rect().pos, &self.text, self.selection.range(), @@ -450,7 +450,7 @@ widget! { ); } if mgr.has_char_focus(self.id()).0 { - draw_handle.edit_marker( + draw.edit_marker( self.rect().pos, self.text.as_ref(), class, diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index 7ded4c21a..307d894bf 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -40,10 +40,10 @@ widget! { make_layout!(self.core; frame(self.inner)) } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { - draw_handle.outer_frame(self.core_data().rect); + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + draw.outer_frame(self.core_data().rect); let disabled = disabled || self.is_disabled(); - self.inner.draw(draw_handle, mgr, disabled); + self.inner.draw(draw, mgr, disabled); } } } diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 70c963777..37a0bb199 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -109,10 +109,10 @@ widget! { // TODO: more efficient find_id and draw? - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); for child in &self.widgets { - child.1.draw(draw_handle, mgr, disabled) + child.1.draw(draw, mgr, disabled) } } } diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index 4fad5450e..d69cd8c6a 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -36,14 +36,14 @@ widget! { } #[cfg(feature = "min_spec")] - default fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + default fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); - draw_handle.text_effects(self.core.rect.pos, &self.label, TextClass::Label, state); + draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label, state); } #[cfg(not(feature = "min_spec"))] - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); - draw_handle.text_effects(self.core.rect.pos, &self.label, TextClass::Label, state); + draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label, state); } } @@ -65,10 +65,10 @@ widget! { #[cfg(feature = "min_spec")] impl Layout for AccelLabel { - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); let accel = mgr.show_accel_labels(); - draw_handle.text_accel( + draw.text_accel( self.core.rect.pos, &self.label, accel, @@ -81,9 +81,9 @@ impl Layout for AccelLabel { // Str/String representations have no effects, so use simpler draw call #[cfg(feature = "min_spec")] impl<'a> Layout for Label<&'a str> { - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); - draw_handle.text( + draw.text( self.core.rect.pos, self.label.as_ref(), TextClass::Label, @@ -93,9 +93,9 @@ impl<'a> Layout for Label<&'a str> { } #[cfg(feature = "min_spec")] impl Layout for StringLabel { - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); - draw_handle.text( + draw.text( self.core.rect.pos, self.label.as_ref(), TextClass::Label, diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index d0f74f0b8..5d5bf6f12 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -224,11 +224,11 @@ widget! { Some(self.id()) } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); let solver = layout::RowPositionSolver::new(self.direction); - solver.for_children(&self.widgets, draw_handle.get_clip_rect(), |w| { - w.draw(draw_handle, mgr, disabled) + solver.for_children(&self.widgets, draw.get_clip_rect(), |w| { + w.draw(draw, mgr, disabled) }); } } diff --git a/crates/kas-widgets/src/menu.rs b/crates/kas-widgets/src/menu.rs index 3e1890d74..81eacb3f8 100644 --- a/crates/kas-widgets/src/menu.rs +++ b/crates/kas-widgets/src/menu.rs @@ -126,8 +126,8 @@ impl Layout for Box> { self.as_mut().find_id(coord) } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { - self.as_ref().draw(draw_handle, mgr, disabled); + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + self.as_ref().draw(draw, mgr, disabled); } } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index e52a1394a..2b2e4dee2 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -52,10 +52,10 @@ widget! { }); } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - draw_handle.menu_entry(self.core.rect, self.input_state(mgr, disabled)); + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + draw.menu_entry(self.core.rect, self.input_state(mgr, disabled)); let pos = self.core.rect.pos + self.label_off; - draw_handle.text_accel( + draw.text_accel( pos, &self.label, mgr.show_accel_labels(), @@ -196,11 +196,11 @@ widget! { self } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.checkbox.input_state(mgr, disabled); - draw_handle.menu_entry(self.core.rect, state); - self.checkbox.draw(draw_handle, mgr, state.disabled()); - self.label.draw(draw_handle, mgr, state.disabled()); + draw.menu_entry(self.core.rect, state); + self.checkbox.draw(draw, mgr, state.disabled()); + self.label.draw(draw, mgr, state.disabled()); } } } diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 0ee58d965..0681bfd64 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -71,8 +71,8 @@ widget! { self.bar.set_rect(mgr, rect, align); } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { - self.bar.draw(draw_handle, mgr, disabled); + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + self.bar.draw(draw, mgr, disabled); } } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index b7bde9cb0..cdafa85f0 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -158,14 +158,14 @@ widget! { None } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let mut state = self.input_state(mgr, disabled); if self.popup_id.is_some() { state.insert(InputState::DEPRESS); } - draw_handle.menu_entry(self.core.rect, state); + draw.menu_entry(self.core.rect, state); let pos = self.core.rect.pos + self.label_off; - draw_handle.text_accel( + draw.text_accel( pos, &self.label, mgr.show_accel_labels(), diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs index e8da7626a..e688ef8c2 100644 --- a/crates/kas-widgets/src/nav_frame.rs +++ b/crates/kas-widgets/src/nav_frame.rs @@ -55,10 +55,10 @@ widget! { self.inner.set_rect(mgr, rect, align); } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let input_state = self.input_state(mgr, disabled); - draw_handle.nav_frame(self.rect(), input_state); - self.inner.draw(draw_handle, mgr, input_state.disabled()); + draw.nav_frame(self.rect(), input_state); + self.inner.draw(draw, mgr, input_state.disabled()); } } diff --git a/crates/kas-widgets/src/progress.rs b/crates/kas-widgets/src/progress.rs index c134cf2e6..660bbc63c 100644 --- a/crates/kas-widgets/src/progress.rs +++ b/crates/kas-widgets/src/progress.rs @@ -98,10 +98,10 @@ widget! { self.core.rect = rect; } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.input_state(mgr, disabled); - draw_handle.progress_bar(self.core.rect, dir, state, self.value); + draw.progress_bar(self.core.rect, dir, state, self.value); } } } diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index 67eaa80d4..76f9e38c8 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -88,8 +88,8 @@ widget! { self.core.rect = rect; } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - draw_handle.radiobox(self.core.rect, self.state, self.input_state(mgr, disabled)); + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + draw.radiobox(self.core.rect, self.state, self.input_state(mgr, disabled)); } } diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index bb1717f26..c3065f25d 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -346,9 +346,9 @@ widget! { self.scroll_offset() } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); - draw_handle.with_clip_region(self.core.rect, self.scroll_offset(), &mut |handle| { + draw.with_clip_region(self.core.rect, self.scroll_offset(), &mut |handle| { self.inner.draw(handle, mgr, disabled) }); } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index eaa6ff8fc..41b904449 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -51,17 +51,17 @@ widget! { self.scroll_offset() } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let class = TextClass::LabelScroll; let state = self.input_state(mgr, disabled); - draw_handle.with_clip_region(self.rect(), self.view_offset, &mut |draw_handle| { + draw.with_clip_region(self.rect(), self.view_offset, &mut |draw| { if self.selection.is_empty() { - draw_handle.text(self.rect().pos, self.text.as_ref(), class, state); + draw.text(self.rect().pos, self.text.as_ref(), class, state); } else { // TODO(opt): we could cache the selection rectangles here to make // drawing more efficient (self.text.highlight_lines(range) output). // The same applies to the edit marker below. - draw_handle.text_selected( + draw.text_selected( self.rect().pos, &self.text, self.selection.range(), diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index a3ac273aa..f5eb4a6d6 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -230,10 +230,10 @@ widget! { None // handle is not navigable } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.handle.input_state(mgr, disabled); - draw_handle.scrollbar(self.core.rect, self.handle.rect(), dir, state); + draw.scrollbar(self.core.rect, self.handle.rect(), dir, state); } } @@ -503,15 +503,15 @@ widget! { &mut self.inner } - fn draw_(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw_(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); if self.show_bars.0 { - self.horiz_bar.draw(draw_handle, mgr, disabled); + self.horiz_bar.draw(draw, mgr, disabled); } if self.show_bars.1 { - self.vert_bar.draw(draw_handle, mgr, disabled); + self.vert_bar.draw(draw, mgr, disabled); } - self.inner.draw(draw_handle, mgr, disabled); + self.inner.draw(draw, mgr, disabled); } } @@ -591,33 +591,33 @@ widget! { #[cfg(feature = "min_spec")] default fn draw( &self, - draw_handle: &mut dyn DrawHandle, + draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool, ) { - self.draw_(draw_handle, mgr, disabled); + self.draw_(draw, mgr, disabled); } #[cfg(not(feature = "min_spec"))] - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { - self.draw_(draw_handle, mgr, disabled); + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + self.draw_(draw, mgr, disabled); } } #[cfg(feature = "min_spec")] impl Layout for ScrollBars> { - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled() || self.inner.is_disabled(); // Enlarge clip region to *our* rect: - draw_handle.with_clip_region(self.core.rect, self.inner.scroll_offset(), &mut |handle| { + draw.with_clip_region(self.core.rect, self.inner.scroll_offset(), &mut |handle| { self.inner.inner().draw(handle, mgr, disabled) }); // Use a second clip region to force draw order: - draw_handle.with_clip_region(self.core.rect, Offset::ZERO, &mut |draw_handle| { + draw.with_clip_region(self.core.rect, Offset::ZERO, &mut |draw| { if self.show_bars.0 { - self.horiz_bar.draw(draw_handle, mgr, disabled); + self.horiz_bar.draw(draw, mgr, disabled); } if self.show_bars.1 { - self.vert_bar.draw(draw_handle, mgr, disabled); + self.vert_bar.draw(draw, mgr, disabled); } }); } diff --git a/crates/kas-widgets/src/separator.rs b/crates/kas-widgets/src/separator.rs index 8f215ebad..20e16a4c8 100644 --- a/crates/kas-widgets/src/separator.rs +++ b/crates/kas-widgets/src/separator.rs @@ -54,8 +54,8 @@ widget! { SizeRules::extract_fixed(axis, size_handle.separator(), margins) } - fn draw(&self, draw_handle: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) { - draw_handle.separator(self.core.rect); + fn draw(&self, draw: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) { + draw.separator(self.core.rect); } } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 57435bdce..c9ef17bcc 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -245,10 +245,10 @@ widget! { None // handle is not navigable } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.input_state(mgr, disabled) | self.handle.input_state(mgr, disabled); - draw_handle.slider(self.core.rect, self.handle.rect(), dir, state); + draw.slider(self.core.rect, self.handle.rect(), dir, state); } } diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index dbe848d55..9a5e34ee4 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -206,18 +206,18 @@ widget! { Some(self.id()) } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { // as with find_id, there's not much harm in invoking the solver twice let solver = layout::RowPositionSolver::new(self.direction); let disabled = disabled || self.is_disabled(); - solver.for_children(&self.widgets, draw_handle.get_clip_rect(), |w| { - w.draw(draw_handle, mgr, disabled) + solver.for_children(&self.widgets, draw.get_clip_rect(), |w| { + w.draw(draw, mgr, disabled) }); let solver = layout::RowPositionSolver::new(self.direction); - solver.for_children(&self.handles, draw_handle.get_clip_rect(), |w| { - draw_handle.separator(w.rect()) + solver.for_children(&self.handles, draw.get_clip_rect(), |w| { + draw.separator(w.rect()) }); } } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 724a42519..2c163f479 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -86,10 +86,10 @@ widget! { None } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); if self.active < self.widgets.len() { - self.widgets[self.active].draw(draw_handle, mgr, disabled); + self.widgets[self.active].draw(draw, mgr, disabled); } } } diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index 39d41f4d6..86b10fd69 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -536,15 +536,15 @@ widget! { Some(self.id()) } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); let offset = self.scroll_offset(); - draw_handle.with_clip_region(self.core.rect, offset, &mut |draw_handle| { + draw.with_clip_region(self.core.rect, offset, &mut |draw| { for child in &self.widgets[..self.cur_len.cast()] { - child.widget.draw(draw_handle, mgr, disabled); + child.widget.draw(draw, mgr, disabled); if let Some(ref key) = child.key { if self.is_selected(key) { - draw_handle.selection_box(child.widget.rect()); + draw.selection_box(child.widget.rect()); } } } diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index f87d5fd64..365afcfcc 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -491,16 +491,16 @@ widget! { Some(self.id()) } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); let offset = self.scroll_offset(); let num = usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1); - draw_handle.with_clip_region(self.core.rect, offset, &mut |draw_handle| { + draw.with_clip_region(self.core.rect, offset, &mut |draw| { for child in &self.widgets[..num] { if let Some(ref key) = child.key { - child.widget.draw(draw_handle, mgr, disabled); + child.widget.draw(draw, mgr, disabled); if self.is_selected(key) { - draw_handle.selection_box(child.widget.rect()); + draw.selection_box(child.widget.rect()); } } } diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index dd95b0f38..c13496fa7 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -56,13 +56,13 @@ widget! { } #[inline] - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); - self.w.draw(draw_handle, mgr, disabled); + self.w.draw(draw, mgr, disabled); for (_, popup) in &self.popups { if let Some(widget) = self.find_leaf(popup.id) { - draw_handle.with_overlay(widget.rect(), &mut |draw_handle| { - widget.draw(draw_handle, mgr, disabled); + draw.with_overlay(widget.rect(), &mut |draw| { + widget.draw(draw, mgr, disabled); }); } } diff --git a/examples/async-event.rs b/examples/async-event.rs index e700d32f5..5e2278f6b 100644 --- a/examples/async-event.rs +++ b/examples/async-event.rs @@ -63,8 +63,8 @@ widget! { let factor = size_handle.scale_factor(); SizeRules::fixed_scaled(100.0, 10.0, factor) } - fn draw(&self, draw_handle: &mut dyn DrawHandle, _: &ManagerState, _: bool) { - let draw = draw_handle.draw_device(); + fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { + let draw = draw.draw_device(); let col = *self.colour.lock().unwrap(); draw.rect((self.rect()).into(), col); } diff --git a/examples/clock.rs b/examples/clock.rs index 452e70500..c1f134aec 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -12,7 +12,7 @@ use log::info; use std::f32::consts::PI; use std::time::Duration; -use kas::draw::{color, DrawIface, DrawRounded, PassType, TextClass}; +use kas::draw::{color, Draw, DrawIface, DrawRounded, PassType}; use kas::geom::{Offset, Quad, Vec2}; use kas::shell::draw::DrawPipe; use kas::text::util::set_text_and_prepare; @@ -62,17 +62,18 @@ widget! { self.time_pos = pos; } - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { let col_face = color::Rgba::grey(0.4); + let col_time = color::Rgba::grey(0.0); + let col_date = color::Rgba::grey(0.2); let col_hands = color::Rgba8Srgb::rgb(124, 124, 170).into(); let col_secs = color::Rgba8Srgb::rgb(203, 124, 124).into(); - let text_class = TextClass::Label; - let state = self.input_state(mgr, disabled); // We use the low-level draw device to draw our clock. This means it is // not themeable, but gives us much more flexible draw routines. - let draw = draw_handle.draw_device(); - // Use use DrawRounded methods, thus must downcast: + let draw = draw.draw_device(); + // Rust does not (yet!) support downcast to trait-object, thus we must use the shell's + // DrawPipe (which is otherwise an implementation detail). This supports DrawRounded. let mut draw = DrawIface::>::downcast_from(draw).unwrap(); let rect = self.core.rect; @@ -92,6 +93,9 @@ widget! { draw.rounded_line(centre + v * (r - l), centre + v * r, w, col_face); } + draw.text(self.date_pos.into(), self.date.as_ref(), col_date); + draw.text(self.time_pos.into(), self.time.as_ref(), col_time); + // We use a new pass to control the draw order (force in front). let mut draw = draw.new_pass(rect, Offset::ZERO, PassType::Clip); let mut line_seg = |t: f32, r1: f32, r2: f32, w, col| { @@ -107,9 +111,6 @@ widget! { line_seg(a_hour, 0.0, half * 0.55, half * 0.03, col_hands); line_seg(a_min, 0.0, half * 0.8, half * 0.015, col_hands); line_seg(a_sec, 0.0, half * 0.9, half * 0.005, col_secs); - - draw_handle.text(self.date_pos, self.date.as_ref(), text_class, state); - draw_handle.text(self.time_pos, self.time.as_ref(), text_class, state); } } diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index f4d223b69..757167690 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -340,8 +340,8 @@ widget! { self.rel_width = rel_width.0 as f32; } - fn draw(&self, draw_handle: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) { - let draw = draw_handle.draw_device(); + fn draw(&self, draw: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) { + let draw = draw.draw_device(); let draw = DrawIface::>::downcast_from(draw).unwrap(); let p = (self.alpha, self.delta, self.rel_width, self.iter); draw.draw.custom(draw.get_pass(), self.core.rect, p); From da73ebdd2873fe1b43b090c6bd8933b459cc9ea0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 2 Dec 2021 15:50:17 +0000 Subject: [PATCH 22/53] Prefer ManagerState over event::ManagerState --- crates/kas-core/src/core/impls.rs | 4 ++-- crates/kas-core/src/core/widget.rs | 2 +- crates/kas-resvg/src/canvas.rs | 4 ++-- crates/kas-resvg/src/svg.rs | 4 ++-- crates/kas-widgets/src/adapter/reserve.rs | 4 ++-- crates/kas-widgets/src/button.rs | 4 ++-- crates/kas-widgets/src/checkbox.rs | 2 +- crates/kas-widgets/src/combobox.rs | 2 +- crates/kas-widgets/src/drag.rs | 2 +- crates/kas-widgets/src/editbox.rs | 4 ++-- crates/kas-widgets/src/filler.rs | 4 ++-- crates/kas-widgets/src/frame.rs | 4 ++-- crates/kas-widgets/src/grid.rs | 2 +- crates/kas-widgets/src/list.rs | 2 +- crates/kas-widgets/src/menu.rs | 2 +- crates/kas-widgets/src/menu/menubar.rs | 2 +- crates/kas-widgets/src/menu/submenu.rs | 2 +- crates/kas-widgets/src/nav_frame.rs | 2 +- crates/kas-widgets/src/scroll.rs | 2 +- crates/kas-widgets/src/scroll_label.rs | 2 +- crates/kas-widgets/src/scrollbar.rs | 15 +++++---------- crates/kas-widgets/src/separator.rs | 2 +- crates/kas-widgets/src/slider.rs | 2 +- crates/kas-widgets/src/splitter.rs | 2 +- crates/kas-widgets/src/sprite.rs | 4 ++-- crates/kas-widgets/src/stack.rs | 2 +- examples/mandlebrot/mandlebrot.rs | 2 +- 27 files changed, 40 insertions(+), 45 deletions(-) diff --git a/crates/kas-core/src/core/impls.rs b/crates/kas-core/src/core/impls.rs index 00e9a4849..b6f0f6615 100644 --- a/crates/kas-core/src/core/impls.rs +++ b/crates/kas-core/src/core/impls.rs @@ -9,7 +9,7 @@ use std::any::Any; use super::*; use crate::draw::{DrawHandle, SizeHandle}; -use crate::event::{self, Event, Manager, Response}; +use crate::event::{self, Event, Manager, ManagerState, Response}; use crate::geom::{Coord, Rect}; use crate::layout::{AlignHints, AxisInfo, SizeRules}; use crate::{CoreData, WidgetId}; @@ -98,7 +98,7 @@ impl Layout for Box> { self.as_mut().find_id(coord) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.as_ref().draw(draw, mgr, disabled); } } diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index d52bc2e66..4c2b1b247 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -508,7 +508,7 @@ pub trait Layout: WidgetChildren { /// determine active visual effects. /// /// The default impl draws all children. TODO: have default? - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); for i in 0..self.num_children() { if let Some(child) = self.get_child(i) { diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index 2c6579e57..ff4a031a9 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -7,7 +7,7 @@ use kas::draw::{ImageFormat, ImageId}; use kas::layout::{SpriteDisplay, SpriteScaling}; -use kas::{event, prelude::*}; +use kas::prelude::*; use tiny_skia::{Color, Pixmap}; /// Draws to a [`Canvas`]'s [`Pixmap`] @@ -126,7 +126,7 @@ widget! { } } - fn draw(&self, draw: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { if let Some(id) = self.image_id { draw.image(id, self.rect()); } diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 33d856fde..7cc998277 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -10,7 +10,7 @@ use kas::draw::{ImageFormat, ImageId}; use kas::geom::Vec2; use kas::layout::MarginSelector; -use kas::{event, prelude::*}; +use kas::prelude::*; use std::path::PathBuf; use tiny_skia::Pixmap; @@ -182,7 +182,7 @@ widget! { } } - fn draw(&self, draw: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { if let Some(id) = self.image_id { draw.image(id, self.rect()); } diff --git a/crates/kas-widgets/src/adapter/reserve.rs b/crates/kas-widgets/src/adapter/reserve.rs index 11bc575b3..12b85aca2 100644 --- a/crates/kas-widgets/src/adapter/reserve.rs +++ b/crates/kas-widgets/src/adapter/reserve.rs @@ -5,7 +5,7 @@ //! Size reservation -use kas::{event, prelude::*}; +use kas::prelude::*; /// Parameterisation of [`Reserve`] using a function pointer /// @@ -84,7 +84,7 @@ widget! { self.inner.set_rect(mgr, rect, align); } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); self.inner.draw(theme, mgr, disabled); } diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index 0f03be991..1534d350b 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -66,7 +66,7 @@ widget! { self.label.set_rect(mgr, rect, align); } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { theme.button(self.core.rect, self.color, self.input_state(mgr, disabled)); self.label.draw(theme, mgr, disabled); } @@ -248,7 +248,7 @@ widget! { }); } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { theme.button(self.core.rect, self.color, self.input_state(mgr, disabled)); let pos = self.core.rect.pos + self.frame_offset; let accel = mgr.show_accel_labels(); diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 3071d86b3..2e396ecab 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -36,7 +36,7 @@ widget! { self.core.rect = rect; } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { theme.checkbox(self.core.rect, self.state, self.input_state(mgr, disabled)); } } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index b5a30c1a3..94c6d138e 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -55,7 +55,7 @@ widget! { None } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let mut state = self.input_state(mgr, disabled); if self.popup_id.is_some() { state.insert(InputState::DEPRESS); diff --git a/crates/kas-widgets/src/drag.rs b/crates/kas-widgets/src/drag.rs index 2508addf4..236cf33e8 100644 --- a/crates/kas-widgets/src/drag.rs +++ b/crates/kas-widgets/src/drag.rs @@ -53,7 +53,7 @@ widget! { self.track = rect; } - fn draw(&self, _: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) {} + fn draw(&self, _: &mut dyn DrawHandle, _: &ManagerState, _: bool) {} } impl event::Handler for DragHandle { diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index a06a3a1f7..7b75b828a 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -199,7 +199,7 @@ widget! { self.inner.set_rect(mgr, rect, align); } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { // We draw highlights for input state of inner: let disabled = disabled || self.is_disabled() || self.inner.is_disabled(); let mut input_state = self.inner.input_state(mgr, disabled); @@ -427,7 +427,7 @@ widget! { self.scroll_offset() } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let class = if self.multi_line { TextClass::EditMulti } else { diff --git a/crates/kas-widgets/src/filler.rs b/crates/kas-widgets/src/filler.rs index 0b20bca41..73b3ef982 100644 --- a/crates/kas-widgets/src/filler.rs +++ b/crates/kas-widgets/src/filler.rs @@ -5,7 +5,7 @@ //! Filler widget -use kas::{event, prelude::*}; +use kas::prelude::*; widget! { /// A space filler @@ -30,7 +30,7 @@ widget! { SizeRules::empty(stretch) } - fn draw(&self, _: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) {} + fn draw(&self, _: &mut dyn DrawHandle, _: &ManagerState, _: bool) {} } } diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index 307d894bf..faf42f0a8 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -6,7 +6,7 @@ //! A simple frame use kas::macros::make_layout; -use kas::{event, layout, prelude::*}; +use kas::{layout, prelude::*}; widget! { /// A frame around content @@ -40,7 +40,7 @@ widget! { make_layout!(self.core; frame(self.inner)) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { draw.outer_frame(self.core_data().rect); let disabled = disabled || self.is_disabled(); self.inner.draw(draw, mgr, disabled); diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 37a0bb199..f88e1d9d0 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -109,7 +109,7 @@ widget! { // TODO: more efficient find_id and draw? - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); for child in &self.widgets { child.1.draw(draw, mgr, disabled) diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 5d5bf6f12..eef9c2ce6 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -224,7 +224,7 @@ widget! { Some(self.id()) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); let solver = layout::RowPositionSolver::new(self.direction); solver.for_children(&self.widgets, draw.get_clip_rect(), |w| { diff --git a/crates/kas-widgets/src/menu.rs b/crates/kas-widgets/src/menu.rs index 81eacb3f8..9e94433de 100644 --- a/crates/kas-widgets/src/menu.rs +++ b/crates/kas-widgets/src/menu.rs @@ -126,7 +126,7 @@ impl Layout for Box> { self.as_mut().find_id(coord) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.as_ref().draw(draw, mgr, disabled); } } diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 0681bfd64..052bec36d 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -71,7 +71,7 @@ widget! { self.bar.set_rect(mgr, rect, align); } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.bar.draw(draw, mgr, disabled); } } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index cdafa85f0..8f282b322 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -158,7 +158,7 @@ widget! { None } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let mut state = self.input_state(mgr, disabled); if self.popup_id.is_some() { state.insert(InputState::DEPRESS); diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs index e688ef8c2..b6884c998 100644 --- a/crates/kas-widgets/src/nav_frame.rs +++ b/crates/kas-widgets/src/nav_frame.rs @@ -55,7 +55,7 @@ widget! { self.inner.set_rect(mgr, rect, align); } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let input_state = self.input_state(mgr, disabled); draw.nav_frame(self.rect(), input_state); self.inner.draw(draw, mgr, input_state.disabled()); diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index c3065f25d..6c75e7064 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -346,7 +346,7 @@ widget! { self.scroll_offset() } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); draw.with_clip_region(self.core.rect, self.scroll_offset(), &mut |handle| { self.inner.draw(handle, mgr, disabled) diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 41b904449..5e80e1ed8 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -51,7 +51,7 @@ widget! { self.scroll_offset() } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let class = TextClass::LabelScroll; let state = self.input_state(mgr, disabled); draw.with_clip_region(self.rect(), self.view_offset, &mut |draw| { diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index f5eb4a6d6..9d65e06a8 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -230,7 +230,7 @@ widget! { None // handle is not navigable } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.handle.input_state(mgr, disabled); draw.scrollbar(self.core.rect, self.handle.rect(), dir, state); @@ -503,7 +503,7 @@ widget! { &mut self.inner } - fn draw_(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw_(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); if self.show_bars.0 { self.horiz_bar.draw(draw, mgr, disabled); @@ -589,23 +589,18 @@ widget! { } #[cfg(feature = "min_spec")] - default fn draw( - &self, - draw: &mut dyn DrawHandle, - mgr: &event::ManagerState, - disabled: bool, - ) { + default fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.draw_(draw, mgr, disabled); } #[cfg(not(feature = "min_spec"))] - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.draw_(draw, mgr, disabled); } } #[cfg(feature = "min_spec")] impl Layout for ScrollBars> { - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled() || self.inner.is_disabled(); // Enlarge clip region to *our* rect: draw.with_clip_region(self.core.rect, self.inner.scroll_offset(), &mut |handle| { diff --git a/crates/kas-widgets/src/separator.rs b/crates/kas-widgets/src/separator.rs index 20e16a4c8..452ba1713 100644 --- a/crates/kas-widgets/src/separator.rs +++ b/crates/kas-widgets/src/separator.rs @@ -54,7 +54,7 @@ widget! { SizeRules::extract_fixed(axis, size_handle.separator(), margins) } - fn draw(&self, draw: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { draw.separator(self.core.rect); } } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index c9ef17bcc..cb897053f 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -245,7 +245,7 @@ widget! { None // handle is not navigable } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.input_state(mgr, disabled) | self.handle.input_state(mgr, disabled); draw.slider(self.core.rect, self.handle.rect(), dir, state); diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 9a5e34ee4..b095cc82c 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -206,7 +206,7 @@ widget! { Some(self.id()) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { // as with find_id, there's not much harm in invoking the solver twice let solver = layout::RowPositionSolver::new(self.direction); diff --git a/crates/kas-widgets/src/sprite.rs b/crates/kas-widgets/src/sprite.rs index 720c92ead..d68866c77 100644 --- a/crates/kas-widgets/src/sprite.rs +++ b/crates/kas-widgets/src/sprite.rs @@ -6,7 +6,7 @@ //! 2D pixmap widget use kas::layout::SpriteDisplay; -use kas::{event, prelude::*}; +use kas::prelude::*; use std::path::PathBuf; widget! { @@ -48,7 +48,7 @@ widget! { self.core_data_mut().rect = self.sprite.align_rect(rect, align); } - fn draw(&self, draw: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { if let Some(id) = self.id { draw.image(id, self.rect()); } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 2c163f479..534800664 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -86,7 +86,7 @@ widget! { None } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); if self.active < self.widgets.len() { self.widgets[self.active].draw(draw, mgr, disabled); diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index 757167690..dd4318368 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -340,7 +340,7 @@ widget! { self.rel_width = rel_width.0 as f32; } - fn draw(&self, draw: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) { + fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { let draw = draw.draw_device(); let draw = DrawIface::>::downcast_from(draw).unwrap(); let p = (self.alpha, self.delta, self.rel_width, self.iter); From 9b553eeaded80ef0990dde9a79d35cd8d767ccc7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 2 Dec 2021 16:04:37 +0000 Subject: [PATCH 23/53] Fix Button widget's event handling --- crates/kas-widgets/src/button.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index 1534d350b..a85fd3314 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -66,6 +66,15 @@ widget! { self.label.set_rect(mgr, rect, align); } + #[inline] + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + // We steal click events on self.label + Some(self.id()) + } + fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { theme.button(self.core.rect, self.color, self.input_state(mgr, disabled)); self.label.draw(theme, mgr, disabled); @@ -177,7 +186,7 @@ widget! { impl SendEvent for Self { fn send(&mut self, mgr: &mut Manager, id: WidgetId, event: Event) -> Response { - if id < self.label.id() { + if id < self.id() { self.label.send(mgr, id, event).void_into() } else { debug_assert_eq!(id, self.id()); From 25db05ee7d0feb24266ad56fe623351e7b7c18e8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 2 Dec 2021 16:05:23 +0000 Subject: [PATCH 24/53] Make Layout::draw take &mut self This is not really desirable but not too problematic. Motivation: layout visitor can now generate draw. --- crates/kas-core/src/core/impls.rs | 4 ++-- crates/kas-core/src/core/widget.rs | 4 ++-- crates/kas-core/src/draw/draw.rs | 2 +- crates/kas-core/src/layout/row_solver.rs | 9 +++++++-- crates/kas-macros/src/layout.rs | 2 +- crates/kas-macros/src/widget.rs | 2 +- crates/kas-resvg/src/canvas.rs | 6 +++--- crates/kas-resvg/src/svg.rs | 2 +- crates/kas-widgets/src/adapter/label.rs | 6 +++--- crates/kas-widgets/src/adapter/reserve.rs | 4 ++-- crates/kas-widgets/src/button.rs | 12 ++++++------ crates/kas-widgets/src/checkbox.rs | 4 ++-- crates/kas-widgets/src/combobox.rs | 6 +++--- crates/kas-widgets/src/drag.rs | 2 +- crates/kas-widgets/src/editbox.rs | 4 ++-- crates/kas-widgets/src/filler.rs | 2 +- crates/kas-widgets/src/frame.rs | 2 +- crates/kas-widgets/src/grid.rs | 4 ++-- crates/kas-widgets/src/label.rs | 10 +++++----- crates/kas-widgets/src/list.rs | 4 ++-- crates/kas-widgets/src/menu.rs | 4 ++-- crates/kas-widgets/src/menu/menu_entry.rs | 4 ++-- crates/kas-widgets/src/menu/menubar.rs | 2 +- crates/kas-widgets/src/menu/submenu.rs | 2 +- crates/kas-widgets/src/nav_frame.rs | 2 +- crates/kas-widgets/src/progress.rs | 2 +- crates/kas-widgets/src/radiobox.rs | 2 +- crates/kas-widgets/src/scroll.rs | 2 +- crates/kas-widgets/src/scroll_label.rs | 2 +- crates/kas-widgets/src/scrollbar.rs | 10 +++++----- crates/kas-widgets/src/separator.rs | 2 +- crates/kas-widgets/src/slider.rs | 2 +- crates/kas-widgets/src/splitter.rs | 6 +++--- crates/kas-widgets/src/sprite.rs | 2 +- crates/kas-widgets/src/stack.rs | 2 +- crates/kas-widgets/src/view/list_view.rs | 6 +++--- crates/kas-widgets/src/view/matrix_view.rs | 6 +++--- crates/kas-widgets/src/window.rs | 4 ++-- examples/async-event.rs | 2 +- examples/canvas.rs | 2 +- examples/clock.rs | 2 +- examples/mandlebrot/mandlebrot.rs | 2 +- 42 files changed, 83 insertions(+), 78 deletions(-) diff --git a/crates/kas-core/src/core/impls.rs b/crates/kas-core/src/core/impls.rs index b6f0f6615..3120da8d2 100644 --- a/crates/kas-core/src/core/impls.rs +++ b/crates/kas-core/src/core/impls.rs @@ -98,8 +98,8 @@ impl Layout for Box> { self.as_mut().find_id(coord) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - self.as_ref().draw(draw, mgr, disabled); + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + self.as_mut().draw(draw, mgr, disabled); } } diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 4c2b1b247..643db5225 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -508,10 +508,10 @@ pub trait Layout: WidgetChildren { /// determine active visual effects. /// /// The default impl draws all children. TODO: have default? - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); for i in 0..self.num_children() { - if let Some(child) = self.get_child(i) { + if let Some(child) = self.get_child_mut(i) { child.draw(draw, mgr, disabled); } } diff --git a/crates/kas-core/src/draw/draw.rs b/crates/kas-core/src/draw/draw.rs index 9ec257521..d9ab5af1b 100644 --- a/crates/kas-core/src/draw/draw.rs +++ b/crates/kas-core/src/draw/draw.rs @@ -29,7 +29,7 @@ use std::any::Any; /// # _pd: std::marker::PhantomData, /// # } /// impl CircleWidget { -/// fn draw(&self, draw: &mut dyn DrawHandle) { +/// fn draw(&mut self, draw: &mut dyn DrawHandle) { /// // This type assumes usage of kas_wgpu without a custom draw pipe: /// type DrawIface = DrawIface>; /// if let Some(mut draw) = DrawIface::downcast_from(draw.draw_device()) { diff --git a/crates/kas-core/src/layout/row_solver.rs b/crates/kas-core/src/layout/row_solver.rs index 484705906..30d4cf847 100644 --- a/crates/kas-core/src/layout/row_solver.rs +++ b/crates/kas-core/src/layout/row_solver.rs @@ -337,7 +337,12 @@ impl RowPositionSolver { } /// Call `f` on each child intersecting the given `rect` - pub fn for_children(self, widgets: &[W], rect: Rect, mut f: F) { + pub fn for_children( + self, + widgets: &mut [W], + rect: Rect, + mut f: F, + ) { let (pos, end) = match self.direction.is_reversed() { false => (rect.pos, rect.pos2()), true => (rect.pos2(), rect.pos), @@ -362,7 +367,7 @@ impl RowPositionSolver { Err(_) => 0, }; - for child in widgets[start..].iter() { + for child in widgets[start..].iter_mut() { let do_break = match self.direction.as_direction() { Direction::Right => child.rect().pos.0 >= end.0, Direction::Down => child.rect().pos.1 >= end.1, diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs index 178ceb579..cc5ec459e 100644 --- a/crates/kas-macros/src/layout.rs +++ b/crates/kas-macros/src/layout.rs @@ -242,7 +242,7 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> } fn draw( - &self, + &mut self, draw: &mut dyn ::kas::draw::DrawHandle, mgr: &::kas::event::ManagerState, disabled: bool, diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 1211842b7..2a03e680b 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -251,7 +251,7 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { } #[inline] fn draw( - &self, + &mut self, draw: &mut dyn ::kas::draw::DrawHandle, mgr: &::kas::event::ManagerState, disabled: bool, diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index ff4a031a9..591120dda 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -16,7 +16,7 @@ pub trait CanvasDrawable: std::fmt::Debug + 'static { /// /// This is called whenever the [`Pixmap`] is resized. One should check the /// pixmap's dimensions and scale the contents appropriately. - fn draw(&self, pixmap: &mut Pixmap); + fn draw(&mut self, pixmap: &mut Pixmap); } widget! { @@ -113,7 +113,7 @@ widget! { mgr.draw_shared(|ds| ds.image_free(id)); } self.pixmap = Pixmap::new(size.0, size.1); - let program = &self.program; + let program = &mut self.program; self.image_id = self.pixmap.as_mut().map(|pm| { program.draw(pm); mgr.draw_shared(|ds| { @@ -126,7 +126,7 @@ widget! { } } - fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { if let Some(id) = self.image_id { draw.image(id, self.rect()); } diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 7cc998277..8aebd444d 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -182,7 +182,7 @@ widget! { } } - fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { if let Some(id) = self.image_id { draw.image(id, self.rect()); } diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index 63019c23a..f827c3097 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -95,12 +95,12 @@ widget! { }); } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); - self.inner.draw(theme, mgr, disabled); + self.inner.draw(draw, mgr, disabled); let accel = mgr.show_accel_labels(); let state = self.input_state(mgr, disabled); - theme.text_accel(self.label_pos, &self.label, accel, TextClass::Label, state); + draw.text_accel(self.label_pos, &self.label, accel, TextClass::Label, state); } } diff --git a/crates/kas-widgets/src/adapter/reserve.rs b/crates/kas-widgets/src/adapter/reserve.rs index 12b85aca2..809acdc53 100644 --- a/crates/kas-widgets/src/adapter/reserve.rs +++ b/crates/kas-widgets/src/adapter/reserve.rs @@ -84,9 +84,9 @@ widget! { self.inner.set_rect(mgr, rect, align); } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); - self.inner.draw(theme, mgr, disabled); + self.inner.draw(draw, mgr, disabled); } } } diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index a85fd3314..4811af408 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -75,9 +75,9 @@ widget! { Some(self.id()) } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - theme.button(self.core.rect, self.color, self.input_state(mgr, disabled)); - self.label.draw(theme, mgr, disabled); + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + draw.button(self.core.rect, self.color, self.input_state(mgr, disabled)); + self.label.draw(draw, mgr, disabled); } } @@ -257,12 +257,12 @@ widget! { }); } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - theme.button(self.core.rect, self.color, self.input_state(mgr, disabled)); + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + draw.button(self.core.rect, self.color, self.input_state(mgr, disabled)); let pos = self.core.rect.pos + self.frame_offset; let accel = mgr.show_accel_labels(); let state = self.input_state(mgr, disabled); - theme.text_accel(pos, &self.label, accel, TextClass::Button, state); + draw.text_accel(pos, &self.label, accel, TextClass::Button, state); } } diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 2e396ecab..35486dd58 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -36,8 +36,8 @@ widget! { self.core.rect = rect; } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - theme.checkbox(self.core.rect, self.state, self.input_state(mgr, disabled)); + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + draw.checkbox(self.core.rect, self.state, self.input_state(mgr, disabled)); } } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 94c6d138e..1338f1ae1 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -55,13 +55,13 @@ widget! { None } - fn draw(&self, theme: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let mut state = self.input_state(mgr, disabled); if self.popup_id.is_some() { state.insert(InputState::DEPRESS); } - theme.button(self.core.rect, None, state); - theme.text( + draw.button(self.core.rect, None, state); + draw.text( self.core.rect.pos, self.label.as_ref(), TextClass::Button, diff --git a/crates/kas-widgets/src/drag.rs b/crates/kas-widgets/src/drag.rs index 236cf33e8..cae68cc16 100644 --- a/crates/kas-widgets/src/drag.rs +++ b/crates/kas-widgets/src/drag.rs @@ -53,7 +53,7 @@ widget! { self.track = rect; } - fn draw(&self, _: &mut dyn DrawHandle, _: &ManagerState, _: bool) {} + fn draw(&mut self, _: &mut dyn DrawHandle, _: &ManagerState, _: bool) {} } impl event::Handler for DragHandle { diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index 7b75b828a..37fd91cd3 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -199,7 +199,7 @@ widget! { self.inner.set_rect(mgr, rect, align); } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { // We draw highlights for input state of inner: let disabled = disabled || self.is_disabled() || self.inner.is_disabled(); let mut input_state = self.inner.input_state(mgr, disabled); @@ -427,7 +427,7 @@ widget! { self.scroll_offset() } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let class = if self.multi_line { TextClass::EditMulti } else { diff --git a/crates/kas-widgets/src/filler.rs b/crates/kas-widgets/src/filler.rs index 73b3ef982..bb9f560d8 100644 --- a/crates/kas-widgets/src/filler.rs +++ b/crates/kas-widgets/src/filler.rs @@ -30,7 +30,7 @@ widget! { SizeRules::empty(stretch) } - fn draw(&self, _: &mut dyn DrawHandle, _: &ManagerState, _: bool) {} + fn draw(&mut self, _: &mut dyn DrawHandle, _: &ManagerState, _: bool) {} } } diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index faf42f0a8..e2568deca 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -40,7 +40,7 @@ widget! { make_layout!(self.core; frame(self.inner)) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { draw.outer_frame(self.core_data().rect); let disabled = disabled || self.is_disabled(); self.inner.draw(draw, mgr, disabled); diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index f88e1d9d0..2a20f408b 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -109,9 +109,9 @@ widget! { // TODO: more efficient find_id and draw? - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); - for child in &self.widgets { + for child in &mut self.widgets { child.1.draw(draw, mgr, disabled) } } diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index d69cd8c6a..69bfcf557 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -36,12 +36,12 @@ widget! { } #[cfg(feature = "min_spec")] - default fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + default fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label, state); } #[cfg(not(feature = "min_spec"))] - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label, state); } @@ -65,7 +65,7 @@ widget! { #[cfg(feature = "min_spec")] impl Layout for AccelLabel { - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); let accel = mgr.show_accel_labels(); draw.text_accel( @@ -81,7 +81,7 @@ impl Layout for AccelLabel { // Str/String representations have no effects, so use simpler draw call #[cfg(feature = "min_spec")] impl<'a> Layout for Label<&'a str> { - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); draw.text( self.core.rect.pos, @@ -93,7 +93,7 @@ impl<'a> Layout for Label<&'a str> { } #[cfg(feature = "min_spec")] impl Layout for StringLabel { - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.input_state(mgr, disabled); draw.text( self.core.rect.pos, diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index eef9c2ce6..e9d84b188 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -224,10 +224,10 @@ widget! { Some(self.id()) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); let solver = layout::RowPositionSolver::new(self.direction); - solver.for_children(&self.widgets, draw.get_clip_rect(), |w| { + solver.for_children(&mut self.widgets, draw.get_clip_rect(), |w| { w.draw(draw, mgr, disabled) }); } diff --git a/crates/kas-widgets/src/menu.rs b/crates/kas-widgets/src/menu.rs index 9e94433de..6763cdd06 100644 --- a/crates/kas-widgets/src/menu.rs +++ b/crates/kas-widgets/src/menu.rs @@ -126,8 +126,8 @@ impl Layout for Box> { self.as_mut().find_id(coord) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - self.as_ref().draw(draw, mgr, disabled); + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + self.as_mut().draw(draw, mgr, disabled); } } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 2b2e4dee2..483c87e79 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -52,7 +52,7 @@ widget! { }); } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { draw.menu_entry(self.core.rect, self.input_state(mgr, disabled)); let pos = self.core.rect.pos + self.label_off; draw.text_accel( @@ -196,7 +196,7 @@ widget! { self } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let state = self.checkbox.input_state(mgr, disabled); draw.menu_entry(self.core.rect, state); self.checkbox.draw(draw, mgr, state.disabled()); diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 052bec36d..337862756 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -71,7 +71,7 @@ widget! { self.bar.set_rect(mgr, rect, align); } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.bar.draw(draw, mgr, disabled); } } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 8f282b322..33a70aee6 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -158,7 +158,7 @@ widget! { None } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let mut state = self.input_state(mgr, disabled); if self.popup_id.is_some() { state.insert(InputState::DEPRESS); diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs index b6884c998..41bc18153 100644 --- a/crates/kas-widgets/src/nav_frame.rs +++ b/crates/kas-widgets/src/nav_frame.rs @@ -55,7 +55,7 @@ widget! { self.inner.set_rect(mgr, rect, align); } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let input_state = self.input_state(mgr, disabled); draw.nav_frame(self.rect(), input_state); self.inner.draw(draw, mgr, input_state.disabled()); diff --git a/crates/kas-widgets/src/progress.rs b/crates/kas-widgets/src/progress.rs index 660bbc63c..ce4c90f0a 100644 --- a/crates/kas-widgets/src/progress.rs +++ b/crates/kas-widgets/src/progress.rs @@ -98,7 +98,7 @@ widget! { self.core.rect = rect; } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.input_state(mgr, disabled); draw.progress_bar(self.core.rect, dir, state, self.value); diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index 76f9e38c8..b150aed04 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -88,7 +88,7 @@ widget! { self.core.rect = rect; } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { draw.radiobox(self.core.rect, self.state, self.input_state(mgr, disabled)); } } diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index 6c75e7064..bc452e769 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -346,7 +346,7 @@ widget! { self.scroll_offset() } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); draw.with_clip_region(self.core.rect, self.scroll_offset(), &mut |handle| { self.inner.draw(handle, mgr, disabled) diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 5e80e1ed8..13df42623 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -51,7 +51,7 @@ widget! { self.scroll_offset() } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let class = TextClass::LabelScroll; let state = self.input_state(mgr, disabled); draw.with_clip_region(self.rect(), self.view_offset, &mut |draw| { diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index 9d65e06a8..441e80312 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -230,7 +230,7 @@ widget! { None // handle is not navigable } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.handle.input_state(mgr, disabled); draw.scrollbar(self.core.rect, self.handle.rect(), dir, state); @@ -503,7 +503,7 @@ widget! { &mut self.inner } - fn draw_(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw_(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); if self.show_bars.0 { self.horiz_bar.draw(draw, mgr, disabled); @@ -589,18 +589,18 @@ widget! { } #[cfg(feature = "min_spec")] - default fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + default fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.draw_(draw, mgr, disabled); } #[cfg(not(feature = "min_spec"))] - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.draw_(draw, mgr, disabled); } } #[cfg(feature = "min_spec")] impl Layout for ScrollBars> { - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled() || self.inner.is_disabled(); // Enlarge clip region to *our* rect: draw.with_clip_region(self.core.rect, self.inner.scroll_offset(), &mut |handle| { diff --git a/crates/kas-widgets/src/separator.rs b/crates/kas-widgets/src/separator.rs index 452ba1713..e466ec64a 100644 --- a/crates/kas-widgets/src/separator.rs +++ b/crates/kas-widgets/src/separator.rs @@ -54,7 +54,7 @@ widget! { SizeRules::extract_fixed(axis, size_handle.separator(), margins) } - fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { draw.separator(self.core.rect); } } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index cb897053f..56c0b6d31 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -245,7 +245,7 @@ widget! { None // handle is not navigable } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.input_state(mgr, disabled) | self.handle.input_state(mgr, disabled); draw.slider(self.core.rect, self.handle.rect(), dir, state); diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index b095cc82c..1a98b0b27 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -206,17 +206,17 @@ widget! { Some(self.id()) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { // as with find_id, there's not much harm in invoking the solver twice let solver = layout::RowPositionSolver::new(self.direction); let disabled = disabled || self.is_disabled(); - solver.for_children(&self.widgets, draw.get_clip_rect(), |w| { + solver.for_children(&mut self.widgets, draw.get_clip_rect(), |w| { w.draw(draw, mgr, disabled) }); let solver = layout::RowPositionSolver::new(self.direction); - solver.for_children(&self.handles, draw.get_clip_rect(), |w| { + solver.for_children(&mut self.handles, draw.get_clip_rect(), |w| { draw.separator(w.rect()) }); } diff --git a/crates/kas-widgets/src/sprite.rs b/crates/kas-widgets/src/sprite.rs index d68866c77..901713f74 100644 --- a/crates/kas-widgets/src/sprite.rs +++ b/crates/kas-widgets/src/sprite.rs @@ -48,7 +48,7 @@ widget! { self.core_data_mut().rect = self.sprite.align_rect(rect, align); } - fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { if let Some(id) = self.id { draw.image(id, self.rect()); } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 534800664..ce0145404 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -86,7 +86,7 @@ widget! { None } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); if self.active < self.widgets.len() { self.widgets[self.active].draw(draw, mgr, disabled); diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index 86b10fd69..aa2f26097 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -536,14 +536,14 @@ widget! { Some(self.id()) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); let offset = self.scroll_offset(); draw.with_clip_region(self.core.rect, offset, &mut |draw| { - for child in &self.widgets[..self.cur_len.cast()] { + for child in &mut self.widgets[..self.cur_len.cast()] { child.widget.draw(draw, mgr, disabled); if let Some(ref key) = child.key { - if self.is_selected(key) { + if self.selection.contains(key) { draw.selection_box(child.widget.rect()); } } diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 365afcfcc..eed61c26e 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -491,15 +491,15 @@ widget! { Some(self.id()) } - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); let offset = self.scroll_offset(); let num = usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1); draw.with_clip_region(self.core.rect, offset, &mut |draw| { - for child in &self.widgets[..num] { + for child in &mut self.widgets[..num] { if let Some(ref key) = child.key { child.widget.draw(draw, mgr, disabled); - if self.is_selected(key) { + if self.selection.contains(key) { draw.selection_box(child.widget.rect()); } } diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index c13496fa7..b3284cab2 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -56,11 +56,11 @@ widget! { } #[inline] - fn draw(&self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); self.w.draw(draw, mgr, disabled); for (_, popup) in &self.popups { - if let Some(widget) = self.find_leaf(popup.id) { + if let Some(widget) = self.w.find_leaf_mut(popup.id) { draw.with_overlay(widget.rect(), &mut |draw| { widget.draw(draw, mgr, disabled); }); diff --git a/examples/async-event.rs b/examples/async-event.rs index 5e2278f6b..c3bc3c857 100644 --- a/examples/async-event.rs +++ b/examples/async-event.rs @@ -63,7 +63,7 @@ widget! { let factor = size_handle.scale_factor(); SizeRules::fixed_scaled(100.0, 10.0, factor) } - fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { let draw = draw.draw_device(); let col = *self.colour.lock().unwrap(); draw.rect((self.rect()).into(), col); diff --git a/examples/canvas.rs b/examples/canvas.rs index d84825c85..47d87e428 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -13,7 +13,7 @@ use kas::widgets::Window; #[derive(Debug)] struct Program; impl CanvasDrawable for Program { - fn draw(&self, pixmap: &mut Pixmap) { + fn draw(&mut self, pixmap: &mut Pixmap) { let size = (200.0, 200.0); let scale = Transform::from_scale( f32::conv(pixmap.width()) / size.0, diff --git a/examples/clock.rs b/examples/clock.rs index c1f134aec..2954d2a83 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -62,7 +62,7 @@ widget! { self.time_pos = pos; } - fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { let col_face = color::Rgba::grey(0.4); let col_time = color::Rgba::grey(0.0); let col_date = color::Rgba::grey(0.2); diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index dd4318368..681ad8d64 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -340,7 +340,7 @@ widget! { self.rel_width = rel_width.0 as f32; } - fn draw(&self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, _: &ManagerState, _: bool) { let draw = draw.draw_device(); let draw = DrawIface::>::downcast_from(draw).unwrap(); let p = (self.alpha, self.delta, self.rel_width, self.iter); From 27af9adcad207b4c3b4ec72041c327e53397789e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 2 Dec 2021 16:20:51 +0000 Subject: [PATCH 25/53] Implement Layout::draw via layout visitor --- crates/kas-core/src/core/widget.rs | 7 ++--- crates/kas-core/src/layout/visitor.rs | 41 +++++++++++++++++++++++++-- crates/kas-widgets/src/frame.rs | 6 ---- crates/kas-widgets/src/list.rs | 8 ------ 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 643db5225..3716a10b0 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -510,11 +510,8 @@ pub trait Layout: WidgetChildren { /// The default impl draws all children. TODO: have default? fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); - for i in 0..self.num_children() { - if let Some(child) = self.get_child_mut(i) { - child.draw(draw, mgr, disabled); - } - } + let rect = self.rect(); + self.layout().draw(rect, draw, mgr, disabled); } } diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index a126127f2..f039e0eda 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -7,8 +7,8 @@ use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules, Storage}; use super::{RowSetter, RowSolver, RowStorage}; -use crate::draw::SizeHandle; -use crate::event::Manager; +use crate::draw::{DrawHandle, SizeHandle}; +use crate::event::{Manager, ManagerState}; use crate::geom::{Offset, Rect, Size}; use crate::{dir::Directional, WidgetConfig}; use std::any::Any; @@ -64,6 +64,8 @@ trait Visitor { fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints); fn is_reversed(&mut self) -> bool; + + fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool); } /// A layout visitor @@ -157,6 +159,24 @@ impl<'a> Layout<'a> { LayoutType::Visitor(layout) => layout.is_reversed(), } } + + /// Draw a widget's children + /// + /// Special: the widget's own `rect` must be passed in. + /// TODO: pass in CoreData instead and use to construct storage dynamically? + pub fn draw( + &mut self, + rect: Rect, + draw: &mut dyn DrawHandle, + mgr: &ManagerState, + disabled: bool, + ) { + match &mut self.layout { + LayoutType::None => (), + LayoutType::Single(child) => child.draw(draw, mgr, disabled), + LayoutType::Visitor(layout) => layout.draw(rect, draw, mgr, disabled), + } + } } /// Implement row/column layout for children @@ -191,6 +211,18 @@ where fn is_reversed(&mut self) -> bool { self.direction.is_reversed() } + + fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + /* TODO: use position solver for large draws (requires array of widgets) + let solver = RowPositionSolver::new(self.direction); + solver.for_children(&mut self.widgets, draw.get_clip_rect(), |w| { + w.draw(rect, draw, mgr, disabled) + }); + */ + for mut child in &mut self.children { + child.draw(rect, draw, mgr, disabled); + } + } } /// Layout storage for frame layout @@ -230,4 +262,9 @@ impl<'a> Visitor for Frame<'a> { fn is_reversed(&mut self) -> bool { replace(&mut self.child, Layout::default()).is_reversed() } + + fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + draw.outer_frame(rect); + replace(&mut self.child, Layout::default()).draw(rect, draw, mgr, disabled); + } } diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index e2568deca..9af189fb3 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -39,11 +39,5 @@ widget! { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { make_layout!(self.core; frame(self.inner)) } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - draw.outer_frame(self.core_data().rect); - let disabled = disabled || self.is_disabled(); - self.inner.draw(draw, mgr, disabled); - } } } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index e9d84b188..c31b1870c 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -223,14 +223,6 @@ widget! { Some(self.id()) } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - let disabled = disabled || self.is_disabled(); - let solver = layout::RowPositionSolver::new(self.direction); - solver.for_children(&mut self.widgets, draw.get_clip_rect(), |w| { - w.draw(draw, mgr, disabled) - }); - } } impl event::SendEvent for Self { From 684d1ccf8a64dad31776c7f0dd5b4908c086eb2d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 3 Dec 2021 10:04:12 +0000 Subject: [PATCH 26/53] Add slice layout visitor --- crates/kas-core/src/layout/row_solver.rs | 16 ++++-- crates/kas-core/src/layout/visitor.rs | 72 +++++++++++++++++++++--- crates/kas-widgets/src/list.rs | 5 +- 3 files changed, 75 insertions(+), 18 deletions(-) diff --git a/crates/kas-core/src/layout/row_solver.rs b/crates/kas-core/src/layout/row_solver.rs index 30d4cf847..9b540c015 100644 --- a/crates/kas-core/src/layout/row_solver.rs +++ b/crates/kas-core/src/layout/row_solver.rs @@ -12,7 +12,7 @@ use super::{Align, AlignHints, AxisInfo, SizeRules}; use super::{RowStorage, RowTemp, RulesSetter, RulesSolver}; use crate::dir::{Direction, Directional}; use crate::geom::{Coord, Rect}; -use crate::Widget; +use crate::WidgetConfig; /// A [`RulesSolver`] for rows (and, without loss of generality, for columns). /// @@ -284,7 +284,7 @@ impl RowPositionSolver { RowPositionSolver { direction } } - fn binary_search(self, widgets: &[W], coord: Coord) -> Result { + fn binary_search(self, widgets: &[W], coord: Coord) -> Result { match self.direction.as_direction() { Direction::Right => widgets.binary_search_by_key(&coord.0, |w| w.rect().pos.0), Direction::Down => widgets.binary_search_by_key(&coord.1, |w| w.rect().pos.1), @@ -297,7 +297,7 @@ impl RowPositionSolver { /// /// Returns `None` when the coordinates lie within the margin area or /// outside of the parent widget. - pub fn find_child_index(self, widgets: &[W], coord: Coord) -> Option { + pub fn find_child_index(self, widgets: &[W], coord: Coord) -> Option { match self.binary_search(widgets, coord) { Ok(i) => Some(i), Err(i) if self.direction.is_reversed() => { @@ -322,7 +322,7 @@ impl RowPositionSolver { /// Returns `None` when the coordinates lie within the margin area or /// outside of the parent widget. #[inline] - pub fn find_child(self, widgets: &[W], coord: Coord) -> Option<&W> { + pub fn find_child(self, widgets: &[W], coord: Coord) -> Option<&W> { self.find_child_index(widgets, coord).map(|i| &widgets[i]) } @@ -331,13 +331,17 @@ impl RowPositionSolver { /// Returns `None` when the coordinates lie within the margin area or /// outside of the parent widget. #[inline] - pub fn find_child_mut(self, widgets: &mut [W], coord: Coord) -> Option<&mut W> { + pub fn find_child_mut( + self, + widgets: &mut [W], + coord: Coord, + ) -> Option<&mut W> { self.find_child_index(widgets, coord) .map(|i| &mut widgets[i]) } /// Call `f` on each child intersecting the given `rect` - pub fn for_children( + pub fn for_children( self, widgets: &mut [W], rect: Rect, diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index f039e0eda..8cfccebc2 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -6,7 +6,7 @@ //! Layout visitor use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules, Storage}; -use super::{RowSetter, RowSolver, RowStorage}; +use super::{DynRowStorage, RowPositionSolver, RowSetter, RowSolver, RowStorage}; use crate::draw::{DrawHandle, SizeHandle}; use crate::event::{Manager, ManagerState}; use crate::geom::{Offset, Rect, Size}; @@ -115,7 +115,7 @@ impl<'a> Layout<'a> { Layout { layout, hints } } - /// Construct a list layout using an iterator over sub-layouts + /// Construct a row/column layout over an iterator of layouts pub fn list(list: I, direction: D, data: &'a mut S, hints: AlignHints) -> Self where I: ExactSizeIterator> + 'a, @@ -130,6 +130,30 @@ impl<'a> Layout<'a> { Layout { layout, hints } } + /// Construct a row/column layout over a slice of widgets + /// + /// In contrast to [`Layout::list`], `slice` can only be used over a slice + /// of a single type of widget, enabling some optimisations: `O(log n)` for + /// `draw` and `find_id`. Some other methods, however, remain `O(n)`, thus + /// the optimisations are not (currently) so useful. + pub fn slice( + slice: &'a mut [W], + direction: D, + data: &'a mut DynRowStorage, + hints: AlignHints, + ) -> Self + where + W: WidgetConfig, + D: Directional, + { + let layout = LayoutType::Visitor(Box::new(Slice { + data, + direction, + children: slice, + })); + Layout { layout, hints } + } + /// Get size rules for the given axis pub fn size_rules(mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { match &mut self.layout { @@ -213,18 +237,50 @@ where } fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - /* TODO: use position solver for large draws (requires array of widgets) - let solver = RowPositionSolver::new(self.direction); - solver.for_children(&mut self.widgets, draw.get_clip_rect(), |w| { - w.draw(rect, draw, mgr, disabled) - }); - */ for mut child in &mut self.children { child.draw(rect, draw, mgr, disabled); } } } +/// A row/column over a slice +struct Slice<'a, W: WidgetConfig, D: Directional> { + data: &'a mut DynRowStorage, + direction: D, + children: &'a mut [W], +} + +impl<'a, W: WidgetConfig, D: Directional> Visitor for Slice<'a, W, D> { + fn size_rules(&mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + let dim = (self.direction, self.children.len()); + let mut solver = RowSolver::new(axis, dim, self.data); + for (n, child) in self.children.iter_mut().enumerate() { + solver.for_child(self.data, n, |axis| child.size_rules(sh, axis)); + } + solver.finish(self.data) + } + + fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { + let dim = (self.direction, self.children.len()); + let mut setter = RowSetter::, _>::new(rect, dim, align, self.data); + + for (n, child) in self.children.iter_mut().enumerate() { + child.set_rect(mgr, setter.child_rect(self.data, n), align); + } + } + + fn is_reversed(&mut self) -> bool { + self.direction.is_reversed() + } + + fn draw(&mut self, _: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + let solver = RowPositionSolver::new(self.direction); + solver.for_children(self.children, draw.get_clip_rect(), |w| { + w.draw(draw, mgr, disabled) + }); + } +} + /// Layout storage for frame layout #[derive(Default, Debug)] pub struct FrameStorage { diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index c31b1870c..2b1d01a2b 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -205,10 +205,7 @@ widget! { impl Layout for Self { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { - let iter = self.widgets.iter_mut().map(|w| { - layout::Layout::single(w.as_widget_mut(), AlignHints::NONE) - }); - layout::Layout::list(iter, self.direction, &mut self.data, AlignHints::NONE) + layout::Layout::slice(&mut self.widgets, self.direction, &mut self.data, AlignHints::NONE) } fn find_id(&mut self, coord: Coord) -> Option { From af79e7c2d9e86c84a8dda48e1686ff9b70c59843 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 3 Dec 2021 10:37:19 +0000 Subject: [PATCH 27/53] Add GridDimensions type --- crates/kas-core/src/layout/grid_solver.rs | 25 +++++++++++++++-------- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-macros/src/layout.rs | 7 ++++++- crates/kas-widgets/src/grid.rs | 20 ++++++++---------- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/crates/kas-core/src/layout/grid_solver.rs b/crates/kas-core/src/layout/grid_solver.rs index ac536c3a9..638c68b1e 100644 --- a/crates/kas-core/src/layout/grid_solver.rs +++ b/crates/kas-core/src/layout/grid_solver.rs @@ -30,6 +30,15 @@ impl DefaultWithLen for Vec { } } +/// Grid dimensions +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub struct GridDimensions { + pub cols: u32, + pub rows: u32, + pub col_spans: u32, + pub row_spans: u32, +} + /// Per-child information #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct GridChildInfo { @@ -73,13 +82,13 @@ impl GridSolver Self { - let col_spans = CSR::default_with_len(dim.2.cast()); - let row_spans = RSR::default_with_len(dim.3.cast()); + pub fn new(axis: AxisInfo, dim: GridDimensions, storage: &mut S) -> Self { + let col_spans = CSR::default_with_len(dim.col_spans.cast()); + let row_spans = RSR::default_with_len(dim.row_spans.cast()); - storage.set_dims(dim.0.cast(), dim.1.cast()); + storage.set_dims(dim.cols.cast(), dim.rows.cast()); let mut solver = GridSolver { axis, @@ -263,11 +272,11 @@ impl GridSetter { /// Argument order is consistent with other [`RulesSetter`]s. /// /// - `rect`: the [`Rect`] within which to position children - /// - `(cols, rows)`: number of columns and rows + /// - `dim`: grid dimensions /// - `align`: alignment hints /// - `storage`: access to the solver's storage - pub fn new(rect: Rect, dim: (u32, u32, u32, u32), align: AlignHints, storage: &mut S) -> Self { - let (cols, rows) = (dim.0.cast(), dim.1.cast()); + pub fn new(rect: Rect, dim: GridDimensions, align: AlignHints, storage: &mut S) -> Self { + let (cols, rows) = (dim.cols.cast(), dim.rows.cast()); let mut w_offsets = RT::default(); w_offsets.set_len(cols); let mut h_offsets = CT::default(); diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 32c219cac..800bcfd82 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -48,7 +48,7 @@ mod visitor; use crate::dir::{Direction, Directional}; pub use align::{Align, AlignHints, CompleteAlignment}; -pub use grid_solver::{DefaultWithLen, GridChildInfo, GridSetter, GridSolver}; +pub use grid_solver::{DefaultWithLen, GridChildInfo, GridDimensions, GridSetter, GridSolver}; pub use row_solver::{RowPositionSolver, RowSetter, RowSolver}; pub use single_solver::{SingleSetter, SingleSolver}; pub use size_rules::SizeRules; diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs index cc5ec459e..af229a786 100644 --- a/crates/kas-macros/src/layout.rs +++ b/crates/kas-macros/src/layout.rs @@ -195,7 +195,12 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> LayoutType::Left => quote! { (::kas::dir::Left, #ucols) }, LayoutType::Down => quote! { (::kas::dir::Down, #urows) }, LayoutType::Up => quote! { (::kas::dir::Up, #urows) }, - LayoutType::Grid => quote! { (#cols, #rows, #col_spans, #row_spans) }, + LayoutType::Grid => quote! { ::kas::layout::GridDimensions { + cols: #cols, + rows: #rows, + col_spans: #col_spans, + row_spans: #row_spans + } }, }; if let Some(ref method) = layout.draw { diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 2a20f408b..bbbdbba15 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -5,9 +5,8 @@ //! A grid widget -use kas::layout::{ - DynGridStorage, GridChildInfo, GridSetter, GridSolver, RulesSetter, RulesSolver, -}; +use kas::layout::{DynGridStorage, GridChildInfo, GridDimensions, GridSetter, GridSolver}; +use kas::layout::{RulesSetter, RulesSolver}; use kas::{event, prelude::*}; use std::ops::{Index, IndexMut}; @@ -55,7 +54,7 @@ widget! { core: CoreData, widgets: Vec<(GridChildInfo, W)>, data: DynGridStorage, - dim: (u32, u32, u32, u32), + dim: GridDimensions, } impl WidgetChildren for Self { @@ -156,19 +155,18 @@ impl Grid { } fn calc_dim(&mut self) { - let (mut cols, mut rows) = (0, 0); - let (mut col_spans, mut row_spans) = (0, 0); + let mut dim = GridDimensions::default(); for child in &self.widgets { - cols = cols.max(child.0.col_end); - rows = rows.max(child.0.row_end); + dim.cols = dim.cols.max(child.0.col_end); + dim.rows = dim.rows.max(child.0.row_end); if child.0.col_end - child.0.col > 1 { - col_spans += 1; + dim.col_spans += 1; } if child.0.row_end - child.0.row > 1 { - row_spans += 1; + dim.row_spans += 1; } } - self.dim = (cols, rows, col_spans, row_spans); + self.dim = dim; } /// Construct via a builder From 4791765844e84ca8dd9d05aee571af4ea8c0e103 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 3 Dec 2021 10:43:56 +0000 Subject: [PATCH 28/53] Add grid layout visitor --- crates/kas-core/src/layout/visitor.rs | 55 ++++++++++++++++++++++++++- crates/kas-widgets/src/grid.rs | 46 +++++----------------- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 8cfccebc2..0f80ff82e 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -7,6 +7,7 @@ use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules, Storage}; use super::{DynRowStorage, RowPositionSolver, RowSetter, RowSolver, RowStorage}; +use super::{GridChildInfo, GridDimensions, GridSetter, GridSolver, GridStorage}; use crate::draw::{DrawHandle, SizeHandle}; use crate::event::{Manager, ManagerState}; use crate::geom::{Offset, Rect, Size}; @@ -84,7 +85,7 @@ enum LayoutType<'a> { /// A single child widget Single(&'a mut dyn WidgetConfig), /// An embedded layout - Visitor(Box), // TODO: inline storage? + Visitor(Box), } impl<'a> Default for Layout<'a> { @@ -154,6 +155,20 @@ impl<'a> Layout<'a> { Layout { layout, hints } } + /// Construct a grid layout over an iterator of `(cell, layout)` items + pub fn grid(iter: I, dim: GridDimensions, data: &'a mut S, hints: AlignHints) -> Self + where + I: Iterator)> + 'a, + S: GridStorage, + { + let layout = LayoutType::Visitor(Box::new(Grid { + data, + dim, + children: iter, + })); + Layout { layout, hints } + } + /// Get size rules for the given axis pub fn size_rules(mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { match &mut self.layout { @@ -281,6 +296,44 @@ impl<'a, W: WidgetConfig, D: Directional> Visitor for Slice<'a, W, D> { } } +/// Implement grid layout for children +struct Grid<'a, S, I> { + data: &'a mut S, + dim: GridDimensions, + children: I, +} + +impl<'a, S: GridStorage, I> Visitor for Grid<'a, S, I> +where + I: Iterator)>, +{ + fn size_rules(&mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, self.data); + for (info, child) in &mut self.children { + solver.for_child(self.data, info, |axis| child.size_rules(sh, axis)); + } + solver.finish(self.data) + } + + fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { + let mut setter = GridSetter::, Vec<_>, _>::new(rect, self.dim, align, self.data); + for (info, child) in &mut self.children { + child.set_rect(mgr, setter.child_rect(self.data, info), align); + } + } + + fn is_reversed(&mut self) -> bool { + // TODO: replace is_reversed with direct implementation of spatial_nav + false + } + + fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + for (_, mut child) in &mut self.children { + child.draw(rect, draw, mgr, disabled); + } + } +} + /// Layout storage for frame layout #[derive(Default, Debug)] pub struct FrameStorage { diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index bbbdbba15..c07d23dc2 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -5,9 +5,8 @@ //! A grid widget -use kas::layout::{DynGridStorage, GridChildInfo, GridDimensions, GridSetter, GridSolver}; -use kas::layout::{RulesSetter, RulesSolver}; -use kas::{event, prelude::*}; +use kas::layout::{DynGridStorage, GridChildInfo, GridDimensions}; +use kas::{event, layout, prelude::*}; use std::ops::{Index, IndexMut}; /// A grid of boxed widgets @@ -80,39 +79,14 @@ widget! { } impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, &mut self.data); - for child in self.widgets.iter_mut() { - solver.for_child(&mut self.data, child.0, |axis| { - child.1.size_rules(size_handle, axis) - }); - } - solver.finish(&mut self.data) - } - - fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - let mut setter = - GridSetter::, Vec, _>::new(rect, self.dim, align, &mut self.data); - - for child in self.widgets.iter_mut() { - child - .1 - .set_rect(mgr, setter.child_rect(&mut self.data, child.0), align); - } - } - - // TODO: we should probably implement spatial_nav (the same is true for - // macro-generated grid widgets). - // fn spatial_nav(&self, reverse: bool, from: Option) -> Option { .. } - - // TODO: more efficient find_id and draw? - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - let disabled = disabled || self.is_disabled(); - for child in &mut self.widgets { - child.1.draw(draw, mgr, disabled) - } + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + let hints = AlignHints::NONE; + layout::Layout::grid( + self.widgets.iter_mut().map(move |(info, w)| (*info, layout::Layout::single(w, hints))), + self.dim, + &mut self.data, + hints, + ) } } From 6b46c865416116dc5c94ed95efcb087491b6f7d2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 3 Dec 2021 11:18:59 +0000 Subject: [PATCH 29/53] Visitor: use only special variants for alignment --- crates/kas-core/src/layout/visitor.rs | 91 +++++++++++++++++++-------- crates/kas-macros/src/make_layout.rs | 54 +++++++++------- crates/kas-widgets/src/grid.rs | 4 +- crates/kas-widgets/src/list.rs | 2 +- 4 files changed, 97 insertions(+), 54 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 0f80ff82e..aba6f74dc 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -14,7 +14,6 @@ use crate::geom::{Offset, Rect, Size}; use crate::{dir::Directional, WidgetConfig}; use std::any::Any; use std::iter::ExactSizeIterator; -use std::mem::replace; /// Chaining layout storage /// @@ -75,7 +74,6 @@ trait Visitor { /// algorithm details are implemented over this visitor. pub struct Layout<'a> { layout: LayoutType<'a>, - hints: AlignHints, } /// Items which can be placed in a layout @@ -84,6 +82,10 @@ enum LayoutType<'a> { None, /// A single child widget Single(&'a mut dyn WidgetConfig), + /// A single child widget with alignment + AlignSingle(&'a mut dyn WidgetConfig, AlignHints), + /// Apply alignment hints to some sub-layout + AlignLayout(Box>, AlignHints), /// An embedded layout Visitor(Box), } @@ -98,26 +100,37 @@ impl<'a> Layout<'a> { /// Construct an empty layout pub fn none() -> Self { let layout = LayoutType::None; - let hints = AlignHints::NONE; - Layout { layout, hints } + Layout { layout } } /// Construct a single-item layout - pub fn single(widget: &'a mut dyn WidgetConfig, hints: AlignHints) -> Self { + pub fn single(widget: &'a mut dyn WidgetConfig) -> Self { let layout = LayoutType::Single(widget); - Layout { layout, hints } + Layout { layout } + } + + /// Construct a single-item layout with alignment hints + pub fn align_single(widget: &'a mut dyn WidgetConfig, hints: AlignHints) -> Self { + let layout = LayoutType::AlignSingle(widget, hints); + Layout { layout } + } + + /// Align a sub-layout + pub fn align(layout: Self, hints: AlignHints) -> Self { + let layout = LayoutType::AlignLayout(Box::new(layout), hints); + Layout { layout } } /// Construct a frame around a sub-layout /// /// This frame has dimensions according to [`SizeHandle::frame`]. - pub fn frame(data: &'a mut FrameStorage, child: Self, hints: AlignHints) -> Self { + pub fn frame(data: &'a mut FrameStorage, child: Self) -> Self { let layout = LayoutType::Visitor(Box::new(Frame { data, child })); - Layout { layout, hints } + Layout { layout } } /// Construct a row/column layout over an iterator of layouts - pub fn list(list: I, direction: D, data: &'a mut S, hints: AlignHints) -> Self + pub fn list(list: I, direction: D, data: &'a mut S) -> Self where I: ExactSizeIterator> + 'a, D: Directional, @@ -128,7 +141,7 @@ impl<'a> Layout<'a> { direction, children: list, })); - Layout { layout, hints } + Layout { layout } } /// Construct a row/column layout over a slice of widgets @@ -137,12 +150,7 @@ impl<'a> Layout<'a> { /// of a single type of widget, enabling some optimisations: `O(log n)` for /// `draw` and `find_id`. Some other methods, however, remain `O(n)`, thus /// the optimisations are not (currently) so useful. - pub fn slice( - slice: &'a mut [W], - direction: D, - data: &'a mut DynRowStorage, - hints: AlignHints, - ) -> Self + pub fn slice(slice: &'a mut [W], direction: D, data: &'a mut DynRowStorage) -> Self where W: WidgetConfig, D: Directional, @@ -152,11 +160,11 @@ impl<'a> Layout<'a> { direction, children: slice, })); - Layout { layout, hints } + Layout { layout } } /// Construct a grid layout over an iterator of `(cell, layout)` items - pub fn grid(iter: I, dim: GridDimensions, data: &'a mut S, hints: AlignHints) -> Self + pub fn grid(iter: I, dim: GridDimensions, data: &'a mut S) -> Self where I: Iterator)> + 'a, S: GridStorage, @@ -166,24 +174,41 @@ impl<'a> Layout<'a> { dim, children: iter, })); - Layout { layout, hints } + Layout { layout } } /// Get size rules for the given axis + #[inline] pub fn size_rules(mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + self.size_rules_(sh, axis) + } + fn size_rules_(&mut self, sh: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { match &mut self.layout { LayoutType::None => SizeRules::EMPTY, LayoutType::Single(child) => child.size_rules(sh, axis), + LayoutType::AlignSingle(child, _) => child.size_rules(sh, axis), + LayoutType::AlignLayout(layout, _) => layout.size_rules_(sh, axis), LayoutType::Visitor(visitor) => visitor.size_rules(sh, axis), } } /// Apply a given `rect` to self + #[inline] pub fn set_rect(mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - let align = self.hints.combine(align); + self.set_rect_(mgr, rect, align); + } + fn set_rect_(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { match &mut self.layout { LayoutType::None => (), LayoutType::Single(child) => child.set_rect(mgr, rect, align), + LayoutType::AlignSingle(child, hints) => { + let align = hints.combine(align); + child.set_rect(mgr, rect, align); + } + LayoutType::AlignLayout(layout, hints) => { + let align = hints.combine(align); + layout.set_rect_(mgr, rect, align); + } LayoutType::Visitor(layout) => layout.set_rect(mgr, rect, align), } } @@ -191,10 +216,16 @@ impl<'a> Layout<'a> { /// Return true if layout is up/left /// /// This is a lazy method of implementing tab order for reversible layouts. + #[inline] pub fn is_reversed(mut self) -> bool { + self.is_reversed_() + } + fn is_reversed_(&mut self) -> bool { match &mut self.layout { LayoutType::None => false, LayoutType::Single(_) => false, + LayoutType::AlignSingle(_, _) => false, + LayoutType::AlignLayout(layout, _) => layout.is_reversed_(), LayoutType::Visitor(layout) => layout.is_reversed(), } } @@ -203,16 +234,22 @@ impl<'a> Layout<'a> { /// /// Special: the widget's own `rect` must be passed in. /// TODO: pass in CoreData instead and use to construct storage dynamically? + #[inline] pub fn draw( - &mut self, + mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool, ) { + self.draw_(rect, draw, mgr, disabled); + } + fn draw_(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { match &mut self.layout { LayoutType::None => (), LayoutType::Single(child) => child.draw(draw, mgr, disabled), + LayoutType::AlignSingle(child, _) => child.draw(draw, mgr, disabled), + LayoutType::AlignLayout(layout, _) => layout.draw_(rect, draw, mgr, disabled), LayoutType::Visitor(layout) => layout.draw(rect, draw, mgr, disabled), } } @@ -252,7 +289,7 @@ where } fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - for mut child in &mut self.children { + for child in &mut self.children { child.draw(rect, draw, mgr, disabled); } } @@ -328,7 +365,7 @@ where } fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - for (_, mut child) in &mut self.children { + for (_, child) in &mut self.children { child.draw(rect, draw, mgr, disabled); } } @@ -355,7 +392,7 @@ struct Frame<'a> { impl<'a> Visitor for Frame<'a> { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let frame_rules = size_handle.frame(axis.is_vertical()); - let child_rules = replace(&mut self.child, Layout::default()).size_rules(size_handle, axis); + let child_rules = self.child.size_rules_(size_handle, axis); let (rules, offset, size) = frame_rules.surround_as_margin(child_rules); self.data.offset.set_component(axis, offset); self.data.size.set_component(axis, size); @@ -365,15 +402,15 @@ impl<'a> Visitor for Frame<'a> { fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { rect.pos += self.data.offset; rect.size -= self.data.size; - replace(&mut self.child, Layout::default()).set_rect(mgr, rect, align); + self.child.set_rect_(mgr, rect, align); } fn is_reversed(&mut self) -> bool { - replace(&mut self.child, Layout::default()).is_reversed() + self.child.is_reversed_() } fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { draw.outer_frame(rect); - replace(&mut self.child, Layout::default()).draw(rect, draw, mgr, disabled); + self.child.draw_(rect, draw, mgr, disabled); } } diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index ed1db37d8..c2f05af54 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -31,9 +31,11 @@ pub struct Input { } enum Layout { - Single(syn::Expr, Align), - Frame(Box, Align), - List(List, Align), + Align(Box, Align), + AlignSingle(syn::Expr, Align), + Single(syn::Expr), + Frame(Box), + List(List), } struct List { @@ -42,7 +44,6 @@ struct List { } enum Align { - None, Center, Stretch, } @@ -59,35 +60,36 @@ impl Parse for Input { impl Parse for Layout { fn parse(input: ParseStream) -> Result { - let mut align = Align::None; - - let mut lookahead = input.lookahead1(); + let lookahead = input.lookahead1(); if lookahead.peek(kw::align) { let _: kw::align = input.parse()?; - align = parse_align(input)?; - + let align = parse_align(input)?; let _: Token![:] = input.parse()?; - lookahead = input.lookahead1(); - } - if lookahead.peek(Token![self]) { - Ok(Layout::Single(input.parse()?, align)) + if input.peek(Token![self]) { + Ok(Layout::AlignSingle(input.parse()?, align)) + } else { + let layout: Layout = input.parse()?; + Ok(Layout::Align(Box::new(layout), align)) + } + } else if lookahead.peek(Token![self]) { + Ok(Layout::Single(input.parse()?)) } else if lookahead.peek(kw::frame) { let _: kw::frame = input.parse()?; let inner; let _ = parenthesized!(inner in input); let layout: Layout = inner.parse()?; - Ok(Layout::Frame(Box::new(layout), align)) + Ok(Layout::Frame(Box::new(layout))) } else if lookahead.peek(kw::column) { let _: kw::column = input.parse()?; let dir = quote! { ::kas::dir::Down }; let list = parse_layout_list(input)?; - Ok(Layout::List(List { dir, list }, align)) + Ok(Layout::List(List { dir, list })) } else if lookahead.peek(kw::row) { let _: kw::row = input.parse()?; let dir = quote! { ::kas::dir::Right }; let list = parse_layout_list(input)?; - Ok(Layout::List(List { dir, list }, align)) + Ok(Layout::List(List { dir, list })) } else { Err(lookahead.error()) } @@ -131,7 +133,6 @@ fn parse_layout_list(input: ParseStream) -> Result> { impl quote::ToTokens for Align { fn to_tokens(&self, toks: &mut Toks) { toks.append_all(match self { - Align::None => quote! { ::kas::layout::AlignHints::NONE }, Align::Center => quote! { ::kas::layout::AlignHints::CENTER }, Align::Stretch => quote! { ::kas::layout::AlignHints::STRETCH }, }); @@ -141,18 +142,25 @@ impl quote::ToTokens for Align { impl Layout { fn generate(&self) -> Toks { match self { - Layout::Single(expr, align) => quote! { - layout::Layout::single(#expr.as_widget_mut(), #align) + Layout::Align(layout, align) => { + let inner = layout.generate(); + quote! { layout::Layout::align(#inner, #align) } + } + Layout::AlignSingle(expr, align) => { + quote! { layout::Layout::align_single(#expr.as_widget_mut(), #align) } + } + Layout::Single(expr) => quote! { + layout::Layout::single(#expr.as_widget_mut()) }, - Layout::Frame(layout, align) => { + Layout::Frame(layout) => { let inner = layout.generate(); quote! { let (data, next) = _chain.storage::<::kas::layout::FrameStorage>(); _chain = next; - layout::Layout::frame(data, #inner, #align) + layout::Layout::frame(data, #inner) } } - Layout::List(List { dir, list }, align) => { + Layout::List(List { dir, list }) => { let len = list.len(); let storage = if len > 16 { quote! { ::kas::layout::DynRowStorage } @@ -173,7 +181,7 @@ impl Layout { } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; - quote! { ::kas::layout::Layout::list(#iter, #dir, #data, #align) } + quote! { ::kas::layout::Layout::list(#iter, #dir, #data) } } } } diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index c07d23dc2..86fcc0c89 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -80,12 +80,10 @@ widget! { impl Layout for Self { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { - let hints = AlignHints::NONE; layout::Layout::grid( - self.widgets.iter_mut().map(move |(info, w)| (*info, layout::Layout::single(w, hints))), + self.widgets.iter_mut().map(move |(info, w)| (*info, layout::Layout::single(w))), self.dim, &mut self.data, - hints, ) } } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 2b1d01a2b..9fdb8c385 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -205,7 +205,7 @@ widget! { impl Layout for Self { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { - layout::Layout::slice(&mut self.widgets, self.direction, &mut self.data, AlignHints::NONE) + layout::Layout::slice(&mut self.widgets, self.direction, &mut self.data) } fn find_id(&mut self, coord: Coord) -> Option { From d3715957e17ad30fdeb7f721cd43eb6e44d88d14 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 3 Dec 2021 12:57:13 +0000 Subject: [PATCH 30/53] make_layout: support list(dir) and slice(dir); tweak syntax --- crates/kas-macros/src/lib.rs | 48 +++++++++++---- crates/kas-macros/src/make_layout.rs | 90 ++++++++++++++++++++++++---- crates/kas-widgets/src/list.rs | 2 +- crates/kas-widgets/src/scrollbar.rs | 2 +- examples/counter.rs | 4 +- src/macros.rs | 16 +---- 6 files changed, 122 insertions(+), 40 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index c2289be6f..3339fa573 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -188,34 +188,60 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// Macro to make a `kas::layout::Layout` /// +/// Generates some type of layout, often over child widgets. +/// The widget's core data is required (usually a field named `core`). +/// /// # Syntax /// -/// > _Align_ :\ +/// > _AlignType_ :\ /// >    `center` | `stretch` /// > -/// > _AlignInfo_ :\ -/// >    `align` `(` _Align_ `)` +/// > _Align_ :\ +/// >    `align` `(` _AlignType_ `)` `:` _Layout_ +/// > +/// > _Direction_ :\ +/// >    `left` | `right` | `up` | `down` | `self` `.` _Member_ +/// > +/// > _Field_ :\ +/// >    `self` `.` _Member_ | _Expr_ +/// > +/// > _ListPre_ :\ +/// >    `column` | `row` | `list` `(` _Direction_ `)` /// > /// > _List_ :\ -/// >    `[` _Layout_ `]` +/// >    _ListPre_ `:` `[` _Layout_ `]` +/// > +/// > _Slice_ :\ +/// >    `slice` `(` _Direction_ `)` `:` `self` `.` _Member_ /// > -/// > _LayoutType_ :\ -/// >       `self` `.` _Member_ -/// > | `column` _List_ -/// > | `row` _List_ +/// > _Frame_ :\ +/// >    `frame` `(` _Layout_ `)` /// > /// > _Layout_ :\ -/// >    _AlignInfo_? _LayoutType_ +/// >       _Align_ | _Single_ | _List_ | _Slice_ | _Frame_ /// > /// > _MakeLayout_:\ -/// >    `(` _CoreData_ `;` _Layout_ +/// >    `(` _CoreData_ `;` _Layout_ `)` +/// +/// ## Notes +/// +/// Fields are specified via `self.NAME`; referencing is implied (the macro +/// converts to `&mut self.NAME` or a suitable method call). Embedded field +/// access (e.g. `self.x.y`) is also supported. +/// +/// `row` and `column` are abbreviations for `list(right)` and `list(down)` +/// respectively. +/// +/// _Slice_ is a variant of _List_ over a single struct field, supporting +/// `AsMut` for some widget type `W`. +/// +/// _Member_ is a field name (struct) or number (tuple struct). /// /// # Example /// /// ```none /// make_layout!(self.core; row[self.a, self.b]) /// ``` - #[proc_macro_error] #[proc_macro] pub fn make_layout(input: TokenStream) -> TokenStream { diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index c2f05af54..a958d8cbb 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -5,7 +5,7 @@ use proc_macro2::TokenStream as Toks; use quote::{quote, TokenStreamExt}; -use syn::parse::{Parse, ParseStream, Result}; +use syn::parse::{Error, Parse, ParseStream, Result}; use syn::{bracketed, parenthesized, Token}; #[allow(non_camel_case_types)] @@ -23,6 +23,8 @@ mod kw { custom_keyword!(center); custom_keyword!(stretch); custom_keyword!(frame); + custom_keyword!(list); + custom_keyword!(slice); } pub struct Input { @@ -35,12 +37,16 @@ enum Layout { AlignSingle(syn::Expr, Align), Single(syn::Expr), Frame(Box), - List(List), + List(Direction, Vec), + Slice(Direction, syn::Expr), } -struct List { - dir: Toks, - list: Vec, +enum Direction { + Left, + Right, + Up, + Down, + Expr(Toks), } enum Align { @@ -82,14 +88,35 @@ impl Parse for Layout { Ok(Layout::Frame(Box::new(layout))) } else if lookahead.peek(kw::column) { let _: kw::column = input.parse()?; - let dir = quote! { ::kas::dir::Down }; + let dir = Direction::Down; + let _: Token![:] = input.parse()?; let list = parse_layout_list(input)?; - Ok(Layout::List(List { dir, list })) + Ok(Layout::List(dir, list)) } else if lookahead.peek(kw::row) { let _: kw::row = input.parse()?; - let dir = quote! { ::kas::dir::Right }; + let dir = Direction::Right; + let _: Token![:] = input.parse()?; + let list = parse_layout_list(input)?; + Ok(Layout::List(dir, list)) + } else if lookahead.peek(kw::list) { + let _: kw::list = input.parse()?; + let inner; + let _ = parenthesized!(inner in input); + let dir: Direction = inner.parse()?; + let _: Token![:] = input.parse()?; let list = parse_layout_list(input)?; - Ok(Layout::List(List { dir, list })) + Ok(Layout::List(dir, list)) + } else if lookahead.peek(kw::slice) { + let _: kw::slice = input.parse()?; + let inner; + let _ = parenthesized!(inner in input); + let dir: Direction = inner.parse()?; + let _: Token![:] = input.parse()?; + if input.peek(Token![self]) { + Ok(Layout::Slice(dir, input.parse()?)) + } else { + Err(Error::new(input.span(), "expected `self`")) + } } else { Err(lookahead.error()) } @@ -130,6 +157,29 @@ fn parse_layout_list(input: ParseStream) -> Result> { Ok(list) } +impl Parse for Direction { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::right) { + let _: kw::right = input.parse()?; + Ok(Direction::Right) + } else if lookahead.peek(kw::down) { + let _: kw::down = input.parse()?; + Ok(Direction::Down) + } else if lookahead.peek(kw::left) { + let _: kw::left = input.parse()?; + Ok(Direction::Left) + } else if lookahead.peek(kw::up) { + let _: kw::up = input.parse()?; + Ok(Direction::Up) + } else if lookahead.peek(Token![self]) { + Ok(Direction::Expr(input.parse()?)) + } else { + Err(lookahead.error()) + } + } +} + impl quote::ToTokens for Align { fn to_tokens(&self, toks: &mut Toks) { toks.append_all(match self { @@ -139,6 +189,18 @@ impl quote::ToTokens for Align { } } +impl quote::ToTokens for Direction { + fn to_tokens(&self, toks: &mut Toks) { + match self { + Direction::Left => toks.append_all(quote! { ::kas::dir::Left }), + Direction::Right => toks.append_all(quote! { ::kas::dir::Right }), + Direction::Up => toks.append_all(quote! { ::kas::dir::Up }), + Direction::Down => toks.append_all(quote! { ::kas::dir::Down }), + Direction::Expr(expr) => expr.to_tokens(toks), + } + } +} + impl Layout { fn generate(&self) -> Toks { match self { @@ -160,7 +222,7 @@ impl Layout { layout::Layout::frame(data, #inner) } } - Layout::List(List { dir, list }) => { + Layout::List(dir, list) => { let len = list.len(); let storage = if len > 16 { quote! { ::kas::layout::DynRowStorage } @@ -183,6 +245,14 @@ impl Layout { quote! { ::kas::layout::Layout::list(#iter, #dir, #data) } } + Layout::Slice(dir, expr) => { + let data = quote! { { + let (data, next) = _chain.storage::<::kas::layout::DynRowStorage>(); + _chain = next; + data + } }; + quote! { ::kas::layout::Layout::slice(&mut #expr, #dir, #data) } + } } } } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 9fdb8c385..1602d3861 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -205,7 +205,7 @@ widget! { impl Layout for Self { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { - layout::Layout::slice(&mut self.widgets, self.direction, &mut self.data) + make_layout!(self.core; slice(self.direction): self.widgets) } fn find_id(&mut self, coord: Coord) -> Option { diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index 441e80312..e4c0ee2fd 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -604,7 +604,7 @@ widget! { let disabled = disabled || self.is_disabled() || self.inner.is_disabled(); // Enlarge clip region to *our* rect: draw.with_clip_region(self.core.rect, self.inner.scroll_offset(), &mut |handle| { - self.inner.inner().draw(handle, mgr, disabled) + self.inner.inner_mut().draw(handle, mgr, disabled) }); // Use a second clip region to force draw order: draw.with_clip_region(self.core.rect, Offset::ZERO, &mut |draw| { diff --git a/examples/counter.rs b/examples/counter.rs index 0de8e82a2..9dc87204f 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -33,9 +33,9 @@ fn main() -> Result<(), kas::shell::Error> { impl Layout for Self { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { make_layout!(self.core; - column[ + column: [ align(center): self.display, - row[self.b_decr, self.b_incr], + row: [self.b_decr, self.b_incr], ] ) } diff --git a/src/macros.rs b/src/macros.rs index b47ccaa26..97b244955 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -65,7 +65,7 @@ //! They support multiple parameters, e.g. `#[widget(config=noauto, children=noauto)]`. //! //! These attributes may be used on fields: `widget`, `widget_core`, -//! `widget_derive`, `layout_data`. +//! `widget_derive`. //! The `widget` attribute supports multiple parameters, //! discussed below (e.g. `#[widget(row=1, use_msg=f)]`). //! Fields without attributes (plain data fields) are fine too. @@ -161,19 +161,6 @@ //! - `halign = ...` — one of `default`, `left`, `centre`, `center`, `right`, `stretch` //! - `valign = ...` — one of `default`, `top`, `centre`, `center`, `bottom`, `stretch` //! -//! **Layout data storage** -//! -//! When deriving [`Layout`], data storage is required (exception: layout -//! `single` requires no storage, but defining it anyway is harmless). -//! The [`LayoutData`] trait is also derived and used to specify the required -//! data type. The `#[layout_data]` attribute is required to identify this -//! storage, resulting in a field like the following: -//! ```none -//! #[layout_data] layout_data: ::Data, -//! ``` -//! This field supports `Default` and `Clone`, thus may be constructed with -//! `layout_data: Default::default()`. -//! //! ### WidgetConfig //! //! The [`WidgetConfig`] trait allows additional configuration of widget @@ -320,7 +307,6 @@ //! #[layout(column)] //! struct MyWidget { //! #[widget_core] core: CoreData, -//! #[layout_data] layout_data: ::Data, //! #[widget] label: StrLabel, //! #[widget(use_msg = handler)] child: W, //! } From 4b6fd456f62cf7f90c6f9c745e955efcedb33ba0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 3 Dec 2021 14:56:56 +0000 Subject: [PATCH 31/53] Widget macro: remove support for #[layout(draw=METHOD)] --- crates/kas-core/src/core/widget.rs | 1 - crates/kas-macros/src/args.rs | 13 +--------- crates/kas-macros/src/layout.rs | 6 ----- crates/kas-widgets/src/menu/menu_entry.rs | 31 ++++++++++++++++------- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 3716a10b0..e26a89272 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -481,7 +481,6 @@ pub trait Layout: WidgetChildren { /// through all children in order is too slow. /// /// This must not be called before [`Layout::set_rect`]. - #[inline] fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; diff --git a/crates/kas-macros/src/args.rs b/crates/kas-macros/src/args.rs index 2b994895f..d17691160 100644 --- a/crates/kas-macros/src/args.rs +++ b/crates/kas-macros/src/args.rs @@ -797,7 +797,6 @@ pub struct LayoutArgs { pub span: Span, pub layout: LayoutType, pub area: Option, - pub draw: Option, } impl Parse for LayoutArgs { @@ -851,7 +850,6 @@ impl Parse for LayoutArgs { } let mut area = None; - let mut draw = None; while !content.is_empty() { let lookahead = content.lookahead1(); @@ -859,10 +857,6 @@ impl Parse for LayoutArgs { let _: kw::area = content.parse()?; let _: Eq = content.parse()?; area = Some(content.parse()?); - } else if draw.is_none() && lookahead.peek(kw::draw) { - let _: kw::draw = content.parse()?; - let _: Eq = content.parse()?; - draw = Some(content.parse()?); } else { return Err(lookahead.error()); } @@ -872,12 +866,7 @@ impl Parse for LayoutArgs { } } - Ok(LayoutArgs { - span, - layout, - area, - draw, - }) + Ok(LayoutArgs { span, layout, area }) } } diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs index af229a786..867a70d0b 100644 --- a/crates/kas-macros/src/layout.rs +++ b/crates/kas-macros/src/layout.rs @@ -203,12 +203,6 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> } }, }; - if let Some(ref method) = layout.draw { - draw = quote! { - self.#method(draw, mgr, disabled); - } - }; - Ok(quote! { fn size_rules(&mut self, sh: &mut dyn ::kas::draw::SizeHandle, axis: ::kas::layout::AxisInfo) -> ::kas::layout::SizeRules diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 483c87e79..03ae4c3c8 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -8,7 +8,7 @@ use super::Menu; use crate::{AccelLabel, CheckBoxBare}; use kas::draw::TextClass; -use kas::prelude::*; +use kas::{layout, prelude::*}; use std::fmt::Debug; widget! { @@ -123,7 +123,6 @@ widget! { #[autoimpl(Debug)] #[autoimpl(HasBool on checkbox)] #[derive(Clone, Default)] - #[layout(row, area=checkbox, draw=draw)] pub struct MenuToggle { #[widget_core] core: CoreData, @@ -140,6 +139,27 @@ widget! { } } + impl Layout for Self { + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + make_layout!(self.core; row: [ self.checkbox, self.label]) + } + + #[inline] + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + Some(self.id()) + } + + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + let state = self.checkbox.input_state(mgr, disabled); + draw.menu_entry(self.core.rect, state); + self.checkbox.draw(draw, mgr, state.disabled()); + self.label.draw(draw, mgr, state.disabled()); + } + } + impl Handler for Self where M: From { type Msg = M; } @@ -195,12 +215,5 @@ widget! { self.checkbox = self.checkbox.with_state(state); self } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - let state = self.checkbox.input_state(mgr, disabled); - draw.menu_entry(self.core.rect, state); - self.checkbox.draw(draw, mgr, state.disabled()); - self.label.draw(draw, mgr, state.disabled()); - } } } From e37d1258c10754352cfe5301edb140e85f492ba6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 3 Dec 2021 17:34:42 +0000 Subject: [PATCH 32/53] Rows before columns These are not (x, y) coordinates, so it's better to take inspiration from reading order (big endian) and intuition. We already had row-before-column more often than not! --- crates/kas-core/src/layout/grid_solver.rs | 54 +++++++++--------- crates/kas-core/src/layout/storage.rs | 18 +++--- crates/kas-core/src/updatable/data_impls.rs | 4 +- crates/kas-core/src/updatable/data_traits.rs | 2 +- crates/kas-core/src/updatable/shared_rc.rs | 4 +- crates/kas-macros/src/layout.rs | 16 +++--- crates/kas-widgets/src/grid.rs | 26 ++++----- crates/kas-widgets/src/view/matrix_view.rs | 59 ++++++++++++-------- 8 files changed, 99 insertions(+), 84 deletions(-) diff --git a/crates/kas-core/src/layout/grid_solver.rs b/crates/kas-core/src/layout/grid_solver.rs index 638c68b1e..3c7df2842 100644 --- a/crates/kas-core/src/layout/grid_solver.rs +++ b/crates/kas-core/src/layout/grid_solver.rs @@ -33,33 +33,33 @@ impl DefaultWithLen for Vec { /// Grid dimensions #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub struct GridDimensions { - pub cols: u32, pub rows: u32, - pub col_spans: u32, + pub cols: u32, pub row_spans: u32, + pub col_spans: u32, } /// Per-child information #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct GridChildInfo { - /// Column index (first column when in a span) - pub col: u32, - /// One-past-last index of column span (`col_end = col + 1` without span) - pub col_end: u32, /// Row index (first row when in a span) pub row: u32, /// One-past-last index of row span (`row_end = row + 1` without span) pub row_end: u32, + /// Column index (first column when in a span) + pub col: u32, + /// One-past-last index of column span (`col_end = col + 1` without span) + pub col_end: u32, } impl GridChildInfo { - /// Construct from col and row - pub fn new(col: u32, row: u32) -> Self { + /// Construct from row and column + pub fn new(row: u32, col: u32) -> Self { GridChildInfo { - col, - col_end: col + 1, row, row_end: row + 1, + col, + col_end: col + 1, } } } @@ -67,16 +67,16 @@ impl GridChildInfo { /// A [`RulesSolver`] for grids supporting cell-spans /// /// This implementation relies on the caller to provide storage for solver data. -pub struct GridSolver { +pub struct GridSolver { axis: AxisInfo, - col_spans: CSR, row_spans: RSR, - next_col_span: usize, + col_spans: CSR, next_row_span: usize, + next_col_span: usize, _s: PhantomData, } -impl GridSolver { +impl GridSolver { /// Construct. /// /// Argument order is consistent with other [`RulesSolver`]s. @@ -85,17 +85,17 @@ impl GridSolver Self { - let col_spans = CSR::default_with_len(dim.col_spans.cast()); let row_spans = RSR::default_with_len(dim.row_spans.cast()); + let col_spans = CSR::default_with_len(dim.col_spans.cast()); - storage.set_dims(dim.cols.cast(), dim.rows.cast()); + storage.set_dims(dim.rows.cast(), dim.cols.cast()); let mut solver = GridSolver { axis, - col_spans, row_spans, - next_col_span: 0, + col_spans, next_row_span: 0, + next_col_span: 0, _s: Default::default(), }; solver.prepare(storage); @@ -125,10 +125,10 @@ impl GridSolver RulesSolver for GridSolver +impl RulesSolver for GridSolver where - CSR: AsRef<[(SizeRules, u32, u32)]> + AsMut<[(SizeRules, u32, u32)]>, RSR: AsRef<[(SizeRules, u32, u32)]> + AsMut<[(SizeRules, u32, u32)]>, + CSR: AsRef<[(SizeRules, u32, u32)]> + AsMut<[(SizeRules, u32, u32)]>, { type Storage = S; type ChildInfo = GridChildInfo; @@ -260,8 +260,8 @@ where /// A [`RulesSetter`] for grids supporting cell-spans pub struct GridSetter { - w_offsets: RT, - h_offsets: CT, + h_offsets: RT, + w_offsets: CT, pos: Coord, _s: PhantomData, } @@ -276,13 +276,13 @@ impl GridSetter { /// - `align`: alignment hints /// - `storage`: access to the solver's storage pub fn new(rect: Rect, dim: GridDimensions, align: AlignHints, storage: &mut S) -> Self { - let (cols, rows) = (dim.cols.cast(), dim.rows.cast()); - let mut w_offsets = RT::default(); - w_offsets.set_len(cols); - let mut h_offsets = CT::default(); + let (rows, cols) = (dim.rows.cast(), dim.cols.cast()); + let mut h_offsets = RT::default(); h_offsets.set_len(rows); + let mut w_offsets = CT::default(); + w_offsets.set_len(cols); - storage.set_dims(cols, rows); + storage.set_dims(rows, cols); if cols > 0 { let align = align.horiz.unwrap_or(Align::Default); diff --git a/crates/kas-core/src/layout/storage.rs b/crates/kas-core/src/layout/storage.rs index e284bc8c5..ef896308b 100644 --- a/crates/kas-core/src/layout/storage.rs +++ b/crates/kas-core/src/layout/storage.rs @@ -152,7 +152,7 @@ where /// Details are hidden (for internal use only). pub trait GridStorage: sealed::Sealed + Clone { #[doc(hidden)] - fn set_dims(&mut self, cols: usize, rows: usize); + fn set_dims(&mut self, rows: usize, cols: usize); #[doc(hidden)] fn width_rules(&mut self) -> &mut [SizeRules] { @@ -185,9 +185,9 @@ pub trait GridStorage: sealed::Sealed + Clone { /// Fixed-length grid storage /// -/// Uses const-generics arguments `C, R` (the number of columns and rows). +/// Uses const-generics arguments `R, C` (the number of rows and columns). #[derive(Clone, Debug)] -pub struct FixedGridStorage { +pub struct FixedGridStorage { width_rules: [SizeRules; C], height_rules: [SizeRules; R], width_total: SizeRules, @@ -196,7 +196,7 @@ pub struct FixedGridStorage { heights: [i32; R], } -impl Default for FixedGridStorage { +impl Default for FixedGridStorage { fn default() -> Self { FixedGridStorage { width_rules: [SizeRules::default(); C], @@ -209,14 +209,14 @@ impl Default for FixedGridStorage { } } -impl Storage for FixedGridStorage { +impl Storage for FixedGridStorage { fn as_any_mut(&mut self) -> &mut dyn Any { self } } -impl GridStorage for FixedGridStorage { - fn set_dims(&mut self, cols: usize, rows: usize) { +impl GridStorage for FixedGridStorage { + fn set_dims(&mut self, rows: usize, cols: usize) { assert_eq!(self.width_rules.as_ref().len(), cols); assert_eq!(self.height_rules.as_ref().len(), rows); assert_eq!(self.widths.len(), cols); @@ -268,7 +268,7 @@ impl Storage for DynGridStorage { } impl GridStorage for DynGridStorage { - fn set_dims(&mut self, cols: usize, rows: usize) { + fn set_dims(&mut self, rows: usize, cols: usize) { self.width_rules.resize(cols, SizeRules::EMPTY); self.height_rules.resize(rows, SizeRules::EMPTY); self.widths.resize(cols, 0); @@ -308,6 +308,6 @@ mod sealed { impl Sealed for super::DynRowStorage {} impl Sealed for Vec {} impl Sealed for [i32; L] {} - impl Sealed for super::FixedGridStorage {} + impl Sealed for super::FixedGridStorage {} impl Sealed for super::DynGridStorage {} } diff --git a/crates/kas-core/src/updatable/data_impls.rs b/crates/kas-core/src/updatable/data_impls.rs index 1a6b11a21..fe5ed7f78 100644 --- a/crates/kas-core/src/updatable/data_impls.rs +++ b/crates/kas-core/src/updatable/data_impls.rs @@ -167,8 +167,8 @@ macro_rules! impl_via_deref { self.deref().row_iter_vec_from(start, limit) } - fn make_key(col: &Self::ColKey, row: &Self::RowKey) -> Self::Key { - <$t>::make_key(col, row) + fn make_key(row: &Self::RowKey, col: &Self::ColKey) -> Self::Key { + <$t>::make_key(row, col) } } }; diff --git a/crates/kas-core/src/updatable/data_traits.rs b/crates/kas-core/src/updatable/data_traits.rs index 61e81616e..8ba269310 100644 --- a/crates/kas-core/src/updatable/data_traits.rs +++ b/crates/kas-core/src/updatable/data_traits.rs @@ -179,7 +179,7 @@ pub trait MatrixData: Debug { fn row_iter_vec_from(&self, start: usize, limit: usize) -> Vec; /// Make a key from parts - fn make_key(col: &Self::ColKey, row: &Self::RowKey) -> Self::Key; + fn make_key(row: &Self::RowKey, col: &Self::ColKey) -> Self::Key; } /// Trait for writable data matrices diff --git a/crates/kas-core/src/updatable/shared_rc.rs b/crates/kas-core/src/updatable/shared_rc.rs index 1163109e9..b64f826b6 100644 --- a/crates/kas-core/src/updatable/shared_rc.rs +++ b/crates/kas-core/src/updatable/shared_rc.rs @@ -136,8 +136,8 @@ impl MatrixData for SharedRc { (self.0).1.borrow().row_iter_vec_from(start, limit) } - fn make_key(col: &Self::ColKey, row: &Self::RowKey) -> Self::Key { - T::make_key(col, row) + fn make_key(row: &Self::RowKey, col: &Self::ColKey) -> Self::Key { + T::make_key(row, col) } } impl MatrixDataMut for SharedRc { diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs index 867a70d0b..c296ae549 100644 --- a/crates/kas-macros/src/layout.rs +++ b/crates/kas-macros/src/layout.rs @@ -54,15 +54,15 @@ pub(crate) fn data_type(children: &[Child], layout: &LayoutArgs) -> Result<(Toks } } - let col_temp = if cols > 16 { + let row_temp = if rows > 16 { quote! { Vec } } else { - quote! { [i32; #cols] } + quote! { [i32; #rows] } }; - let row_temp = if rows > 16 { + let col_temp = if cols > 16 { quote! { Vec } } else { - quote! { [i32; #rows] } + quote! { [i32; #cols] } }; Ok(match layout.layout { @@ -84,15 +84,15 @@ pub(crate) fn data_type(children: &[Child], layout: &LayoutArgs) -> Result<(Toks (dt, solver, setter) } LayoutType::Grid => { - let dt = quote! { ::kas::layout::FixedGridStorage::<#cols, #rows> }; + let dt = quote! { ::kas::layout::FixedGridStorage::<#rows, #cols> }; let solver = quote! { ::kas::layout::GridSolver::< - [(::kas::layout::SizeRules, u32, u32); #col_spans], [(::kas::layout::SizeRules, u32, u32); #row_spans], + [(::kas::layout::SizeRules, u32, u32); #col_spans], #dt, > }; - let setter = quote! { ::kas::layout::GridSetter::<#col_temp, #row_temp, #dt> }; + let setter = quote! { ::kas::layout::GridSetter::<#row_temp, #col_temp, #dt> }; (dt, solver, setter) } }) @@ -188,7 +188,7 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> }); } - let (ucols, urows) = (cols as usize, rows as usize); + let (urows, ucols) = (rows as usize, cols as usize); let dim = match layout.layout { LayoutType::Single => quote! { () }, LayoutType::Right => quote! { (::kas::dir::Right, #ucols) }, diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 86fcc0c89..19f7058dd 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -220,8 +220,8 @@ impl<'a, W: Widget> GridBuilder<'a, W> { /// /// The child is added to the end of the "list", thus appears last in /// navigation order. - pub fn push_cell(&mut self, col: u32, row: u32, widget: W) { - let info = GridChildInfo::new(col, row); + pub fn push_cell(&mut self, row: u32, col: u32, widget: W) { + let info = GridChildInfo::new(row, col); self.push(info, widget); } @@ -229,43 +229,43 @@ impl<'a, W: Widget> GridBuilder<'a, W> { /// /// The child is added to the end of the "list", thus appears last in /// navigation order. - pub fn with_cell(self, col: u32, row: u32, widget: W) -> Self { - self.with_cell_span(col, row, 1, 1, widget) + pub fn with_cell(self, row: u32, col: u32, widget: W) -> Self { + self.with_cell_span(row, col, 1, 1, widget) } /// Add a child widget to the given cell, with spans /// - /// Parameters `col_span` and `row_span` are the number of columns/rows + /// Parameters `row_span` and `col_span` are the number of rows/columns /// spanned and should each be at least 1. /// /// The child is added to the end of the "list", thus appears last in /// navigation order. - pub fn push_cell_span(&mut self, col: u32, row: u32, col_span: u32, row_span: u32, widget: W) { + pub fn push_cell_span(&mut self, row: u32, col: u32, row_span: u32, col_span: u32, widget: W) { let info = GridChildInfo { - col, - col_end: col + col_span, row, row_end: row + row_span, + col, + col_end: col + col_span, }; self.push(info, widget); } /// Add a child widget to the given cell, with spans, builder style /// - /// Parameters `col_span` and `row_span` are the number of columns/rows + /// Parameters `row_span` and `col_span` are the number of rows/columns /// spanned and should each be at least 1. /// /// The child is added to the end of the "list", thus appears last in /// navigation order. pub fn with_cell_span( mut self, - col: u32, row: u32, - col_span: u32, + col: u32, row_span: u32, + col_span: u32, widget: W, ) -> Self { - self.push_cell_span(col, row, col_span, row_span, widget); + self.push_cell_span(row, col, row_span, col_span, widget); self } @@ -326,7 +326,7 @@ impl<'a, W: Widget> GridBuilder<'a, W> { } /// Get the first index of a child occupying the given cell, if any - pub fn find_child_cell(&self, col: u32, row: u32) -> Option { + pub fn find_child_cell(&self, row: u32, col: u32) -> Option { for (i, (info, _)) in self.0.iter().enumerate() { if info.col <= col && col < info.col_end && info.row <= row && row < info.row_end { return Some(i); diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index eed61c26e..36e160d2b 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -18,6 +18,12 @@ use log::{debug, trace}; use std::time::Instant; use UpdatableHandler as UpdHandler; +#[derive(Clone, Copy, Debug, Default)] +struct Dim { + rows: i32, + cols: i32, +} + #[derive(Clone, Debug, Default)] struct WidgetData { key: Option, @@ -55,10 +61,9 @@ widget! { data: T, widgets: Vec>, align_hints: AlignHints, - // TODO: the following three all have units of "the number of rows/cols" - ideal_len: Size, - alloc_len: Size, - cur_len: Size, + ideal_len: Dim, + alloc_len: Dim, + cur_len: Dim, child_size_min: Size, child_size_ideal: Size, child_inter_margin: Size, @@ -90,9 +95,9 @@ widget! { data, widgets: Default::default(), align_hints: Default::default(), - ideal_len: Size(5, 3), - alloc_len: Size::ZERO, - cur_len: Size::ZERO, + ideal_len: Dim { rows: 3, cols: 5 }, + alloc_len: Dim::default(), + cur_len: Dim::default(), child_size_min: Size::ZERO, child_size_ideal: Size::ZERO, child_inter_margin: Size::ZERO, @@ -238,8 +243,8 @@ widget! { /// /// This affects the (ideal) size request and whether children are sized /// according to their ideal or minimum size but not the minimum size. - pub fn with_num_visible(mut self, cols: i32, rows: i32) -> Self { - self.ideal_len = Size(cols, rows); + pub fn with_num_visible(mut self, rows: i32, cols: i32) -> Self { + self.ideal_len = Dim { rows, cols }; self } @@ -257,11 +262,14 @@ widget! { let first_row = usize::conv(u64::conv(offset.1) / u64::conv(skip.1)); let cols = self .data - .col_iter_vec_from(first_col, self.alloc_len.0.cast()); + .col_iter_vec_from(first_col, self.alloc_len.cols.cast()); let rows = self .data - .row_iter_vec_from(first_row, self.alloc_len.1.cast()); - self.cur_len = Size(cols.len().cast(), rows.len().cast()); + .row_iter_vec_from(first_row, self.alloc_len.rows.cast()); + self.cur_len = Dim { + rows: rows.len().cast(), + cols: cols.len().cast(), + }; let pos_start = self.core.rect.pos + self.frame_offset; let mut rect = Rect::new(pos_start, self.child_size); @@ -273,7 +281,7 @@ widget! { let ri = first_row + rn; let i = (ci % cols.len()) + (ri % rows.len()) * cols.len(); let w = &mut self.widgets[i]; - let key = T::make_key(col, row); + let key = T::make_key(row, col); if w.key.as_ref() != Some(&key) { if let Some(item) = self.data.get_cloned(&key) { w.key = Some(key.clone()); @@ -369,7 +377,11 @@ widget! { self.child_inter_margin .set_component(axis, (m.0 + m.1).max(inner_margin)); - rules.multiply_with_margin(2, self.ideal_len.extract(axis)); + let ideal_len = match axis.is_vertical() { + false => self.ideal_len.cols, + true => self.ideal_len.rows, + }; + rules.multiply_with_margin(2, ideal_len); rules.set_stretch(rules.stretch().max(Stretch::High)); let (rules, offset, size) = frame.surround_with_margin(rules); @@ -382,12 +394,12 @@ widget! { self.core.rect = rect; let mut child_size = rect.size - self.frame_size; - if child_size.0 >= self.ideal_len.0 * self.child_size_ideal.0 { + if child_size.0 >= self.ideal_len.cols * self.child_size_ideal.0 { child_size.0 = self.child_size_ideal.0; } else { child_size.0 = self.child_size_min.0; } - if child_size.1 >= self.ideal_len.1 * self.child_size_ideal.1 { + if child_size.1 >= self.ideal_len.rows * self.child_size_ideal.1 { child_size.1 = self.child_size_ideal.1; } else { child_size.1 = self.child_size_min.1; @@ -397,7 +409,10 @@ widget! { let skip = child_size + self.child_inter_margin; let vis_len = (rect.size + skip - Size::splat(1)).cwise_div(skip) + Size::splat(1); - self.alloc_len = vis_len; + self.alloc_len = Dim { + cols: vis_len.0, + rows: vis_len.1, + }; let old_num = self.widgets.len(); let num = usize::conv(vis_len.0) * usize::conv(vis_len.1); @@ -432,7 +447,7 @@ widget! { ) -> Option { let _ = mgr; // TODO: this needs a rewrite like ListView::spatial_nav - let cur_len = usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1); + let cur_len = usize::conv(self.cur_len.cols) * usize::conv(self.cur_len.rows); if cur_len == 0 { return None; } @@ -460,7 +475,7 @@ widget! { let offset = self.scroll_offset(); let ci = usize::conv(u64::conv(offset.0) / u64::conv(skip.0)); let ri = usize::conv(u64::conv(offset.1) / u64::conv(skip.1)); - let (cols, rows): (usize, usize) = (self.cur_len.0.cast(), self.cur_len.1.cast()); + let (rows, cols): (usize, usize) = (self.cur_len.rows.cast(), self.cur_len.cols.cast()); let mut data = (ci % cols) * rows + (ri % rows); if reverse { data += last; @@ -480,7 +495,7 @@ widget! { } let coord = coord + self.scroll.offset(); - let num = usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1); + let num = usize::conv(self.cur_len.cols) * usize::conv(self.cur_len.rows); for child in &mut self.widgets[..num] { if child.key.is_some() { if let Some(id) = child.widget.find_id(coord) { @@ -494,7 +509,7 @@ widget! { fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); let offset = self.scroll_offset(); - let num = usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1); + let num = usize::conv(self.cur_len.cols) * usize::conv(self.cur_len.rows); draw.with_clip_region(self.core.rect, offset, &mut |draw| { for child in &mut self.widgets[..num] { if let Some(ref key) = child.key { @@ -664,7 +679,7 @@ widget! { let id = self.id(); let (action, response) = if let Event::Command(cmd, _) = event { // Simplified version of logic in update_widgets - let (cols, rows): (usize, usize) = (self.cur_len.0.cast(), self.cur_len.1.cast()); + let (cols, rows): (usize, usize) = (self.cur_len.cols.cast(), self.cur_len.rows.cast()); let skip = self.child_size + self.child_inter_margin; let offset = self.scroll_offset(); From bae4851f03c5f2f644107527aa09ccccc6c8d030 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 3 Dec 2021 17:41:06 +0000 Subject: [PATCH 33/53] make_layout: support grid --- crates/kas-macros/src/make_layout.rs | 135 ++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index a958d8cbb..6853ed592 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -6,7 +6,7 @@ use proc_macro2::TokenStream as Toks; use quote::{quote, TokenStreamExt}; use syn::parse::{Error, Parse, ParseStream, Result}; -use syn::{bracketed, parenthesized, Token}; +use syn::{braced, bracketed, parenthesized, LitInt, Token}; #[allow(non_camel_case_types)] mod kw { @@ -25,6 +25,7 @@ mod kw { custom_keyword!(frame); custom_keyword!(list); custom_keyword!(slice); + custom_keyword!(grid); } pub struct Input { @@ -39,6 +40,7 @@ enum Layout { Frame(Box), List(Direction, Vec), Slice(Direction, syn::Expr), + Grid(GridDimensions, Vec<(CellInfo, Layout)>), } enum Direction { @@ -54,6 +56,68 @@ enum Align { Stretch, } +#[derive(Default)] +struct GridDimensions { + rows: u32, + cols: u32, + row_spans: u32, + col_spans: u32, +} +struct CellInfo { + row: u32, + row_end: u32, + col: u32, + col_end: u32, +} +impl Parse for CellInfo { + fn parse(input: ParseStream) -> Result { + let row = input.parse::()?.base10_parse()?; + let row_end = if input.peek(Token![..]) { + let _ = input.parse::(); + let lit = input.parse::()?; + let end = lit.base10_parse()?; + if row >= end { + return Err(Error::new(lit.span(), format!("expected value > {}", row))); + } + end + } else { + row + 1 + }; + + let col = input.parse::()?.base10_parse()?; + let col_end = if input.peek(Token![..]) { + let _ = input.parse::(); + let lit = input.parse::()?; + let end = lit.base10_parse()?; + if col >= end { + return Err(Error::new(lit.span(), format!("expected value > {}", col))); + } + end + } else { + col + 1 + }; + + Ok(CellInfo { + row, + row_end, + col, + col_end, + }) + } +} +impl GridDimensions { + fn update(&mut self, cell: &CellInfo) { + self.rows = self.rows.max(cell.row_end); + self.cols = self.cols.max(cell.col_end); + if cell.row_end - cell.row > 1 { + self.row_spans += 1; + } + if cell.col_end - cell.col > 1 { + self.col_spans += 1; + } + } +} + impl Parse for Input { fn parse(input: ParseStream) -> Result { let core = input.parse()?; @@ -117,6 +181,10 @@ impl Parse for Layout { } else { Err(Error::new(input.span(), "expected `self`")) } + } else if lookahead.peek(kw::grid) { + let _: kw::grid = input.parse()?; + let _: Token![:] = input.parse()?; + Ok(parse_grid(input)?) } else { Err(lookahead.error()) } @@ -157,6 +225,29 @@ fn parse_layout_list(input: ParseStream) -> Result> { Ok(list) } +fn parse_grid(input: ParseStream) -> Result { + let inner; + let _ = braced!(inner in input); + + let mut dim = GridDimensions::default(); + let mut cells = vec![]; + while !inner.is_empty() { + let info = inner.parse()?; + dim.update(&info); + let _: Token![,] = inner.parse()?; + let layout = inner.parse()?; + cells.push((info, layout)); + + if inner.is_empty() { + break; + } + + let _: Token![;] = inner.parse()?; + } + + Ok(Layout::Grid(dim, cells)) +} + impl Parse for Direction { fn parse(input: ParseStream) -> Result { let lookahead = input.lookahead1(); @@ -201,6 +292,19 @@ impl quote::ToTokens for Direction { } } +impl quote::ToTokens for GridDimensions { + fn to_tokens(&self, toks: &mut Toks) { + let (rows, cols) = (self.rows, self.cols); + let (row_spans, col_spans) = (self.row_spans, self.col_spans); + toks.append_all(quote! { ::kas::layout::GridDimensions { + rows: #rows, + cols: #cols, + row_spans: #row_spans, + col_spans: #col_spans, + } }); + } +} + impl Layout { fn generate(&self) -> Toks { match self { @@ -253,6 +357,35 @@ impl Layout { } }; quote! { ::kas::layout::Layout::slice(&mut #expr, #dir, #data) } } + Layout::Grid(dim, cells) => { + let (rows, cols) = (dim.rows, dim.cols); + let data = quote! { { + let (data, next) = _chain.storage::<::kas::layout::FixedGridStorage<#rows, #cols>(); + _chain = next; + data + } }; + + let mut items = Toks::new(); + for item in cells { + let (row, row_end) = (item.0.row, item.0.row_end); + let (col, col_end) = (item.0.col, item.0.col_end); + let layout = item.1.generate(); + items.append_all(quote! { + ( + ::kas::layout::GridChildInfo { + row: #row, + row_end: #row_end, + col: #col, + col_end: #col_end, + }, + #layout, + ) + }); + } + let iter = quote! { { let arr = [#items]; arr.into_iter() } }; + + quote! { ::kas::layout::Layout::grid(#iter, #dim, #data) } + } } } } From cbca5ad8ca81d1cddf24ef80c7041a86446da733 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 3 Dec 2021 18:00:10 +0000 Subject: [PATCH 34/53] Fix: find_id impl for CheckBox and RadioBox --- crates/kas-macros/src/args.rs | 18 +++++++++++------- crates/kas-macros/src/layout.rs | 14 ++++++++++++++ crates/kas-widgets/src/checkbox.rs | 2 +- crates/kas-widgets/src/radiobox.rs | 2 +- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/crates/kas-macros/src/args.rs b/crates/kas-macros/src/args.rs index d17691160..c81486e8d 100644 --- a/crates/kas-macros/src/args.rs +++ b/crates/kas-macros/src/args.rs @@ -342,7 +342,7 @@ fn member(index: usize, ident: Option) -> Member { mod kw { use syn::custom_keyword; - custom_keyword!(area); + custom_keyword!(find_id); custom_keyword!(layout); custom_keyword!(col); custom_keyword!(row); @@ -796,7 +796,7 @@ impl ToTokens for LayoutType { pub struct LayoutArgs { pub span: Span, pub layout: LayoutType, - pub area: Option, + pub find_id: Option, } impl Parse for LayoutArgs { @@ -849,14 +849,14 @@ impl Parse for LayoutArgs { let _: Comma = content.parse()?; } - let mut area = None; + let mut find_id = None; while !content.is_empty() { let lookahead = content.lookahead1(); - if area.is_none() && lookahead.peek(kw::area) { - let _: kw::area = content.parse()?; + if find_id.is_none() && lookahead.peek(kw::find_id) { + let _: kw::find_id = content.parse()?; let _: Eq = content.parse()?; - area = Some(content.parse()?); + find_id = Some(content.parse()?); } else { return Err(lookahead.error()); } @@ -866,7 +866,11 @@ impl Parse for LayoutArgs { } } - Ok(LayoutArgs { span, layout, area }) + Ok(LayoutArgs { + span, + layout, + find_id, + }) } } diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs index c296ae549..599bcac98 100644 --- a/crates/kas-macros/src/layout.rs +++ b/crates/kas-macros/src/layout.rs @@ -203,6 +203,18 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> } }, }; + let find_id = match layout.find_id.as_ref() { + None => quote! {}, + Some(find_id) => quote! { + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + if !self.rect().contains(coord) { + return None; + } + #find_id + } + }, + }; + Ok(quote! { fn size_rules(&mut self, sh: &mut dyn ::kas::draw::SizeHandle, axis: ::kas::layout::AxisInfo) -> ::kas::layout::SizeRules @@ -240,6 +252,8 @@ pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> #set_rect } + #find_id + fn draw( &mut self, draw: &mut dyn ::kas::draw::DrawHandle, diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 35486dd58..6f0793813 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -131,7 +131,7 @@ widget! { #[autoimpl(Debug)] #[autoimpl(HasBool on checkbox)] #[derive(Clone, Default)] - #[layout(row, area=checkbox)] + #[layout(row, find_id=Some(self.checkbox.id()))] pub struct CheckBox { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index b150aed04..0aacc5738 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -173,7 +173,7 @@ widget! { #[autoimpl(Debug)] #[autoimpl(HasBool on radiobox)] #[derive(Clone)] - #[layout(row, area=radiobox)] + #[layout(row, find_id=Some(self.radiobox.id()))] pub struct RadioBox { #[widget_core] core: CoreData, From 2050b28e8546110116fd69cebfef113b72209821 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 4 Dec 2021 14:02:33 +0000 Subject: [PATCH 35/53] This replaces the old #[layout] attribute as well as position and alignment specifiers within child #[widget] attributes. --- crates/kas-macros/src/args.rs | 436 ++++----------------- crates/kas-macros/src/layout.rs | 266 ------------- crates/kas-macros/src/lib.rs | 9 +- crates/kas-macros/src/make_layout.rs | 175 ++++++--- crates/kas-macros/src/widget.rs | 64 ++- crates/kas-widgets/src/adapter/map.rs | 4 +- crates/kas-widgets/src/checkbox.rs | 10 +- crates/kas-widgets/src/combobox.rs | 9 +- crates/kas-widgets/src/dialog.rs | 4 +- crates/kas-widgets/src/drag.rs | 6 +- crates/kas-widgets/src/editbox.rs | 6 +- crates/kas-widgets/src/nav_frame.rs | 4 +- crates/kas-widgets/src/radiobox.rs | 5 +- crates/kas-widgets/src/scroll_label.rs | 4 +- crates/kas-widgets/src/scrollbar.rs | 4 +- crates/kas-widgets/src/slider.rs | 5 +- crates/kas-widgets/src/view/filter_list.rs | 4 +- crates/kas-widgets/src/view/single_view.rs | 4 +- examples/calculator.rs | 71 ++-- examples/cursors.rs | 4 +- examples/custom-theme.rs | 24 +- examples/data-list-view.rs | 12 +- examples/data-list.rs | 12 +- examples/filter-list.rs | 8 +- examples/gallery.rs | 113 ++++-- examples/layout.rs | 23 +- examples/mandlebrot/mandlebrot.rs | 24 +- examples/markdown.rs | 14 +- examples/splitter.rs | 8 +- examples/stopwatch.rs | 4 +- examples/sync-counter.rs | 17 +- src/macros.rs | 34 +- 32 files changed, 544 insertions(+), 843 deletions(-) delete mode 100644 crates/kas-macros/src/layout.rs diff --git a/crates/kas-macros/src/args.rs b/crates/kas-macros/src/args.rs index c81486e8d..556bc0a32 100644 --- a/crates/kas-macros/src/args.rs +++ b/crates/kas-macros/src/args.rs @@ -3,6 +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::make_layout; use proc_macro2::{Punct, Spacing, Span, TokenStream, TokenTree}; use proc_macro_error::{abort, emit_error}; use quote::{quote, ToTokens, TokenStreamExt}; @@ -13,20 +14,17 @@ use syn::token::{Brace, Colon, Comma, Eq, For, Impl, Paren, Semi}; use syn::{braced, bracketed, parenthesized, parse_quote}; use syn::{ AttrStyle, Attribute, ConstParam, Expr, Field, Fields, FieldsNamed, GenericParam, Generics, - Ident, Index, ItemImpl, Lifetime, LifetimeDef, Lit, Member, Path, Token, Type, TypeParam, - TypePath, TypeTraitObject, Visibility, + Ident, Index, ItemImpl, Lifetime, LifetimeDef, Member, Path, Token, Type, TypeParam, TypePath, + TypeTraitObject, Visibility, }; -#[derive(Debug)] pub struct Child { pub ident: Member, pub args: WidgetAttrArgs, } -#[derive(Debug)] pub struct Widget { pub attr_widget: WidgetArgs, - pub attr_layout: Option, pub attr_handler: Option, pub extra_attrs: Vec, @@ -46,7 +44,6 @@ pub struct Widget { impl Parse for Widget { fn parse(input: ParseStream) -> Result { let mut attr_widget = None; - let mut attr_layout = None; let mut attr_handler = None; let mut extra_attrs = Vec::new(); @@ -60,12 +57,6 @@ impl Parse for Widget { } else { emit_error!(attr.span(), "multiple #[widget(..)] attributes on type"); } - } else if attr.path == parse_quote! { layout } { - if attr_layout.is_some() { - emit_error!(attr.span(), "multiple #[layout(..)] attributes on type"); - } else { - attr_layout = Some(syn::parse2(attr.tokens)?); - } } else if attr.path == parse_quote! { handler } { if attr_handler.is_some() { emit_error!(attr.span(), "multiple #[handler(..)] attributes on type"); @@ -169,7 +160,6 @@ impl Parse for Widget { Ok(Widget { attr_widget, - attr_layout, attr_handler, extra_attrs, vis, @@ -419,81 +409,13 @@ impl Handler { #[derive(Debug)] pub struct WidgetAttrArgs { - pub col: Option, - pub row: Option, - pub cspan: Option, - pub rspan: Option, - pub halign: Option, - pub valign: Option, pub update: Option, pub handler: Handler, } -#[derive(Debug)] -pub struct GridPos(pub u32, pub u32, pub u32, pub u32); - -impl WidgetAttrArgs { - // Parse widget position, filling in missing information with defaults. - pub fn as_pos(&self) -> Result { - fn parse_lit(lit: &Lit) -> Result { - match lit { - Lit::Int(li) => li.base10_parse(), - _ => Err(Error::new(lit.span(), "expected integer literal")), - } - } - - Ok(GridPos( - self.col.as_ref().map(parse_lit).unwrap_or(Ok(0))?, - self.row.as_ref().map(parse_lit).unwrap_or(Ok(0))?, - self.cspan.as_ref().map(parse_lit).unwrap_or(Ok(1))?, - self.rspan.as_ref().map(parse_lit).unwrap_or(Ok(1))?, - )) - } - - fn match_align(ident: &Ident, horiz: bool) -> Result { - Ok(match ident { - ident if ident == "default" => quote! { ::kas::layout::Align::Default }, - ident if horiz && ident == "left" => quote! { ::kas::layout::Align::TL }, - ident if !horiz && ident == "top" => quote! { ::kas::layout::Align::TL }, - ident if ident == "centre" || ident == "center" => { - quote! { ::kas::layout::Align::Centre } - } - ident if horiz && ident == "right" => quote! { ::kas::layout::Align::BR }, - ident if !horiz && ident == "bottom" => quote! { ::kas::layout::Align::BR }, - ident if ident == "stretch" => quote! { ::kas::layout::Align::Stretch }, - ident => { - return Err(Error::new( - ident.span(), - "expected one of `default`, `centre`, `stretch`, `top` or `bottom` (if vertical), `left` or `right` (if horizontal)", - )); - } - }) - } - pub fn halign_toks(&self) -> Result> { - if let Some(ref ident) = self.halign { - Ok(Some(Self::match_align(ident, true)?)) - } else { - Ok(None) - } - } - pub fn valign_toks(&self) -> Result> { - if let Some(ref ident) = self.valign { - Ok(Some(Self::match_align(ident, false)?)) - } else { - Ok(None) - } - } -} - impl Parse for WidgetAttrArgs { fn parse(input: ParseStream) -> Result { let mut args = WidgetAttrArgs { - col: None, - row: None, - cspan: None, - rspan: None, - halign: None, - valign: None, update: None, handler: Handler::None, }; @@ -506,45 +428,7 @@ impl Parse for WidgetAttrArgs { loop { let lookahead = content.lookahead1(); - if args.col.is_none() && lookahead.peek(kw::col) { - let _: kw::col = content.parse()?; - let _: Eq = content.parse()?; - args.col = Some(content.parse()?); - } else if args.col.is_none() && lookahead.peek(kw::column) { - let _: kw::column = content.parse()?; - let _: Eq = content.parse()?; - args.col = Some(content.parse()?); - } else if args.row.is_none() && lookahead.peek(kw::row) { - let _: kw::row = content.parse()?; - let _: Eq = content.parse()?; - args.row = Some(content.parse()?); - } else if args.cspan.is_none() && lookahead.peek(kw::cspan) { - let _: kw::cspan = content.parse()?; - let _: Eq = content.parse()?; - args.cspan = Some(content.parse()?); - } else if args.rspan.is_none() && lookahead.peek(kw::rspan) { - let _: kw::rspan = content.parse()?; - let _: Eq = content.parse()?; - args.rspan = Some(content.parse()?); - } else if args.halign.is_none() && args.valign.is_none() && lookahead.peek(kw::align) { - let _: kw::align = content.parse()?; - let _: Eq = content.parse()?; - let ident: Ident = content.parse()?; - if ident == "centre" || ident == "center" || ident == "stretch" { - args.halign = Some(ident.clone()); - args.valign = Some(ident); - } else { - return Err(Error::new(ident.span(), "expected `centre` or `center`")); - } - } else if args.halign.is_none() && lookahead.peek(kw::halign) { - let _: kw::halign = content.parse()?; - let _: Eq = content.parse()?; - args.halign = Some(content.parse()?); - } else if args.valign.is_none() && lookahead.peek(kw::valign) { - let _: kw::valign = content.parse()?; - let _: Eq = content.parse()?; - args.valign = Some(content.parse()?); - } else if args.update.is_none() && lookahead.peek(kw::update) { + if args.update.is_none() && lookahead.peek(kw::update) { let _: kw::update = content.parse()?; let _: Eq = content.parse()?; args.update = Some(content.parse()?); @@ -585,57 +469,13 @@ impl Parse for WidgetAttrArgs { impl ToTokens for WidgetAttrArgs { fn to_tokens(&self, tokens: &mut TokenStream) { - if self.col.is_some() - || self.row.is_some() - || self.cspan.is_some() - || self.rspan.is_some() - || self.halign.is_some() - || self.valign.is_some() - || !self.handler.is_none() - { - let comma = TokenTree::from(Punct::new(',', Spacing::Alone)); + if !self.update.is_none() || !self.handler.is_none() { let mut args = TokenStream::new(); - if let Some(ref lit) = self.col { - args.append_all(quote! { col = #lit }); - } - if let Some(ref lit) = self.row { - if !args.is_empty() { - args.append(comma.clone()); - } - args.append_all(quote! { row = #lit }); - } - if let Some(ref lit) = self.cspan { - if !args.is_empty() { - args.append(comma.clone()); - } - args.append_all(quote! { cspan = #lit }); - } - if let Some(ref lit) = self.rspan { - if !args.is_empty() { - args.append(comma.clone()); - } - args.append_all(quote! { rspan = #lit }); - } - if let Some(ref ident) = self.halign { - if !args.is_empty() { - args.append(comma.clone()); - } - args.append_all(quote! { halign = #ident }); - } - if let Some(ref ident) = self.valign { - if !args.is_empty() { - args.append(comma.clone()); - } - args.append_all(quote! { valign = #ident }); - } if let Some(ref ident) = self.update { - if !args.is_empty() { - args.append(comma.clone()); - } args.append_all(quote! { update = #ident }); } if !self.handler.is_none() && !args.is_empty() { - args.append(comma); + args.append(TokenTree::from(Punct::new(',', Spacing::Alone))); } match &self.handler { Handler::None => (), @@ -661,213 +501,103 @@ impl ToTokens for WidgetAttr { } } -impl ToTokens for GridPos { - fn to_tokens(&self, tokens: &mut TokenStream) { - let (c, r, cs, rs) = (&self.0, &self.1, &self.2, &self.3); - tokens.append_all(quote! { (#c, #r, #cs, #rs) }); - } -} - -#[derive(Debug)] -pub struct WidgetArgs { - pub config: Option, - pub derive: Option, -} - -impl Default for WidgetArgs { - fn default() -> Self { - WidgetArgs { - config: Some(WidgetConfig::default()), - derive: None, +macro_rules! property { + ($name:ident : $ty:ty = $def:expr ; $kw:path : $input:ident => $parse:expr ;) => { + pub struct $name { + /// Some(span) if set, None if default + pub span: Option, + /// Value (default or set) + pub value: $ty, } - } -} - -#[derive(Debug)] -pub struct WidgetConfig { - pub key_nav: bool, - pub hover_highlight: bool, - pub cursor_icon: Expr, -} - -impl Default for WidgetConfig { - fn default() -> Self { - WidgetConfig { - key_nav: false, - hover_highlight: false, - cursor_icon: parse_quote! { ::kas::event::CursorIcon::Default }, - } - } -} - -impl Parse for WidgetArgs { - fn parse(input: ParseStream) -> Result { - let mut config = None; - let mut derive = None; - - if !input.is_empty() { - let content; - let _ = parenthesized!(content in input); - - while !content.is_empty() { - let lookahead = content.lookahead1(); - if lookahead.peek(kw::config) && config.is_none() { - let _: kw::config = content.parse()?; - - let content2; - let _ = parenthesized!(content2 in content); - - let mut conf = WidgetConfig::default(); - let mut have_key_nav = false; - let mut have_hover_highlight = false; - let mut have_cursor_icon = false; - - while !content2.is_empty() { - let lookahead = content2.lookahead1(); - if lookahead.peek(kw::key_nav) && !have_key_nav { - let _: kw::key_nav = content2.parse()?; - let _: Eq = content2.parse()?; - let value: syn::LitBool = content2.parse()?; - conf.key_nav = value.value; - have_key_nav = true; - } else if lookahead.peek(kw::hover_highlight) && !have_hover_highlight { - let _: kw::hover_highlight = content2.parse()?; - let _: Eq = content2.parse()?; - let value: syn::LitBool = content2.parse()?; - conf.hover_highlight = value.value; - have_hover_highlight = true; - } else if lookahead.peek(kw::cursor_icon) && !have_cursor_icon { - let _: kw::cursor_icon = content2.parse()?; - let _: Eq = content2.parse()?; - conf.cursor_icon = content2.parse()?; - have_cursor_icon = true; - } else { - return Err(lookahead.error()); - }; - - if content2.peek(Comma) { - let _: Comma = content2.parse()?; - } - } - config = Some(conf); - } else if lookahead.peek(kw::derive) && derive.is_none() { - let _: kw::derive = content.parse()?; - let _: Eq = content.parse()?; - let _: Token![self] = content.parse()?; - let _: Token![.] = content.parse()?; - derive = Some(content.parse()?); - } else { - return Err(lookahead.error()); - } - - if content.peek(Comma) { - let _: Comma = content.parse()?; + impl Default for $name { + fn default() -> Self { + $name { + span: None, + value: $def, } } } - - Ok(WidgetArgs { config, derive }) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum LayoutType { - Single, - Right, - Left, - Down, - Up, - Grid, -} - -impl ToTokens for LayoutType { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.append_all(match self { - LayoutType::Single | LayoutType::Grid => unreachable!(), - LayoutType::Right => quote! { ::kas::dir::Right }, - LayoutType::Left => quote! { ::kas::dir::Left }, - LayoutType::Down => quote! { ::kas::dir::Down }, - LayoutType::Up => quote! { ::kas::dir::Up }, - }) - } + impl Parse for $name { + fn parse($input: ParseStream) -> Result { + let span = Some($input.parse::<$kw>()?.span()); + let _: Eq = $input.parse()?; + let value = $parse; + Ok($name { span, value }) + } + } + }; } - -#[derive(Debug)] -pub struct LayoutArgs { - pub span: Span, - pub layout: LayoutType, - pub find_id: Option, +property!( + KeyNav: bool = false; + kw::key_nav : input => input.parse::()?.value; +); +property!( + HoverHighlight: bool = false; + kw::hover_highlight : input => input.parse::()?.value; +); +property!( + CursorIcon: Expr = parse_quote! { ::kas::event::CursorIcon::Default }; + kw::cursor_icon : input => input.parse()?; +); +property!( + FindId: Option = None; + kw::find_id : input => Some(input.parse()?); +); + +#[derive(Default)] +pub struct WidgetArgs { + pub key_nav: KeyNav, + pub hover_highlight: HoverHighlight, + pub cursor_icon: CursorIcon, + pub derive: Option, + pub layout: Option, + pub find_id: FindId, } -impl Parse for LayoutArgs { +impl Parse for WidgetArgs { fn parse(input: ParseStream) -> Result { - if input.is_empty() { - return Err(Error::new( - input.span(), - "expected attribute parameters: `(..)`", - )); - } - - let span = input.span(); + let mut key_nav = KeyNav::default(); + let mut hover_highlight = HoverHighlight::default(); + let mut cursor_icon = CursorIcon::default(); + let mut derive = None; + let mut layout = None; + let mut find_id = FindId::default(); let content; - let _ = parenthesized!(content in input); - - let lookahead = content.lookahead1(); - let layout = if lookahead.peek(kw::single) { - let _: kw::single = content.parse()?; - LayoutType::Single - } else if lookahead.peek(kw::row) { - let _: kw::row = content.parse()?; - LayoutType::Right - } else if lookahead.peek(kw::right) { - let _: kw::right = content.parse()?; - LayoutType::Right - } else if lookahead.peek(kw::left) { - let _: kw::left = content.parse()?; - LayoutType::Left - } else if lookahead.peek(kw::col) { - let _: kw::col = content.parse()?; - LayoutType::Down - } else if lookahead.peek(kw::column) { - let _: kw::column = content.parse()?; - LayoutType::Down - } else if lookahead.peek(kw::down) { - let _: kw::down = content.parse()?; - LayoutType::Down - } else if lookahead.peek(kw::up) { - let _: kw::up = content.parse()?; - LayoutType::Up - } else if lookahead.peek(kw::grid) { - let _: kw::grid = content.parse()?; - LayoutType::Grid - } else { - return Err(lookahead.error()); - }; - - if content.peek(Comma) { - let _: Comma = content.parse()?; - } - - let mut find_id = None; + let _ = braced!(content in input); while !content.is_empty() { let lookahead = content.lookahead1(); - if find_id.is_none() && lookahead.peek(kw::find_id) { - let _: kw::find_id = content.parse()?; + if lookahead.peek(kw::key_nav) && key_nav.span.is_none() { + key_nav = content.parse()?; + } else if lookahead.peek(kw::hover_highlight) && hover_highlight.span.is_none() { + hover_highlight = content.parse()?; + } else if lookahead.peek(kw::cursor_icon) && cursor_icon.span.is_none() { + cursor_icon = content.parse()?; + } else if lookahead.peek(kw::derive) && derive.is_none() { + let _: kw::derive = content.parse()?; + let _: Eq = content.parse()?; + let _: Token![self] = content.parse()?; + let _: Token![.] = content.parse()?; + derive = Some(content.parse()?); + } else if lookahead.peek(kw::layout) && layout.is_none() { + let _: kw::layout = content.parse()?; let _: Eq = content.parse()?; - find_id = Some(content.parse()?); + layout = Some(content.parse()?); + } else if content.peek(kw::find_id) { + find_id = content.parse()?; } else { return Err(lookahead.error()); } - if content.peek(Comma) { - let _: Comma = content.parse()?; - } + let _ = content.parse::()?; } - Ok(LayoutArgs { - span, + Ok(WidgetArgs { + key_nav, + hover_highlight, + cursor_icon, + derive, layout, find_id, }) diff --git a/crates/kas-macros/src/layout.rs b/crates/kas-macros/src/layout.rs deleted file mode 100644 index 599bcac98..000000000 --- a/crates/kas-macros/src/layout.rs +++ /dev/null @@ -1,266 +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 - -use crate::args::{Child, LayoutArgs, LayoutType}; -use proc_macro2::TokenStream as Toks; -use quote::{quote, TokenStreamExt}; -use syn::parse::{Error, Result}; -use syn::Member; - -pub(crate) fn data_type(children: &[Child], layout: &LayoutArgs) -> Result<(Toks, Toks, Toks)> { - if layout.layout == LayoutType::Single && children.len() != 1 { - return Err(Error::new( - layout.span, - format_args!( - "expected 1 child marked #[widget] when using layout 'single'; found {}", - children.len() - ), - )); - } - - let mut cols: usize = 0; - let mut rows: usize = 0; - let mut col_spans: usize = 0; - let mut row_spans: usize = 0; - - for child in children.iter() { - let args = &child.args; - - match layout.layout { - LayoutType::Single => (), - LayoutType::Right | LayoutType::Left => { - cols += 1; - rows = 1; - } - LayoutType::Down | LayoutType::Up => { - cols = 1; - rows += 1; - } - LayoutType::Grid => { - let pos = args.as_pos()?; - let c1 = pos.0 + pos.2; - let r1 = pos.1 + pos.3; - cols = cols.max(c1 as usize); - rows = rows.max(r1 as usize); - if pos.2 > 1 { - col_spans += 1; - } - if pos.3 > 1 { - row_spans += 1; - } - } - } - } - - let row_temp = if rows > 16 { - quote! { Vec } - } else { - quote! { [i32; #rows] } - }; - let col_temp = if cols > 16 { - quote! { Vec } - } else { - quote! { [i32; #cols] } - }; - - Ok(match layout.layout { - LayoutType::Single => ( - quote! { () }, - quote! { ::kas::layout::SingleSolver }, - quote! { ::kas::layout::SingleSetter }, - ), - l @ LayoutType::Right | l @ LayoutType::Left => { - let dt = quote! { ::kas::layout::FixedRowStorage::<#cols> }; - let solver = quote! { ::kas::layout::RowSolver::<#dt> }; - let setter = quote! { ::kas::layout::RowSetter::<#l, #col_temp, #dt> }; - (dt, solver, setter) - } - l @ LayoutType::Down | l @ LayoutType::Up => { - let dt = quote! { ::kas::layout::FixedRowStorage::<#rows> }; - let solver = quote! { ::kas::layout::RowSolver::<#dt> }; - let setter = quote! { ::kas::layout::RowSetter::<#l, #row_temp, #dt> }; - (dt, solver, setter) - } - LayoutType::Grid => { - let dt = quote! { ::kas::layout::FixedGridStorage::<#rows, #cols> }; - let solver = quote! { - ::kas::layout::GridSolver::< - [(::kas::layout::SizeRules, u32, u32); #row_spans], - [(::kas::layout::SizeRules, u32, u32); #col_spans], - #dt, - > - }; - let setter = quote! { ::kas::layout::GridSetter::<#row_temp, #col_temp, #dt> }; - (dt, solver, setter) - } - }) -} - -pub(crate) fn derive(core: &Member, children: &[Child], layout: &LayoutArgs) -> Result { - let (storage_type, solver_type, setter_type) = data_type(children, layout)?; - - let mut cols = 0u32; - let mut rows = 0u32; - let mut col_spans = 0u32; - let mut row_spans = 0u32; - let mut size = Toks::new(); - let mut set_rect = Toks::new(); - let mut draw = quote! { - use ::kas::{geom::Coord, WidgetCore}; - let rect = draw.get_clip_rect(); - let pos1 = rect.pos; - let pos2 = rect.pos2(); - let disabled = disabled || self.is_disabled(); - }; - - for child in children.iter() { - let ident = &child.ident; - let args = &child.args; - - let child_info = match layout.layout { - LayoutType::Single => quote! { () }, - LayoutType::Right | LayoutType::Left => { - let col = cols as usize; - cols += 1; - rows = 1; - - quote! { #col } - } - LayoutType::Down | LayoutType::Up => { - let row = rows as usize; - cols = 1; - rows += 1; - - quote! { #row } - } - LayoutType::Grid => { - let pos = args.as_pos()?; - let (c0, c1) = (pos.0, pos.0 + pos.2); - let (r0, r1) = (pos.1, pos.1 + pos.3); - cols = cols.max(c1); - rows = rows.max(r1); - if pos.2 > 1 { - col_spans += 1; - } - if pos.3 > 1 { - row_spans += 1; - } - - quote! { - ::kas::layout::GridChildInfo { - col: #c0, - col_end: #c1, - row: #r0, - row_end: #r1, - } - } - } - }; - - size.append_all(quote! { - let child = &mut self.#ident; - solver.for_child( - data, - #child_info, - |axis| child.size_rules(sh, axis) - ); - }); - - set_rect.append_all(quote! { let mut align2 = align; }); - if let Some(toks) = args.halign_toks()? { - set_rect.append_all(quote! { align2.horiz = Some(#toks); }); - } - if let Some(toks) = args.valign_toks()? { - set_rect.append_all(quote! { align2.vert = Some(#toks); }); - } - set_rect.append_all(quote! { - self.#ident.set_rect(_mgr, setter.child_rect(data, #child_info), align2); - }); - - draw.append_all(quote! { - let c1 = self.#ident.rect().pos; - let c2 = self.#ident.rect().pos2(); - if c1.0 <= pos2.0 && c2.0 >= pos1.0 && c1.1 <= pos2.1 && c2.1 >= pos1.1 { - self.#ident.draw(draw, mgr, disabled); - } - }); - } - - let (urows, ucols) = (rows as usize, cols as usize); - let dim = match layout.layout { - LayoutType::Single => quote! { () }, - LayoutType::Right => quote! { (::kas::dir::Right, #ucols) }, - LayoutType::Left => quote! { (::kas::dir::Left, #ucols) }, - LayoutType::Down => quote! { (::kas::dir::Down, #urows) }, - LayoutType::Up => quote! { (::kas::dir::Up, #urows) }, - LayoutType::Grid => quote! { ::kas::layout::GridDimensions { - cols: #cols, - rows: #rows, - col_spans: #col_spans, - row_spans: #row_spans - } }, - }; - - let find_id = match layout.find_id.as_ref() { - None => quote! {}, - Some(find_id) => quote! { - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { - if !self.rect().contains(coord) { - return None; - } - #find_id - } - }, - }; - - Ok(quote! { - fn size_rules(&mut self, sh: &mut dyn ::kas::draw::SizeHandle, axis: ::kas::layout::AxisInfo) - -> ::kas::layout::SizeRules - { - use ::kas::WidgetCore; - use ::kas::layout::RulesSolver; - - let (data, _) = self.#core.layout.storage::<#storage_type>(); - let mut solver = #solver_type::new( - axis, - #dim, - data, - ); - #size - solver.finish(data) - } - - fn set_rect( - &mut self, - _mgr: &mut ::kas::event::Manager, - rect: ::kas::geom::Rect, - align: ::kas::layout::AlignHints - ) { - use ::kas::{WidgetCore, Widget}; - use ::kas::layout::{RulesSetter}; - self.core.rect = rect; - - let (data, _) = self.#core.layout.storage::<#storage_type>(); - let mut setter = #setter_type::new( - rect, - #dim, - align, - data, - ); - #set_rect - } - - #find_id - - fn draw( - &mut self, - draw: &mut dyn ::kas::draw::DrawHandle, - mgr: &::kas::event::ManagerState, - disabled: bool, - ) { - #draw - } - }) -} diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 3339fa573..6c7b9b8a9 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -16,7 +16,6 @@ use syn::{GenericParam, Generics, ItemStruct}; mod args; mod autoimpl; -mod layout; mod make_layout; mod make_widget; pub(crate) mod where_clause; @@ -173,7 +172,9 @@ fn extend_generics(generics: &mut Generics, in_generics: &Generics) { #[proc_macro] pub fn widget(input: TokenStream) -> TokenStream { let args = parse_macro_input!(input as args::Widget); - widget::widget(args).into() + widget::widget(args) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } /// Macro to create a widget with anonymous type @@ -246,7 +247,9 @@ pub fn make_widget(input: TokenStream) -> TokenStream { #[proc_macro] pub fn make_layout(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as make_layout::Input); - make_layout::make_layout(input).into() + make_layout::make_layout(input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } /// Macro to derive `From` diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 6853ed592..c4e8de7bb 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -3,10 +3,11 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -use proc_macro2::TokenStream as Toks; +use proc_macro2::{Span, TokenStream as Toks}; use quote::{quote, TokenStreamExt}; use syn::parse::{Error, Parse, ParseStream, Result}; -use syn::{braced, bracketed, parenthesized, LitInt, Token}; +use syn::spanned::Spanned; +use syn::{braced, bracketed, parenthesized, Expr, LitInt, Member, Token}; #[allow(non_camel_case_types)] mod kw { @@ -26,23 +27,40 @@ mod kw { custom_keyword!(list); custom_keyword!(slice); custom_keyword!(grid); + custom_keyword!(single); } pub struct Input { - core: syn::Expr, - layout: Layout, + pub core: Expr, + pub layout: Tree, +} + +pub struct Tree(Layout); +impl Tree { + pub fn generate<'a, I: ExactSizeIterator>( + &'a self, + children: I, + ) -> Result { + self.0.generate(Some(children)) + } } enum Layout { Align(Box, Align), - AlignSingle(syn::Expr, Align), - Single(syn::Expr), + AlignSingle(Expr, Align), + Single(Span), + Widget(Expr), Frame(Box), - List(Direction, Vec), - Slice(Direction, syn::Expr), + List(Direction, List), + Slice(Direction, Expr), Grid(GridDimensions, Vec<(CellInfo, Layout)>), } +enum List { + List(Vec), + Glob(Span), +} + enum Direction { Left, Right, @@ -84,6 +102,8 @@ impl Parse for CellInfo { row + 1 }; + let _ = input.parse::()?; + let col = input.parse::()?.base10_parse()?; let col_end = if input.peek(Token![..]) { let _ = input.parse::(); @@ -128,6 +148,12 @@ impl Parse for Input { } } +impl Parse for Tree { + fn parse(input: ParseStream) -> Result { + Ok(Tree(input.parse()?)) + } +} + impl Parse for Layout { fn parse(input: ParseStream) -> Result { let lookahead = input.lookahead1(); @@ -143,7 +169,10 @@ impl Parse for Layout { Ok(Layout::Align(Box::new(layout), align)) } } else if lookahead.peek(Token![self]) { - Ok(Layout::Single(input.parse()?)) + Ok(Layout::Widget(input.parse()?)) + } else if lookahead.peek(kw::single) { + let tok: kw::single = input.parse()?; + Ok(Layout::Single(tok.span())) } else if lookahead.peek(kw::frame) { let _: kw::frame = input.parse()?; let inner; @@ -207,22 +236,30 @@ fn parse_align(input: ParseStream) -> Result { } } -fn parse_layout_list(input: ParseStream) -> Result> { - let inner; - let _ = bracketed!(inner in input); - - let mut list = vec![]; - while !inner.is_empty() { - list.push(inner.parse::()?); +fn parse_layout_list(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![*]) { + let tok = input.parse::()?; + Ok(List::Glob(tok.span())) + } else if lookahead.peek(syn::token::Bracket) { + let inner; + let _ = bracketed!(inner in input); + + let mut list = vec![]; + while !inner.is_empty() { + list.push(inner.parse::()?); + + if inner.is_empty() { + break; + } - if inner.is_empty() { - break; + let _: Token![,] = inner.parse()?; } - let _: Token![,] = inner.parse()?; + Ok(List::List(list)) + } else { + Err(lookahead.error()) } - - Ok(list) } fn parse_grid(input: ParseStream) -> Result { @@ -234,7 +271,7 @@ fn parse_grid(input: ParseStream) -> Result { while !inner.is_empty() { let info = inner.parse()?; dim.update(&info); - let _: Token![,] = inner.parse()?; + let _: Token![:] = inner.parse()?; let layout = inner.parse()?; cells.push((info, layout)); @@ -306,28 +343,78 @@ impl quote::ToTokens for GridDimensions { } impl Layout { - fn generate(&self) -> Toks { - match self { + // Optionally pass in the list of children, but not when already in a + // multi-element layout (list/slice/grid). + fn generate<'a, I: ExactSizeIterator>( + &'a self, + children: Option, + ) -> Result { + Ok(match self { Layout::Align(layout, align) => { - let inner = layout.generate(); - quote! { layout::Layout::align(#inner, #align) } + let inner = layout.generate(children)?; + quote! { ::kas::layout::Layout::align(#inner, #align) } } Layout::AlignSingle(expr, align) => { - quote! { layout::Layout::align_single(#expr.as_widget_mut(), #align) } + quote! { ::kas::layout::Layout::align_single(#expr.as_widget_mut(), #align) } } - Layout::Single(expr) => quote! { - layout::Layout::single(#expr.as_widget_mut()) + Layout::Widget(expr) => quote! { + ::kas::layout::Layout::single(#expr.as_widget_mut()) }, + Layout::Single(span) => { + if let Some(mut iter) = children { + if iter.len() != 1 { + return Err(Error::new( + *span, + "layout `single`: widget does not have exactly one child", + )); + } + let child = iter.next().unwrap(); + quote! { + ::kas::layout::Layout::single(self.#child.as_widget_mut()) + } + } else { + return Err(Error::new( + *span, + "layout `single` is unavailable in this context", + )); + } + } Layout::Frame(layout) => { - let inner = layout.generate(); + let inner = layout.generate(children)?; quote! { let (data, next) = _chain.storage::<::kas::layout::FrameStorage>(); _chain = next; - layout::Layout::frame(data, #inner) + ::kas::layout::Layout::frame(data, #inner) } } Layout::List(dir, list) => { - let len = list.len(); + let len; + let mut items = Toks::new(); + match list { + List::List(list) => { + len = list.len(); + for item in list { + let item = item.generate::>(None)?; + items.append_all(quote! { #item, }); + } + } + List::Glob(span) => { + if let Some(iter) = children { + len = iter.len(); + for member in iter { + items.append_all(quote! { + ::kas::layout::Layout::single(self.#member.as_widget_mut()), + }); + } + } else { + return Err(Error::new( + *span, + "glob `*` is unavailable in this context", + )); + } + } + } + let storage = if len > 16 { quote! { ::kas::layout::DynRowStorage } } else { @@ -340,11 +427,6 @@ impl Layout { data } }; - let mut items = Toks::new(); - for item in list { - let item = item.generate(); - items.append_all(quote! { #item, }); - } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; quote! { ::kas::layout::Layout::list(#iter, #dir, #data) } @@ -358,9 +440,9 @@ impl Layout { quote! { ::kas::layout::Layout::slice(&mut #expr, #dir, #data) } } Layout::Grid(dim, cells) => { - let (rows, cols) = (dim.rows, dim.cols); + let (rows, cols) = (dim.rows as usize, dim.cols as usize); let data = quote! { { - let (data, next) = _chain.storage::<::kas::layout::FixedGridStorage<#rows, #cols>(); + let (data, next) = _chain.storage::<::kas::layout::FixedGridStorage<#rows, #cols>>(); _chain = next; data } }; @@ -369,7 +451,7 @@ impl Layout { for item in cells { let (row, row_end) = (item.0.row, item.0.row_end); let (col, col_end) = (item.0.col, item.0.col_end); - let layout = item.1.generate(); + let layout = item.1.generate::>(None)?; items.append_all(quote! { ( ::kas::layout::GridChildInfo { @@ -379,22 +461,23 @@ impl Layout { col_end: #col_end, }, #layout, - ) + ), }); } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; quote! { ::kas::layout::Layout::grid(#iter, #dim, #data) } } - } + }) } } -pub fn make_layout(input: Input) -> Toks { +pub fn make_layout(input: Input) -> Result { let core = &input.core; - let layout = input.layout.generate(); - quote! { { + let layout = input.layout.0.generate::>(None)?; + Ok(quote! { { + use ::kas::WidgetCore; let mut _chain = &mut #core.layout; #layout - } } + } }) } diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 2a03e680b..851ee1d95 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -4,14 +4,14 @@ // https://www.apache.org/licenses/LICENSE-2.0 use crate::args::{Handler, Widget}; -use crate::{extend_generics, layout}; +use crate::extend_generics; use proc_macro2::TokenStream; -use proc_macro_error::emit_error; +use proc_macro_error::{emit_error, emit_warning}; use quote::{quote, TokenStreamExt}; -use syn::parse_quote; use syn::spanned::Spanned; +use syn::{parse_quote, Result}; -pub(crate) fn widget(mut args: Widget) -> TokenStream { +pub(crate) fn widget(mut args: Widget) -> Result { let mut toks = quote! { #args }; let name = &args.ident; @@ -188,10 +188,9 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { } if impl_widget_config { - let config = args.attr_widget.config.unwrap_or_default(); - let key_nav = config.key_nav; - let hover_highlight = config.hover_highlight; - let cursor_icon = config.cursor_icon; + let key_nav = args.attr_widget.key_nav.value; + let hover_highlight = args.attr_widget.hover_highlight.value; + let cursor_icon = args.attr_widget.cursor_icon.value; toks.append_all(quote! { impl #impl_generics ::kas::WidgetConfig @@ -208,6 +207,16 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { } } }); + } else { + if let Some(span) = args.attr_widget.key_nav.span { + emit_warning!(span, "unused due to manual impl of `WidgetConfig`"); + } + if let Some(span) = args.attr_widget.hover_highlight.span { + emit_warning!(span, "unused due to manual impl of `WidgetConfig`"); + } + if let Some(span) = args.attr_widget.cursor_icon.span { + emit_warning!(span, "unused due to manual impl of `WidgetConfig`"); + } } if let Some(inner) = opt_derive { @@ -260,18 +269,35 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { } } }); - } else if let Some(ref layout) = args.attr_layout { + } else if let Some(layout) = args.attr_widget.layout.take() { + let find_id = match args.attr_widget.find_id.value { + None => quote! {}, + Some(find_id) => quote! { + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + if !self.rect().contains(coord) { + return None; + } + #find_id + } + }, + }; + let core = args.core_data.as_ref().unwrap(); - match layout::derive(core, &args.children, layout) { - Ok(fns) => toks.append_all(quote! { - impl #impl_generics ::kas::Layout - for #name #ty_generics #where_clause - { - #fns + let layout = layout.generate(args.children.iter().map(|c| &c.ident))?; + + toks.append_all(quote! { + impl #impl_generics ::kas::Layout for #name #ty_generics #where_clause { + fn layout<'a>(&'a mut self) -> ::kas::layout::Layout<'a> { + use ::kas::WidgetCore; + let mut _chain = &mut self.#core.layout; + #layout } - }), - Err(err) => return err.to_compile_error(), - } + + #find_id + } + }); + } else if let Some(span) = args.attr_widget.find_id.span { + emit_warning!(span, "unused without generated impl of `Layout`"); } if let Some(index) = handler_impl { @@ -418,5 +444,5 @@ pub(crate) fn widget(mut args: Widget) -> TokenStream { }); } - toks + Ok(toks) } diff --git a/crates/kas-widgets/src/adapter/map.rs b/crates/kas-widgets/src/adapter/map.rs index aeffb2c5f..f56daa31d 100644 --- a/crates/kas-widgets/src/adapter/map.rs +++ b/crates/kas-widgets/src/adapter/map.rs @@ -14,7 +14,9 @@ widget! { #[autoimpl(Deref, DerefMut on inner)] #[autoimpl(class_traits where W: trait on inner)] #[derive(Clone)] - #[layout(single)] + #[widget{ + layout = single; + }] #[handler(msg=M)] pub struct MapResponse { #[widget_core] diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 6f0793813..33156c3bf 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -13,7 +13,10 @@ widget! { /// A bare checkbox (no label) #[autoimpl(Debug skip on_toggle)] #[derive(Clone, Default)] - #[widget(config(key_nav = true, hover_highlight = true))] + #[widget{ + key_nav = true; + hover_highlight = true; + }] pub struct CheckBoxBare { #[widget_core] core: CoreData, @@ -131,7 +134,10 @@ widget! { #[autoimpl(Debug)] #[autoimpl(HasBool on checkbox)] #[derive(Clone, Default)] - #[layout(row, find_id=Some(self.checkbox.id()))] + #[widget{ + layout = row: *; + find_id = Some(self.checkbox.id()); + }] pub struct CheckBox { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 1338f1ae1..106f75392 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -18,7 +18,10 @@ widget! { /// A combobox presents a menu with a fixed set of choices when clicked. #[autoimpl(Debug skip on_select)] #[derive(Clone)] - #[widget(config(key_nav = true, hover_highlight = true))] + #[widget{ + key_nav = true; + hover_highlight = true; + }] pub struct ComboBox { #[widget_core] core: CoreData, @@ -399,7 +402,9 @@ impl ComboBox { widget! { #[derive(Clone, Debug)] - #[layout(single)] + #[widget{ + layout = single; + }] #[handler(msg=(usize, ()))] struct ComboPopup { #[widget_core] diff --git a/crates/kas-widgets/src/dialog.rs b/crates/kas-widgets/src/dialog.rs index 5d9aa073c..86781d237 100644 --- a/crates/kas-widgets/src/dialog.rs +++ b/crates/kas-widgets/src/dialog.rs @@ -17,7 +17,9 @@ use kas::WindowId; widget! { /// A simple message box. #[derive(Clone, Debug)] - #[layout(column)] + #[widget{ + layout = column: *; + }] pub struct MessageBox { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/drag.rs b/crates/kas-widgets/src/drag.rs index cae68cc16..81bde9f81 100644 --- a/crates/kas-widgets/src/drag.rs +++ b/crates/kas-widgets/src/drag.rs @@ -27,7 +27,11 @@ widget! { /// 4. Optionally, this widget can handle clicks on the track area via /// [`DragHandle::handle_press_on_track`]. #[derive(Clone, Debug, Default)] - #[widget(config(hover_highlight = true, key_nav = true, cursor_icon = event::CursorIcon::Grab))] + #[widget{ + hover_highlight = true; + key_nav = true; + cursor_icon = event::CursorIcon::Grab; + }] pub struct DragHandle { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index 37fd91cd3..4c60d428b 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -361,7 +361,11 @@ widget! { /// line-wrapping and a larger vertical height). This mode is only recommended /// for short texts for performance reasons. #[derive(Clone, Default, Debug)] - #[widget(config(key_nav = true, hover_highlight = true, cursor_icon = event::CursorIcon::Text))] + #[widget{ + key_nav = true; + hover_highlight = true; + cursor_icon = event::CursorIcon::Text; + }] pub struct EditField { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs index 41bc18153..e0285a158 100644 --- a/crates/kas-widgets/src/nav_frame.rs +++ b/crates/kas-widgets/src/nav_frame.rs @@ -15,7 +15,9 @@ widget! { #[autoimpl(Deref, DerefMut on inner)] #[autoimpl(class_traits where W: trait on inner)] #[derive(Clone, Debug, Default)] - #[widget(config(key_nav = true))] + #[widget{ + key_nav = true; + }] pub struct NavFrame { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index 0aacc5738..95d50fba4 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -173,7 +173,10 @@ widget! { #[autoimpl(Debug)] #[autoimpl(HasBool on radiobox)] #[derive(Clone)] - #[layout(row, find_id=Some(self.radiobox.id()))] + #[widget{ + find_id = Some(self.radiobox.id()); + layout = row: *; + }] pub struct RadioBox { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 13df42623..78ad04e86 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -17,7 +17,9 @@ use kas::text::SelectionHelper; widget! { /// A text label supporting scrolling and selection #[derive(Clone, Default, Debug)] - #[widget(config(cursor_icon = event::CursorIcon::Text))] + #[widget{ + cursor_icon = event::CursorIcon::Text; + }] pub struct ScrollLabel { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index e4c0ee2fd..a9a745140 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -324,7 +324,9 @@ widget! { #[autoimpl(Deref, DerefMut on 0)] #[autoimpl(class_traits where W: trait on 0)] #[derive(Clone, Debug, Default)] - #[widget(derive = self.0)] + #[widget{ + derive = self.0; + }] #[handler(msg = ::Msg)] pub struct ScrollBarRegion(ScrollBars>); diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 56c0b6d31..9088b07d0 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -87,7 +87,10 @@ widget! { /// Sliders allow user input of a value from a fixed range. #[derive(Clone, Debug, Default)] #[handler(msg = T)] - #[widget(config(key_nav = true, hover_highlight = true))] + #[widget{ + key_nav = true; + hover_highlight = true; + }] pub struct Slider { #[widget_core] core: CoreData, diff --git a/crates/kas-widgets/src/view/filter_list.rs b/crates/kas-widgets/src/view/filter_list.rs index 56e01d8e3..adf632739 100644 --- a/crates/kas-widgets/src/view/filter_list.rs +++ b/crates/kas-widgets/src/view/filter_list.rs @@ -161,7 +161,9 @@ widget! { /// avoids this. // TODO: impl Clone #[derive(Debug)] - #[layout(single)] + #[widget{ + layout = single; + }] pub struct FilterListView< D: Directional, T: ListData + UpdHandler + 'static, diff --git a/crates/kas-widgets/src/view/single_view.rs b/crates/kas-widgets/src/view/single_view.rs index 5a41324a9..4765aebb2 100644 --- a/crates/kas-widgets/src/view/single_view.rs +++ b/crates/kas-widgets/src/view/single_view.rs @@ -25,7 +25,9 @@ widget! { /// [`driver`] module or a custom implementation may be used. #[autoimpl(Debug skip view)] #[derive(Clone)] - #[layout(single)] + #[widget{ + layout = single; + }] pub struct SingleView< T: SingleData + UpdHandler<(), V::Msg> + 'static, V: Driver = driver::Default, diff --git a/examples/calculator.rs b/examples/calculator.rs index eb70ae217..4ae8545ce 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -29,45 +29,42 @@ fn main() -> Result<(), kas::shell::Error> { env_logger::init(); let buttons = make_widget! { - #[layout(grid)] + #[widget{ + layout = grid: { + 0, 0: self.b_clear; 0, 1: self.b_div; 0, 2: self.b_mul; 0, 3: self.b_sub; + 1, 0: self.b7; 1, 1: self.b8; 1, 2: self.b9; + 1..3, 3: align(stretch): self.b_add; + 2, 0: self.b4; 2, 1: self.b5; 2, 2: self.b6; + 3, 0: self.b1; 3, 1: self.b2; 3, 2: self.b3; + 3..5, 3: align(stretch): self.b_eq; + 4, 0..2: self.b0; 4, 2: self.b_dot; + }; + }] #[handler(msg = Key)] struct { // Buttons get keyboard bindings through the "&" item (e.g. "&1" // binds both main and numpad 1 key) and via `with_keys`. - #[widget(col = 0, row = 0)] - _ = TextButton::new_msg("&clear", Key::Clear).with_keys(&[VK::Delete]), - #[widget(col = 1, row = 0)] - _ = TextButton::new_msg("&÷", Key::Divide).with_keys(&[VK::Slash]), - #[widget(col = 2, row = 0)] - _ = TextButton::new_msg("&×", Key::Multiply).with_keys(&[VK::Asterisk]), - #[widget(col = 3, row = 0)] - _ = TextButton::new_msg("&−", Key::Subtract), - #[widget(col = 0, row = 1)] - _ = TextButton::new_msg("&7", Key::Char('7')), - #[widget(col = 1, row = 1)] - _ = TextButton::new_msg("&8", Key::Char('8')), - #[widget(col = 2, row = 1)] - _ = TextButton::new_msg("&9", Key::Char('9')), - #[widget(col = 3, row = 1, rspan = 2, align = stretch)] - _ = TextButton::new_msg("&+", Key::Add), - #[widget(col = 0, row = 2)] - _ = TextButton::new_msg("&4", Key::Char('4')), - #[widget(col = 1, row = 2)] - _ = TextButton::new_msg("&5", Key::Char('5')), - #[widget(col = 2, row = 2)] - _ = TextButton::new_msg("&6", Key::Char('6')), - #[widget(col = 0, row = 3)] - _ = TextButton::new_msg("&1", Key::Char('1')), - #[widget(col = 1, row = 3)] - _ = TextButton::new_msg("&2", Key::Char('2')), - #[widget(col = 2, row = 3)] - _ = TextButton::new_msg("&3", Key::Char('3')), - #[widget(col = 3, row = 3, rspan = 2, align = stretch)] - _ = TextButton::new_msg("&=", Key::Equals).with_keys(&[VK::Return, VK::NumpadEnter]), - #[widget(col = 0, row = 4, cspan = 2)] - _ = TextButton::new_msg("&0", Key::Char('0')), - #[widget(col = 2, row = 4)] - _ = TextButton::new_msg("&.", Key::Char('.')), + #[widget] b_clear = TextButton::new_msg("&clear", Key::Clear) + .with_keys(&[VK::Delete]), + #[widget] b_eq = TextButton::new_msg("&=", Key::Equals) + .with_keys(&[VK::Return, VK::NumpadEnter]), + #[widget] b_sub = TextButton::new_msg("&−", Key::Subtract), + #[widget] b_add = TextButton::new_msg("&+", Key::Add), + #[widget] b_div = TextButton::new_msg("&÷", Key::Divide) + .with_keys(&[VK::Slash]), + #[widget] b_mul = TextButton::new_msg("&×", Key::Multiply) + .with_keys(&[VK::Asterisk]), + #[widget] b_dot = TextButton::new_msg("&.", Key::Char('.')), + #[widget] b0 = TextButton::new_msg("&0", Key::Char('0')), + #[widget] b1 = TextButton::new_msg("&1", Key::Char('1')), + #[widget] b2 = TextButton::new_msg("&2", Key::Char('2')), + #[widget] b3 = TextButton::new_msg("&3", Key::Char('3')), + #[widget] b4 = TextButton::new_msg("&4", Key::Char('4')), + #[widget] b5 = TextButton::new_msg("&5", Key::Char('5')), + #[widget] b6 = TextButton::new_msg("&6", Key::Char('6')), + #[widget] b7 = TextButton::new_msg("&7", Key::Char('7')), + #[widget] b8 = TextButton::new_msg("&8", Key::Char('8')), + #[widget] b9 = TextButton::new_msg("&9", Key::Char('9')), } impl kas::WidgetConfig for Self { fn configure(&mut self, mgr: &mut Manager) { @@ -77,7 +74,9 @@ fn main() -> Result<(), kas::shell::Error> { } }; let content = make_widget! { - #[layout(column)] + #[widget{ + layout = column: *; + }] #[handler(msg = VoidMsg)] struct { #[widget] display: impl HasString = EditBox::new("0").editable(false).multi_line(true), diff --git a/examples/cursors.rs b/examples/cursors.rs index 0cf531bcc..c41d50618 100644 --- a/examples/cursors.rs +++ b/examples/cursors.rs @@ -11,7 +11,9 @@ use kas::widgets::{Column, Label, StrLabel, Window}; widget! { #[derive(Clone, Debug)] - #[layout(single)] + #[widget{ + layout = single; + }] struct CursorWidget { #[widget_core] core: CoreData, diff --git a/examples/custom-theme.rs b/examples/custom-theme.rs index 06843eb52..8be769867 100644 --- a/examples/custom-theme.rs +++ b/examples/custom-theme.rs @@ -109,14 +109,22 @@ fn main() -> Result<(), kas::shell::Error> { env_logger::init(); let widgets = make_widget! { - #[layout(grid)] + #[widget{ + layout = grid: { + 1, 1: self.label; + 0, 1: self.white; + 1, 2: self.red; + 2, 1: self.yellow; + 1, 0: self.green; + }; + }] #[handler(msg = Item)] struct { - #[widget(row=1, col=1)] _ = Label::new("Custom theme demo\nChoose your colour!"), - #[widget(row=0, col=1)] _ = TextButton::new_msg("&White", Item::White), - #[widget(row=1, col=2)] _ = TextButton::new_msg("&Red", Item::Red), - #[widget(row=2, col=1)] _ = TextButton::new_msg("&Yellow", Item::Yellow), - #[widget(row=1, col=0)] _ = TextButton::new_msg("&Green", Item::Green), + #[widget] label = Label::new("Custom theme demo\nChoose your colour!"), + #[widget] white = TextButton::new_msg("&White", Item::White), + #[widget] red = TextButton::new_msg("&Red", Item::Red), + #[widget] yellow = TextButton::new_msg("&Yellow", Item::Yellow), + #[widget] green = TextButton::new_msg("&Green", Item::Green), } }; @@ -125,7 +133,9 @@ fn main() -> Result<(), kas::shell::Error> { let window = Window::new( "Theme demo", make_widget! { - #[layout(single)] + #[widget{ + layout = single; + }] #[handler(msg = VoidMsg)] struct { #[widget(use_msg = handler)] _ = widgets, diff --git a/examples/data-list-view.rs b/examples/data-list-view.rs index e49267e1d..65562c4cf 100644 --- a/examples/data-list-view.rs +++ b/examples/data-list-view.rs @@ -152,7 +152,9 @@ impl EditGuard for ListEntryGuard { widget! { // The list entry #[derive(Clone, Debug)] - #[layout(column)] + #[widget{ + layout = column: *; + }] #[handler(msg=EntryMsg)] struct ListEntry { #[widget_core] @@ -199,7 +201,9 @@ fn main() -> Result<(), kas::shell::Error> { env_logger::init(); let controls = make_widget! { - #[layout(row)] + #[widget{ + layout = row: *; + }] #[handler(msg = Control)] struct { #[widget] _ = Label::new("Number of rows:"), @@ -239,7 +243,9 @@ fn main() -> Result<(), kas::shell::Error> { let window = Window::new( "Dynamic widget demo", make_widget! { - #[layout(column)] + #[widget{ + layout = column: *; + }] #[handler(msg = VoidMsg)] struct { #[widget] _ = Label::new("Demonstration of dynamic widget creation / deletion"), diff --git a/examples/data-list.rs b/examples/data-list.rs index 0288f3e9b..048c4f121 100644 --- a/examples/data-list.rs +++ b/examples/data-list.rs @@ -67,7 +67,9 @@ widget! { // UpdateHandle, which is quite slow with thousands of entries! // (This issue does not occur when RadioBoxes are independent.) #[derive(Clone, Debug)] - #[layout(column)] + #[widget{ + layout = column: *; + }] #[handler(msg=EntryMsg)] struct ListEntry { #[widget_core] @@ -98,7 +100,9 @@ fn main() -> Result<(), kas::shell::Error> { env_logger::init(); let controls = make_widget! { - #[layout(row)] + #[widget{ + layout = row: *; + }] #[handler(msg = Control)] struct { #[widget] _ = Label::new("Number of rows:"), @@ -138,7 +142,9 @@ fn main() -> Result<(), kas::shell::Error> { let window = Window::new( "Dynamic widget demo", make_widget! { - #[layout(column)] + #[widget{ + layout = column: *; + }] #[handler(msg = VoidMsg)] struct { #[widget] _ = Label::new("Demonstration of dynamic widget creation / deletion"), diff --git a/examples/filter-list.rs b/examples/filter-list.rs index 62348a3cd..50f29e0a5 100644 --- a/examples/filter-list.rs +++ b/examples/filter-list.rs @@ -32,7 +32,9 @@ fn main() -> Result<(), kas::shell::Error> { let r = UpdateHandle::new(); let selection_mode = make_widget! { - #[layout(right)] + #[widget{ + layout = list(right): *; + }] #[handler(msg = SelectionMode)] struct { #[widget] _ = Label::new("Selection:"), @@ -50,7 +52,9 @@ fn main() -> Result<(), kas::shell::Error> { let window = Window::new( "Filter-list", make_widget! { - #[layout(down)] + #[widget{ + layout = list(down): *; + }] #[handler(msg = VoidMsg)] struct { #[widget(use_msg = set_selection_mode)] _ = selection_mode, diff --git a/examples/gallery.rs b/examples/gallery.rs index bcab063a2..470d13990 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -47,18 +47,19 @@ impl EditGuard for Guard { widget! { #[derive(Debug)] - #[layout(grid)] + #[widget{ + layout = grid: { + 0, 0..3: self.edit; + 1, 0: self.fill; 1, 1: self.cancel; 1, 2: self.save; + }; + }] struct TextEditPopup { #[widget_core] core: CoreData, - #[widget(cspan = 3)] - edit: EditBox, - #[widget(row = 1, col = 0)] - fill: Filler, - #[widget(row=1, col=1, flatmap_msg = close)] - cancel: TextButton, - #[widget(row=1, col=2, flatmap_msg = close)] - save: TextButton, + #[widget] edit: EditBox, + #[widget] fill: Filler, + #[widget(flatmap_msg = close)] cancel: TextButton, + #[widget(flatmap_msg = close)] save: TextButton, commit: bool, } impl TextEditPopup { @@ -164,7 +165,9 @@ fn main() -> Result<(), kas::shell::Error> { ]); let popup_edit_box = make_widget! { - #[layout(row)] + #[widget{ + layout = row: *; + }] struct { #[widget] label: StringLabel = Label::from("Use button to edit →"), #[widget(use_msg = edit)] edit = TextButton::new_msg("&Edit", ()), @@ -214,17 +217,35 @@ fn main() -> Result<(), kas::shell::Error> { let radio = UpdateHandle::new(); let widgets = make_widget! { - #[layout(grid)] + // TODO: this would be better expressed with a column layout, though we + // want better alignment controls first (which are also needed for menus). + #[widget{ + layout = grid: { + 0, 0: self.sll; 0, 1: self.sl; + 1, 0: self.ebl; 1, 1: self.eb; + 2, 0: self.tbl; 2, 1: self.tb; + 3, 0: self.bil; 3, 1: self.bi; + 4, 0: self.cbl; 4, 1: self.cb; + 5, 0: self.rbl; 5, 1: self.rb; + 6, 0: self.rb2l; 6, 1: self.rb2; + 7, 0: self.cbbl; 7, 1: self.cbb; + 8, 0: self.sdl; 8, 1: self.sd; + 9, 0: self.scl; 9, 1: self.sc; + 10, 0: self.pgl; 10, 1: self.pg; + 11, 0: self.svl; 11, 1: align(center): self.sv; + 12, 0: self.pul; 12, 1: self.pu; + }; + }] #[handler(msg = Item)] struct { - #[widget(row=0, col=0)] _ = Label::new("ScrollLabel"), - #[widget(row=0, col=1)] _ = ScrollLabel::new(text), - #[widget(row=1, col=0)] _ = Label::new("EditBox"), - #[widget(row=1, col=1)] _ = EditBox::new("edit me").with_guard(Guard), - #[widget(row=2, col=0)] _ = Label::new("TextButton"), - #[widget(row=2, col=1)] _ = TextButton::new_msg("&Press me", Item::Button), - #[widget(row=3, col=0)] _ = Label::new("Button"), - #[widget(row=3, col=1)] _ = row![ + #[widget] sll = Label::new("ScrollLabel"), + #[widget] sl = ScrollLabel::new(text), + #[widget] ebl = Label::new("EditBox"), + #[widget] eb = EditBox::new("edit me").with_guard(Guard), + #[widget] tbl = Label::new("TextButton"), + #[widget] tb = TextButton::new_msg("&Press me", Item::Button), + #[widget] bil = Label::new("Button"), + #[widget] bi = row![ Button::new_msg(Image::new("res/sun_32.png"), Item::LightTheme) .with_color(Rgb::rgb(0.3, 0.4, 0.5)) .with_keys(&[VK::L]), @@ -232,34 +253,32 @@ fn main() -> Result<(), kas::shell::Error> { .with_color(Rgb::grey(0.1)) .with_keys(&[VK::K]), ], - #[widget(row=4, col=0)] _ = Label::new("CheckBox"), - #[widget(row=4, col=1)] _ = CheckBox::new("&Check me") + #[widget] cbl = Label::new("CheckBox"), + #[widget] cb = CheckBox::new("&Check me") .with_state(true) .on_toggle(|_, check| Some(Item::Check(check))), - #[widget(row=5, col=0)] _ = Label::new("RadioBox"), - #[widget(row=5, col=1)] _ = RadioBox::new("radio box &1", radio) + #[widget] rbl = Label::new("RadioBox"), + #[widget] rb = RadioBox::new("radio box &1", radio) .on_select(|_| Some(Item::Radio(1))), - #[widget(row=6, col=0)] _ = Label::new("RadioBox"), - #[widget(row=6, col=1)] _ = RadioBox::new("radio box &2", radio) + #[widget] rb2l = Label::new("RadioBox"), + #[widget] rb2 = RadioBox::new("radio box &2", radio) .with_state(true) .on_select(|_| Some(Item::Radio(2))), - #[widget(row=7, col=0)] _ = Label::new("ComboBox"), - #[widget(row=7, col=1)] _ = - ComboBox::new(&["&One", "T&wo", "Th&ree"], 0) + #[widget] cbbl = Label::new("ComboBox"), + #[widget] cbb = ComboBox::new(&["&One", "T&wo", "Th&ree"], 0) .on_select(|_, index| Some(Item::Combo((index + 1).cast()))), - #[widget(row=8, col=0)] _ = Label::new("Slider"), - #[widget(row=8, col=1, map_msg = handle_slider)] s = + #[widget] sdl = Label::new("Slider"), + #[widget(map_msg = handle_slider)] sd = Slider::::new(0, 10, 1).with_value(0), - #[widget(row=9, col=0)] _ = Label::new("ScrollBar"), - #[widget(row=9, col=1, map_msg = handle_scroll)] sc: ScrollBar = + #[widget] scl = Label::new("ScrollBar"), + #[widget(map_msg = handle_scroll)] sc: ScrollBar = ScrollBar::new().with_limits(100, 20), - #[widget(row=10, col=1)] pg: ProgressBar = ProgressBar::new(), - #[widget(row=10, col=0)] _ = Label::new("ProgressBar"), - #[widget(row=11, col=0)] _ = Label::new("SVG"), - #[widget(row=11, col=1, align=centre)] _ = - Svg::from_path_and_factors("res/rustacean-flat-happy.svg", 0.1, 0.3), - #[widget(row=12, col=0)] _ = Label::new("Child window"), - #[widget(row=12, col=1)] _ = popup_edit_box, + #[widget] pg: ProgressBar = ProgressBar::new(), + #[widget] pgl = Label::new("ProgressBar"), + #[widget] svl = Label::new("SVG"), + #[widget] sv = Svg::from_path_and_factors("res/rustacean-flat-happy.svg", 0.1, 0.3), + #[widget] pul = Label::new("Child window"), + #[widget] pu = popup_edit_box, } impl Self { fn handle_slider(&mut self, _: &mut Manager, msg: i32) -> Item { @@ -274,7 +293,9 @@ fn main() -> Result<(), kas::shell::Error> { }; let head = make_widget! { - #[layout(row)] + #[widget{ + layout = row: *; + }] #[handler(msg = VoidMsg)] struct { #[widget] _ = Label::new("Widget Gallery"), @@ -285,11 +306,17 @@ fn main() -> Result<(), kas::shell::Error> { let mut window = Window::new( "Widget Gallery", make_widget! { - #[layout(column)] + #[widget{ + layout = column: [ + self.menubar, + align(center): self.head, + self.gallery, + ]; + }] #[handler(msg = VoidMsg)] struct { - #[widget(use_msg = menu)] _ = menubar, - #[widget(halign = centre)] _ = Frame::new(head), + #[widget(use_msg = menu)] menubar = menubar, + #[widget] head = Frame::new(head), #[widget(use_msg = activations)] gallery: for> ScrollBarRegion = ScrollBarRegion::new(widgets), diff --git a/examples/layout.rs b/examples/layout.rs index c407b4754..e8951d64e 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -18,15 +18,24 @@ fn main() -> Result<(), kas::shell::Error> { let window = Window::new( "Layout demo", make_widget! { - #[layout(grid)] + #[widget{ + layout = grid: { + 0, 1: self.title; + 0, 2: self.check; + 1, 0..3: self.lipsum; + 2, 0: align(center): self.abc; + 2..4, 1..3: align(stretch): self.crasit; + 3, 0: self.edit; + }; + }] #[handler(msg = VoidMsg)] struct { - #[widget(row=0, col=1)] _ = Label::new("Layout demo"), - #[widget(row=1, col=0, cspan=3)] _ = Label::new(lipsum), - #[widget(row=2, col=0, halign=centre)] _ = Label::new("abc אבג def"), - #[widget(row=2, col=1, cspan=2, rspan=2, halign=stretch)] _ = ScrollLabel::new(crasit), - #[widget(row=3, col=0)] _ = EditBox::new("A small\nsample\nof text").multi_line(true), - #[widget(row=0, col=2)] _ = CheckBoxBare::new(), + #[widget] title = Label::new("Layout demo"), + #[widget] lipsum = Label::new(lipsum), + #[widget] abc = Label::new("abc אבג def"), + #[widget] crasit = ScrollLabel::new(crasit), + #[widget] edit = EditBox::new("A small\nsample\nof text").multi_line(true), + #[widget] check = CheckBoxBare::new(), } }, ); diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index 681ad8d64..c5fd4aaae 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -418,20 +418,22 @@ widget! { widget! { #[derive(Debug)] - #[layout(grid)] + #[widget{ + layout = grid: { + 0, 0..2: self.label; + 1, 0: align(center): self.iters; + 2, 0: self.slider; + 1..3, 1..3: self.mbrot; + }; + }] #[handler(msg = event::VoidMsg)] struct MandlebrotWindow { - #[widget_core] - core: CoreData, - #[widget(cspan = 2)] - label: Label, - #[widget(row=1, halign=centre)] - iters: ReserveP>, - #[widget(row=2, use_msg = iter)] - slider: Slider, + #[widget_core] core: CoreData, + #[widget] label: Label, + #[widget] iters: ReserveP>, + #[widget(use_msg = iter)] slider: Slider, // extra col span allows use of Label's margin - #[widget(col=1, cspan=2, row=1, rspan=2, use_msg = mbrot)] - mbrot: Mandlebrot, + #[widget(use_msg = mbrot)] mbrot: Mandlebrot, } impl MandlebrotWindow { diff --git a/examples/markdown.rs b/examples/markdown.rs index 0ae5a1ec1..687ebd841 100644 --- a/examples/markdown.rs +++ b/examples/markdown.rs @@ -42,14 +42,20 @@ It also supports lists: let window = Window::new( "Markdown parser", make_widget! { - #[layout(grid)] + #[widget{ + layout = grid: { + 0..2, 0: self.editor; + 0, 1: self.label; + 1, 1: self.b_update; + }; + }] #[handler(msg = VoidMsg)] struct { - #[widget(row=0, col=0, rspan=2)] editor: EditBox = + #[widget] editor: EditBox = EditBox::new(doc).multi_line(true), - #[widget(row=0, col=1)] label: ScrollBarRegion> = + #[widget] label: ScrollBarRegion> = ScrollBarRegion::new(Label::new(Markdown::new(doc)?)), - #[widget(row=1, col=1, use_msg=update)] _ = TextButton::new_msg("&Update", ()), + #[widget(use_msg=update)] b_update = TextButton::new_msg("&Update", ()), } impl Self { fn update(&mut self, mgr: &mut Manager, _: ()) { diff --git a/examples/splitter.rs b/examples/splitter.rs index 3b50a7c60..cd46cebe3 100644 --- a/examples/splitter.rs +++ b/examples/splitter.rs @@ -19,7 +19,9 @@ fn main() -> Result<(), kas::shell::Error> { env_logger::init(); let buttons = make_widget! { - #[layout(row)] + #[widget{ + layout = row: *; + }] #[handler(msg = Message)] struct { #[widget] _ = TextButton::new_msg("−", Message::Decr), @@ -35,7 +37,9 @@ fn main() -> Result<(), kas::shell::Error> { "Slitter panes", make_widget! { // TODO: use vertical splitter - #[layout(column)] + #[widget{ + layout = column: *; + }] #[handler(msg = VoidMsg)] struct { #[widget(use_msg = handle_button)] buttons -> Message = buttons, diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index e4f22a56e..77f6e4c60 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -18,7 +18,9 @@ use kas::WidgetCore; fn make_window() -> Box { // Construct a row widget, with state and children let stopwatch = make_widget! { - #[layout(row)] + #[widget{ + layout = row: *; + }] struct { #[widget] display: impl HasString = Frame::new(Label::new("0.000".to_string())), #[widget(use_msg = reset)] _ = TextButton::new_msg("&reset", ()), diff --git a/examples/sync-counter.rs b/examples/sync-counter.rs index cadbbfe84..6eae79a5a 100644 --- a/examples/sync-counter.rs +++ b/examples/sync-counter.rs @@ -9,7 +9,7 @@ use kas::event::{Manager, VoidMsg}; use kas::macros::make_widget; use kas::updatable::SharedRc; use kas::widgets::view::SingleView; -use kas::widgets::{row, TextButton, Window}; +use kas::widgets::{TextButton, Window}; fn main() -> Result<(), kas::shell::Error> { env_logger::init(); @@ -17,16 +17,19 @@ fn main() -> Result<(), kas::shell::Error> { let window = Window::new( "Counter", make_widget! { - #[layout(column)] #[derive(Clone)] + #[widget{ + layout = column: [ + align(center): self.counter, + row: [self.b_decr, self.b_incr], + ]; + }] #[handler(msg = VoidMsg)] struct { // SingleView embeds a shared value, here default-constructed to 0 - #[widget(halign=centre)] counter: SingleView> = Default::default(), - #[widget(use_msg = update)] buttons -> i32 = row![ - TextButton::new_msg("−", -1), - TextButton::new_msg("+", 1), - ], + #[widget] counter: SingleView> = Default::default(), + #[widget(use_msg = update)] b_decr = TextButton::new_msg("−", -1), + #[widget(use_msg = update)] b_incr = TextButton::new_msg("+", 1), } impl Self { fn update(&mut self, mgr: &mut Manager, msg: i32) { diff --git a/src/macros.rs b/src/macros.rs index 97b244955..e932fe9db 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -76,7 +76,9 @@ //! //! widget! { //! #[derive(Clone, Debug)] -//! #[layout(single)] +//! #[widget{ +//! layout = single; +//! }] //! struct WrapperWidget { //! #[widget_core] core: CoreData, //! #[widget] child: W, @@ -116,18 +118,14 @@ //! by default, and the derived implementation is only useful for widgets with //! at least one child and which don't directly draw themselves. //! -//! The trait may be derived via a `layout` attribute, e.g. `#[layout(single)]`. +//! The trait may be derived via a `layout` property, e.g. `#[widget{ layout = single; }]`. //! One of the following values must appear first in the parameter list: //! //! - `single` — the widget wraps a single child, with no border or margin -//! - `col`, `column` or `down` — child widgets are arranged in a vertical -//! column, top-to-bottom -//! - `up` — reversed column -//! - `row` or `right` — child widgets are arranged in a horizontal row, -//! left-to-right -//! - `left` — reversed row -//! - `grid` — child widgets are arranged in a grid; position is specified -//! via parameters to the `#[widget]` attribute on child fields +//! - `list(DIRECTION): LIST` where `DIRECTION` is one of `left`, `right`, +//! `up`, `down` and `LIST` is either `*` or `[ ... ]` +//! - `column` or `row`: these are synonyms for `list(down)` and `list(right)` +//! - `grid: { ... }` — child widgets are arranged in a grid (see examples) //! //! Additional parameters are optional: //! @@ -220,7 +218,9 @@ //! # use kas::{CoreData, Layout, Widget, event::Handler}; //! widget! { //! #[derive(Clone, Debug, Default)] -//! #[layout(single)] +//! #[widget{ +//! layout = single; +//! }] //! #[handler(msg = ::Msg)] //! pub struct Frame { //! #[widget_core] @@ -304,7 +304,9 @@ //! //! widget! { //! #[derive(Debug)] -//! #[layout(column)] +//! #[widget{ +//! layout = column: *; +//! }] //! struct MyWidget { //! #[widget_core] core: CoreData, //! #[widget] label: StrLabel, @@ -354,7 +356,9 @@ //! } //! //! let button_box = make_widget!{ -//! #[layout(row)] +//! #[widget{ +//! layout = row: *; +//! }] //! #[handler(msg = OkCancel)] //! #[derive(Clone)] // optional //! struct { @@ -364,7 +368,9 @@ //! }; //! //! let window = Window::new("Question", make_widget! { -//! #[layout(column)] +//! #[widget{ +//! layout = column: *; +//! }] //! #[handler(msg = VoidMsg)] //! struct { //! #[widget] _ = Label::new("Would you like to print a message?"), From 4134ff3bf181d20178b18e60863433ee2b764653 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 5 Dec 2021 10:44:52 +0000 Subject: [PATCH 36/53] Menubar: use layout=single; fix mouse-move handling --- crates/kas-widgets/src/menu/menubar.rs | 41 ++++++++------------------ 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 337862756..baf62bf4d 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -16,12 +16,14 @@ widget! { /// This widget houses a sequence of menu buttons, allowing input actions across /// menus. #[derive(Clone, Debug)] + #[widget{ + layout = single; + }] pub struct MenuBar { #[widget_core] core: CoreData, #[widget] pub bar: IndexedList>, - ideal: Size, // Open mode. Used to close with click on root only when previously open. opening: bool, delayed_open: Option, @@ -47,35 +49,12 @@ widget! { MenuBar { core: Default::default(), bar: IndexedList::new_with_direction(direction, menus), - ideal: Size::ZERO, opening: false, delayed_open: None, } } } - // NOTE: we could use layout(single) except for alignment - impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let rules = self.bar.size_rules(size_handle, axis); - self.ideal.set_component(axis, rules.ideal_size()); - rules - } - - fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - self.core_data_mut().rect = rect; - let rect = align - .complete(Align::Default, Align::Default) - .aligned_rect(self.ideal, rect); - let align = AlignHints::new(Some(Align::Default), Some(Align::Default)); - self.bar.set_rect(mgr, rect, align); - } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - self.bar.draw(draw, mgr, disabled); - } - } - impl, D: Directional, M: 'static> event::Handler for MenuBar { type Msg = M; @@ -227,17 +206,23 @@ widget! { fn set_menu_path(&mut self, mgr: &mut Manager, target: Option, set_focus: bool) { self.delayed_open = None; if let Some(id) = target { - // We should close other sub-menus before opening let mut child = None; + let mut current = None; for i in 0..self.bar.len() { if self.bar[i].is_ancestor_of(id) { child = Some(i); - } else { - self.bar[i].set_menu_path(mgr, None, set_focus); + } + if self.bar[i].menu_is_open() { + current = Some(i); } } if let Some(i) = child { - self.bar[i].set_menu_path(mgr, target, set_focus); + if child != current { + if let Some(j) = current { + self.bar[j].set_menu_path(mgr, None, set_focus); + } + self.bar[i].set_menu_path(mgr, target, set_focus); + } } } else { for i in 0..self.bar.len() { From 37d67c64c347d421a87f788fd9d896b2de1d5cef Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 5 Dec 2021 11:32:25 +0000 Subject: [PATCH 37/53] New methods and doc on WithLabel and AccelLabel --- crates/kas-widgets/src/adapter/label.rs | 28 +++++++++++++++++++++++++ crates/kas-widgets/src/checkbox.rs | 2 +- crates/kas-widgets/src/label.rs | 12 +++++++++-- crates/kas-widgets/src/radiobox.rs | 2 +- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index f827c3097..7f555db04 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -12,6 +12,9 @@ use kas::{event, layout, prelude::*}; widget! { /// A wrapper widget with a label + /// + /// The label supports accelerator keys, which activate `self.inner` on + /// usage. #[autoimpl(Deref, DerefMut on inner)] #[derive(Clone, Default, Debug)] #[handler(msg = W::Msg)] @@ -46,6 +49,18 @@ widget! { } } + /// Get the direction + #[inline] + pub fn direction(&self) -> Direction { + self.dir.as_direction() + } + + /// Deconstruct into `(inner, label)` + #[inline] + pub fn deconstruct(self) -> (W, Text) { + (self.inner, self.label) + } + /// Set text in an existing `Label` /// /// Note: this must not be called before fonts have been initialised @@ -60,6 +75,12 @@ widget! { } } + impl WidgetConfig for Self { + fn configure(&mut self, mgr: &mut Manager) { + mgr.add_accel_keys(self.inner.id(), self.keys()); + } + } + impl Layout for Self { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let (data, _) = self.core.layout.storage::>(); @@ -95,6 +116,13 @@ widget! { }); } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + Some(self.inner.id()) + } + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); self.inner.draw(draw, mgr, disabled); diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 33156c3bf..d5a079e0b 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -130,7 +130,7 @@ widget! { } widget! { - /// A checkable box with optional label + /// A checkbox with label #[autoimpl(Debug)] #[autoimpl(HasBool on checkbox)] #[derive(Clone, Default)] diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index 69bfcf557..76d1b3a9e 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -157,8 +157,16 @@ pub type StringLabel = Label; /// A label supporting an accelerator key /// -/// Accelerator keys are not useful on plain labels, but this widget may be -/// embedded within a parent (e.g. `CheckBox` uses this). +/// Accelerator keys are not useful on plain labels. To be useful, a parent +/// widget must do something like: +/// ```no_test +/// impl WidgetConfig for Self { +/// fn configure(&mut self, mgr: &mut Manager) { +/// let target = self.id(); // widget receiving Event::Activate +/// mgr.add_accel_keys(target, self.label.keys()); +/// } +//// } +/// ``` pub type AccelLabel = Label; impl AccelLabel { diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index 95d50fba4..0f37e67bc 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -169,7 +169,7 @@ widget! { } widget! { - /// A radiobox with optional label + /// A radiobox with label #[autoimpl(Debug)] #[autoimpl(HasBool on radiobox)] #[derive(Clone)] From 27f50f8c99b338321f001d4be631cc772ebeaac3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 6 Dec 2021 19:37:21 +0000 Subject: [PATCH 38/53] Layout visitor's draw method: do not pass rect --- crates/kas-core/src/core/widget.rs | 3 +- crates/kas-core/src/layout/visitor.rs | 44 ++++++++++++--------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index e26a89272..aec161ead 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -509,8 +509,7 @@ pub trait Layout: WidgetChildren { /// The default impl draws all children. TODO: have default? fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let disabled = disabled || self.is_disabled(); - let rect = self.rect(); - self.layout().draw(rect, draw, mgr, disabled); + self.layout().draw(draw, mgr, disabled); } } diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index aba6f74dc..f11a7a5fa 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -65,7 +65,7 @@ trait Visitor { fn is_reversed(&mut self) -> bool; - fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool); + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool); } /// A layout visitor @@ -231,26 +231,17 @@ impl<'a> Layout<'a> { } /// Draw a widget's children - /// - /// Special: the widget's own `rect` must be passed in. - /// TODO: pass in CoreData instead and use to construct storage dynamically? #[inline] - pub fn draw( - mut self, - rect: Rect, - draw: &mut dyn DrawHandle, - mgr: &ManagerState, - disabled: bool, - ) { - self.draw_(rect, draw, mgr, disabled); - } - fn draw_(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + pub fn draw(mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + self.draw_(draw, mgr, disabled); + } + fn draw_(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { match &mut self.layout { LayoutType::None => (), LayoutType::Single(child) => child.draw(draw, mgr, disabled), LayoutType::AlignSingle(child, _) => child.draw(draw, mgr, disabled), - LayoutType::AlignLayout(layout, _) => layout.draw_(rect, draw, mgr, disabled), - LayoutType::Visitor(layout) => layout.draw(rect, draw, mgr, disabled), + LayoutType::AlignLayout(layout, _) => layout.draw_(draw, mgr, disabled), + LayoutType::Visitor(layout) => layout.draw(draw, mgr, disabled), } } } @@ -288,9 +279,9 @@ where self.direction.is_reversed() } - fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { for child in &mut self.children { - child.draw(rect, draw, mgr, disabled); + child.draw(draw, mgr, disabled); } } } @@ -325,7 +316,7 @@ impl<'a, W: WidgetConfig, D: Directional> Visitor for Slice<'a, W, D> { self.direction.is_reversed() } - fn draw(&mut self, _: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let solver = RowPositionSolver::new(self.direction); solver.for_children(self.children, draw.get_clip_rect(), |w| { w.draw(draw, mgr, disabled) @@ -364,9 +355,9 @@ where false } - fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { for (_, child) in &mut self.children { - child.draw(rect, draw, mgr, disabled); + child.draw(draw, mgr, disabled); } } } @@ -374,6 +365,10 @@ where /// Layout storage for frame layout #[derive(Default, Debug)] pub struct FrameStorage { + // NOTE: potentially rect is redundant (e.g. with widget's rect) but if we + // want an alternative as a generic solution then all draw methods must + // calculate and pass the child's rect, which is probably worse. + rect: Rect, offset: Offset, size: Size, } @@ -400,6 +395,7 @@ impl<'a> Visitor for Frame<'a> { } fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { + self.data.rect = rect; rect.pos += self.data.offset; rect.size -= self.data.size; self.child.set_rect_(mgr, rect, align); @@ -409,8 +405,8 @@ impl<'a> Visitor for Frame<'a> { self.child.is_reversed_() } - fn draw(&mut self, rect: Rect, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - draw.outer_frame(rect); - self.child.draw_(rect, draw, mgr, disabled); + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + draw.outer_frame(self.data.rect); + self.child.draw_(draw, mgr, disabled); } } From e3b0e52bbe5127bb932dcdf9ac7dc435ec1c5479 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 7 Dec 2021 10:43:51 +0000 Subject: [PATCH 39/53] New layout text component; multiple uses --- crates/kas-core/src/core/widget.rs | 4 +- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 83 +++++++++++++++++------ crates/kas-widgets/src/adapter/label.rs | 53 +++------------ crates/kas-widgets/src/editbox.rs | 28 ++------ crates/kas-widgets/src/menu/menu_entry.rs | 32 +++------ crates/kas-widgets/src/menu/submenu.rs | 34 +++------- crates/kas-widgets/src/window.rs | 11 +-- 8 files changed, 105 insertions(+), 142 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index aec161ead..d5a870bc2 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -508,8 +508,8 @@ pub trait Layout: WidgetChildren { /// /// The default impl draws all children. TODO: have default? fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - let disabled = disabled || self.is_disabled(); - self.layout().draw(draw, mgr, disabled); + let state = self.input_state(mgr, disabled); + self.layout().draw(draw, mgr, state); } } diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 800bcfd82..a868be667 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -60,7 +60,7 @@ pub use storage::{ DynGridStorage, DynRowStorage, FixedGridStorage, FixedRowStorage, GridStorage, RowStorage, RowTemp, Storage, }; -pub use visitor::{FrameStorage, Layout, StorageChain}; +pub use visitor::{FrameStorage, Layout, StorageChain, TextStorage}; /// Information on which axis is being resized /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index f11a7a5fa..a341e8d9e 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -8,9 +8,10 @@ use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules, Storage}; use super::{DynRowStorage, RowPositionSolver, RowSetter, RowSolver, RowStorage}; use super::{GridChildInfo, GridDimensions, GridSetter, GridSolver, GridStorage}; -use crate::draw::{DrawHandle, SizeHandle}; +use crate::draw::{DrawHandle, InputState, SizeHandle, TextClass}; use crate::event::{Manager, ManagerState}; -use crate::geom::{Offset, Rect, Size}; +use crate::geom::{Coord, Offset, Rect, Size}; +use crate::text::{Align, TextApi, TextApiExt}; use crate::{dir::Directional, WidgetConfig}; use std::any::Any; use std::iter::ExactSizeIterator; @@ -65,7 +66,7 @@ trait Visitor { fn is_reversed(&mut self) -> bool; - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool); + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState); } /// A layout visitor @@ -129,6 +130,12 @@ impl<'a> Layout<'a> { Layout { layout } } + /// Place a text element in the layout + pub fn text(data: &'a mut TextStorage, text: &'a mut dyn TextApi, class: TextClass) -> Self { + let layout = LayoutType::Visitor(Box::new(Text { data, text, class })); + Layout { layout } + } + /// Construct a row/column layout over an iterator of layouts pub fn list(list: I, direction: D, data: &'a mut S) -> Self where @@ -232,16 +239,17 @@ impl<'a> Layout<'a> { /// Draw a widget's children #[inline] - pub fn draw(mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - self.draw_(draw, mgr, disabled); + pub fn draw(mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { + self.draw_(draw, mgr, state); } - fn draw_(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw_(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { + let disabled = state.contains(InputState::DISABLED); match &mut self.layout { LayoutType::None => (), LayoutType::Single(child) => child.draw(draw, mgr, disabled), LayoutType::AlignSingle(child, _) => child.draw(draw, mgr, disabled), - LayoutType::AlignLayout(layout, _) => layout.draw_(draw, mgr, disabled), - LayoutType::Visitor(layout) => layout.draw(draw, mgr, disabled), + LayoutType::AlignLayout(layout, _) => layout.draw_(draw, mgr, state), + LayoutType::Visitor(layout) => layout.draw(draw, mgr, state), } } } @@ -279,9 +287,9 @@ where self.direction.is_reversed() } - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { for child in &mut self.children { - child.draw(draw, mgr, disabled); + child.draw(draw, mgr, state); } } } @@ -316,10 +324,10 @@ impl<'a, W: WidgetConfig, D: Directional> Visitor for Slice<'a, W, D> { self.direction.is_reversed() } - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { let solver = RowPositionSolver::new(self.direction); solver.for_children(self.children, draw.get_clip_rect(), |w| { - w.draw(draw, mgr, disabled) + w.draw(draw, mgr, state.contains(InputState::DISABLED)) }); } } @@ -355,22 +363,24 @@ where false } - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { for (_, child) in &mut self.children { - child.draw(draw, mgr, disabled); + child.draw(draw, mgr, state); } } } /// Layout storage for frame layout -#[derive(Default, Debug)] +#[derive(Clone, Default, Debug)] pub struct FrameStorage { + /// Size used by frame (sum of widths of borders) + pub size: Size, + /// Offset of frame contents from parent position + pub offset: Offset, // NOTE: potentially rect is redundant (e.g. with widget's rect) but if we // want an alternative as a generic solution then all draw methods must // calculate and pass the child's rect, which is probably worse. rect: Rect, - offset: Offset, - size: Size, } impl Storage for FrameStorage { fn as_any_mut(&mut self) -> &mut dyn Any { @@ -405,8 +415,43 @@ impl<'a> Visitor for Frame<'a> { self.child.is_reversed_() } - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { draw.outer_frame(self.data.rect); - self.child.draw_(draw, mgr, disabled); + self.child.draw_(draw, mgr, state); + } +} + +/// Layout storage for text element +#[derive(Clone, Default, Debug)] +pub struct TextStorage { + /// Position of text + pub pos: Coord, +} + +struct Text<'a> { + data: &'a mut TextStorage, + text: &'a mut dyn TextApi, + class: TextClass, +} + +impl<'a> Visitor for Text<'a> { + fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + size_handle.text_bound(self.text, self.class, axis) + } + + fn set_rect(&mut self, _mgr: &mut Manager, rect: Rect, align: AlignHints) { + self.data.pos = rect.pos; + self.text.update_env(|env| { + env.set_bounds(rect.size.into()); + env.set_align(align.unwrap_or(Align::Default, Align::Centre)); + }); + } + + fn is_reversed(&mut self) -> bool { + false + } + + fn draw(&mut self, draw: &mut dyn DrawHandle, _mgr: &ManagerState, state: InputState) { + draw.text_effects(self.data.pos, self.text, self.class, state); } } diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index 7f555db04..672a8f370 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -6,7 +6,6 @@ //! Wrapper adding a label use kas::draw::TextClass; -use kas::layout::{RulesSetter, RulesSolver}; use kas::text::util::set_text_and_prepare; use kas::{event, layout, prelude::*}; @@ -24,7 +23,8 @@ widget! { dir: D, #[widget] inner: W, - label_pos: Coord, + layout_store: layout::FixedRowStorage<2>, + label_store: layout::TextStorage, label: Text, } @@ -44,7 +44,8 @@ widget! { core: Default::default(), dir: direction, inner, - label_pos: Default::default(), + layout_store: Default::default(), + label_store: Default::default(), label: Text::new_multi(label.into()), } } @@ -82,38 +83,12 @@ widget! { } impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let (data, _) = self.core.layout.storage::>(); - let mut solver = layout::RowSolver::new(axis, (self.dir, 2), data); - let child = &mut self.inner; - solver.for_child(data, 0usize, |axis| { - child.size_rules(size_handle, axis) - }); - let label = &mut self.label; - solver.for_child(data, 1usize, |axis| { - size_handle.text_bound(label, TextClass::Label, axis) - }); - solver.finish(data) - } - - fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - let dim = (self.dir, 2); - let (data, _) = self.core.layout.storage::>(); - let mut setter = layout::RowSetter::<_, [i32; 2], _>::new( - rect, - dim, - align, - data, - ); - let rect = setter.child_rect(data, 0); - self.inner.set_rect(mgr, rect, align); - let rect = setter.child_rect(data, 1); - self.label_pos = rect.pos; - self.label.update_env(|env| { - env.set_bounds(rect.size.into()); - env.set_align(align.unwrap_or(Align::Default, Align::Centre)); - }); + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + let arr = [ + layout::Layout::single(&mut self.inner), + layout::Layout::text(&mut self.label_store, &mut self.label, TextClass::Label), + ]; + layout::Layout::list(arr.into_iter(), self.dir, &mut self.layout_store) } fn find_id(&mut self, coord: Coord) -> Option { @@ -122,14 +97,6 @@ widget! { } Some(self.inner.id()) } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - let disabled = disabled || self.is_disabled(); - self.inner.draw(draw, mgr, disabled); - let accel = mgr.show_accel_labels(); - let state = self.input_state(mgr, disabled); - draw.text_accel(self.label_pos, &self.label, accel, TextClass::Label, state); - } } impl HasStr for Self { diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index 4c60d428b..737b38150 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -10,6 +10,7 @@ use kas::draw::TextClass; use kas::event::components::{TextInput, TextInputAction}; use kas::event::{self, Command, ScrollDelta}; use kas::geom::Vec2; +use kas::layout; use kas::prelude::*; use kas::text::SelectionHelper; use std::fmt::Debug; @@ -177,26 +178,13 @@ widget! { core: CoreData, #[widget] inner: EditField, - offset: Offset, - frame_size: Size, + layout_frame: layout::FrameStorage, } impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let frame_rules = size_handle.edit_surround(axis.is_vertical()); - let child_rules = self.inner.size_rules(size_handle, axis); - - let (rules, offset, size) = frame_rules.surround_as_margin(child_rules); - self.offset.set_component(axis, offset); - self.frame_size.set_component(axis, size); - rules - } - - fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { - self.core.rect = rect; - rect.pos += self.offset; - rect.size -= self.frame_size; - self.inner.set_rect(mgr, rect, align); + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + let inner = layout::Layout::single(&mut self.inner); + layout::Layout::frame(&mut self.layout_frame, inner) } fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { @@ -219,8 +207,7 @@ impl EditBox<()> { EditBox { core: Default::default(), inner: EditField::new(text), - offset: Offset::ZERO, - frame_size: Size::ZERO, + layout_frame: Default::default(), } } @@ -236,8 +223,7 @@ impl EditBox<()> { EditBox { core: self.core, inner: self.inner.with_guard(guard), - offset: self.offset, - frame_size: self.frame_size, + layout_frame: self.layout_frame, } } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 03ae4c3c8..57725b6ea 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -18,8 +18,8 @@ widget! { #[widget_core] core: kas::CoreData, label: Text, - label_off: Offset, - frame_size: Size, + layout_label: layout::TextStorage, + layout_frame: layout::FrameStorage, msg: M, } @@ -34,29 +34,15 @@ widget! { } impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let frame_rules = size_handle.menu_frame(axis.is_vertical()); - let text_rules = size_handle.text_bound(&mut self.label, TextClass::MenuLabel, axis); - let (rules, offset, size) = frame_rules.surround_as_margin(text_rules); - self.label_off.set_component(axis, offset); - self.frame_size.set_component(axis, size); - rules - } - - fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - let size = rect.size - self.frame_size; - self.label.update_env(|env| { - env.set_bounds(size.into()); - env.set_align(align.unwrap_or(Align::Default, Align::Centre)); - }); + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + let inner = layout::Layout::text(&mut self.layout_label, &mut self.label, TextClass::MenuLabel); + layout::Layout::frame(&mut self.layout_frame, inner) } fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { draw.menu_entry(self.core.rect, self.input_state(mgr, disabled)); - let pos = self.core.rect.pos + self.label_off; draw.text_accel( - pos, + self.layout_label.pos, &self.label, mgr.show_accel_labels(), TextClass::MenuLabel, @@ -75,8 +61,8 @@ widget! { MenuEntry { core: Default::default(), label: Text::new_single(label.into()), - label_off: Offset::ZERO, - frame_size: Size::ZERO, + layout_label: Default::default(), + layout_frame: Default::default(), msg, } } @@ -99,7 +85,7 @@ widget! { if self.label.text().keys() != string.keys() { action |= TkAction::RECONFIGURE; } - let avail = self.core.rect.size.clamped_sub(self.frame_size); + let avail = self.core.rect.size.clamped_sub(self.layout_frame.size); action | kas::text::util::set_text_and_prepare(&mut self.label, string, avail) } } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 33a70aee6..f73c70db3 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -10,7 +10,7 @@ use crate::Column; use kas::draw::TextClass; use kas::event::{self, Command, ConfigureManager}; use kas::prelude::*; -use kas::WindowId; +use kas::{layout, WindowId}; widget! { /// A sub-menu @@ -21,8 +21,8 @@ widget! { direction: D, pub(crate) key_nav: bool, label: Text, - label_off: Offset, - frame_size: Size, + label_store: layout::TextStorage, + frame_store: layout::FrameStorage, #[widget] pub list: Column, popup_id: Option, @@ -64,8 +64,8 @@ widget! { direction, key_nav: true, label: Text::new_single(label.into()), - label_off: Offset::ZERO, - frame_size: Size::ZERO, + label_store: Default::default(), + frame_store: Default::default(), list: Column::new(list), popup_id: None, } @@ -135,22 +135,9 @@ widget! { } impl kas::Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let frame_rules = size_handle.menu_frame(axis.is_vertical()); - let text_rules = size_handle.text_bound(&mut self.label, TextClass::MenuLabel, axis); - let (rules, offset, size) = frame_rules.surround_as_margin(text_rules); - self.label_off.set_component(axis, offset); - self.frame_size.set_component(axis, size); - rules - } - - fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - let size = rect.size - self.frame_size; - self.label.update_env(|env| { - env.set_bounds(size.into()); - env.set_align(align.unwrap_or(Align::Default, Align::Centre)); - }); + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + let label = layout::Layout::text(&mut self.label_store, &mut self.label, TextClass::MenuLabel); + layout::Layout::frame(&mut self.frame_store, label) } fn spatial_nav(&mut self, _: &mut Manager, _: bool, _: Option) -> Option { @@ -164,9 +151,8 @@ widget! { state.insert(InputState::DEPRESS); } draw.menu_entry(self.core.rect, state); - let pos = self.core.rect.pos + self.label_off; draw.text_accel( - pos, + self.label_store.pos, &self.label, mgr.show_accel_labels(), TextClass::MenuLabel, @@ -284,7 +270,7 @@ widget! { if self.label.text().keys() != string.keys() { action |= TkAction::RECONFIGURE; } - let avail = self.core.rect.size.clamped_sub(self.frame_size); + let avail = self.core.rect.size.clamped_sub(self.frame_store.size); action | kas::text::util::set_text_and_prepare(&mut self.label, string, avail) } } diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index b3284cab2..323ebcdb4 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -31,15 +31,8 @@ widget! { impl Layout for Self { #[inline] - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - // Note: we do not consider popups, since they are usually temporary - self.w.size_rules(size_handle, axis) - } - - #[inline] - fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - self.w.set_rect(mgr, rect, align); + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + layout::Layout::single(&mut self.w) } #[inline] From f2cd45f82b602bd936a06c96810d73b7e909c4da Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 7 Dec 2021 13:54:28 +0000 Subject: [PATCH 40/53] Layout visitor: embed frame layout --- crates/kas-core/src/layout/visitor.rs | 64 +++++++++++---------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index a341e8d9e..cb77da246 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -87,6 +87,8 @@ enum LayoutType<'a> { AlignSingle(&'a mut dyn WidgetConfig, AlignHints), /// Apply alignment hints to some sub-layout AlignLayout(Box>, AlignHints), + /// Frame around content + Frame(Box>, &'a mut FrameStorage), /// An embedded layout Visitor(Box), } @@ -126,7 +128,7 @@ impl<'a> Layout<'a> { /// /// This frame has dimensions according to [`SizeHandle::frame`]. pub fn frame(data: &'a mut FrameStorage, child: Self) -> Self { - let layout = LayoutType::Visitor(Box::new(Frame { data, child })); + let layout = LayoutType::Frame(Box::new(child), data); Layout { layout } } @@ -195,6 +197,14 @@ impl<'a> Layout<'a> { LayoutType::Single(child) => child.size_rules(sh, axis), LayoutType::AlignSingle(child, _) => child.size_rules(sh, axis), LayoutType::AlignLayout(layout, _) => layout.size_rules_(sh, axis), + LayoutType::Frame(child, storage) => { + let frame_rules = sh.frame(axis.is_vertical()); + let child_rules = child.size_rules_(sh, axis); + let (rules, offset, size) = frame_rules.surround_as_margin(child_rules); + storage.offset.set_component(axis, offset); + storage.size.set_component(axis, size); + rules + } LayoutType::Visitor(visitor) => visitor.size_rules(sh, axis), } } @@ -204,7 +214,7 @@ impl<'a> Layout<'a> { pub fn set_rect(mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { self.set_rect_(mgr, rect, align); } - fn set_rect_(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { + fn set_rect_(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { match &mut self.layout { LayoutType::None => (), LayoutType::Single(child) => child.set_rect(mgr, rect, align), @@ -216,6 +226,12 @@ impl<'a> Layout<'a> { let align = hints.combine(align); layout.set_rect_(mgr, rect, align); } + LayoutType::Frame(child, storage) => { + storage.rect = rect; + rect.pos += storage.offset; + rect.size -= storage.size; + child.set_rect_(mgr, rect, align); + } LayoutType::Visitor(layout) => layout.set_rect(mgr, rect, align), } } @@ -230,9 +246,10 @@ impl<'a> Layout<'a> { fn is_reversed_(&mut self) -> bool { match &mut self.layout { LayoutType::None => false, - LayoutType::Single(_) => false, - LayoutType::AlignSingle(_, _) => false, - LayoutType::AlignLayout(layout, _) => layout.is_reversed_(), + LayoutType::Single(_) | LayoutType::AlignSingle(_, _) => false, + LayoutType::AlignLayout(layout, _) | LayoutType::Frame(layout, _) => { + layout.is_reversed_() + } LayoutType::Visitor(layout) => layout.is_reversed(), } } @@ -249,6 +266,10 @@ impl<'a> Layout<'a> { LayoutType::Single(child) => child.draw(draw, mgr, disabled), LayoutType::AlignSingle(child, _) => child.draw(draw, mgr, disabled), LayoutType::AlignLayout(layout, _) => layout.draw_(draw, mgr, state), + LayoutType::Frame(child, storage) => { + draw.outer_frame(storage.rect); + child.draw_(draw, mgr, state); + } LayoutType::Visitor(layout) => layout.draw(draw, mgr, state), } } @@ -388,39 +409,6 @@ impl Storage for FrameStorage { } } -/// A frame around other content -struct Frame<'a> { - data: &'a mut FrameStorage, - child: Layout<'a>, -} - -impl<'a> Visitor for Frame<'a> { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let frame_rules = size_handle.frame(axis.is_vertical()); - let child_rules = self.child.size_rules_(size_handle, axis); - let (rules, offset, size) = frame_rules.surround_as_margin(child_rules); - self.data.offset.set_component(axis, offset); - self.data.size.set_component(axis, size); - rules - } - - fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { - self.data.rect = rect; - rect.pos += self.data.offset; - rect.size -= self.data.size; - self.child.set_rect_(mgr, rect, align); - } - - fn is_reversed(&mut self) -> bool { - self.child.is_reversed_() - } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { - draw.outer_frame(self.data.rect); - self.child.draw_(draw, mgr, state); - } -} - /// Layout storage for text element #[derive(Clone, Default, Debug)] pub struct TextStorage { From b8cbb69723c4f368581561f4e796064711ed75fc Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 7 Dec 2021 14:00:36 +0000 Subject: [PATCH 41/53] Layout visitor: support nav_frame --- crates/kas-core/src/layout/visitor.rs | 30 +++++++++++++++++++++++---- crates/kas-macros/src/make_layout.rs | 16 ++++++++++++++ crates/kas-widgets/src/nav_frame.rs | 29 +------------------------- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index cb77da246..f986d356c 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -89,6 +89,8 @@ enum LayoutType<'a> { AlignLayout(Box>, AlignHints), /// Frame around content Frame(Box>, &'a mut FrameStorage), + /// Navigation frame around content + NavFrame(Box>, &'a mut FrameStorage), /// An embedded layout Visitor(Box), } @@ -132,6 +134,14 @@ impl<'a> Layout<'a> { Layout { layout } } + /// Construct a navigation frame around a sub-layout + /// + /// This frame has dimensions according to [`SizeHandle::frame`]. + pub fn nav_frame(data: &'a mut FrameStorage, child: Self) -> Self { + let layout = LayoutType::NavFrame(Box::new(child), data); + Layout { layout } + } + /// Place a text element in the layout pub fn text(data: &'a mut TextStorage, text: &'a mut dyn TextApi, class: TextClass) -> Self { let layout = LayoutType::Visitor(Box::new(Text { data, text, class })); @@ -205,6 +215,14 @@ impl<'a> Layout<'a> { storage.size.set_component(axis, size); rules } + LayoutType::NavFrame(child, storage) => { + let frame_rules = sh.nav_frame(axis.is_vertical()); + let child_rules = child.size_rules_(sh, axis); + let (rules, offset, size) = frame_rules.surround_as_margin(child_rules); + storage.offset.set_component(axis, offset); + storage.size.set_component(axis, size); + rules + } LayoutType::Visitor(visitor) => visitor.size_rules(sh, axis), } } @@ -226,7 +244,7 @@ impl<'a> Layout<'a> { let align = hints.combine(align); layout.set_rect_(mgr, rect, align); } - LayoutType::Frame(child, storage) => { + LayoutType::Frame(child, storage) | LayoutType::NavFrame(child, storage) => { storage.rect = rect; rect.pos += storage.offset; rect.size -= storage.size; @@ -247,9 +265,9 @@ impl<'a> Layout<'a> { match &mut self.layout { LayoutType::None => false, LayoutType::Single(_) | LayoutType::AlignSingle(_, _) => false, - LayoutType::AlignLayout(layout, _) | LayoutType::Frame(layout, _) => { - layout.is_reversed_() - } + LayoutType::AlignLayout(layout, _) + | LayoutType::Frame(layout, _) + | LayoutType::NavFrame(layout, _) => layout.is_reversed_(), LayoutType::Visitor(layout) => layout.is_reversed(), } } @@ -270,6 +288,10 @@ impl<'a> Layout<'a> { draw.outer_frame(storage.rect); child.draw_(draw, mgr, state); } + LayoutType::NavFrame(child, storage) => { + draw.nav_frame(storage.rect, state); + child.draw_(draw, mgr, state); + } LayoutType::Visitor(layout) => layout.draw(draw, mgr, state), } } diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index c4e8de7bb..3fbfbbddc 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -24,6 +24,7 @@ mod kw { custom_keyword!(center); custom_keyword!(stretch); custom_keyword!(frame); + custom_keyword!(nav_frame); custom_keyword!(list); custom_keyword!(slice); custom_keyword!(grid); @@ -51,6 +52,7 @@ enum Layout { Single(Span), Widget(Expr), Frame(Box), + NavFrame(Box), List(Direction, List), Slice(Direction, Expr), Grid(GridDimensions, Vec<(CellInfo, Layout)>), @@ -179,6 +181,12 @@ impl Parse for Layout { let _ = parenthesized!(inner in input); let layout: Layout = inner.parse()?; Ok(Layout::Frame(Box::new(layout))) + } else if lookahead.peek(kw::nav_frame) { + let _: kw::nav_frame = input.parse()?; + let inner; + let _ = parenthesized!(inner in input); + let layout: Layout = inner.parse()?; + Ok(Layout::NavFrame(Box::new(layout))) } else if lookahead.peek(kw::column) { let _: kw::column = input.parse()?; let dir = Direction::Down; @@ -387,6 +395,14 @@ impl Layout { ::kas::layout::Layout::frame(data, #inner) } } + Layout::NavFrame(layout) => { + let inner = layout.generate(children)?; + quote! { + let (data, next) = _chain.storage::<::kas::layout::FrameStorage>(); + _chain = next; + ::kas::layout::Layout::nav_frame(data, #inner) + } + } Layout::List(dir, list) => { let len; let mut items = Toks::new(); diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs index e0285a158..9cf6d5c05 100644 --- a/crates/kas-widgets/src/nav_frame.rs +++ b/crates/kas-widgets/src/nav_frame.rs @@ -17,14 +17,13 @@ widget! { #[derive(Clone, Debug, Default)] #[widget{ key_nav = true; + layout = nav_frame(self.inner); }] pub struct NavFrame { #[widget_core] core: CoreData, #[widget] pub inner: W, - offset: Offset, - size: Size, } impl Self { @@ -34,36 +33,10 @@ widget! { NavFrame { core: Default::default(), inner, - offset: Offset::ZERO, - size: Size::ZERO, } } } - impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let frame_rules = size_handle.nav_frame(axis.is_vertical()); - let child_rules = self.inner.size_rules(size_handle, axis); - let (rules, offset, size) = frame_rules.surround_as_margin(child_rules); - self.offset.set_component(axis, offset); - self.size.set_component(axis, size); - rules - } - - fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { - self.core.rect = rect; - rect.pos += self.offset; - rect.size -= self.size; - self.inner.set_rect(mgr, rect, align); - } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - let input_state = self.input_state(mgr, disabled); - draw.nav_frame(self.rect(), input_state); - self.inner.draw(draw, mgr, input_state.disabled()); - } - } - impl event::Handler for Self { type Msg = ::Msg; From 668b8735ff7a886083240d43908f51a65e33d633 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 7 Dec 2021 17:08:11 +0000 Subject: [PATCH 42/53] Update kas-text: use US spelling --- crates/kas-core/Cargo.toml | 2 +- crates/kas-core/src/layout/align.rs | 8 ++++---- crates/kas-core/src/layout/grid_solver.rs | 4 ++-- crates/kas-core/src/layout/row_solver.rs | 2 +- crates/kas-core/src/layout/size_types.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 2 +- crates/kas-resvg/src/svg.rs | 2 +- crates/kas-wgpu/Cargo.toml | 2 +- crates/kas-widgets/src/button.rs | 6 +++--- crates/kas-widgets/src/checkbox.rs | 2 +- crates/kas-widgets/src/combobox.rs | 2 +- crates/kas-widgets/src/label.rs | 2 +- crates/kas-widgets/src/progress.rs | 2 +- crates/kas-widgets/src/radiobox.rs | 2 +- crates/kas-widgets/src/scrollbar.rs | 2 +- examples/clock.rs | 2 +- 16 files changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index ef059596b..2a6e4c1fd 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -79,7 +79,7 @@ path = "../kas-macros" [dependencies.kas-text] # version = "0.4.0" git = "https://github.com/kas-gui/kas-text.git" -rev = "60db64b" +rev = "818515e" [dependencies.winit] # Provides translations for several winit types diff --git a/crates/kas-core/src/layout/align.rs b/crates/kas-core/src/layout/align.rs index af52ac71d..350a8280c 100644 --- a/crates/kas-core/src/layout/align.rs +++ b/crates/kas-core/src/layout/align.rs @@ -25,7 +25,7 @@ pub use crate::text::Align; /// # let rect = Rect::new(Coord::ZERO, Size::ZERO); /// let pref_size = Size(30, 20); // usually size comes from SizeHandle /// let rect = align -/// .complete(Align::Stretch, Align::Centre) +/// .complete(Align::Stretch, Align::Center) /// .aligned_rect(pref_size, rect); /// // self.core.rect = rect; /// ``` @@ -40,7 +40,7 @@ impl AlignHints { pub const NONE: AlignHints = AlignHints::new(None, None); /// Center on both axes - pub const CENTER: AlignHints = AlignHints::new(Some(Align::Centre), Some(Align::Centre)); + pub const CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::Center)); /// Stretch on both axes pub const STRETCH: AlignHints = AlignHints::new(Some(Align::Stretch), Some(Align::Stretch)); @@ -88,7 +88,7 @@ impl CompleteAlignment { let mut size = rect.size; if ideal.0 < size.0 && self.halign != Align::Stretch { pos.0 += match self.halign { - Align::Centre => (size.0 - ideal.0) / 2, + Align::Center => (size.0 - ideal.0) / 2, Align::BR => size.0 - ideal.0, Align::Default | Align::TL | Align::Stretch => 0, }; @@ -96,7 +96,7 @@ impl CompleteAlignment { } if ideal.1 < size.1 && self.valign != Align::Stretch { pos.1 += match self.valign { - Align::Centre => (size.1 - ideal.1) / 2, + Align::Center => (size.1 - ideal.1) / 2, Align::BR => size.1 - ideal.1, Align::Default | Align::TL | Align::Stretch => 0, }; diff --git a/crates/kas-core/src/layout/grid_solver.rs b/crates/kas-core/src/layout/grid_solver.rs index 3c7df2842..49bf827fb 100644 --- a/crates/kas-core/src/layout/grid_solver.rs +++ b/crates/kas-core/src/layout/grid_solver.rs @@ -296,7 +296,7 @@ impl GridSetter { target = max_size; w_offsets.as_mut()[0] = match align { Align::Default | Align::TL | Align::Stretch => 0, - Align::Centre => extra / 2, + Align::Center => extra / 2, Align::BR => extra, }; } @@ -322,7 +322,7 @@ impl GridSetter { target = max_size; h_offsets.as_mut()[0] = match align { Align::Default | Align::TL | Align::Stretch => 0, - Align::Centre => extra / 2, + Align::Center => extra / 2, Align::BR => extra, }; } diff --git a/crates/kas-core/src/layout/row_solver.rs b/crates/kas-core/src/layout/row_solver.rs index 9b540c015..1c28c6192 100644 --- a/crates/kas-core/src/layout/row_solver.rs +++ b/crates/kas-core/src/layout/row_solver.rs @@ -145,7 +145,7 @@ impl RowSetter { width = max_size; let offset = match align { Align::Default | Align::TL | Align::Stretch => 0, - Align::Centre => extra / 2, + Align::Center => extra / 2, Align::BR => extra, }; if is_horiz { diff --git a/crates/kas-core/src/layout/size_types.rs b/crates/kas-core/src/layout/size_types.rs index cd92c10da..90facac10 100644 --- a/crates/kas-core/src/layout/size_types.rs +++ b/crates/kas-core/src/layout/size_types.rs @@ -239,7 +239,7 @@ impl SpriteDisplay { AspectScaling::Free => rect.size, }; align - .complete(Align::Centre, Align::Centre) + .complete(Align::Center, Align::Center) .aligned_rect(ideal, rect) } } diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index f986d356c..ee41eee76 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -453,7 +453,7 @@ impl<'a> Visitor for Text<'a> { self.data.pos = rect.pos; self.text.update_env(|env| { env.set_bounds(rect.size.into()); - env.set_align(align.unwrap_or(Align::Default, Align::Centre)); + env.set_align(align.unwrap_or(Align::Default, Align::Center)); }); } diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 8aebd444d..d1bbbbd0a 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -147,7 +147,7 @@ widget! { let size = match self.ideal_size.aspect_scale_to(rect.size) { Some(size) => { self.core_data_mut().rect = align - .complete(Align::Centre, Align::Centre) + .complete(Align::Center, Align::Center) .aligned_rect(size, rect); Into::<(u32, u32)>::into(size) } diff --git a/crates/kas-wgpu/Cargo.toml b/crates/kas-wgpu/Cargo.toml index 517b745cc..922fe662e 100644 --- a/crates/kas-wgpu/Cargo.toml +++ b/crates/kas-wgpu/Cargo.toml @@ -60,7 +60,7 @@ default-features = false [dependencies.kas-text] # version = "0.4.0" git = "https://github.com/kas-gui/kas-text.git" -rev = "60db64b" +rev = "818515e" [build-dependencies] glob = "0.3" diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index 4811af408..89f20fcb0 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -58,7 +58,7 @@ widget! { fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { let mut rect = align - .complete(Align::Centre, Align::Centre) + .complete(Align::Center, Align::Center) .aligned_rect(self.ideal_size, rect); self.core.rect = rect; rect.pos += self.frame_offset; @@ -247,13 +247,13 @@ widget! { fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) { let rect = align - .complete(Align::Stretch, Align::Centre) + .complete(Align::Stretch, Align::Center) .aligned_rect(self.ideal_size, rect); self.core.rect = rect; let size = rect.size - self.frame_size; self.label.update_env(|env| { env.set_bounds(size.into()); - env.set_align((Align::Centre, Align::Centre)); + env.set_align((Align::Center, Align::Center)); }); } diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index d5a079e0b..055ad0406 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -34,7 +34,7 @@ widget! { fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) { let rect = align - .complete(Align::Centre, Align::Centre) + .complete(Align::Center, Align::Center) .aligned_rect(self.rect().size, rect); self.core.rect = rect; } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 106f75392..ac948f75e 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -49,7 +49,7 @@ widget! { self.core.rect = rect; self.label.update_env(|env| { env.set_bounds(rect.size.into()); - env.set_align(align.unwrap_or(Align::Centre, Align::Centre)); + env.set_align(align.unwrap_or(Align::Center, Align::Center)); }); } diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index 76d1b3a9e..e0dbf9df6 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -31,7 +31,7 @@ widget! { self.core.rect = rect; self.label.update_env(|env| { env.set_bounds(rect.size.into()); - env.set_align(align.unwrap_or(Align::Default, Align::Centre)); + env.set_align(align.unwrap_or(Align::Default, Align::Center)); }); } diff --git a/crates/kas-widgets/src/progress.rs b/crates/kas-widgets/src/progress.rs index ce4c90f0a..7bdc5152f 100644 --- a/crates/kas-widgets/src/progress.rs +++ b/crates/kas-widgets/src/progress.rs @@ -93,7 +93,7 @@ widget! { let mut ideal_size = Size::splat(self.width); ideal_size.set_component(self.direction, i32::MAX); let rect = align - .complete(Align::Centre, Align::Centre) + .complete(Align::Center, Align::Center) .aligned_rect(ideal_size, rect); self.core.rect = rect; } diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index 0f37e67bc..bd771c990 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -83,7 +83,7 @@ widget! { fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) { let rect = align - .complete(Align::Centre, Align::Centre) + .complete(Align::Center, Align::Center) .aligned_rect(self.rect().size, rect); self.core.rect = rect; } diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index a9a745140..3d5b13022 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -219,7 +219,7 @@ widget! { let mut ideal_size = Size::splat(self.width); ideal_size.set_component(self.direction, i32::MAX); let rect = align - .complete(Align::Centre, Align::Centre) + .complete(Align::Center, Align::Center) .aligned_rect(ideal_size, rect); self.core.rect = rect; self.handle.set_rect(mgr, rect, align); diff --git a/examples/clock.rs b/examples/clock.rs index 2954d2a83..3599b6815 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -147,7 +147,7 @@ widget! { impl Clock { fn new() -> Self { let env = kas::text::Environment { - align: (Align::Centre, Align::Centre), + align: (Align::Center, Align::Center), dpp: 1.0, ..Default::default() }; From 123e5131c09cbeebd07ec7479205f245d80baff6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 7 Dec 2021 17:09:25 +0000 Subject: [PATCH 43/53] EditField: let text object handle alignment --- crates/kas-widgets/src/editbox.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index 737b38150..64600fce3 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -358,7 +358,6 @@ widget! { view_offset: Offset, editable: bool, multi_line: bool, - ideal_height: i32, text: Text, required: Vec2, selection: SelectionHelper, @@ -379,24 +378,15 @@ widget! { } else { TextClass::Edit }; - let rules = size_handle.text_bound(&mut self.text, class, axis); - if axis.is_vertical() { - self.ideal_height = rules.ideal_size(); - } - rules + size_handle.text_bound(&mut self.text, class, axis) } - fn set_rect(&mut self, _: &mut Manager, mut rect: Rect, align: AlignHints) { - if !self.multi_line { - let excess = (rect.size.1 - self.ideal_height).max(0); - let offset = match align.vert { - Some(Align::TL) => 0, - Some(Align::BR) => excess, - _ => excess / 2, - }; - rect.pos.1 += offset; - rect.size.1 -= excess; - } + fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) { + let valign = if self.multi_line { + Align::Default + } else { + Align::Center + }; self.core.rect = rect; let size = rect.size; @@ -404,7 +394,7 @@ widget! { self.required = self .text .update_env(|env| { - env.set_align(align.unwrap_or(Align::Default, Align::Default)); + env.set_align(align.unwrap_or(Align::Default, valign)); env.set_bounds(size.into()); env.set_wrap(multi_line); }) @@ -616,7 +606,6 @@ impl EditField<()> { view_offset: Default::default(), editable: true, multi_line: false, - ideal_height: 0, text: Text::new(Default::default(), text), required: Vec2::ZERO, selection: SelectionHelper::new(len, len), @@ -644,7 +633,6 @@ impl EditField<()> { view_offset: self.view_offset, editable: self.editable, multi_line: self.multi_line, - ideal_height: self.ideal_height, text: self.text, required: self.required, selection: self.selection, From f92c1cf9a9f8fbb9622f68f9e14befcb28e3702d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 8 Dec 2021 14:25:40 +0000 Subject: [PATCH 44/53] Button: remove unneeded alignment code --- crates/kas-widgets/src/button.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index 89f20fcb0..acc653826 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -56,10 +56,7 @@ widget! { rules } - fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - let mut rect = align - .complete(Align::Center, Align::Center) - .aligned_rect(self.ideal_size, rect); + fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { self.core.rect = rect; rect.pos += self.frame_offset; rect.size -= self.frame_size; @@ -246,14 +243,11 @@ widget! { } fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) { - let rect = align - .complete(Align::Stretch, Align::Center) - .aligned_rect(self.ideal_size, rect); self.core.rect = rect; let size = rect.size - self.frame_size; self.label.update_env(|env| { env.set_bounds(size.into()); - env.set_align((Align::Center, Align::Center)); + env.set_align(align.unwrap_or(Align::Center, Align::Center)); }); } From 7cec098039dbe9272cb6cb9c736ca09e3ca06a3e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 8 Dec 2021 14:31:17 +0000 Subject: [PATCH 45/53] Rename Button::label field to widget [breaking] This is more consistent with other widgets --- crates/kas-widgets/src/button.rs | 42 +++++++++++++++----------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index acc653826..e9988b3e1 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -16,9 +16,9 @@ widget! { /// Default alignment is centred. Content (label) alignment is derived from the /// button alignment. #[autoimpl(Debug skip on_push)] - #[autoimpl(class_traits where L: trait on label)] + #[autoimpl(class_traits where W: trait on inner)] #[derive(Clone)] - pub struct Button, M: 'static> { + pub struct Button, M: 'static> { #[widget_core] core: kas::CoreData, keys1: VirtualKeyCodes, @@ -27,7 +27,7 @@ widget! { ideal_size: Size, color: Option, #[widget] - pub label: L, + pub inner: W, on_push: Option Option>>, } @@ -47,7 +47,7 @@ widget! { impl Layout for Self { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let frame_rules = size_handle.button_surround(axis.is_vertical()); - let content_rules = self.label.size_rules(size_handle, axis); + let content_rules = self.inner.size_rules(size_handle, axis); let (rules, offset, size) = frame_rules.surround_as_margin(content_rules); self.frame_size.set_component(axis, size); @@ -60,7 +60,7 @@ widget! { self.core.rect = rect; rect.pos += self.frame_offset; rect.size -= self.frame_size; - self.label.set_rect(mgr, rect, align); + self.inner.set_rect(mgr, rect, align); } #[inline] @@ -68,20 +68,20 @@ widget! { if !self.rect().contains(coord) { return None; } - // We steal click events on self.label + // We steal click events on self.inner Some(self.id()) } fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { draw.button(self.core.rect, self.color, self.input_state(mgr, disabled)); - self.label.draw(draw, mgr, disabled); + self.inner.draw(draw, mgr, disabled); } } - impl> Button { - /// Construct a button with given `label` + impl> Button { + /// Construct a button with given `inner` widget #[inline] - pub fn new(label: L) -> Self { + pub fn new(inner: W) -> Self { Button { core: Default::default(), keys1: Default::default(), @@ -89,7 +89,7 @@ widget! { frame_offset: Default::default(), ideal_size: Default::default(), color: None, - label, + inner, on_push: None, } } @@ -100,7 +100,7 @@ widget! { /// closure `f` is called. The result of `f` is converted to /// [`Response::Msg`] or [`Response::None`] and returned to the parent. #[inline] - pub fn on_push(self, f: F) -> Button + pub fn on_push(self, f: F) -> Button where F: Fn(&mut Manager) -> Option + 'static, { @@ -111,42 +111,40 @@ widget! { frame_offset: self.frame_offset, ideal_size: self.ideal_size, color: self.color, - label: self.label, + inner: self.inner, on_push: Some(Rc::new(f)), } } } impl Self { - /// Construct a button with a given `label` and event handler `f` + /// Construct a button with a given `inner` widget and event handler `f` /// /// On activation (through user input events or [`Event::Activate`]) the /// closure `f` is called. The result of `f` is converted to /// [`Response::Msg`] or [`Response::None`] and returned to the parent. #[inline] - pub fn new_on(label: L, f: F) -> Self + pub fn new_on(inner: W, f: F) -> Self where F: Fn(&mut Manager) -> Option + 'static, { - Button::new(label).on_push(f) + Button::new(inner).on_push(f) } - /// Construct a button with a given `label` and payload `msg` + /// Construct a button with a given `inner` and payload `msg` /// /// On activation (through user input events or [`Event::Activate`]) a clone /// of `msg` is returned to the parent widget. Click actions must be /// implemented through a handler on the parent widget (or other ancestor). #[inline] - pub fn new_msg(label: L, msg: M) -> Self + pub fn new_msg(inner: W, msg: M) -> Self where M: Clone, { - Self::new_on(label, move |_| Some(msg.clone())) + Self::new_on(inner, move |_| Some(msg.clone())) } /// Add accelerator keys (chain style) - /// - /// These keys are added to those inferred from the label via `&` marks. pub fn with_keys(mut self, keys: &[VirtualKeyCode]) -> Self { self.keys1.clear(); self.keys1.extend_from_slice(keys); @@ -184,7 +182,7 @@ widget! { impl SendEvent for Self { fn send(&mut self, mgr: &mut Manager, id: WidgetId, event: Event) -> Response { if id < self.id() { - self.label.send(mgr, id, event).void_into() + self.inner.send(mgr, id, event).void_into() } else { debug_assert_eq!(id, self.id()); Manager::handle_generic(self, mgr, event) From 751725f1f6e73fa883a9c7c17f537d948b4f6e9a Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 8 Dec 2021 14:43:34 +0000 Subject: [PATCH 46/53] Add button layout visitor --- crates/kas-core/src/layout/visitor.rs | 35 +++++++++-- crates/kas-widgets/src/button.rs | 83 ++++++--------------------- crates/kas-widgets/src/combobox.rs | 38 ++++-------- 3 files changed, 60 insertions(+), 96 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index ee41eee76..c2826a8bf 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -8,7 +8,7 @@ use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules, Storage}; use super::{DynRowStorage, RowPositionSolver, RowSetter, RowSolver, RowStorage}; use super::{GridChildInfo, GridDimensions, GridSetter, GridSolver, GridStorage}; -use crate::draw::{DrawHandle, InputState, SizeHandle, TextClass}; +use crate::draw::{color::Rgb, DrawHandle, InputState, SizeHandle, TextClass}; use crate::event::{Manager, ManagerState}; use crate::geom::{Coord, Offset, Rect, Size}; use crate::text::{Align, TextApi, TextApiExt}; @@ -91,6 +91,8 @@ enum LayoutType<'a> { Frame(Box>, &'a mut FrameStorage), /// Navigation frame around content NavFrame(Box>, &'a mut FrameStorage), + /// Button frame around content + Button(Box>, &'a mut FrameStorage, Option), /// An embedded layout Visitor(Box), } @@ -142,6 +144,12 @@ impl<'a> Layout<'a> { Layout { layout } } + /// Construct a button frame around a sub-layout + pub fn button(data: &'a mut FrameStorage, child: Self, color: Option) -> Self { + let layout = LayoutType::Button(Box::new(child), data, color); + Layout { layout } + } + /// Place a text element in the layout pub fn text(data: &'a mut TextStorage, text: &'a mut dyn TextApi, class: TextClass) -> Self { let layout = LayoutType::Visitor(Box::new(Text { data, text, class })); @@ -223,6 +231,14 @@ impl<'a> Layout<'a> { storage.size.set_component(axis, size); rules } + LayoutType::Button(child, storage, _) => { + let frame_rules = sh.button_surround(axis.is_vertical()); + let child_rules = child.size_rules_(sh, axis); + let (rules, offset, size) = frame_rules.surround_as_margin(child_rules); + storage.offset.set_component(axis, offset); + storage.size.set_component(axis, size); + rules + } LayoutType::Visitor(visitor) => visitor.size_rules(sh, axis), } } @@ -244,7 +260,9 @@ impl<'a> Layout<'a> { let align = hints.combine(align); layout.set_rect_(mgr, rect, align); } - LayoutType::Frame(child, storage) | LayoutType::NavFrame(child, storage) => { + LayoutType::Frame(child, storage) + | LayoutType::NavFrame(child, storage) + | LayoutType::Button(child, storage, _) => { storage.rect = rect; rect.pos += storage.offset; rect.size -= storage.size; @@ -267,7 +285,8 @@ impl<'a> Layout<'a> { LayoutType::Single(_) | LayoutType::AlignSingle(_, _) => false, LayoutType::AlignLayout(layout, _) | LayoutType::Frame(layout, _) - | LayoutType::NavFrame(layout, _) => layout.is_reversed_(), + | LayoutType::NavFrame(layout, _) + | LayoutType::Button(layout, _, _) => layout.is_reversed_(), LayoutType::Visitor(layout) => layout.is_reversed(), } } @@ -292,6 +311,10 @@ impl<'a> Layout<'a> { draw.nav_frame(storage.rect, state); child.draw_(draw, mgr, state); } + LayoutType::Button(child, storage, color) => { + draw.button(storage.rect, *color, state); + child.draw_(draw, mgr, state); + } LayoutType::Visitor(layout) => layout.draw(draw, mgr, state), } } @@ -450,10 +473,14 @@ impl<'a> Visitor for Text<'a> { } fn set_rect(&mut self, _mgr: &mut Manager, rect: Rect, align: AlignHints) { + let halign = match self.class { + TextClass::Button => Align::Center, + _ => Align::Default, + }; self.data.pos = rect.pos; self.text.update_env(|env| { env.set_bounds(rect.size.into()); - env.set_align(align.unwrap_or(Align::Default, Align::Center)); + env.set_align(align.unwrap_or(halign, Align::Center)); }); } diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index e9988b3e1..add94d233 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -7,6 +7,7 @@ use kas::draw::{color::Rgb, TextClass}; use kas::event::{self, VirtualKeyCode, VirtualKeyCodes}; +use kas::layout; use kas::prelude::*; use std::rc::Rc; @@ -22,9 +23,7 @@ widget! { #[widget_core] core: kas::CoreData, keys1: VirtualKeyCodes, - frame_size: Size, - frame_offset: Offset, - ideal_size: Size, + layout_frame: layout::FrameStorage, color: Option, #[widget] pub inner: W, @@ -45,22 +44,9 @@ widget! { } impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let frame_rules = size_handle.button_surround(axis.is_vertical()); - let content_rules = self.inner.size_rules(size_handle, axis); - - let (rules, offset, size) = frame_rules.surround_as_margin(content_rules); - self.frame_size.set_component(axis, size); - self.frame_offset.set_component(axis, offset); - self.ideal_size.set_component(axis, rules.ideal_size()); - rules - } - - fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) { - self.core.rect = rect; - rect.pos += self.frame_offset; - rect.size -= self.frame_size; - self.inner.set_rect(mgr, rect, align); + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + let inner = layout::Layout::single(&mut self.inner); + layout::Layout::button(&mut self.layout_frame, inner, self.color) } #[inline] @@ -71,11 +57,6 @@ widget! { // We steal click events on self.inner Some(self.id()) } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - draw.button(self.core.rect, self.color, self.input_state(mgr, disabled)); - self.inner.draw(draw, mgr, disabled); - } } impl> Button { @@ -85,9 +66,7 @@ widget! { Button { core: Default::default(), keys1: Default::default(), - frame_size: Default::default(), - frame_offset: Default::default(), - ideal_size: Default::default(), + layout_frame: Default::default(), color: None, inner, on_push: None, @@ -107,9 +86,7 @@ widget! { Button { core: self.core, keys1: self.keys1, - frame_size: self.frame_size, - frame_offset: self.frame_offset, - ideal_size: self.ideal_size, + layout_frame: self.layout_frame, color: self.color, inner: self.inner, on_push: Some(Rc::new(f)), @@ -206,9 +183,8 @@ widget! { #[widget_core] core: kas::CoreData, keys1: VirtualKeyCodes, - frame_size: Size, - frame_offset: Offset, - ideal_size: Size, + layout_frame: layout::FrameStorage, + layout_text: layout::TextStorage, color: Option, label: Text, on_push: Option Option>>, @@ -229,32 +205,9 @@ widget! { } impl Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let frame_rules = size_handle.button_surround(axis.is_vertical()); - let content_rules = size_handle.text_bound(&mut self.label, TextClass::Button, axis); - - let (rules, offset, size) = frame_rules.surround_as_margin(content_rules); - self.frame_size.set_component(axis, size); - self.frame_offset.set_component(axis, offset); - self.ideal_size.set_component(axis, rules.ideal_size()); - rules - } - - fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - let size = rect.size - self.frame_size; - self.label.update_env(|env| { - env.set_bounds(size.into()); - env.set_align(align.unwrap_or(Align::Center, Align::Center)); - }); - } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - draw.button(self.core.rect, self.color, self.input_state(mgr, disabled)); - let pos = self.core.rect.pos + self.frame_offset; - let accel = mgr.show_accel_labels(); - let state = self.input_state(mgr, disabled); - draw.text_accel(pos, &self.label, accel, TextClass::Button, state); + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + let inner = layout::Layout::text(&mut self.layout_text, &mut self.label, TextClass::Button); + layout::Layout::button(&mut self.layout_frame, inner, self.color) } } @@ -267,9 +220,8 @@ widget! { TextButton { core: Default::default(), keys1: Default::default(), - frame_size: Default::default(), - frame_offset: Default::default(), - ideal_size: Default::default(), + layout_frame: Default::default(), + layout_text: Default::default(), color: None, label: text, on_push: None, @@ -289,9 +241,8 @@ widget! { TextButton { core: self.core, keys1: self.keys1, - frame_size: self.frame_size, - frame_offset: self.frame_offset, - ideal_size: self.ideal_size, + layout_frame: self.layout_frame, + layout_text: self.layout_text, color: self.color, label: self.label, on_push: Some(Rc::new(f)), @@ -359,7 +310,7 @@ widget! { if self.label.text().keys() != string.keys() { action |= TkAction::RECONFIGURE; } - let avail = self.core.rect.size.clamped_sub(self.frame_size); + let avail = self.core.rect.size.clamped_sub(self.layout_frame.size); action | kas::text::util::set_text_and_prepare(&mut self.label, string, avail) } } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index ac948f75e..1a5ee8eea 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -8,6 +8,7 @@ use super::{IndexedColumn, MenuEntry}; use kas::draw::TextClass; use kas::event::{self, Command, GrabMode}; +use kas::layout; use kas::prelude::*; use kas::WindowId; use std::rc::Rc; @@ -26,7 +27,8 @@ widget! { #[widget_core] core: CoreData, label: Text, - frame_size: Size, + layout_frame: layout::FrameStorage, + layout_text: layout::TextStorage, #[widget] popup: ComboPopup, active: usize, @@ -36,21 +38,9 @@ widget! { } impl kas::Layout for Self { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let frame_rules = size_handle.button_surround(axis.is_vertical()); - let content_rules = size_handle.text_bound(&mut self.label, TextClass::Button, axis); - - let (rules, _offset, size) = frame_rules.surround_as_margin(content_rules); - self.frame_size.set_component(axis, size); - rules - } - - fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - self.label.update_env(|env| { - env.set_bounds(rect.size.into()); - env.set_align(align.unwrap_or(Align::Center, Align::Center)); - }); + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + let inner = layout::Layout::text(&mut self.layout_text, &mut self.label, TextClass::Button); + layout::Layout::button(&mut self.layout_frame, inner, None) } fn spatial_nav(&mut self, _: &mut Manager, _: bool, _: Option) -> Option { @@ -63,13 +53,7 @@ widget! { if self.popup_id.is_some() { state.insert(InputState::DEPRESS); } - draw.button(self.core.rect, None, state); - draw.text( - self.core.rect.pos, - self.label.as_ref(), - TextClass::Button, - state, - ); + self.layout().draw(draw, mgr, state); } } @@ -212,7 +196,8 @@ impl ComboBox { ComboBox { core: Default::default(), label, - frame_size: Default::default(), + layout_frame: Default::default(), + layout_text: Default::default(), popup: ComboPopup { core: Default::default(), inner: IndexedColumn::new(entries), @@ -237,7 +222,8 @@ impl ComboBox { ComboBox { core: self.core, label: self.label, - frame_size: self.frame_size, + layout_frame: self.layout_frame, + layout_text: self.layout_text, popup: self.popup, active: self.active, opening: self.opening, @@ -267,7 +253,7 @@ impl ComboBox { } else { "".to_string() }; - let avail = self.core.rect.size.clamped_sub(self.frame_size); + let avail = self.core.rect.size.clamped_sub(self.layout_frame.size); kas::text::util::set_text_and_prepare(&mut self.label, string, avail) } else { TkAction::empty() From ed8021cb46c332a8825214c06b5a8575a50a7f0b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 8 Dec 2021 16:26:30 +0000 Subject: [PATCH 47/53] Implement Layout::find_id based on Layout::layout Manual implementations: -3 +4 At least the default is more likely to behave as expected and there is a warning when this is not the case. --- crates/kas-core/src/core/widget.rs | 30 ++++++-------- crates/kas-core/src/layout/visitor.rs | 49 ++++++++++++++++++++++- crates/kas-macros/src/widget.rs | 24 ++++++++++- crates/kas-widgets/src/adapter/label.rs | 2 + crates/kas-widgets/src/adapter/reserve.rs | 15 +++---- crates/kas-widgets/src/button.rs | 9 ----- crates/kas-widgets/src/list.rs | 13 ------ crates/kas-widgets/src/menu/menu_entry.rs | 10 +---- crates/kas-widgets/src/scroll.rs | 7 ++++ crates/kas-widgets/src/scrollbar.rs | 17 ++++++++ crates/kas-widgets/src/slider.rs | 7 ++++ 11 files changed, 122 insertions(+), 61 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index d5a870bc2..06f0c996a 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -463,6 +463,18 @@ pub trait Layout: WidgetChildren { /// Find a widget by coordinate /// + /// This method has a default implementation, but this may need to be + /// overridden if any of the following are true: + /// + /// - [`Self::layout`] is not implemented and there are child widgets + /// - Any child widget should not receive events despite having a + /// placement in the layout — e.g. push-buttons (but note that + /// [`kas::layout::Layout::button`] does take this into account) + /// - Mouse/touch events should be passed to a child widget *outside* of + /// this child's area — e.g. a label next to a checkbox + /// - The child widget is in a translated coordinate space *not equal* to + /// [`Self::translation`] + /// /// This method translates from coordinates (usually from mouse input) to a /// [`WidgetId`]. It is expected to do the following: /// @@ -470,28 +482,12 @@ pub trait Layout: WidgetChildren { /// - Find the child which should respond to input at `coord`, if any, and /// call `find_id` recursively on this child /// - Otherwise return `self.id()` - /// - /// The default implementation suffices so long as none of the following - /// are true: - /// - /// - Child widgets should not be matched even though their area covers - /// `coord` - /// - Child widgets have offset not equal to [`Layout::translation`] - /// - This widget has very large numbers of children such that iterating - /// through all children in order is too slow. - /// - /// This must not be called before [`Layout::set_rect`]. fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; } let coord = coord + self.translation(); - for n in 0..self.num_children() { - if let Some(id) = self.get_child_mut(n).and_then(|w| w.find_id(coord)) { - return Some(id); - } - } - Some(self.id()) + self.layout().find_id(coord).or(Some(self.id())) } /// Draw a widget and its children diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index c2826a8bf..8f94a8f34 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -12,6 +12,7 @@ use crate::draw::{color::Rgb, DrawHandle, InputState, SizeHandle, TextClass}; use crate::event::{Manager, ManagerState}; use crate::geom::{Coord, Offset, Rect, Size}; use crate::text::{Align, TextApi, TextApiExt}; +use crate::WidgetId; use crate::{dir::Directional, WidgetConfig}; use std::any::Any; use std::iter::ExactSizeIterator; @@ -66,6 +67,8 @@ trait Visitor { fn is_reversed(&mut self) -> bool; + fn find_id(&mut self, coord: Coord) -> Option; + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState); } @@ -291,6 +294,26 @@ impl<'a> Layout<'a> { } } + /// Find a widget by coordinate + /// + /// Does not return the widget's own identifier. See example usage in + /// [`Layout::find_id`]. + #[inline] + pub fn find_id(mut self, coord: Coord) -> Option { + self.find_id_(coord) + } + fn find_id_(&mut self, coord: Coord) -> Option { + match &mut self.layout { + LayoutType::None => None, + LayoutType::Single(child) | LayoutType::AlignSingle(child, _) => child.find_id(coord), + LayoutType::AlignLayout(layout, _) => layout.find_id_(coord), + LayoutType::Frame(child, _) | LayoutType::NavFrame(child, _) => child.find_id_(coord), + // Buttons steal clicks, hence Button never returns ID of content + LayoutType::Button(_, _, _) => None, + LayoutType::Visitor(layout) => layout.find_id(coord), + } + } + /// Draw a widget's children #[inline] pub fn draw(mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { @@ -300,8 +323,9 @@ impl<'a> Layout<'a> { let disabled = state.contains(InputState::DISABLED); match &mut self.layout { LayoutType::None => (), - LayoutType::Single(child) => child.draw(draw, mgr, disabled), - LayoutType::AlignSingle(child, _) => child.draw(draw, mgr, disabled), + LayoutType::Single(child) | LayoutType::AlignSingle(child, _) => { + child.draw(draw, mgr, disabled) + } LayoutType::AlignLayout(layout, _) => layout.draw_(draw, mgr, state), LayoutType::Frame(child, storage) => { draw.outer_frame(storage.rect); @@ -353,6 +377,11 @@ where self.direction.is_reversed() } + fn find_id(&mut self, coord: Coord) -> Option { + // TODO(opt): more efficient search strategy? + self.children.find_map(|child| child.find_id(coord)) + } + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { for child in &mut self.children { child.draw(draw, mgr, state); @@ -390,6 +419,13 @@ impl<'a, W: WidgetConfig, D: Directional> Visitor for Slice<'a, W, D> { self.direction.is_reversed() } + fn find_id(&mut self, coord: Coord) -> Option { + let solver = RowPositionSolver::new(self.direction); + solver + .find_child_mut(self.children, coord) + .and_then(|child| child.find_id(coord)) + } + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { let solver = RowPositionSolver::new(self.direction); solver.for_children(self.children, draw.get_clip_rect(), |w| { @@ -429,6 +465,11 @@ where false } + fn find_id(&mut self, coord: Coord) -> Option { + // TODO(opt): more efficient search strategy? + self.children.find_map(|(_, child)| child.find_id(coord)) + } + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, state: InputState) { for (_, child) in &mut self.children { child.draw(draw, mgr, state); @@ -488,6 +529,10 @@ impl<'a> Visitor for Text<'a> { false } + fn find_id(&mut self, _: Coord) -> Option { + None + } + fn draw(&mut self, draw: &mut dyn DrawHandle, _mgr: &ManagerState, state: InputState) { draw.text_effects(self.data.pos, self.text, self.class, state); } diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 851ee1d95..4d253ecc7 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -6,7 +6,7 @@ use crate::args::{Handler, Widget}; use crate::extend_generics; use proc_macro2::TokenStream; -use proc_macro_error::{emit_error, emit_warning}; +use proc_macro_error::{emit_call_site_warning, emit_error, emit_warning}; use quote::{quote, TokenStreamExt}; use syn::spanned::Spanned; use syn::{parse_quote, Result}; @@ -27,6 +27,7 @@ pub(crate) fn widget(mut args: Widget) -> Result { let mut impl_widget_children = true; let mut impl_widget_config = true; + let mut has_find_id_impl = args.attr_widget.layout.is_some(); let mut handler_impl = None; let mut send_event_impl = None; for (index, impl_) in args.extra_impls.iter().enumerate() { @@ -54,6 +55,23 @@ pub(crate) fn widget(mut args: Widget) -> Result { } // TODO: if args.widget_attr.config.is_some() { warn unused } impl_widget_config = false; + } else if *path == parse_quote! { ::kas::Layout } + || *path == parse_quote! { kas::Layout } + || *path == parse_quote! { Layout } + { + if args.attr_widget.layout.is_some() { + emit_error!( + impl_.span(), + "impl conflicts with use of #[widget(layout=...;)]" + ); + } + for item in &impl_.items { + if let syn::ImplItem::Method(method) = item { + if method.sig.ident == "layout" || method.sig.ident == "find_id" { + has_find_id_impl = true; + } + } + } } else if *path == parse_quote! { ::kas::event::Handler } || *path == parse_quote! { kas::event::Handler } || *path == parse_quote! { event::Handler } @@ -83,6 +101,10 @@ pub(crate) fn widget(mut args: Widget) -> Result { } } + if !has_find_id_impl && (!impl_widget_children || !args.children.is_empty()) { + emit_call_site_warning!("widget appears to have children yet does not implement Layout::layout or Layout::find_id; this may cause incorrect handling of mouse/touch events"); + } + let (mut impl_generics, ty_generics, mut where_clause) = args.generics.split_for_impl(); let widget_name = name.to_string(); diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index 672a8f370..f18dbf4ac 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -14,6 +14,8 @@ widget! { /// /// The label supports accelerator keys, which activate `self.inner` on /// usage. + /// + /// Mouse/touch input on the label sends events to the inner widget. #[autoimpl(Deref, DerefMut on inner)] #[derive(Clone, Default, Debug)] #[handler(msg = W::Msg)] diff --git a/crates/kas-widgets/src/adapter/reserve.rs b/crates/kas-widgets/src/adapter/reserve.rs index 809acdc53..13aaff25e 100644 --- a/crates/kas-widgets/src/adapter/reserve.rs +++ b/crates/kas-widgets/src/adapter/reserve.rs @@ -5,6 +5,7 @@ //! Size reservation +use kas::layout; use kas::prelude::*; /// Parameterisation of [`Reserve`] using a function pointer @@ -73,20 +74,14 @@ widget! { } impl Layout for Self { + fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + layout::Layout::single(&mut self.inner) + } + fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let inner_rules = self.inner.size_rules(size_handle, axis); let reserve_rules = (self.reserve)(size_handle, axis); inner_rules.max(reserve_rules) } - - fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) { - self.core.rect = rect; - self.inner.set_rect(mgr, rect, align); - } - - fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { - let disabled = disabled || self.is_disabled(); - self.inner.draw(draw, mgr, disabled); - } } } diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index add94d233..baf0025f6 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -48,15 +48,6 @@ widget! { let inner = layout::Layout::single(&mut self.inner); layout::Layout::button(&mut self.layout_frame, inner, self.color) } - - #[inline] - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - // We steal click events on self.inner - Some(self.id()) - } } impl> Button { diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 1602d3861..daa0dddd7 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -207,19 +207,6 @@ widget! { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { make_layout!(self.core; slice(self.direction): self.widgets) } - - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - - let solver = layout::RowPositionSolver::new(self.direction); - if let Some(child) = solver.find_child_mut(&mut self.widgets, coord) { - return child.find_id(coord); - } - - Some(self.id()) - } } impl event::SendEvent for Self { diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 57725b6ea..8d2923ee2 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -127,15 +127,7 @@ widget! { impl Layout for Self { fn layout<'a>(&'a mut self) -> layout::Layout<'a> { - make_layout!(self.core; row: [ self.checkbox, self.label]) - } - - #[inline] - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - Some(self.id()) + make_layout!(self.core; row: [self.checkbox, self.label]) } fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index bc452e769..f36b4cc49 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -341,6 +341,13 @@ widget! { .set_sizes(rect.size, child_size + self.frame_size); } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + self.inner.find_id(coord + self.translation()) + } + #[inline] fn translation(&self) -> Offset { self.scroll_offset() diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index 3d5b13022..28f641329 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -230,6 +230,13 @@ widget! { None // handle is not navigable } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + self.handle.find_id(coord).or(Some(self.id())) + } + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.handle.input_state(mgr, disabled); @@ -590,6 +597,16 @@ widget! { } } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + self.vert_bar.find_id(coord) + .or_else(|| self.horiz_bar.find_id(coord)) + .or_else(|| self.inner.find_id(coord)) + .or(Some(self.id())) + } + #[cfg(feature = "min_spec")] default fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { self.draw_(draw, mgr, disabled); diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 9088b07d0..42de47e4e 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -248,6 +248,13 @@ widget! { None // handle is not navigable } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + self.handle.find_id(coord).or(Some(self.id())) + } + fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) { let dir = self.direction.as_direction(); let state = self.input_state(mgr, disabled) | self.handle.input_state(mgr, disabled); From 1981631656f841fb46c37ae4e1e595d2b5d2b91d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 8 Dec 2021 19:47:00 +0000 Subject: [PATCH 48/53] EditField: do not use Command::Tab --- crates/kas-widgets/src/editbox.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index 64600fce3..4babf01b7 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -811,7 +811,9 @@ impl EditField { Command::Return if self.multi_line => { Action::Insert('\n'.encode_utf8(&mut buf), LastEdit::Insert) } - Command::Tab => Action::Insert('\t'.encode_utf8(&mut buf), LastEdit::Insert), + // NOTE: we might choose to optionally handle Tab in the future, + // but without some workaround it prevents keyboard navigation. + // Command::Tab => Action::Insert('\t'.encode_utf8(&mut buf), LastEdit::Insert), Command::Left => { let mut cursor = GraphemeCursor::new(pos, self.text.str_len(), true); cursor From 5321c81c1def0f6544ee5881ee58a06f3450985f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Dec 2021 10:59:07 +0000 Subject: [PATCH 49/53] Update winit to 0.26 (fixes build) --- crates/kas-core/Cargo.toml | 2 +- crates/kas-wgpu/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index 2a6e4c1fd..0917eaf11 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -83,6 +83,6 @@ rev = "818515e" [dependencies.winit] # Provides translations for several winit types -version = "0.25" +version = "0.26" optional = true features = ["serde"] diff --git a/crates/kas-wgpu/Cargo.toml b/crates/kas-wgpu/Cargo.toml index 922fe662e..d39c2705f 100644 --- a/crates/kas-wgpu/Cargo.toml +++ b/crates/kas-wgpu/Cargo.toml @@ -38,7 +38,7 @@ futures = "0.3" log = "0.4" smallvec = "1.6.1" wgpu = { version = "0.11.0", features = ["spirv"] } -winit = "0.25" +winit = "0.26" thiserror = "1.0.23" window_clipboard = { version = "0.2.0", optional = true } guillotiere = "0.6.0" From fa4ea520838c015c6fa34ca61bfcf40a03632648 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Dec 2021 11:04:33 +0000 Subject: [PATCH 50/53] Fix doc link --- crates/kas-core/src/core/widget.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 06f0c996a..21f9d62fe 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -469,7 +469,7 @@ pub trait Layout: WidgetChildren { /// - [`Self::layout`] is not implemented and there are child widgets /// - Any child widget should not receive events despite having a /// placement in the layout — e.g. push-buttons (but note that - /// [`kas::layout::Layout::button`] does take this into account) + /// [`crate::layout::Layout::button`] does take this into account) /// - Mouse/touch events should be passed to a child widget *outside* of /// this child's area — e.g. a label next to a checkbox /// - The child widget is in a translated coordinate space *not equal* to diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 8f94a8f34..f9c768659 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -148,6 +148,9 @@ impl<'a> Layout<'a> { } /// Construct a button frame around a sub-layout + /// + /// Generates a button frame containing the child node. Mouse/touch input + /// on the button reports input to `self`, not to the child node. pub fn button(data: &'a mut FrameStorage, child: Self, color: Option) -> Self { let layout = LayoutType::Button(Box::new(child), data, color); Layout { layout } From fe7db8f1c0f599aa56808b8901fa0836f058c6b8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Dec 2021 11:13:56 +0000 Subject: [PATCH 51/53] Fix src/macros doc-tests --- src/macros.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index e932fe9db..87bde6d4d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -282,7 +282,9 @@ //! #[autoimpl(Deref, DerefMut on 0)] //! #[autoimpl(class_traits where W: trait on 0)] //! #[derive(Clone, Debug, Default)] -//! #[widget(derive = self.0)] +//! #[widget{ +//! derive = self.0; +//! }] //! #[handler(msg = ::Msg)] //! pub struct ScrollBarRegion(ScrollBars>); //! } @@ -297,7 +299,7 @@ //! use kas::event::{Handler, Manager, Response, VoidMsg}; //! use kas::macros::widget; //! use kas::widgets::StrLabel; -//! use kas::{CoreData, LayoutData, Widget}; +//! use kas::{CoreData, Widget}; //! //! #[derive(Debug)] //! enum ChildMessage { A } From d19a04f29cd7903e80ce9a3852a644a1f01cf1e6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Dec 2021 11:44:26 +0000 Subject: [PATCH 52/53] Make Clippy happy --- crates/kas-core/src/core/widget.rs | 4 ++-- crates/kas-core/src/layout/visitor.rs | 8 +------- crates/kas-macros/src/args.rs | 7 +++++-- crates/kas-widgets/src/adapter/label.rs | 2 +- crates/kas-widgets/src/adapter/reserve.rs | 2 +- crates/kas-widgets/src/button.rs | 4 ++-- crates/kas-widgets/src/combobox.rs | 2 +- crates/kas-widgets/src/editbox.rs | 2 +- crates/kas-widgets/src/frame.rs | 2 +- crates/kas-widgets/src/grid.rs | 2 +- crates/kas-widgets/src/list.rs | 2 +- crates/kas-widgets/src/menu/menu_entry.rs | 4 ++-- crates/kas-widgets/src/menu/submenu.rs | 2 +- crates/kas-widgets/src/window.rs | 2 +- 14 files changed, 21 insertions(+), 24 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 21f9d62fe..158a35254 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -365,7 +365,7 @@ pub trait Layout: WidgetChildren { /// /// If used, this allows automatic implementation of `size_rules` and /// `set_rect` methods. The default case is the empty layout. - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { Default::default() // TODO: remove default impl } @@ -487,7 +487,7 @@ pub trait Layout: WidgetChildren { return None; } let coord = coord + self.translation(); - self.layout().find_id(coord).or(Some(self.id())) + self.layout().find_id(coord).or_else(|| Some(self.id())) } /// Draw a widget and its children diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index f9c768659..336dd17e4 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -24,15 +24,9 @@ use std::iter::ExactSizeIterator; /// used. We therefore use a simple linked list. #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] -#[derive(Debug)] +#[derive(Debug, Default)] pub struct StorageChain(Option<(Box, Box)>); -impl Default for StorageChain { - fn default() -> Self { - StorageChain(None) - } -} - impl StorageChain { /// Access layout storage /// diff --git a/crates/kas-macros/src/args.rs b/crates/kas-macros/src/args.rs index 556bc0a32..a72821eeb 100644 --- a/crates/kas-macros/src/args.rs +++ b/crates/kas-macros/src/args.rs @@ -396,9 +396,12 @@ pub enum Handler { Discard, } impl Handler { - pub fn is_none(&self) -> bool { + fn is_none(&self) -> bool { *self == Handler::None } + fn is_some(&self) -> bool { + *self != Handler::None + } pub fn any_ref(&self) -> Option<&Ident> { match self { Handler::None | Handler::Discard => None, @@ -469,7 +472,7 @@ impl Parse for WidgetAttrArgs { impl ToTokens for WidgetAttrArgs { fn to_tokens(&self, tokens: &mut TokenStream) { - if !self.update.is_none() || !self.handler.is_none() { + if self.update.is_some() || self.handler.is_some() { let mut args = TokenStream::new(); if let Some(ref ident) = self.update { args.append_all(quote! { update = #ident }); diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index f18dbf4ac..e269de2c9 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -85,7 +85,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { let arr = [ layout::Layout::single(&mut self.inner), layout::Layout::text(&mut self.label_store, &mut self.label, TextClass::Label), diff --git a/crates/kas-widgets/src/adapter/reserve.rs b/crates/kas-widgets/src/adapter/reserve.rs index 13aaff25e..29a21db2d 100644 --- a/crates/kas-widgets/src/adapter/reserve.rs +++ b/crates/kas-widgets/src/adapter/reserve.rs @@ -74,7 +74,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { layout::Layout::single(&mut self.inner) } diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index baf0025f6..093ad32e7 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -44,7 +44,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { let inner = layout::Layout::single(&mut self.inner); layout::Layout::button(&mut self.layout_frame, inner, self.color) } @@ -196,7 +196,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { let inner = layout::Layout::text(&mut self.layout_text, &mut self.label, TextClass::Button); layout::Layout::button(&mut self.layout_frame, inner, self.color) } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 1a5ee8eea..0885dd79a 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -38,7 +38,7 @@ widget! { } impl kas::Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { let inner = layout::Layout::text(&mut self.layout_text, &mut self.label, TextClass::Button); layout::Layout::button(&mut self.layout_frame, inner, None) } diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs index 4babf01b7..d792d1f2f 100644 --- a/crates/kas-widgets/src/editbox.rs +++ b/crates/kas-widgets/src/editbox.rs @@ -182,7 +182,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { let inner = layout::Layout::single(&mut self.inner); layout::Layout::frame(&mut self.layout_frame, inner) } diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index 9af189fb3..41e9fcc9b 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -36,7 +36,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { make_layout!(self.core; frame(self.inner)) } } diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 19f7058dd..7d629fb1a 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -79,7 +79,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { layout::Layout::grid( self.widgets.iter_mut().map(move |(info, w)| (*info, layout::Layout::single(w))), self.dim, diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index daa0dddd7..910c5c438 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -204,7 +204,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { make_layout!(self.core; slice(self.direction): self.widgets) } } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 8d2923ee2..af9da406c 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -34,7 +34,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { let inner = layout::Layout::text(&mut self.layout_label, &mut self.label, TextClass::MenuLabel); layout::Layout::frame(&mut self.layout_frame, inner) } @@ -126,7 +126,7 @@ widget! { } impl Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { make_layout!(self.core; row: [self.checkbox, self.label]) } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index f73c70db3..4ac522274 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -135,7 +135,7 @@ widget! { } impl kas::Layout for Self { - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { let label = layout::Layout::text(&mut self.label_store, &mut self.label, TextClass::MenuLabel); layout::Layout::frame(&mut self.frame_store, label) } diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index 323ebcdb4..6141cbbb6 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -31,7 +31,7 @@ widget! { impl Layout for Self { #[inline] - fn layout<'a>(&'a mut self) -> layout::Layout<'a> { + fn layout(&mut self) -> layout::Layout<'_> { layout::Layout::single(&mut self.w) } From 7f2a9c23d1fec66aa9bdac93f2d5901f67aa69a4 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Dec 2021 14:08:07 +0000 Subject: [PATCH 53/53] Clippy: do not use lazy eval of str::len --- crates/kas-core/src/text/selection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/kas-core/src/text/selection.rs b/crates/kas-core/src/text/selection.rs index 83c039675..fc768428f 100644 --- a/crates/kas-core/src/text/selection.rs +++ b/crates/kas-core/src/text/selection.rs @@ -121,7 +121,7 @@ impl SelectionHelper { let pos = start + index; (pos >= range.end).then(|| pos) }) - .unwrap_or_else(|| string.len()); + .unwrap_or(string.len()); } else { start = text.find_line(range.start).map(|r| r.1.start).unwrap_or(0); end = text