Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Replace align!, pack!, margins! with adapters #438

Merged
merged 19 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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