Skip to main content

swiftui_native/views/
concrete.rs

1//! Concrete arguments: optionals, resilient enums, transforms.
2
3use super::*;
4use crate::abi;
5use crate::resolve;
6use core::ffi::c_void;
7use crate::mangle::{self, swift_type};
8
9// ── Concrete arguments: optionals, resilient enums, transforms ──
10
11/// [`opaque_modifier`] with one argument passed by address.
12///
13/// The fallback for everything a register cannot hold: a resilient type, whose
14/// size the caller is not allowed to know, and a loadable one that explodes
15/// into more than four registers — a `CGAffineTransform` is six doubles, and
16/// six will not fit.
17pub(crate) unsafe fn modifier_indirect(
18    f: *const c_void,
19    o: *const c_void,
20    v: &ViewHandle,
21    bytes: &[u8],
22) -> ViewHandle {
23    unsafe { opaque_modifier(f, o, v, &[bytes.as_ptr() as u64], [0.0; 4]) }
24}
25
26/// A resilient enum's case, in a buffer ready to pass by address.
27pub(crate) unsafe fn enum_bytes(ma: *const c_void, tag: u32) -> Vec<u8> {
28    unsafe {
29        let meta = abi::metadata0(ma);
30        let mut buf = vec![0u8; abi::type_bytes(meta)];
31        abi::inject_enum_tag(&mut buf, meta, tag);
32        buf
33    }
34}
35
36/// `Optional<E>` for a resilient enum, in a buffer ready to pass by address.
37pub(crate) unsafe fn opt_enum_bytes(ma: *const c_void, opt_mangled: &[u8], tag: Option<u32>) -> Vec<u8> {
38    unsafe {
39        let payload = abi::metadata0(ma);
40        let opt = abi::resolve_type_by_mangled_name(opt_mangled);
41        assert!(!opt.is_null(), "{}", crate::diag::demangle_type(opt_mangled));
42        let mut buf = vec![0u8; abi::type_bytes(opt)];
43        if let Some(t) = tag {
44            abi::inject_enum_tag(&mut buf[..abi::type_bytes(payload)], payload, t);
45        }
46        abi::store_optional_tag(&mut buf, payload, tag.is_some());
47        buf
48    }
49}
50
51/// An `Optional<T>` as the integer registers it travels in.
52///
53/// The payload is *not* destructured. `Optional<UnitPoint>` is two `CGFloat`s
54/// and a discriminator, and the obvious reading — two float registers and an
55/// integer one — faults: an optional is an enum, and an enum with a payload is
56/// lowered to its raw storage rather than to its fields, so the whole seventeen
57/// bytes travel as three integer registers. A `Double` inside an optional
58/// reaches the callee in x0, not d0.
59///
60/// Payload bytes go in `bytes`, already laid out; `is_some` decides the tag.
61/// Past four words the value would be passed by address instead, which is what
62/// [`modifier_indirect`] is for.
63pub(crate) unsafe fn optional_words(
64    payload_meta: *const c_void,
65    opt_meta: *const c_void,
66    bytes: &[u8],
67    is_some: bool,
68) -> Vec<u64> {
69    unsafe {
70        let stride = abi::type_bytes(opt_meta);
71        let mut buf = vec![0u8; stride.max(8)];
72        if is_some {
73            buf[..bytes.len()].copy_from_slice(bytes);
74        }
75        abi::store_optional_tag(&mut buf, payload_meta, is_some);
76        let words = abi::type_size(opt_meta).div_ceil(8);
77        (0..words)
78            .map(|i| u64::from_le_bytes(buf[i * 8..i * 8 + 8].try_into().expect("word")))
79            .collect()
80    }
81}
82
83/// `Optional<Double>` as the two integer registers it travels in.
84pub(crate) unsafe fn opt_f64_words(v: Option<f64>) -> Vec<u64> {
85    unsafe {
86        let payload = resolve::double_meta();
87        let opt = abi::resolve_type_by_mangled_name(mangle::optional(mangle::DOUBLE).as_bytes());
88        optional_words(payload, opt, &v.unwrap_or(0.0).to_le_bytes(), v.is_some())
89    }
90}
91
92/// `Optional<UnitPoint>` as the three integer registers it travels in.
93pub(crate) unsafe fn opt_unit_point_words(v: Option<(f64, f64)>) -> Vec<u64> {
94    unsafe {
95        let payload = crate::type_by_name!(swift_type!(SwiftUI.UnitPoint).as_bytes());
96        let opt = crate::type_by_name!(swift_type!(opt SwiftUI.UnitPoint).as_bytes());
97        let (x, y) = v.unwrap_or((0.0, 0.0));
98        let mut bytes = [0u8; 16];
99        bytes[..8].copy_from_slice(&x.to_le_bytes());
100        bytes[8..].copy_from_slice(&y.to_le_bytes());
101        optional_words(payload, opt, &bytes, v.is_some())
102    }
103}
104
105/// `Optional<String>` as the two words it travels in.
106///
107/// A `String` has spare bits, so `nil` is a bit pattern inside the existing
108/// sixteen rather than a byte alongside them.
109pub(crate) unsafe fn opt_string_words(v: Option<&str>) -> [u64; 2] {
110    unsafe {
111        let payload = resolve::string_meta();
112        let mut buf = [0u8; 16];
113        if let Some(s) = v {
114            buf.copy_from_slice(&abi::swift_string(s));
115        }
116        abi::store_optional_tag(&mut buf, payload, v.is_some());
117        [
118            u64::from_le_bytes(buf[..8].try_into().expect("string lo")),
119            u64::from_le_bytes(buf[8..].try_into().expect("string hi")),
120        ]
121    }
122}
123
124swift_cases! {
125    /// `SwiftUI.Image.Scale`.
126    pub enum ImageScale {
127        Small = 0,
128        Medium = 1,
129        Large = 2,
130    }
131}
132
133swift_cases! {
134    /// `SwiftUI.Text.TruncationMode`.
135    pub enum TruncationMode {
136        Head = 0,
137        Tail = 1,
138        Middle = 2,
139    }
140}
141
142swift_cases! {
143    /// `SwiftUI.Text.Case`.
144    pub enum TextCase {
145        Uppercase = 0,
146        Lowercase = 1,
147    }
148}
149
150swift_statics! {
151    /// `SwiftUI.HorizontalAlignment`, a frozen word-sized value.
152    pub enum HorizontalAlignment {
153        Leading = c"$s7SwiftUI19HorizontalAlignmentV7leadingACvgZ",
154        Center = c"$s7SwiftUI19HorizontalAlignmentV6centerACvgZ",
155        Trailing = c"$s7SwiftUI19HorizontalAlignmentV8trailingACvgZ",
156        ListRowSeparatorLeading = c"$s7SwiftUI19HorizontalAlignmentV23listRowSeparatorLeadingACvgZ",
157        ListRowSeparatorTrailing = c"$s7SwiftUI19HorizontalAlignmentV24listRowSeparatorTrailingACvgZ",
158    }
159}
160
161swift_statics! {
162    /// `SwiftUI.VerticalAlignment`, a frozen word-sized value.
163    ///
164    /// What an `HStack` aligns its children on, the mirror of
165    /// [`HorizontalAlignment`] for a `VStack`. The two baseline cases align on
166    /// text rather than on the box: `FirstTextBaseline` lines up the first
167    /// lines of each child, which is what makes a row of differently-sized
168    /// labels read as one line.
169    pub enum VerticalAlignment {
170        Top = c"$s7SwiftUI17VerticalAlignmentV3topACvgZ",
171        Center = c"$s7SwiftUI17VerticalAlignmentV6centerACvgZ",
172        Bottom = c"$s7SwiftUI17VerticalAlignmentV6bottomACvgZ",
173        FirstTextBaseline = c"$s7SwiftUI17VerticalAlignmentV17firstTextBaselineACvgZ",
174        LastTextBaseline = c"$s7SwiftUI17VerticalAlignmentV16lastTextBaselineACvgZ",
175    }
176}
177
178swift_statics! {
179    /// `SwiftUI.ScrollAnchorRole`.
180    pub enum ScrollAnchorRole {
181        Alignment = c"$s7SwiftUI16ScrollAnchorRoleV9alignmentACvgZ",
182        SizeChanges = c"$s7SwiftUI16ScrollAnchorRoleV11sizeChangesACvgZ",
183        InitialOffset = c"$s7SwiftUI16ScrollAnchorRoleV13initialOffsetACvgZ",
184    }
185}
186
187/// Apply `.accentColor(_:)`.
188pub fn accent_color(view: &ViewHandle, color: Option<crate::Color>) -> ViewHandle {
189    unsafe {
190        modifier_opt_color(resolve::accent_color_fn(), resolve::accent_color_oret(), view, color)
191    }
192}
193
194/// Apply `.foregroundColor(_:)`, the deprecated environment-writing one.
195///
196/// Not [`foreground_style_color`]: this produces
197/// `_EnvironmentKeyWritingModifier<Color?>` and that produces
198/// `_ForegroundStyleModifier<Color>`. They render the same.
199/// Apply `.foregroundColor(_:)` to a `Color` that is already built.
200///
201/// For one of SwiftUI's named colours, which no set of components can spell.
202/// See [`super::system_color`].
203pub fn foreground_color_value(view: &ViewHandle, color: u64) -> ViewHandle {
204    unsafe {
205        opaque_modifier(
206            resolve::foreground_color_fn(),
207            resolve::foreground_color_oret(),
208            view,
209            &[color],
210            [0.0; 4],
211        )
212    }
213}
214
215/// Apply `.foregroundColor(_:)`.
216pub fn foreground_color(view: &ViewHandle, color: Option<crate::Color>) -> ViewHandle {
217    unsafe {
218        modifier_opt_color(
219            resolve::foreground_color_fn(),
220            resolve::foreground_color_oret(),
221            view,
222            color,
223        )
224    }
225}
226
227/// Apply `.colorMultiply(_:)`.
228pub fn color_multiply(view: &ViewHandle, color: crate::Color) -> ViewHandle {
229    unsafe {
230        let word = color_value(color.r, color.g, color.b, color.a);
231        opaque_modifier(
232            resolve::color_multiply_fn(),
233            resolve::color_multiply_oret(),
234            view,
235            &[word],
236            [0.0; 4],
237        )
238    }
239}
240
241/// Apply `.allowsWindowActivationEvents(_:)`.
242pub fn allows_window_activation_events(view: &ViewHandle, allows: Option<bool>) -> ViewHandle {
243    unsafe {
244        modifier_opt_bool(
245            resolve::allows_window_activation_fn(),
246            resolve::allows_window_activation_oret(),
247            view,
248            allows,
249        )
250    }
251}
252
253/// Apply `.dialogPreventsAppTermination(_:)`.
254pub fn dialog_prevents_app_termination(view: &ViewHandle, prevents: Option<bool>) -> ViewHandle {
255    unsafe {
256        modifier_opt_bool(
257            resolve::dialog_prevents_termination_fn(),
258            resolve::dialog_prevents_termination_oret(),
259            view,
260            prevents,
261        )
262    }
263}
264
265/// Apply `.disableAutocorrection(_:)`.
266pub fn disable_autocorrection(view: &ViewHandle, disable: Option<bool>) -> ViewHandle {
267    unsafe {
268        modifier_opt_bool(
269            resolve::disable_autocorrection_fn(),
270            resolve::disable_autocorrection_oret(),
271            view,
272            disable,
273        )
274    }
275}
276
277/// Apply `.presentationPreventsAppTermination(_:)`.
278pub fn presentation_prevents_app_termination(
279    view: &ViewHandle,
280    prevents: Option<bool>,
281) -> ViewHandle {
282    unsafe {
283        modifier_opt_bool(
284            resolve::presentation_prevents_termination_fn(),
285            resolve::presentation_prevents_termination_oret(),
286            view,
287            prevents,
288        )
289    }
290}
291
292/// Apply `.previewDisplayName(_:)`.
293pub fn preview_display_name(view: &ViewHandle, name: Option<&str>) -> ViewHandle {
294    unsafe {
295        let w = opt_string_words(name);
296        opaque_modifier(
297            resolve::preview_display_name_fn(),
298            resolve::preview_display_name_oret(),
299            view,
300            &w,
301            [0.0; 4],
302        )
303    }
304}
305
306/// Apply `.hueRotation(_:)`, in radians.
307///
308/// An `Angle` stores radians and nothing else, so it is one frozen double and
309/// travels in d0 rather than by address.
310pub fn hue_rotation(view: &ViewHandle, radians: f64) -> ViewHandle {
311    unsafe {
312        modifier_f64(resolve::hue_rotation_fn(), resolve::hue_rotation_oret(), view, radians)
313    }
314}
315
316/// Apply `.gridCellAnchor(_:)`.
317pub fn grid_cell_anchor(view: &ViewHandle, x: f64, y: f64) -> ViewHandle {
318    unsafe {
319        opaque_modifier(
320            resolve::grid_cell_anchor_fn(),
321            resolve::grid_cell_anchor_oret(),
322            view,
323            &[],
324            [x, y, 0.0, 0.0],
325        )
326    }
327}
328
329/// Apply `.presentationCornerRadius(_:)`.
330pub fn presentation_corner_radius(view: &ViewHandle, radius: Option<f64>) -> ViewHandle {
331    unsafe {
332        let w = opt_f64_words(radius);
333        opaque_modifier(
334            resolve::presentation_corner_radius_fn(),
335            resolve::presentation_corner_radius_oret(),
336            view,
337            &w,
338            [0.0; 4],
339        )
340    }
341}
342
343/// Apply `.imageScale(_:)`.
344pub fn image_scale(view: &ViewHandle, scale: ImageScale) -> ViewHandle {
345    unsafe {
346        let b = enum_bytes(resolve::image_scale_ma(), scale.tag());
347        modifier_indirect(resolve::image_scale_fn(), resolve::image_scale_oret(), view, &b)
348    }
349}
350
351/// Apply `.truncationMode(_:)`.
352pub fn truncation_mode(view: &ViewHandle, mode: TruncationMode) -> ViewHandle {
353    unsafe {
354        let b = enum_bytes(resolve::truncation_mode_ma(), mode.tag());
355        modifier_indirect(resolve::truncation_mode_fn(), resolve::truncation_mode_oret(), view, &b)
356    }
357}
358
359/// Apply `.textCase(_:)`.
360pub fn text_case(view: &ViewHandle, case: Option<TextCase>) -> ViewHandle {
361    unsafe {
362        let b = opt_enum_bytes(
363            resolve::text_case_ma(),
364            swift_type!(opt enum SwiftUI.Text.Case).as_bytes(),
365            case.map(TextCase::tag),
366        );
367        modifier_indirect(resolve::text_case_fn(), resolve::text_case_oret(), view, &b)
368    }
369}
370
371/// Apply `.gridColumnAlignment(_:)`.
372pub fn grid_column_alignment(view: &ViewHandle, alignment: HorizontalAlignment) -> ViewHandle {
373    unsafe {
374        let meta = abi::metadata0(resolve::horizontal_alignment_ma());
375        let a = frozen_static(meta, alignment.getter());
376        opaque_modifier(
377            resolve::grid_column_alignment_fn(),
378            resolve::grid_column_alignment_oret(),
379            view,
380            &[a],
381            [0.0; 4],
382        )
383    }
384}
385
386/// Apply `.transformEffect(_:)` with a `CGAffineTransform`'s six components.
387pub fn transform_effect(
388    view: &ViewHandle,
389    a: f64,
390    b: f64,
391    c: f64,
392    d: f64,
393    tx: f64,
394    ty: f64,
395) -> ViewHandle {
396    unsafe {
397        let mut bytes = [0u8; 48];
398        for (i, v) in [a, b, c, d, tx, ty].iter().enumerate() {
399            bytes[i * 8..(i + 1) * 8].copy_from_slice(&v.to_le_bytes());
400        }
401        modifier_indirect(
402            resolve::transform_effect_fn(),
403            resolve::transform_effect_oret(),
404            view,
405            &bytes,
406        )
407    }
408}
409
410/// Apply `.projectionEffect(_:)` with a 3×3 matrix in row-major order.
411pub fn projection_effect(view: &ViewHandle, m: [f64; 9]) -> ViewHandle {
412    unsafe {
413        let mut bytes = [0u8; 72];
414        for (i, v) in m.iter().enumerate() {
415            bytes[i * 8..(i + 1) * 8].copy_from_slice(&v.to_le_bytes());
416        }
417        modifier_indirect(
418            resolve::projection_effect_fn(),
419            resolve::projection_effect_oret(),
420            view,
421            &bytes,
422        )
423    }
424}
425
426/// Apply `.listRowInsets(_:)`.
427pub fn list_row_insets(view: &ViewHandle, insets: Option<[f64; 4]>) -> ViewHandle {
428    unsafe {
429        let payload = abi::metadata0(resolve::edge_insets_ma());
430        let opt = crate::type_by_name!(swift_type!(opt SwiftUI.EdgeInsets).as_bytes());
431        let mut bytes = vec![0u8; abi::type_bytes(opt)];
432        if let Some(v) = insets {
433            for (i, x) in v.iter().enumerate() {
434                bytes[i * 8..(i + 1) * 8].copy_from_slice(&x.to_le_bytes());
435            }
436        }
437        abi::store_optional_tag(&mut bytes, payload, insets.is_some());
438        modifier_indirect(resolve::list_row_insets_fn(), resolve::list_row_insets_oret(), view, &bytes)
439    }
440}
441
442/// Apply `.windowResizeAnchor(_:)`.
443pub fn window_resize_anchor(view: &ViewHandle, anchor: Option<(f64, f64)>) -> ViewHandle {
444    unsafe {
445        let w = opt_unit_point_words(anchor);
446        opaque_modifier(
447            resolve::window_resize_anchor_fn(),
448            resolve::window_resize_anchor_oret(),
449            view,
450            &w,
451            [0.0; 4],
452        )
453    }
454}
455
456/// Apply `.defaultScrollAnchor(_:for:)`.
457pub fn default_scroll_anchor(
458    view: &ViewHandle,
459    anchor: Option<(f64, f64)>,
460    role: ScrollAnchorRole,
461) -> ViewHandle {
462    unsafe {
463        let mut w = opt_unit_point_words(anchor);
464        let meta = abi::metadata0(resolve::scroll_anchor_role_ma());
465        let r = resilient_static(meta, role.getter());
466        w.push(r.as_ptr() as u64);
467        opaque_modifier(
468            resolve::default_scroll_anchor_fn(),
469            resolve::default_scroll_anchor_oret(),
470            view,
471            &w,
472            [0.0; 4],
473        )
474    }
475}
476
477#[cfg(test)]
478mod optional_abi_tests {
479    use super::*;
480
481    /// An optional's payload is not destructured into float registers.
482    ///
483    /// This is the rule that four modifiers faulted on. `Optional<UnitPoint>`
484    /// is seventeen bytes of `CGFloat`, `CGFloat`, tag — and it arrives as
485    /// three *integer* registers, because an enum with a payload is lowered to
486    /// its raw storage rather than to the payload's fields.
487    #[test]
488    fn optional_payloads_travel_as_raw_words() {
489        unsafe {
490            let w = opt_unit_point_words(Some((0.3, 0.6)));
491            assert_eq!(w.len(), 3, "Optional<UnitPoint> is three words");
492            assert_eq!(w[0], 0.3f64.to_bits(), "x is raw bits, not a float register");
493            assert_eq!(w[1], 0.6f64.to_bits(), "y is raw bits, not a float register");
494            assert_eq!(w[2] & 0xff, 0, "some");
495
496            let n = opt_unit_point_words(None);
497            assert_eq!(n.len(), 3);
498            assert_ne!(n[2] & 0xff, 0, "none");
499
500            let d = opt_f64_words(Some(12.0));
501            assert_eq!(d.len(), 2, "Optional<Double> is two words");
502            assert_eq!(d[0], 12.0f64.to_bits());
503        }
504    }
505
506    /// `Optional<Bool>` spends one of a byte's spare values on `nil`, so the
507    /// whole thing is one register rather than a payload plus a tag.
508    #[test]
509    fn optional_bool_is_one_byte() {
510        unsafe {
511            let m = abi::resolve_type_by_mangled_name(mangle::optional(mangle::BOOL).as_bytes());
512            assert_eq!(abi::type_bytes(m), 1);
513            let mut buf = [0u8; 1];
514            buf[0] = 1;
515            abi::store_optional_tag(&mut buf, resolve::bool_meta(), true);
516            assert_eq!(buf[0], 1, "Some(true)");
517            abi::store_optional_tag(&mut buf, resolve::bool_meta(), false);
518            assert!(buf[0] > 1, "nil is a value a Bool cannot hold");
519        }
520    }
521
522    /// `Color` is `@frozen` and one word, so it is passed by value. Sending its
523    /// address instead is what the first draft of this did, and it faulted.
524    #[test]
525    fn color_is_one_frozen_word() {
526        unsafe {
527            let m = crate::type_by_name!(swift_type!(SwiftUI.Color).as_bytes());
528            assert_eq!(abi::type_bytes(m), 8);
529            let o = crate::type_by_name!(swift_type!(opt SwiftUI.Color).as_bytes());
530            assert_eq!(abi::type_bytes(o), 8, "a null reference is already nil");
531        }
532    }
533}