Skip to main content

swiftui_native/view/
appearance.rs

1//! Colour, background, effects and animation — what a view looks like.
2//!
3//! Part of [`crate::view::View`]'s chainable API; Rust allows a type's
4//! inherent methods to be split across modules, and 400 of them in one file
5//! was a file nobody could find anything in.
6
7use super::{Color, View};
8use crate::views;
9
10impl View {
11    // ── Colour and background ──
12
13    /// Apply `.background(_:)` with a color.
14    pub fn bg(self, c: Color) -> Self {
15        Self::new(views::background(&self.handle, c.r, c.g, c.b, c.a))
16    }
17
18    /// `.background { … }` — an arbitrary view behind this one, centred.
19    ///
20    /// SwiftUI has two `background` overloads and they are different
21    /// modifiers, not one with a convenience: [`View::bg`] reaches the one
22    /// generic over `ShapeStyle`, this reaches the one generic over `View`.
23    /// So the argument here can be a whole subtree, not only a colour.
24    pub fn bg_view(self, content: View) -> Self {
25        self.bg_view_aligned(content, views::Alignment::Center)
26    }
27
28    /// `.background(alignment:) { … }`
29    pub fn bg_view_aligned(self, content: View, alignment: views::Alignment) -> Self {
30        Self::new(views::background_content(&self.handle, content.handle(), alignment))
31    }
32
33    /// `.background(.regularMaterial)` and its neighbours — a blur of
34    /// whatever is behind the view rather than a colour over it.
35    pub fn bg_material(self, kind: views::MaterialKind) -> Self {
36        Self::new(views::background_material(&self.handle, kind))
37    }
38
39    /// `.background(LinearGradient(colors:startPoint:endPoint:))`
40    ///
41    /// The points are unit points: `(0, 0)` is top-leading and `(1, 1)`
42    /// bottom-trailing, so a top-to-bottom gradient is `(0.5, 0)`→`(0.5, 1)`.
43    pub fn bg_linear_gradient(self, colors: &[Color], start: (f64, f64), end: (f64, f64)) -> Self {
44        Self::new(views::background_linear_gradient(&self.handle, colors, start, end))
45    }
46
47    /// `.background(RadialGradient(colors:center:startRadius:endRadius:))`
48    pub fn bg_radial_gradient(
49        self,
50        colors: &[Color],
51        center: (f64, f64),
52        start_radius: f64,
53        end_radius: f64,
54    ) -> Self {
55        Self::new(views::background_radial_gradient(
56            &self.handle,
57            colors,
58            center,
59            start_radius,
60            end_radius,
61        ))
62    }
63
64    /// Apply `.opacity(_:)`.
65    ///
66    /// On a `Color` this is `Color.opacity(_:)`, which returns `Color` — the
67    /// same concrete-overload story as `Text.bold()`.
68    pub fn opacity(self, value: f64) -> Self {
69        Self::new(views::view_opacity(&self.handle, value))
70    }
71
72    /// Apply `.cornerRadius(_:)`.
73    pub fn rounded(self, radius: f64) -> Self {
74        Self::new(views::corner_radius(&self.handle, radius))
75    }
76
77    /// Apply `.border(_:width:)`.
78    ///
79    /// Through [`views::border_value`] rather than [`views::border`], because
80    /// the latter takes components and pins alpha to 1 — so a translucent border
81    /// came out opaque and nothing said so.
82    pub fn border(self, c: Color, width: f64) -> Self {
83        Self::new(views::border_value(&self.handle, views::color_value_of(c), width))
84    }
85
86    /// `.border(_:width:)` with one of SwiftUI's own colours.
87    pub fn border_system(self, c: views::SystemColor, width: f64) -> Self {
88        Self::new(views::border_value(&self.handle, views::system_color_value(c), width))
89    }
90
91    /// Apply `f` only when `condition` holds.
92    ///
93    /// Modifier chains often need a branch, and an `if` in the middle of a
94    /// builder chain otherwise forces a temporary binding.
95    ///
96    /// ```ignore
97    /// text("hi").when(selected, |v| v.bold().fg(Color::RED))
98    /// ```
99    pub fn when(self, condition: bool, f: impl FnOnce(Self) -> Self) -> Self {
100        if condition { f(self) } else { self }
101    }
102
103    /// Apply `f` with the contained value when `value` is `Some`.
104    pub fn when_some<T>(self, value: Option<T>, f: impl FnOnce(Self, T) -> Self) -> Self {
105        match value {
106            Some(v) => f(self, v),
107            None => self,
108        }
109    }
110
111    /// Apply one of two branches.
112    pub fn either(
113        self,
114        condition: bool,
115        yes: impl FnOnce(Self) -> Self,
116        no: impl FnOnce(Self) -> Self,
117    ) -> Self {
118        if condition { yes(self) } else { no(self) }
119    }
120
121    /// Shorthand: padding + background + corner radius.
122    pub fn card(self, pad: f64, bg: Color, radius: f64) -> Self {
123        self.padding(pad).bg(bg).rounded(radius)
124    }
125
126    // ── Effects ──
127
128    /// `.shadow(color:radius:x:y:)`
129    pub fn shadow(self, c: Color, radius: f64, dx: f64, dy: f64) -> Self {
130        Self::new(views::shadow(&self.handle, c, radius, dx, dy))
131    }
132
133    /// `.blur(radius:)`
134    pub fn blur(self, radius: f64) -> Self {
135        Self::new(views::blur(&self.handle, radius))
136    }
137
138    /// `.rotationEffect(.degrees(_))` about the centre.
139    pub fn rotate(self, degrees: f64) -> Self {
140        self.rotate_anchored(degrees, (0.5, 0.5))
141    }
142
143    /// `.rotationEffect(.degrees(_), anchor:)` — the point the view turns
144    /// about, as a unit point: `(0, 0)` is top-leading, `(0.5, 0.5)` the centre.
145    ///
146    /// Not a detail: a dial hand turns about its base and a card flips about an
147    /// edge, and neither is expressible by rotating about the middle.
148    pub fn rotate_anchored(self, degrees: f64, anchor: (f64, f64)) -> Self {
149        Self::new(views::rotation(&self.handle, degrees, anchor.0, anchor.1))
150    }
151
152    /// `.scaleEffect(_:)` about the centre.
153    pub fn scale(self, sx: f64, sy: f64) -> Self {
154        self.scale_anchored(sx, sy, (0.5, 0.5))
155    }
156
157    /// `.scaleEffect(_:anchor:)` — grow from a unit point rather than the middle.
158    pub fn scale_anchored(self, sx: f64, sy: f64, anchor: (f64, f64)) -> Self {
159        Self::new(views::scale(&self.handle, sx, sy, anchor.0, anchor.1))
160    }
161
162    /// `.offset(x:y:)`
163    pub fn offset(self, dx: f64, dy: f64) -> Self {
164        Self::new(views::offset(&self.handle, dx, dy))
165    }
166
167    /// `.brightness(_:)`
168    pub fn brightness(self, amount: f64) -> Self {
169        Self::new(views::brightness(&self.handle, amount))
170    }
171
172    /// `.saturation(_:)`
173    pub fn saturation(self, amount: f64) -> Self {
174        Self::new(views::saturation(&self.handle, amount))
175    }
176
177    /// `.contrast(_:)`
178    pub fn contrast(self, amount: f64) -> Self {
179        Self::new(views::contrast(&self.handle, amount))
180    }
181
182    /// `.grayscale(_:)`
183    pub fn grayscale(self, amount: f64) -> Self {
184        Self::new(views::grayscale(&self.handle, amount))
185    }
186
187    // ── Materials and glass ──
188
189    /// `.background(_: Material)`
190    pub fn material(self, kind: views::MaterialKind) -> Self {
191        Self::new(views::background_material(&self.handle, kind))
192    }
193
194    /// `.glassEffect()` — Liquid Glass.
195    pub fn glass(self) -> Self {
196        Self::new(views::glass_effect(&self.handle))
197    }
198
199    /// `.background(LinearGradient(…))`
200    pub fn gradient(self, colors: &[Color], start: (f64, f64), end: (f64, f64)) -> Self {
201        Self::new(views::background_linear_gradient(&self.handle, colors, start, end))
202    }
203
204    /// `.background(RadialGradient(…))`
205    pub fn radial_gradient(
206        self,
207        colors: &[Color],
208        center: (f64, f64),
209        start_radius: f64,
210        end_radius: f64,
211    ) -> Self {
212        Self::new(views::background_radial_gradient(
213            &self.handle, colors, center, start_radius, end_radius,
214        ))
215    }
216
217    /// `.tint(_:)`
218    pub fn tint(self, c: Color) -> Self {
219        Self::new(views::tint(&self.handle, c))
220    }
221
222    /// `.preferredColorScheme(_:)`
223    pub fn color_scheme(self, scheme: views::ColorScheme) -> Self {
224        Self::new(views::preferred_color_scheme(&self.handle, scheme))
225    }
226
227    /// `.controlSize(_:)`
228    pub fn control_size(self, size: views::ControlSize) -> Self {
229        Self::new(views::control_size(&self.handle, size))
230    }
231
232    /// `.buttonStyle(_:)`
233    pub fn button_style(self, style: views::ButtonStyle) -> Self {
234        Self::new(views::button_style(&self.handle, style))
235    }
236
237    /// `.backgroundStyle(_:)` with one of SwiftUI's semantic styles.
238    pub fn background_shape_style(self, style: views::ShapeStyle) -> Self {
239        Self::new(views::background_shape_style(&self.handle, style))
240    }
241
242    /// `.background(_.gradient)` — the automatic gradient derived from a colour.
243    pub fn bg_auto_gradient(self, c: Color) -> Self {
244        Self::new(views::background_any_gradient(&self.handle, views::color_value_of(c)))
245    }
246
247    /// `.background(in: .circle)`
248    pub fn bg_in_circle(self) -> Self {
249        Self::new(views::background_in_circle(&self.handle))
250    }
251
252    /// `.containerBackground(for:alignment:content:)` — the background a
253    /// container hands to its whole area, not just this view's bounds.
254    ///
255    /// See [`views::ContainerBackgroundPlacement`]: two of its cases trap on
256    /// macOS, which is SwiftUI's own availability rather than anything here.
257    pub fn container_background(
258        self,
259        placement: views::ContainerBackgroundPlacement,
260        alignment: views::Alignment,
261        content: View,
262    ) -> Self {
263        Self::new(views::container_background(
264            &self.handle,
265            placement,
266            alignment,
267            content.handle(),
268        ))
269    }
270
271    /// `.foregroundStyle(_:)` with an already-built colour, which is how a
272    /// system colour is passed — see [`views::system_color_value`].
273    pub fn fg_value(self, color: u64) -> Self {
274        Self::new(views::foreground_style_value(&self.handle, color))
275    }
276
277    /// `.foregroundStyle(_:_:)` — primary and secondary, for a symbol drawn in
278    /// two tones.
279    pub fn fg_pair(self, primary: u64, secondary: u64) -> Self {
280        Self::new(views::foreground_style_pair(&self.handle, primary, secondary))
281    }
282
283    /// `.foregroundStyle(_:_:_:)` — three tones.
284    ///
285    /// A palette symbol takes its layers from successive styles.
286    /// [`fg_pair`](Self::fg_pair) covers the two-layer symbols; the three-layer
287    /// ones — and there are plenty in SF Symbols — drew their third layer in
288    /// the secondary colour, because there was no way to say what it should be.
289    pub fn fg_triple(self, primary: u64, secondary: u64, tertiary: u64) -> Self {
290        Self::new(views::foreground_style_triple(
291            &self.handle,
292            primary,
293            secondary,
294            tertiary,
295        ))
296    }
297
298    /// `.tint(_:)` with an already-built colour.
299    pub fn tint_value(self, tint: u64) -> Self {
300        Self::new(views::tint_value(&self.handle, tint))
301    }
302
303    /// `.colorScheme(_:)` — force light or dark for this subtree, as distinct
304    /// from [`View::color_scheme`]'s *preferred* scheme, which asks the whole
305    /// window.
306    pub fn color_scheme_forced(self, value: views::ColorScheme) -> Self {
307        Self::new(views::color_scheme(&self.handle, value))
308    }
309
310    /// `.edgesIgnoringSafeArea(_:)` — the older spelling; see
311    /// [`View::ignores_safe_area`] for the one that takes regions too.
312    pub fn edges_ignoring_safe_area(self, edges: views::EdgeSet) -> Self {
313        Self::new(views::edges_ignoring_safe_area(&self.handle, edges))
314    }
315
316    /// `.persistentSystemOverlays(_:)` — the home indicator and its neighbours.
317    pub fn persistent_system_overlays(self, visibility: views::Visibility) -> Self {
318        Self::new(views::persistent_system_overlays(&self.handle, visibility))
319    }
320
321    // ── Symbols ──
322
323    /// `.symbolVariant(_:)` — `.fill`, `.circle` and so on, without renaming the
324    /// symbol.
325    pub fn symbol_variant(self, variant: views::SymbolVariants) -> Self {
326        Self::new(views::symbol_variant(&self.handle, variant))
327    }
328
329    /// `.symbolRenderingMode(_:)` — `None` restores the default.
330    pub fn symbol_rendering_mode(self, mode: Option<views::SymbolRenderingMode>) -> Self {
331        Self::new(views::symbol_rendering_mode(&self.handle, mode))
332    }
333
334    /// `.labelStyle(_:)` — icon only, title only, or both.
335    pub fn label_style(self, style: views::LabelStyle) -> Self {
336        Self::new(views::label_style(&self.handle, style))
337    }
338
339    /// `.menuStyle(_:)`
340    pub fn menu_style(self, style: views::MenuStyle) -> Self {
341        Self::new(views::menu_style(&self.handle, style))
342    }
343
344    /// `.buttonBorderShape(_:)`
345    pub fn button_border_shape(self, shape: views::ButtonBorderShape) -> Self {
346        Self::new(views::button_border_shape(&self.handle, shape))
347    }
348
349    /// `.scrollTargetBehavior(_:)` — `.paging` and friends.
350    pub fn scroll_target_behavior(self, behavior: views::ScrollTargetBehavior) -> Self {
351        Self::new(views::scroll_target_behavior(&self.handle, behavior))
352    }
353
354    /// `.scrollContentBackground(_:)` — hiding it is how a `List` loses its
355    /// grouped backdrop.
356    pub fn scroll_content_background(self, visibility: views::Visibility) -> Self {
357        Self::new(views::scroll_content_background(&self.handle, visibility))
358    }
359
360    /// `.symbolEffect(.pulse)`
361    pub fn symbol_effect_pulse(self) -> Self {
362        Self::new(views::symbol_effect_pulse(&self.handle))
363    }
364
365    /// `.symbolEffectsRemoved(_:)` — stop inherited effects at this view.
366    pub fn symbol_effects_removed(self, removed: bool) -> Self {
367        Self::new(views::symbol_effects_removed(&self.handle, removed))
368    }
369
370    /// `.symbolColorRenderingMode(.flat)`
371    pub fn symbol_color_rendering_mode_flat(self) -> Self {
372        Self::new(views::symbol_color_rendering_mode_flat(&self.handle))
373    }
374
375    /// `.symbolVariableValueMode(.color)`
376    pub fn symbol_variable_value_mode_color(self) -> Self {
377        Self::new(views::symbol_variable_value_mode_color(&self.handle))
378    }
379
380    // ── Animation ──
381
382    /// `.keyframeAnimator(initialValue:repeating:content:)` — drive a value
383    /// through keyframes and rebuild the content from it.
384    pub fn keyframe_animator(
385        self,
386        initial: f64,
387        to: f64,
388        repeating: bool,
389        content: View,
390    ) -> Self {
391        Self::new(views::keyframe_animator(
392            &self.handle,
393            initial,
394            to,
395            repeating,
396            content.handle(),
397        ))
398    }
399
400    /// `.phaseAnimator(_:content:)` — step through phases, rebuilding at each.
401    pub fn phase_animator(self, phases: &[i64], content: View, animated: bool) -> Self {
402        Self::new(views::phase_animator(&self.handle, phases, content.handle(), animated))
403    }
404
405    /// `.transaction(_:)` — reach the transaction driving the current change,
406    /// which is how an animation is suppressed for one update.
407    pub fn transaction(self, action: impl Fn(views::TransactionRef) + 'static) -> Self {
408        Self::new(views::transaction(&self.handle, action))
409    }
410
411    /// `.geometryGroup()` — animate this subtree's geometry as one, rather than
412    /// letting each child interpolate independently and pull apart.
413    pub fn geometry_group(self) -> Self {
414        Self::new(views::geometry_group(&self.handle))
415    }
416
417    /// `.transformEffect(_:)` — an affine transform, as `[a b c d tx ty]`.
418    pub fn transform_effect(self, a: f64, b: f64, c: f64, d: f64, tx: f64, ty: f64) -> Self {
419        Self::new(views::transform_effect(&self.handle, a, b, c, d, tx, ty))
420    }
421
422    /// `.visualEffect(_:)`
423    pub fn visual_effect(self) -> Self {
424        Self::new(views::visual_effect(&self.handle))
425    }
426
427}