Skip to main content

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});