frus_widgets/positioned.rs
1//! [`Positioned`]: places a layer of a [`crate::Stack`] against the stack's own edges,
2//! instead of filling it.
3//!
4//! A badge on a corner, a caption over an image, a button floating above content — all of
5//! them are a stack with one layer pinned somewhere. Without this a layer can only fill
6//! the box, and the only way to put something in a corner was to give it a transparent
7//! wrapper the size of the stack and align inside it.
8//!
9//! Each edge is optional, and what is given decides both the **size** and the **place**,
10//! exactly as in the reference:
11//!
12//! - `left` **and** `right` → the width is what is between them;
13//! - one of them plus [`Positioned::width`] → that width, at that edge;
14//! - neither → the child's own width, placed by the stack's alignment.
15//!
16//! The vertical axis works the same way with `top`, `bottom` and [`Positioned::height`].
17//! Giving all three of a triple is a contradiction, and the extra one is ignored: the two
18//! edges win, since they are what says where the box is.
19//!
20//! ```ignore
21//! Stack::new()
22//! .layer(photo)
23//! .layer(Positioned::new(badge).top(8.0).right(8.0))
24//! ```
25
26use frus_layout::Style;
27
28use crate::widget::Widget;
29
30/// What a [`Positioned`] pins, in logical pixels from the stack's edges. `None` on an
31/// edge means "not pinned there".
32#[derive(Clone, Copy, Debug, Default, PartialEq)]
33pub struct Positioning {
34 /// The distance from the stack's left edge, or `None` to leave that edge free.
35 pub left: Option<f32>,
36 /// The distance from the stack's top edge, or `None` to leave that edge free.
37 pub top: Option<f32>,
38 /// The distance from the stack's right edge, or `None` to leave that edge free.
39 pub right: Option<f32>,
40 /// The distance from the stack's bottom edge, or `None` to leave that edge free.
41 pub bottom: Option<f32>,
42 /// A fixed width, or `None` to take the one the edges and the content leave.
43 pub width: Option<f32>,
44 /// A fixed height, or `None` to take the one the edges and the content leave.
45 pub height: Option<f32>,
46}
47
48impl Positioning {
49 /// The width this asks for in a stack `available` wide, and `None` when it asks for
50 /// the child's own.
51 pub fn resolved_width(&self, available: f32) -> Option<f32> {
52 match (self.left, self.right) {
53 (Some(l), Some(r)) => Some((available - l - r).max(0.0)),
54 _ => self.width,
55 }
56 }
57
58 /// The height this asks for in a stack `available` tall, and `None` when it asks for
59 /// the child's own.
60 pub fn resolved_height(&self, available: f32) -> Option<f32> {
61 match (self.top, self.bottom) {
62 (Some(t), Some(b)) => Some((available - t - b).max(0.0)),
63 _ => self.height,
64 }
65 }
66
67 /// Where the box goes on one axis, given the stack's extent, the child's own and the
68 /// share of the free space the stack's alignment asks for.
69 ///
70 /// The near edge wins if it is pinned; failing that the far edge places the box by
71 /// its own size; failing both, the stack aligns it.
72 pub fn place(
73 near: Option<f32>,
74 far: Option<f32>,
75 available: f32,
76 own: f32,
77 fraction: f32,
78 ) -> f32 {
79 match (near, far) {
80 (Some(n), _) => n,
81 (None, Some(f)) => available - f - own,
82 (None, None) => (available - own).max(0.0) * fraction,
83 }
84 }
85}
86
87/// A layer pinned against a [`crate::Stack`]'s edges. See the module documentation.
88pub struct Positioned<Msg = crate::callback::Callback> {
89 inner: Box<dyn Widget<Msg>>,
90 spec: Positioning,
91}
92
93impl<Msg> Positioned<Msg> {
94 /// Wraps a layer, pinned nowhere yet — which on its own is the stack's alignment at
95 /// the child's natural size.
96 pub fn new(inner: impl Widget<Msg> + 'static) -> Self {
97 Self {
98 inner: Box::new(inner),
99 spec: Positioning::default(),
100 }
101 }
102
103 /// Distance from the stack's left edge.
104 pub fn left(mut self, px: f32) -> Self {
105 self.spec.left = Some(px);
106 self
107 }
108
109 /// Distance from the stack's top edge.
110 pub fn top(mut self, px: f32) -> Self {
111 self.spec.top = Some(px);
112 self
113 }
114
115 /// Distance from the stack's right edge.
116 pub fn right(mut self, px: f32) -> Self {
117 self.spec.right = Some(px);
118 self
119 }
120
121 /// Distance from the stack's bottom edge.
122 pub fn bottom(mut self, px: f32) -> Self {
123 self.spec.bottom = Some(px);
124 self
125 }
126
127 /// An explicit width, used when only one horizontal edge is pinned.
128 pub fn width(mut self, px: f32) -> Self {
129 self.spec.width = Some(px);
130 self
131 }
132
133 /// An explicit height, used when only one vertical edge is pinned.
134 pub fn height(mut self, px: f32) -> Self {
135 self.spec.height = Some(px);
136 self
137 }
138
139 /// Pins all four edges: the layer fills the stack inset by `px` on every side.
140 pub fn inset(self, px: f32) -> Self {
141 self.left(px).top(px).right(px).bottom(px)
142 }
143
144 /// It does not touch the box — the stack reads the pins and lays the layer out
145 /// against them, which a style cannot express.
146 fn restyle(&self, base: Style) -> Style {
147 base
148 }
149}
150
151crate::transparent::forward_transparent!(Positioned {
152 /// Claimed: the pins are the whole of what this wrapper adds.
153 fn positioned(&self) -> Option<crate::positioned::Positioning> {
154 Some(self.spec)
155 }
156
157 /// Forwarded: pinning a widget says nothing about which widget it is.
158 fn key(&self) -> Option<u64> {
159 self.inner.key()
160 }
161
162 /// Forwarded too: a place is not a palette.
163 fn theme_override(
164 &self,
165 inherited: &crate::theme::Theme,
166 ) -> Option<Box<crate::theme::Theme>> {
167 self.inner.theme_override(inherited)
168 }
169
170 /// Forwarded: a wrapper is its child, and a scoped surface is the child's to impose.
171 fn media_override(&self, inherited: crate::MediaQuery) -> Option<crate::MediaQuery> {
172 self.inner.media_override(inherited)
173 }
174 fn scaffold_override(&self) -> Option<crate::ScaffoldInfo> {
175 self.inner.scaffold_override()
176 }
177 /// Forwarded: a wrapper around a form is still that form (milestone 512).
178 fn autofill_group(&self) -> bool {
179 self.inner.autofill_group()
180 }
181});