Skip to main content

tessera_foundation/
modifier.rs

1//! Shared foundational modifier extensions.
2//!
3//! ## Usage
4//!
5//! Attach reusable layout and semantics behavior to any component subtree.
6
7mod insets;
8mod interaction;
9mod layout;
10mod semantics;
11
12use tessera_ui::{
13    AxisConstraint, CallbackWith, Dp, Modifier, Px, modifier::ModifierCapabilityExt as _,
14};
15
16use crate::alignment::Alignment;
17
18use layout::{
19    AlignmentParentDataModifierNode, ConstraintModifierNode, DefaultMinSizeModifierNode,
20    MinimumInteractiveModifierNode, OffsetModifierNode, PaddingModifierNode,
21    RequiredSizeModifierNode, WeightParentDataModifierNode,
22};
23
24pub use interaction::{
25    ClickableArgs, DragDelta, DraggableArgs, InteractionState, InteractionVisualState,
26    PointerEventContext, SelectableArgs, ToggleableArgs,
27};
28pub use layout::{
29    AlignmentParentData, MinimumInteractiveComponentEnforcement, Padding, WeightParentData,
30};
31pub use semantics::SemanticsArgs;
32
33/// Shared modifier extensions that are not tied to a specific design system.
34pub trait ModifierExt {
35    /// Pads and consumes the space not already occupied by enclosing inset modifiers.
36    fn window_insets_padding(self, insets: tessera_ui::WindowInsets) -> Modifier;
37
38    /// Marks inset space as occupied for nested padding and size modifiers.
39    fn consume_window_insets(self, insets: tessera_ui::WindowInsets) -> Modifier;
40
41    /// Sets width to the remaining left window inset.
42    fn window_insets_left_width(self, insets: tessera_ui::WindowInsets) -> Modifier;
43
44    /// Sets height to the remaining top window inset.
45    fn window_insets_top_height(self, insets: tessera_ui::WindowInsets) -> Modifier;
46
47    /// Sets width to the remaining right window inset.
48    fn window_insets_right_width(self, insets: tessera_ui::WindowInsets) -> Modifier;
49
50    /// Sets height to the remaining bottom window inset.
51    fn window_insets_bottom_height(self, insets: tessera_ui::WindowInsets) -> Modifier;
52
53    /// Adds padding around the content.
54    fn padding(self, padding: Padding) -> Modifier;
55
56    /// Adds symmetric padding on all edges.
57    fn padding_all(self, padding: Dp) -> Modifier;
58
59    /// Adds symmetric padding for horizontal and vertical edges.
60    fn padding_symmetric(self, horizontal: Dp, vertical: Dp) -> Modifier;
61
62    /// Offsets the content without affecting layout size.
63    fn offset(self, x: Dp, y: Dp) -> Modifier;
64
65    /// Constrains the content to an exact size when possible.
66    fn size(self, width: Dp, height: Dp) -> Modifier;
67
68    /// Pins the content to an exact size regardless of incoming constraints.
69    fn required_size(self, width: Dp, height: Dp) -> Modifier;
70
71    /// Pins the content width regardless of incoming constraints.
72    fn required_width(self, width: Dp) -> Modifier;
73
74    /// Pins the content height regardless of incoming constraints.
75    fn required_height(self, height: Dp) -> Modifier;
76
77    /// Applies a default minimum size only when the incoming axis leaves the
78    /// minimum open, so an explicit outer size always wins.
79    fn default_min_size(self, min_width: Dp, min_height: Dp) -> Modifier;
80
81    /// Constrains the content to an exact width when possible.
82    fn width(self, width: Dp) -> Modifier;
83
84    /// Constrains the content to an exact height when possible.
85    fn height(self, height: Dp) -> Modifier;
86
87    /// Constrains the content size within optional min/max bounds.
88    fn size_in(
89        self,
90        min_width: Option<Dp>,
91        max_width: Option<Dp>,
92        min_height: Option<Dp>,
93        max_height: Option<Dp>,
94    ) -> Modifier;
95
96    /// Applies explicit width/height interval constraints.
97    fn constrain(self, width: Option<AxisConstraint>, height: Option<AxisConstraint>) -> Modifier;
98
99    /// Fills the available width within parent bounds.
100    fn fill_max_width(self) -> Modifier;
101
102    /// Fills the available height within parent bounds.
103    fn fill_max_height(self) -> Modifier;
104
105    /// Fills the available size within parent bounds.
106    fn fill_max_size(self) -> Modifier;
107
108    /// Enforces a minimum interactive size by expanding and centering content.
109    fn minimum_interactive_component_size(self) -> Modifier;
110
111    /// Provides weighted parent data for row and column layouts.
112    fn weight(self, weight: f32) -> Modifier;
113
114    /// Provides alignment parent data for layered boxed layouts.
115    fn align(self, alignment: Alignment) -> Modifier;
116
117    /// Emits drag deltas for free-form dragging.
118    fn draggable<C>(self, on_drag_delta: C) -> Modifier
119    where
120        C: Into<CallbackWith<DragDelta, ()>>;
121
122    /// Emits drag deltas with custom drag configuration.
123    fn draggable_with(self, args: DraggableArgs) -> Modifier;
124
125    /// Attaches accessibility semantics metadata to this subtree.
126    fn semantics(self, args: SemanticsArgs) -> Modifier;
127
128    /// Clears descendant semantics and applies the provided metadata.
129    fn clear_and_set_semantics(self, args: SemanticsArgs) -> Modifier;
130}
131
132impl ModifierExt for Modifier {
133    fn window_insets_padding(self, insets: tessera_ui::WindowInsets) -> Modifier {
134        self.push_layout(insets::InsetPadding {
135            insets,
136            consume_only: false,
137        })
138    }
139    fn consume_window_insets(self, insets: tessera_ui::WindowInsets) -> Modifier {
140        self.push_layout(insets::InsetPadding {
141            insets,
142            consume_only: true,
143        })
144    }
145    fn window_insets_left_width(self, insets: tessera_ui::WindowInsets) -> Modifier {
146        self.push_layout(insets::InsetSize { insets, edge: 0 })
147    }
148    fn window_insets_top_height(self, insets: tessera_ui::WindowInsets) -> Modifier {
149        self.push_layout(insets::InsetSize { insets, edge: 1 })
150    }
151    fn window_insets_right_width(self, insets: tessera_ui::WindowInsets) -> Modifier {
152        self.push_layout(insets::InsetSize { insets, edge: 2 })
153    }
154    fn window_insets_bottom_height(self, insets: tessera_ui::WindowInsets) -> Modifier {
155        self.push_layout(insets::InsetSize { insets, edge: 3 })
156    }
157
158    fn padding(self, padding: Padding) -> Modifier {
159        self.push_layout(PaddingModifierNode { padding })
160    }
161
162    fn padding_all(self, padding: Dp) -> Modifier {
163        self.padding(Padding::all(padding))
164    }
165
166    fn padding_symmetric(self, horizontal: Dp, vertical: Dp) -> Modifier {
167        self.padding(Padding::symmetric(horizontal, vertical))
168    }
169
170    fn offset(self, x: Dp, y: Dp) -> Modifier {
171        self.push_placement(OffsetModifierNode { x, y })
172    }
173
174    fn default_min_size(self, min_width: Dp, min_height: Dp) -> Modifier {
175        self.push_layout(DefaultMinSizeModifierNode {
176            min_width,
177            min_height,
178        })
179    }
180
181    fn size(self, width: Dp, height: Dp) -> Modifier {
182        let width_px: Px = width.into();
183        let height_px: Px = height.into();
184        self.push_layout(ConstraintModifierNode {
185            width_override: Some(AxisConstraint::exact(width_px)),
186            height_override: Some(AxisConstraint::exact(height_px)),
187            fill_width: false,
188            fill_height: false,
189            enforce_incoming: true,
190        })
191    }
192
193    fn required_size(self, width: Dp, height: Dp) -> Modifier {
194        let width_px: Px = width.into();
195        let height_px: Px = height.into();
196        self.push_layout(RequiredSizeModifierNode {
197            width: Some(AxisConstraint::exact(width_px)),
198            height: Some(AxisConstraint::exact(height_px)),
199        })
200    }
201
202    fn required_width(self, width: Dp) -> Modifier {
203        let width_px: Px = width.into();
204        self.push_layout(RequiredSizeModifierNode {
205            width: Some(AxisConstraint::exact(width_px)),
206            height: None,
207        })
208    }
209
210    fn required_height(self, height: Dp) -> Modifier {
211        let height_px: Px = height.into();
212        self.push_layout(RequiredSizeModifierNode {
213            width: None,
214            height: Some(AxisConstraint::exact(height_px)),
215        })
216    }
217    fn width(self, width: Dp) -> Modifier {
218        let width_px: Px = width.into();
219        self.push_layout(ConstraintModifierNode {
220            width_override: Some(AxisConstraint::exact(width_px)),
221            height_override: None,
222            fill_width: false,
223            fill_height: false,
224            enforce_incoming: true,
225        })
226    }
227
228    fn height(self, height: Dp) -> Modifier {
229        let height_px: Px = height.into();
230        self.push_layout(ConstraintModifierNode {
231            width_override: None,
232            height_override: Some(AxisConstraint::exact(height_px)),
233            fill_width: false,
234            fill_height: false,
235            enforce_incoming: true,
236        })
237    }
238
239    fn size_in(
240        self,
241        min_width: Option<Dp>,
242        max_width: Option<Dp>,
243        min_height: Option<Dp>,
244        max_height: Option<Dp>,
245    ) -> Modifier {
246        let width = AxisConstraint::new(
247            min_width.map(Into::into).unwrap_or(Px::ZERO),
248            max_width.map(Into::into),
249        );
250        let height = AxisConstraint::new(
251            min_height.map(Into::into).unwrap_or(Px::ZERO),
252            max_height.map(Into::into),
253        );
254        self.push_layout(ConstraintModifierNode {
255            width_override: Some(width),
256            height_override: Some(height),
257            fill_width: false,
258            fill_height: false,
259            enforce_incoming: true,
260        })
261    }
262
263    fn constrain(self, width: Option<AxisConstraint>, height: Option<AxisConstraint>) -> Modifier {
264        self.push_layout(ConstraintModifierNode {
265            width_override: width,
266            height_override: height,
267            fill_width: false,
268            fill_height: false,
269            enforce_incoming: false,
270        })
271    }
272
273    fn fill_max_width(self) -> Modifier {
274        self.push_layout(ConstraintModifierNode {
275            width_override: None,
276            height_override: None,
277            fill_width: true,
278            fill_height: false,
279            enforce_incoming: false,
280        })
281    }
282
283    fn fill_max_height(self) -> Modifier {
284        self.push_layout(ConstraintModifierNode {
285            width_override: None,
286            height_override: None,
287            fill_width: false,
288            fill_height: true,
289            enforce_incoming: false,
290        })
291    }
292
293    fn fill_max_size(self) -> Modifier {
294        self.push_layout(ConstraintModifierNode {
295            width_override: None,
296            height_override: None,
297            fill_width: true,
298            fill_height: true,
299            enforce_incoming: false,
300        })
301    }
302
303    fn minimum_interactive_component_size(self) -> Modifier {
304        self.push_layout(MinimumInteractiveModifierNode)
305    }
306
307    fn weight(self, weight: f32) -> Modifier {
308        self.push_parent_data(WeightParentDataModifierNode { weight })
309    }
310
311    fn align(self, alignment: Alignment) -> Modifier {
312        self.push_parent_data(AlignmentParentDataModifierNode { alignment })
313    }
314
315    fn draggable<C>(self, on_drag_delta: C) -> Modifier
316    where
317        C: Into<CallbackWith<DragDelta, ()>>,
318    {
319        self.draggable_with(DraggableArgs {
320            on_drag_delta: on_drag_delta.into(),
321            ..Default::default()
322        })
323    }
324
325    fn draggable_with(self, args: DraggableArgs) -> Modifier {
326        interaction::apply_draggable_modifier(self, args)
327    }
328
329    fn semantics(self, args: SemanticsArgs) -> Modifier {
330        semantics::apply_semantics_modifier(self, args)
331    }
332
333    fn clear_and_set_semantics(self, mut args: SemanticsArgs) -> Modifier {
334        args.merge_descendants = false;
335        semantics::apply_semantics_modifier(self, args)
336    }
337}