1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! View types used for building interfaces
mod divider;
mod empty_view;
mod foreach;
mod hstack;
pub mod image;
pub mod match_view;
mod modifier;
pub mod shape;
mod spacer;
mod text;
mod vstack;
mod zstack;
pub use divider::Divider;
pub use empty_view::EmptyView;
pub use foreach::ForEach;
pub use hstack::HStack;
pub use image::Image;
pub use modifier::padding;
pub use spacer::Spacer;
pub(crate) use text::WhitespaceWrap;
pub use text::{HorizontalTextAlignment, Text};
pub use vstack::VStack;
pub use zstack::ZStack;
use modifier::{
Animated, BackgroundView, FixedFrame, FixedSize, FlexFrame, ForegroundStyle, GeometryGroup,
Padding, Priority,
};
use crate::{
animation::Animation,
environment::DefaultEnvironment,
primitives::{Point, Size},
render::{CharacterRender, Renderable},
};
pub trait LayoutExtensions: Sized {
/// Applies padding to the specified edges
fn padding(self, edges: padding::Edges, amount: u16) -> Padding<Self> {
Padding::new(edges, amount, self)
}
fn frame(self) -> FixedFrame<Self> {
FixedFrame::new(self)
}
fn flex_frame(self) -> FlexFrame<Self> {
FlexFrame::new(self)
}
/// Proposes ``ProposedDimension::Compact``, resulting in the child view rendering at its ideal
/// size along the specified axis.
fn fixed_size(self, horizontal: bool, vertical: bool) -> FixedSize<Self> {
FixedSize::new(horizontal, vertical, self)
}
/// Sets the priority of the view layout.
///
/// Stacks lay out views in groups of priority, with higher priority views being laid out
/// first. Each set of views in the stack with a given priority are laid out together, with the
/// stack offering the remaining width divided by the number of views in the group.
fn priority(self, priority: i8) -> Priority<Self> {
Priority::new(priority, self)
}
/// Applies an animation to a view tree. All views in the tree will be animated according to
/// the animation curve provided when the value changes. The elapsed duration will be reset
/// if the value changes before the animation is complete.
fn animated<T: PartialEq + Clone>(self, animation: Animation, value: T) -> Animated<Self, T> {
Animated::new(self, animation, value)
}
/// Creates a new coordinate space under which views are positioned, allowing views within the
/// coordinate space to animate relative to a shared origin.
///
/// In the below implementation of a toggle button, the geometry group ensures the circle and
/// capsule always animate together as one element. Without this, compound animations where the
/// toggle frame moves as a result of a parent animation would result in the circle moving outside
/// the capsule. Contrary to what intuition would suggest, simply moving the `.animated` modifier
/// to encompass the entire toggle does not resolve the issue.
///
/// Example:
///
/// ```
/// use core::time::Duration;
/// use buoyant::view::{shape::{Circle, Capsule}, ZStack, padding, LayoutExtensions as _, RenderExtensions as _};
/// use buoyant::animation::Animation;
/// use buoyant::layout::HorizontalAlignment;
/// use buoyant::render::{EmbeddedGraphicsView, Renderable};
/// use embedded_graphics::pixelcolor::Rgb565;
/// use embedded_graphics::prelude::*;
///
/// fn toggle_button(is_on: bool) -> impl EmbeddedGraphicsView<Rgb565> {
/// let (color, alignment) = if is_on {
/// (Rgb565::GREEN, HorizontalAlignment::Trailing)
/// } else {
/// (Rgb565::CSS_LIGHT_GRAY, HorizontalAlignment::Leading)
/// };
///
/// ZStack::new((
/// Capsule.foreground_color(color),
/// Circle
/// .foreground_color(Rgb565::WHITE)
/// .padding(padding::Edges::All, 2)
/// .animated(Animation::ease_in_out(Duration::from_millis(120)), is_on),
/// ))
/// .with_horizontal_alignment(alignment)
/// .geometry_group()
/// .frame().with_width(50).with_height(25)
/// }
/// ```
fn geometry_group(self) -> GeometryGroup<Self> {
GeometryGroup::new(self)
}
/// A view that uses the layout of the foreground view and renders the background
/// behind it.
///
/// Example:
///
/// ```
/// use buoyant::view::{padding::Edges, shape::RoundedRectangle, Text, LayoutExtensions as _, RenderExtensions as _};
/// use buoyant::render::{EmbeddedGraphicsView};
/// use embedded_graphics::{prelude::*, pixelcolor::Rgb565, mono_font::ascii::FONT_9X15_BOLD};
///
/// fn bordered_button() -> impl EmbeddedGraphicsView<Rgb565> {
/// Text::new("Press me", &FONT_9X15_BOLD)
/// .foreground_color(Rgb565::WHITE)
/// .padding(Edges::All, 10)
/// .background(|| {
/// RoundedRectangle::new(10)
/// .foreground_color(Rgb565::BLUE)
/// })
/// }
/// ```
fn background<U>(self, background: impl FnOnce() -> U) -> BackgroundView<Self, U> {
BackgroundView::new(self, background())
}
}
pub trait RenderExtensions<C>: Sized {
/// Sets the foreground color
fn foreground_color(self, color: C) -> ForegroundStyle<Self, C> {
ForegroundStyle::new(color, self)
}
}
impl<T: crate::layout::Layout> LayoutExtensions for T {}
impl<T: Renderable<C>, C> RenderExtensions<C> for T {}
// TODO: Remove this
pub fn make_render_tree<C, V>(view: &V, size: Size) -> V::Renderables
where
V: Renderable<C>,
V::Renderables: CharacterRender<C>,
{
let env = DefaultEnvironment::default();
let layout = view.layout(&size.into(), &env);
view.render_tree(&layout, Point::default(), &env)
}