Skip to content

Commit

Permalink
Merge pull request #438 from kas-gui/work1
Browse files Browse the repository at this point in the history
Replace align!, pack!, margins! with adapters
  • Loading branch information
dhardy authored Jan 26, 2024
2 parents 0aa4345 + 0fbce55 commit 60b3252
Show file tree
Hide file tree
Showing 27 changed files with 703 additions and 655 deletions.
10 changes: 7 additions & 3 deletions crates/kas-core/src/core/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ use std::ops::RangeBounds;

/// A collection of (child) widgets
///
/// Essentially, implementating types are lists of widgets. Simple examples are
/// `Vec<W>` and `[W; N]` where `W: Widget` and `const N: usize`. A more complex
/// example would be a custom struct where each field is a widget.
/// Essentially, a `Collection` is a list of widgets. Notable implementations are:
///
/// - Slices `[W]` where `W: Widget`
/// - Arrays `[W; N]` where `W: Widget` and `const N: usize`
/// - [`Vec`]`<W>` where `W: Widget`
/// - The output of [`kas::collection!`]. This macro constructs an anonymous
/// struct of widgets which implements `Collection`.
pub trait Collection {
/// The associated data type
type Data;
Expand Down
26 changes: 26 additions & 0 deletions crates/kas-core/src/layout/align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,34 @@ impl AlignHints {
/// No hints
pub const NONE: AlignHints = AlignHints::new(None, None);

/// Top, no horizontal hint
pub const TOP: AlignHints = AlignHints::new(None, Some(Align::TL));
/// Bottom, no horizontal hint
pub const BOTTOM: AlignHints = AlignHints::new(None, Some(Align::BR));
/// Left, no vertical hint
pub const LEFT: AlignHints = AlignHints::new(Some(Align::TL), None);
/// Right, no vertical hint
pub const RIGHT: AlignHints = AlignHints::new(Some(Align::BR), None);

/// Top, left
pub const TOP_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::TL));
/// Top, right
pub const TOP_RIGHT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::BR));
/// Bottom, left
pub const BOTTOM_LEFT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::TL));
/// Bottom, right
pub const BOTTOM_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::BR));

/// Center on both axes
pub const CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::Center));
/// Top, center
pub const TOP_CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::TL));
/// Bottom, center
pub const BOTTOM_CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::BR));
/// Center, left
pub const CENTER_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::Center));
/// Center, right
pub const CENTER_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::Center));

/// Stretch on both axes
pub const STRETCH: AlignHints = AlignHints::new(Some(Align::Stretch), Some(Align::Stretch));
Expand Down
116 changes: 67 additions & 49 deletions crates/kas-core/src/layout/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,18 @@ pub trait Visitable {

/// Set size and position
///
/// This method is identical to [`Layout::set_rect`].
/// The caller is expected to set `self.core.rect = rect;`.
/// In other respects, this functions identically to [`Layout::set_rect`].
fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect);

/// Translate a coordinate to an [`Id`]
///
/// Implementations should recursively call `find_id` on children, returning
/// `None` if no child returns an `Id`.
/// This method is simplified relative to [`Layout::find_id`].
/// The caller is expected to
///
/// 1. Return `None` if `!self.rect().contains(coord)`
/// 2. Translate `coord`: `let coord = coord + self.translation();`
/// 3. Call `find_id` (this method), returning its result if not `None`
/// 4. Otherwise return `Some(self.id())`
fn find_id(&mut self, coord: Coord) -> Option<Id>;

/// Draw a widget and its children
Expand Down Expand Up @@ -96,32 +100,6 @@ impl<'a> Visitor<Box<dyn Visitable + 'a>> {
Visitor(Single { widget })
}

/// Construct a single-item layout with alignment hints
pub fn align_single(
widget: &'a mut dyn Layout,
hints: AlignHints,
) -> Visitor<impl Visitable + 'a> {
Self::align(Self::single(widget), hints)
}

/// Construct a sub-layout with alignment hints
pub fn align<C: Visitable + 'a>(child: C, hints: AlignHints) -> Visitor<impl Visitable + 'a> {
Visitor(Align { child, hints })
}

/// Construct a sub-layout which is squashed and aligned
pub fn pack<C: Visitable + 'a>(
storage: &'a mut PackStorage,
child: C,
hints: AlignHints,
) -> Visitor<impl Visitable + 'a> {
Visitor(Pack {
child,
storage,
hints,
})
}

/// Replace the margins of a sub-layout
pub fn margins<C: Visitable + 'a>(
child: C,
Expand Down Expand Up @@ -202,8 +180,33 @@ impl<'a> Visitor<Box<dyn Visitable + 'a>> {
}
}

impl<V: Visitable> Visitor<V> {
/// Apply alignment
pub fn align(self, hints: AlignHints) -> Visitor<impl Visitable> {
Visitor(Align { child: self, hints })
}

/// Apply alignment and squash
pub fn pack<'a>(
self,
hints: AlignHints,
storage: &'a mut PackStorage,
) -> Visitor<impl Visitable + 'a>
where
V: 'a,
{
Visitor(Pack {
child: self,
hints,
storage,
})
}
}

impl<V: Visitable> Visitor<V> {
/// Get size rules for the given axis
///
/// This method is identical to [`Layout::size_rules`].
#[inline]
pub fn size_rules(mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
self.size_rules_(sizer, axis)
Expand All @@ -213,6 +216,9 @@ impl<V: Visitable> Visitor<V> {
}

/// Apply a given `rect` to self
///
/// The caller is expected to set `self.core.rect = rect;`.
/// In other respects, this functions identically to [`Layout::set_rect`].
#[inline]
pub fn set_rect(mut self, cx: &mut ConfigCx, rect: Rect) {
self.set_rect_(cx, rect);
Expand All @@ -221,10 +227,14 @@ impl<V: Visitable> Visitor<V> {
self.0.set_rect(cx, rect);
}

/// Find a widget by coordinate
/// Translate a coordinate to an [`Id`]
///
/// Does not return the widget's own identifier. See example usage in
/// [`Visitor::find_id`].
/// The caller is expected to
///
/// 1. Return `None` if `!self.rect().contains(coord)`
/// 2. Translate `coord`: `let coord = coord + self.translation();`
/// 3. Call `find_id` (this method), returning its result if not `None`
/// 4. Otherwise return `Some(self.id())`
#[inline]
pub fn find_id(mut self, coord: Coord) -> Option<Id> {
self.find_id_(coord)
Expand All @@ -233,7 +243,9 @@ impl<V: Visitable> Visitor<V> {
self.0.find_id(coord)
}

/// Draw a widget's children
/// Draw a widget and its children
///
/// This method is identical to [`Layout::draw`].
#[inline]
pub fn draw(mut self, draw: DrawCx) {
self.draw_(draw);
Expand Down Expand Up @@ -309,17 +321,14 @@ impl<C: Visitable> Visitable for Align<C> {

struct Pack<'a, C: Visitable> {
child: C,
storage: &'a mut PackStorage,
hints: AlignHints,
storage: &'a mut PackStorage,
}

impl<'a, C: Visitable> Visitable for Pack<'a, C> {
fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
let rules = self
.child
.size_rules(sizer, self.storage.apply_align(axis, self.hints));
self.storage.size.set_component(axis, rules.ideal_size());
rules
self.storage
.child_size_rules(self.hints, axis, |axis| self.child.size_rules(sizer, axis))
}

fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) {
Expand Down Expand Up @@ -602,36 +611,45 @@ where
}
}

/// Layout storage for alignment
/// Layout storage for pack
#[derive(Clone, Default, Debug)]
pub struct PackStorage {
align: AlignPair,
size: Size,
}
impl PackStorage {
/// Set alignment
fn apply_align(&mut self, axis: AxisInfo, hints: AlignHints) -> AxisInfo {
/// Calculate child's [`SizeRules`]
pub fn child_size_rules(
&mut self,
hints: AlignHints,
axis: AxisInfo,
size_child: impl FnOnce(AxisInfo) -> SizeRules,
) -> SizeRules {
let axis = axis.with_align_hints(hints);
self.align.set_component(axis, axis.align_or_default());
axis
let rules = size_child(axis);
self.size.set_component(axis, rules.ideal_size());
rules
}

/// Align rect
fn aligned_rect(&self, rect: Rect) -> Rect {
pub fn aligned_rect(&self, rect: Rect) -> Rect {
self.align.aligned_rect(self.size, rect)
}
}

/// Layout storage for frame layout
/// Layout storage for frame
#[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`] assigned to whole frame
///
/// NOTE: for a top-level layout component this is redundant with the
/// widget's rect. For frames deeper within a widget's layout we *could*
/// instead recalculate this (in every draw call etc.).
rect: Rect,
}
impl FrameStorage {
Expand Down
4 changes: 3 additions & 1 deletion crates/kas-core/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ pub use crate::event::{ConfigCx, Event, EventCx, EventState, IsUsed, Unused, Use
#[doc(no_inline)]
pub use crate::geom::{Coord, Offset, Rect, Size};
#[doc(no_inline)]
pub use crate::layout::{Align, AlignPair, AxisInfo, LayoutVisitor, SizeRules, Stretch};
pub use crate::layout::{
Align, AlignHints, AlignPair, AxisInfo, LayoutVisitor, SizeRules, Stretch,
};
#[doc(no_inline)] pub use crate::text::AccessString;
#[doc(no_inline)]
pub use crate::text::{EditableTextApi, TextApi, TextApiExt};
Expand Down
22 changes: 14 additions & 8 deletions crates/kas-macros/src/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,50 @@ use syn::{Expr, Ident, Lifetime, LitStr, Token};
#[derive(Debug)]
pub enum StorIdent {
Named(Ident, Span),
Generated(String, Span),
Generated(Ident, Span),
}
impl From<Lifetime> for StorIdent {
fn from(lt: Lifetime) -> StorIdent {
let span = lt.span();
StorIdent::Named(lt.ident, span)
}
}
impl From<Ident> for StorIdent {
fn from(ident: Ident) -> StorIdent {
let span = ident.span();
StorIdent::Generated(ident, span)
}
}
impl ToTokens for StorIdent {
fn to_tokens(&self, toks: &mut Toks) {
match self {
StorIdent::Named(ident, _) => ident.to_tokens(toks),
StorIdent::Generated(string, span) => Ident::new(string, *span).to_tokens(toks),
StorIdent::Named(ident, _) | StorIdent::Generated(ident, _) => ident.to_tokens(toks),
}
}
}

#[derive(Default)]
pub struct NameGenerator(usize);
impl NameGenerator {
pub fn next(&mut self) -> StorIdent {
pub fn next(&mut self) -> Ident {
let name = format!("_stor{}", self.0);
self.0 += 1;
StorIdent::Generated(name, Span::call_site())
let span = Span::call_site();
Ident::new(&name, span)
}

pub fn parse_or_next(&mut self, input: ParseStream) -> Result<StorIdent> {
if input.peek(Lifetime) {
Ok(input.parse::<Lifetime>()?.into())
} else {
Ok(self.next())
Ok(self.next().into())
}
}
}

pub enum Item {
Label(StorIdent, LitStr),
Widget(StorIdent, Expr),
Label(Ident, LitStr),
Widget(Ident, Expr),
}

impl Item {
Expand Down
Loading

0 comments on commit 60b3252

Please sign in to comment.