Skip to main content

swiftui_native/views/
layout.rs

1//! Layout and compositing modifiers.
2
3use super::*;
4use crate::abi;
5use crate::resolve;
6use core::ffi::c_void;
7use crate::mangle::{self, swift_type};
8
9// ── Layout and compositing modifiers ──
10
11mangle::modifier_types! {
12    /// `ModifiedContent<AnyView, _AspectRatioLayout>`
13    pub(crate) ASPECT_T = ty("_AspectRatioLayout");
14    /// `ModifiedContent<AnyView, _FixedSizeLayout>`
15    pub(crate) FIXED_SIZE_T = ty("_FixedSizeLayout");
16    /// `ModifiedContent<AnyView, _TraitWritingModifier<ZIndexTraitKey>>`
17    pub(crate) Z_INDEX_T = ty("_TraitWritingModifier").of(vec![ty("ZIndexTraitKey")]);
18    /// `ModifiedContent<AnyView, _EnvironmentKeyTransformModifier<Bool>>`
19    pub(crate) DISABLED_T = ty("_EnvironmentKeyTransformModifier").of(vec![Type::Verbatim("Sb".into())]);
20    /// `ModifiedContent<AnyView, _HiddenModifier>`
21    pub(crate) HIDDEN_T = ty("_HiddenModifier");
22    /// `ModifiedContent<AnyView, _BlendModeEffect>`
23    pub(crate) BLEND_T = ty("_BlendModeEffect");
24    /// `ModifiedContent<AnyView, _OverlayModifier<AnyView>>`
25    pub(crate) OVERLAY_ANY_T = ty("_OverlayModifier").of(vec![ty("AnyView")]);
26    /// `ModifiedContent<AnyView, _BackgroundModifier<AnyView>>`
27    pub(crate) BACKGROUND_ANY_T = ty("_BackgroundModifier").of(vec![ty("AnyView")]);
28    /// `ModifiedContent<AnyView, _MaskAlignmentEffect<AnyView>>`
29    pub(crate) MASK_ANY_T = ty("_MaskAlignmentEffect").of(vec![ty("AnyView")]);
30    /// `.clipShape(_:)` results, one per shape.
31    /// `ModifiedContent<AnyView, _ClipEffect<Circle>>`
32    pub(crate) CLIP_CIRCLE_T = ty("_ClipEffect").of(vec![ty("Circle")]);
33    /// `ModifiedContent<AnyView, _ClipEffect<Capsule>>`
34    pub(crate) CLIP_CAPSULE_T = ty("_ClipEffect").of(vec![ty("Capsule")]);
35    /// `ModifiedContent<AnyView, _ClipEffect<Ellipse>>`
36    pub(crate) CLIP_ELLIPSE_T = ty("_ClipEffect").of(vec![ty("Ellipse")]);
37    /// `ModifiedContent<AnyView, _ClipEffect<RoundedRectangle>>`
38    pub(crate) CLIP_RRECT_T = ty("_ClipEffect").of(vec![ty("RoundedRectangle")]);
39}
40
41
42
43
44
45
46
47
48/// `SwiftUI.ContentMode` — whether content fits inside its frame or fills it.
49///
50/// The enum `aspectRatio` and `Image.resizable` take. It is frozen and one byte,
51/// so the case number *is* the ABI: `fit` is 0 and `fill` is 1, which is what
52/// [`aspect_ratio`]'s `bool` has always passed. This gives it its name back,
53/// because `aspect_ratio(&v, 1.5, true)` does not read as "fill".
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
55pub enum ContentMode {
56    /// Scale to fit inside the frame, leaving empty space along one axis.
57    #[default]
58    Fit,
59    /// Scale to fill the frame, cropping along one axis.
60    Fill,
61}
62
63/// `SwiftUI.EdgeInsets` — four insets, one per edge.
64///
65/// Four `CGFloat`s and nothing else, which makes it a homogeneous float
66/// aggregate: it travels in d0–d3 rather than by address, and comes back the
67/// same way. That is why this is a `#[repr(C)]` struct of `f64` and not a
68/// tuple — a Rust tuple is not `repr(C)` and would be returned through memory.
69#[derive(Clone, Copy, Debug, Default, PartialEq)]
70#[repr(C)]
71pub struct EdgeInsets {
72    /// The inset from the top edge.
73    pub top: f64,
74    /// The inset from the leading edge — left in a left-to-right layout.
75    pub leading: f64,
76    /// The inset from the bottom edge.
77    pub bottom: f64,
78    /// The inset from the trailing edge.
79    pub trailing: f64,
80}
81
82impl EdgeInsets {
83    /// The same inset on all four edges.
84    pub fn all(amount: f64) -> Self {
85        Self { top: amount, leading: amount, bottom: amount, trailing: amount }
86    }
87
88    /// `EdgeInsets(top:leading:bottom:trailing:)`, built by SwiftUI itself.
89    ///
90    /// The four fields are laid out exactly as this struct is, so constructing
91    /// one in Rust would give the same bytes — this calls the initialiser anyway,
92    /// because "the same bytes" is an assumption about a resilient type's layout
93    /// and the initialiser is the thing that is guaranteed.
94    pub fn from_swift(top: f64, leading: f64, bottom: f64, trailing: f64) -> Self {
95        unsafe {
96            let (a, b, c, d): (f64, f64, f64, f64);
97            core::arch::asm!("blr {f}", f = in(reg) resolve::edge_insets_init(),
98                inlateout("d0") top => a,
99                inlateout("d1") leading => b,
100                inlateout("d2") bottom => c,
101                inlateout("d3") trailing => d,
102                lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
103                lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
104                lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
105                lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
106                lateout("x16") _, lateout("x17") _, lateout("lr") _,
107                lateout("x20") _, lateout("x21") _, clobber_abi("C"),
108            );
109            Self { top: a, leading: b, bottom: c, trailing: d }
110        }
111    }
112}
113
114/// `.padding(_: EdgeInsets)` — a different inset per edge.
115///
116/// [`padding_set`] takes one length for a set of edges; this takes four. The
117/// insets go in d0–d3, being a homogeneous float aggregate.
118pub fn padding_insets(view: &ViewHandle, insets: EdgeInsets) -> ViewHandle {
119    unsafe {
120        let (meta, wt, size) = super::opaque_result_type(
121            resolve::padding_insets_oret(),
122            &[view.meta, view.wt],
123        );
124        let mut result = vec![0u8; size];
125        let this = view.value_ptr();
126        core::arch::asm!("blr {f}", f = in(reg) resolve::padding_insets_fn(),
127            in("d0") insets.top,
128            in("d1") insets.leading,
129            in("d2") insets.bottom,
130            in("d3") insets.trailing,
131            in("x0") view.meta,
132            in("x1") view.wt,
133            in("x8") result.as_mut_ptr(),
134            in("x20") this,
135            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
136            lateout("x6") _, lateout("x7") _,
137            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
138            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
139            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
140        );
141        super::wrap_owned(result, meta, wt)
142    }
143}
144
145/// `.aspectRatio(_:contentMode:)` with the mode named rather than spelled as a
146/// `bool`.
147///
148/// `None` for the ratio means "keep whatever the content already has", which is
149/// not the same as `Some(1.0)`.
150pub fn aspect_ratio_content_mode(
151    view: &ViewHandle,
152    ratio: Option<f64>,
153    mode: ContentMode,
154) -> ViewHandle {
155    match ratio {
156        Some(r) => aspect_ratio(view, r, mode == ContentMode::Fill),
157        None => aspect_ratio_mode(view, mode == ContentMode::Fill),
158    }
159}
160
161/// Apply `.aspectRatio(_:contentMode:)`. `fit` is case 0, `fill` is case 1.
162pub fn aspect_ratio(view: &ViewHandle, ratio: f64, fill: bool) -> ViewHandle {
163    unsafe {
164        let (_, _, size) = ASPECT_T.applied_to(view.meta);
165        // Optional<CGFloat>: raw bits then tag (0 == some), then ContentMode.
166        let r = abi::call_modifier(resolve::aspect_ratio_fn(), view.value_ptr(), view.meta, view.wt,
167            &[ratio.to_bits(), 0, u64::from(fill)],
168            [0.0; 4],
169            size,
170        );
171        wrap_modified(r, &ASPECT_T, view)
172    }
173}
174
175/// Apply `.aspectRatio(contentMode:)` — the spelling with no ratio.
176///
177/// Same entry point as [`aspect_ratio`] with the `Optional<CGFloat>` set to
178/// `nil`: tag 1, payload ignored. It means "keep whatever ratio the content
179/// already has", which is not the same as passing 1.0.
180pub fn aspect_ratio_mode(view: &ViewHandle, fill: bool) -> ViewHandle {
181    unsafe {
182        let (_, _, size) = ASPECT_T.applied_to(view.meta);
183        let r = abi::call_modifier(resolve::aspect_ratio_fn(), view.value_ptr(), view.meta, view.wt,
184            &[0, 1, u64::from(fill)],
185            [0.0; 4],
186            size,
187        );
188        wrap_modified(r, &ASPECT_T, view)
189    }
190}
191
192/// Apply `.fixedSize(horizontal:vertical:)`.
193pub fn fixed_size(view: &ViewHandle, horizontal: bool, vertical: bool) -> ViewHandle {
194    unsafe {
195        let (_, _, size) = FIXED_SIZE_T.applied_to(view.meta);
196        let r = abi::call_modifier(resolve::fixed_size_fn(), view.value_ptr(), view.meta, view.wt,
197            &[u64::from(horizontal), u64::from(vertical)],
198            [0.0; 4],
199            size,
200        );
201        wrap_modified(r, &FIXED_SIZE_T, view)
202    }
203}
204
205/// Apply `.zIndex(_:)`.
206pub fn z_index(view: &ViewHandle, value: f64) -> ViewHandle {
207    unsafe {
208        let (_, _, size) = Z_INDEX_T.applied_to(view.meta);
209        let r = abi::call_modifier(resolve::z_index_fn(), view.value_ptr(), view.meta, view.wt, &[], [value, 0.0, 0.0, 0.0], size);
210        wrap_modified(r, &Z_INDEX_T, view)
211    }
212}
213
214/// Apply `.disabled(_:)`.
215pub fn disabled(view: &ViewHandle, value: bool) -> ViewHandle {
216    unsafe {
217        let (_, _, size) = DISABLED_T.applied_to(view.meta);
218        let r =
219            abi::call_modifier(resolve::disabled_fn(), view.value_ptr(), view.meta, view.wt, &[u64::from(value)], [0.0; 4], size);
220        wrap_modified(r, &DISABLED_T, view)
221    }
222}
223
224/// Apply `.hidden()`.
225pub fn hidden(view: &ViewHandle) -> ViewHandle {
226    unsafe {
227        let (_, _, size) = HIDDEN_T.applied_to(view.meta);
228        let r = abi::call_modifier(resolve::hidden_fn(), view.value_ptr(), view.meta, view.wt, &[], [0.0; 4], size);
229        wrap_modified(r, &HIDDEN_T, view)
230    }
231}
232
233/// Apply `.blendMode(_:)`. The cases follow `SwiftUI.BlendMode`'s declaration order.
234///
235/// `BlendMode` is a resilient enum, so it is address-only and passed by pointer.
236pub fn blend_mode(view: &ViewHandle, mode: u8) -> ViewHandle {
237    unsafe {
238        let (_, _, size) = BLEND_T.applied_to(view.meta);
239        let mut buf = [mode];
240        let r = abi::call_modifier(resolve::blend_mode_fn(), view.value_ptr(), view.meta, view.wt,
241            &[buf.as_mut_ptr() as u64],
242            [0.0; 4],
243            size,
244        );
245        wrap_modified(r, &BLEND_T, view)
246    }
247}
248
249
250
251
252
253
254/// Apply `.clipShape(_:style:)`.
255///
256/// `shape_bytes` is the shape value, `shape_mangled` names its type, and
257/// `modifier` is the `_ClipEffect` the result wraps the receiver in.
258pub(crate) unsafe fn clip_shape_impl(
259    view: &ViewHandle,
260    shape_bytes: &[u8],
261    shape_mangled: &[u8],
262    modifier: &ModifierType,
263) -> ViewHandle {
264    unsafe {
265        let shape_meta = abi::resolve_type_by_mangled_name(shape_mangled);
266        assert!(!shape_meta.is_null(), "shape metadata");
267        let shape_wt = abi::get_view_wt_for_proto(shape_meta, resolve::shape_proto());
268        assert!(!shape_wt.is_null(), "shape: Shape");
269
270        let mut shape_buf = vec![0u8; abi::type_bytes(shape_meta)];
271        shape_buf[..shape_bytes.len()].copy_from_slice(shape_bytes);
272
273        let (_, _, size) = modifier.applied_to(view.meta);
274        let mut result = vec![0u8; size];
275        let this = view.value_ptr();
276        // clipShape<S: Shape>(_: S, style: FillStyle)
277        //   x0 = @in shape, x1 = FillStyle (both flags packed into one word:
278        //        low byte isEOFilled, next byte isAntialiased),
279        //   x2 = Self.Type, x3 = S.Type, x4 = Self: View WT, x5 = S: Shape WT
280        core::arch::asm!("blr {f}", f = in(reg) resolve::clip_shape_fn(),
281            in("x0") shape_buf.as_mut_ptr(),
282            in("x1") 0x0100u64, // eoFill: false, antialiased: true
283            in("x2") view.meta,
284            in("x3") shape_meta,
285            in("x4") view.wt,
286            in("x5") shape_wt,
287            in("x8") result.as_mut_ptr(),
288            in("x20") this,
289            lateout("x6") _, lateout("x7") _,
290            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
291            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
292            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
293        );
294        wrap_modified(result, modifier, view)
295    }
296}
297
298/// Apply `.clipShape(Circle())`.
299pub fn clip_circle(view: &ViewHandle) -> ViewHandle {
300    unsafe { clip_shape_impl(view, &[], swift_type!(SwiftUI.Circle).as_bytes(), &CLIP_CIRCLE_T) }
301}
302
303/// Apply `.clipShape(Ellipse())`.
304pub fn clip_ellipse(view: &ViewHandle) -> ViewHandle {
305    unsafe { clip_shape_impl(view, &[], swift_type!(SwiftUI.Ellipse).as_bytes(), &CLIP_ELLIPSE_T) }
306}
307
308/// Apply `.clipShape(Capsule())`.
309pub fn clip_capsule(view: &ViewHandle) -> ViewHandle {
310    clip_capsule_styled(view, true)
311}
312
313/// `.clipShape(Capsule(style:))`.
314///
315/// `continuous` is what `.clipShape(.capsule)` means in Swift source — the same
316/// default `Capsule()` has, and case **1** of `RoundedCornerStyle`, not case 0.
317/// [`clip_capsule`] wrote case 0 until a listing needed the natural spelling;
318/// its parity case had been written to say `Capsule(style: .circular)` to suit,
319/// so nothing had ever disagreed.
320pub fn clip_capsule_styled(view: &ViewHandle, continuous: bool) -> ViewHandle {
321    unsafe {
322        clip_shape_impl(
323            view,
324            &[u8::from(continuous)],
325            swift_type!(SwiftUI.Capsule).as_bytes(),
326            &CLIP_CAPSULE_T,
327        )
328    }
329}
330
331/// Apply `.clipShape(RoundedRectangle(cornerRadius:))`.
332pub fn clip_rounded_rect(view: &ViewHandle, radius: f64) -> ViewHandle {
333    clip_rounded_rect_styled(view, radius, true)
334}
335
336/// Apply `.clipShape(RoundedRectangle(cornerRadius:style:))`.
337///
338/// As with [`super::rounded_rectangle_styled`], the style is not optional here
339/// even though Swift source can leave it out: leaving it out means
340/// `.continuous`, and writing case 0 builds the other shape.
341pub fn clip_rounded_rect_styled(view: &ViewHandle, radius: f64, continuous: bool) -> ViewHandle {
342    let mut buf = [0u8; 17];
343    buf[..8].copy_from_slice(&radius.to_le_bytes());
344    buf[8..16].copy_from_slice(&radius.to_le_bytes());
345    buf[16] = u8::from(continuous);
346    unsafe { clip_shape_impl(view, &buf, swift_type!(SwiftUI.RoundedRectangle).as_bytes(), &CLIP_RRECT_T) }
347}
348
349/// Apply `.overlay(alignment:content:)` or `.mask(alignment:_:)`.
350pub(crate) unsafe fn overlay_or_mask(
351    view: &ViewHandle,
352    content: &ViewHandle,
353    func: *const c_void,
354    modifier: &ModifierType,
355    alignment: super::Alignment,
356) -> ViewHandle {
357    unsafe {
358        let (_, _, size) = modifier.applied_to_content(view.meta, content.meta, content.wt);
359        let mut result = vec![0u8; size];
360        let this = view.value_ptr();
361
362        let kid_handles = Child::new(content, Content::Concrete);
363        let handles = kid_handles.element();
364        let payload = TuplePayload { children: handles.as_ptr(), count: 1 };
365        let mut ctx = abi::ClosureCtx::new(emit_tuple, &payload as *const TuplePayload as *mut c_void);
366
367        let [ah, av] = alignment.words();
368        // overlay<V: View>(alignment: Alignment, content: () -> V)
369        //   x0/x1 = Alignment, x2 = closure fn, x3 = closure ctx,
370        //   x4 = Self.Type, x5 = V.Type, x6 = Self: View WT, x7 = V: View WT
371        core::arch::asm!("blr {f}", f = in(reg) func,
372            in("x0") ah, in("x1") av,
373            in("x2") abi::swift_closure_thunk as *const c_void,
374            in("x3") &mut ctx as *mut abi::ClosureCtx,
375            in("x4") view.meta,                  // Self.Type
376            in("x5") content.meta,               // V.Type
377            in("x6") view.wt,                    // Self: View
378            in("x7") content.wt,                 // V: View
379            in("x8") result.as_mut_ptr(),
380            in("x20") this,
381            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
382            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
383            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
384        );
385        let (meta, wt, _) = modifier.applied_to_content(view.meta, content.meta, content.wt);
386        wrap_owned(result, meta, wt)
387    }
388}
389
390/// Apply `.overlay(alignment:content:)`.
391pub fn overlay_aligned(
392    view: &ViewHandle,
393    content: &ViewHandle,
394    alignment: super::Alignment,
395) -> ViewHandle {
396    unsafe { overlay_or_mask(view, content, resolve::overlay_fn(), &OVERLAY_ANY_T, alignment) }
397}
398
399/// Apply `.overlay(alignment: .center) { content }`.
400pub fn overlay(view: &ViewHandle, content: &ViewHandle) -> ViewHandle {
401    overlay_aligned(view, content, super::Alignment::Center)
402}
403
404/// Apply `.background(alignment:content:)`.
405///
406/// The same call as `.overlay(alignment:content:)` with a different symbol:
407/// an `Alignment` in x0/x1, the content closure and its context in x2/x3, four
408/// generic words after that. The colour-taking `background` next door is a
409/// different overload entirely — that one is generic over `ShapeStyle`, this
410/// one over `View`.
411pub fn background_content(
412    view: &ViewHandle,
413    content: &ViewHandle,
414    alignment: super::Alignment,
415) -> ViewHandle {
416    unsafe {
417        overlay_or_mask(
418            view,
419            content,
420            resolve::background_content_fn(),
421            &BACKGROUND_ANY_T,
422            alignment,
423        )
424    }
425}
426
427/// Apply `.mask(alignment:_:)`.
428pub fn mask_aligned(
429    view: &ViewHandle,
430    content: &ViewHandle,
431    alignment: super::Alignment,
432) -> ViewHandle {
433    unsafe { overlay_or_mask(view, content, resolve::mask_fn(), &MASK_ANY_T, alignment) }
434}
435
436/// Apply `.mask(alignment: .center) { content }`.
437pub fn mask(view: &ViewHandle, content: &ViewHandle) -> ViewHandle {
438    mask_aligned(view, content, super::Alignment::Center)
439}
440
441/// Apply `.background(_:in:fillStyle:)` — a style drawn behind, in a shape.
442///
443/// Identical in shape to [`overlay_style_in_shape`], which is the point: both
444/// take a `ShapeStyle` and a `Shape`, so both spill exactly one of their six
445/// generic words.
446/// # Safety
447/// `style` must hold an initialised value of the type `style_meta` describes,
448/// and `style_wt` must be that type's `ShapeStyle` conformance. Nothing here
449/// checks either, and a mismatched pair is read as the type named.
450pub unsafe fn background_style_in_shape(
451    view: &ViewHandle,
452    style: &[u8],
453    style_meta: *const c_void,
454    style_wt: *const c_void,
455    shape: &super::Shape,
456) -> ViewHandle {
457    unsafe {
458        style_in_shape(
459            resolve::background_style_in_fn(),
460            resolve::background_style_in_oret(),
461            view,
462            style,
463            style_meta,
464            style_wt,
465            shape,
466        )
467    }
468}
469
470/// Apply `.background(_:in:fillStyle:)` where the shape is `InsettableShape`.
471///
472/// A second overload with the same spelling. Swift picks *this* one whenever the
473/// shape conforms — a `Capsule` does — and it builds
474/// `_InsettableBackgroundShapeModifier` rather than `_BackgroundShapeModifier`.
475/// The two render identically, so only a type or value comparison tells them
476/// apart. The witness table has to be the `InsettableShape` one, not the
477/// `Shape` one the [`super::Shape`] value carries.
478/// # Safety
479/// `style` must hold an initialised value of the type `style_meta` describes,
480/// and `style_wt` must be that type's `ShapeStyle` conformance. Nothing here
481/// checks either, and a mismatched pair is read as the type named.
482pub unsafe fn background_style_in_insettable(
483    view: &ViewHandle,
484    style: &[u8],
485    style_meta: *const c_void,
486    style_wt: *const c_void,
487    shape: &super::Shape,
488) -> ViewHandle {
489    unsafe {
490        let (bytes, meta, _) = shape.parts();
491        let insettable = abi::get_view_wt_for_proto(meta, resolve::insettable_shape_proto());
492        assert!(!insettable.is_null(), "{} !: InsettableShape", crate::diag::type_name(meta));
493        style_in_shape_with(
494            resolve::background_style_in_insettable_fn(),
495            resolve::background_style_in_insettable_oret(),
496            view,
497            style,
498            style_meta,
499            style_wt,
500            &bytes,
501            meta,
502            insettable,
503        )
504    }
505}
506
507/// Apply `.overlay(_:in:fillStyle:)` — a style drawn inside a shape.
508///
509/// Two generic parameters: the style and the shape. From the IR,
510/// `(sret, ptr style, ptr shape, i16 fillStyle, Self.Type, S.Type, Shape.Type,
511/// Self:View, S:ShapeStyle, Shape:Shape, swiftself)`. The `FillStyle` sits
512/// between the two addresses and the generic words, which leaves x3 through x7
513/// — five registers for six words, so exactly one spills.
514/// # Safety
515/// `style` must hold an initialised value of the type `style_meta` describes,
516/// and `style_wt` must be that type's `ShapeStyle` conformance. Nothing here
517/// checks either, and a mismatched pair is read as the type named.
518pub unsafe fn overlay_style_in_shape(
519    view: &ViewHandle,
520    style: &[u8],
521    style_meta: *const c_void,
522    style_wt: *const c_void,
523    shape: &super::Shape,
524) -> ViewHandle {
525    unsafe {
526        style_in_shape(
527            resolve::overlay_in_fn(),
528            resolve::overlay_in_oret(),
529            view,
530            style,
531            style_meta,
532            style_wt,
533            shape,
534        )
535    }
536}
537
538/// The body `overlay(_:in:)` and `background(_:in:)` share.
539#[allow(clippy::too_many_arguments)]
540unsafe fn style_in_shape(
541    func: *const c_void,
542    oret: *const c_void,
543    view: &ViewHandle,
544    style: &[u8],
545    style_meta: *const c_void,
546    style_wt: *const c_void,
547    shape: &super::Shape,
548) -> ViewHandle {
549    let (b, m, w) = shape.parts();
550    unsafe { style_in_shape_with(func, oret, view, style, style_meta, style_wt, &b, m, w) }
551}
552
553/// As [`style_in_shape`], with the shape's bytes, metadata and witness given
554/// directly — so the witness can be `InsettableShape` rather than `Shape`.
555#[allow(clippy::too_many_arguments)]
556unsafe fn style_in_shape_with(
557    func: *const c_void,
558    oret: *const c_void,
559    view: &ViewHandle,
560    style: &[u8],
561    style_meta: *const c_void,
562    style_wt: *const c_void,
563    shape_bytes: &[u8],
564    shape_meta: *const c_void,
565    shape_wt: *const c_void,
566) -> ViewHandle {
567    unsafe {
568        let shape_bytes = shape_bytes.to_vec();
569        let subst = [
570            view.meta,
571            style_meta,
572            shape_meta,
573            view.wt,
574            style_wt,
575            shape_wt,
576        ];
577        let (meta, wt, size) = super::opaque_result_type(oret, &subst);
578
579        let mut result = vec![0u8; size];
580        let this = view.value_ptr();
581        let mut style_arg = style.to_vec();
582        let mut shape_arg = shape_bytes;
583        shape_arg.push(0);
584        core::arch::asm!(
585            "sub sp, sp, #16",
586            "str {a}, [sp]",
587            "blr {f}",
588            "add sp, sp, #16",
589            f = in(reg) func,
590            a = in(reg) subst[5],
591            in("x0") style_arg.as_mut_ptr(),
592            in("x1") shape_arg.as_mut_ptr(),
593            in("x2") FILL_STYLE_DEFAULT_WORD,
594            in("x3") subst[0],
595            in("x4") subst[1],
596            in("x5") subst[2],
597            in("x6") subst[3],
598            in("x7") subst[4],
599            in("x8") result.as_mut_ptr(),
600            in("x20") this,
601            lateout("x9") _, lateout("x10") _, lateout("x11") _,
602            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
603            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
604            clobber_abi("C"),
605        );
606        wrap_owned(result, meta, wt)
607    }
608}
609
610/// `FillStyle()` — `isEOFilled` false, `isAntialiased` true.
611const FILL_STYLE_DEFAULT_WORD: u64 = 0x0100;
612
613/// Apply `.padding()` — every edge, at whatever length the system picks.
614///
615/// Not `padding(_ length: CGFloat)`. A bare `.padding()` in Swift is
616/// `padding(.all, nil)`, and the `nil` is the point: it asks for the system's
617/// own inset rather than for zero.
618pub fn padding_default(view: &ViewHandle) -> ViewHandle {
619    unsafe {
620        let (meta, wt, size) = super::opaque_result_type(
621            resolve::padding_edges_oret(),
622            &[view.meta, view.wt],
623        );
624        let mut result = vec![0u8; size];
625        let this = view.value_ptr();
626        // x0 = Edge.Set (.all is every bit of a four-bit option set),
627        // x1/x2 = Optional<CGFloat> payload and tag, then the generic words.
628        core::arch::asm!("blr {f}", f = in(reg) resolve::padding_edges_fn(),
629            in("x0") 0x0fu64,
630            in("x1") 0u64, in("x2") 1u64,
631            in("x3") view.meta,
632            in("x4") view.wt,
633            in("x8") result.as_mut_ptr(),
634            in("x20") this,
635            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
636            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
637            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
638            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
639        );
640        wrap_owned(result, meta, wt)
641    }
642}
643
644/// Apply `.padding(_:_:)` — chosen edges, an explicit length.
645///
646/// `edges` is an `Edge.Set` bit pattern: 1 top, 2 leading, 4 bottom, 8 trailing,
647/// so `.vertical` is 5 and `.all` is 15. A `None` length means the system's own
648/// inset, as in [`padding_default`].
649/// `.padding(_:_:)` with the edges named rather than spelled as a bitmask.
650///
651/// [`padding_edges`] takes the raw option-set bits, which a caller has to know;
652/// this reads the member out of SwiftUI's own `Edge.Set` instead.
653pub fn padding_set(view: &ViewHandle, edges: super::EdgeSet, length: Option<f64>) -> ViewHandle {
654    unsafe {
655        let meta = abi::metadata0(resolve::edge_set_ma());
656        let (bits, _) = super::static_getter(edges.resolved(), meta);
657        padding_edges(view, bits, length)
658    }
659}
660
661pub fn padding_edges(view: &ViewHandle, edges: u64, length: Option<f64>) -> ViewHandle {
662    unsafe {
663        let (meta, wt, size) = super::opaque_result_type(
664            resolve::padding_edges_oret(),
665            &[view.meta, view.wt],
666        );
667        let mut result = vec![0u8; size];
668        let this = view.value_ptr();
669        let (bits, tag) = match length {
670            Some(v) => (v.to_bits(), 0u64),
671            None => (0u64, 1u64),
672        };
673        core::arch::asm!("blr {f}", f = in(reg) resolve::padding_edges_fn(),
674            in("x0") edges,
675            in("x1") bits, in("x2") tag,
676            in("x3") view.meta,
677            in("x4") view.wt,
678            in("x8") result.as_mut_ptr(),
679            in("x20") this,
680            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
681            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
682            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
683            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
684        );
685        wrap_owned(result, meta, wt)
686    }
687}
688
689/// Apply `.background(in:fillStyle:)` — the background style, clipped to a shape.
690///
691/// The shape must be `InsettableShape`, not merely `Shape`, so its witness is a
692/// different table from the one [`super::Shape`] carries.
693pub fn background_in_circle(view: &ViewHandle) -> ViewHandle {
694    unsafe {
695        let shape = super::Shape::circle();
696        let (mut bytes, shape_meta, _) = shape.parts();
697        bytes.push(0);
698        let shape_wt = resolve::circle_wt();
699        let subst = [view.meta, shape_meta, view.wt, shape_wt];
700        let (meta, wt, size) =
701            super::opaque_result_type(resolve::background_in_oret(), &subst);
702        let mut result = vec![0u8; size];
703        let this = view.value_ptr();
704        core::arch::asm!("blr {f}", f = in(reg) resolve::background_in_fn(),
705            in("x0") bytes.as_mut_ptr(),
706            in("x1") 0x0100u64,           // FillStyle()
707            in("x2") subst[0], in("x3") subst[1], in("x4") subst[2], in("x5") subst[3],
708            in("x8") result.as_mut_ptr(),
709            in("x20") this,
710            lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
711            lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
712            lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
713            lateout("x21") _, clobber_abi("C"),
714        );
715        wrap_owned(result, meta, wt)
716    }
717}
718
719/// `.backgroundStyle(_:)` with a `Color`.
720///
721/// [`background_style_of`] takes the style as raw bytes and a witness, which
722/// is what an arbitrary `ShapeStyle` needs and what nothing can call without
723/// building one first. These three name the styles that have a Rust spelling,
724/// so the modifier is reachable the way the documentation writes it.
725///
726/// Not `.background(_:)`: that draws behind *this* view, while this says what
727/// style the views *inside* should use for their own backgrounds.
728pub fn background_style_color(view: &ViewHandle, r: f64, g: f64, b: f64, a: f64) -> ViewHandle {
729    background_style_value(view, unsafe { super::color_value(r, g, b, a) })
730}
731
732/// `.backgroundStyle(_:)` with an already-resolved `Color` word.
733pub fn background_style_value(view: &ViewHandle, color: u64) -> ViewHandle {
734    // SAFETY: a resolved `Color` word beside `Color`'s own metadata and its
735    // `ShapeStyle` conformance — the three come from one place and match.
736    unsafe {
737        background_style_of(
738            view,
739            &color.to_le_bytes(),
740            resolve::color_meta(),
741            resolve::color_shapestyle_wt(),
742        )
743    }
744}
745
746/// `.backgroundStyle(_:)` with one of the hierarchical levels.
747pub fn background_style_hierarchical(view: &ViewHandle, level: super::Hierarchical) -> ViewHandle {
748    // SAFETY: `hierarchical_style` returns a value with its own metadata and
749    // conformance, so the three agree by construction.
750    let (style, meta, wt) = super::hierarchical_style(level);
751    unsafe { background_style_of(view, &style, meta, wt) }
752}
753
754/// Apply `.backgroundStyle(_:)` with any `ShapeStyle`.
755/// # Safety
756/// `style` must hold an initialised value of the type `style_meta` describes,
757/// and `style_wt` must be that type's `ShapeStyle` conformance. Nothing here
758/// checks either, and a mismatched pair is read as the type named.
759pub unsafe fn background_style_of(
760    view: &ViewHandle,
761    style: &[u8],
762    style_meta: *const c_void,
763    style_wt: *const c_void,
764) -> ViewHandle {
765    unsafe {
766        let subst = [view.meta, style_meta, view.wt, style_wt];
767        let (meta, wt, size) =
768            super::opaque_result_type(resolve::background_style_oret(), &subst);
769        let mut result = vec![0u8; size];
770        let this = view.value_ptr();
771        let mut arg = style.to_vec();
772        core::arch::asm!("blr {f}", f = in(reg) resolve::background_style_fn(),
773            in("x0") arg.as_mut_ptr(),
774            in("x1") subst[0], in("x2") subst[1], in("x3") subst[2], in("x4") subst[3],
775            in("x8") result.as_mut_ptr(),
776            in("x20") this,
777            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
778            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
779            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
780            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
781        );
782        wrap_owned(result, meta, wt)
783    }
784}
785
786/// `.mask(_:)` — the alignment-free overload, which builds a `_MaskEffect`.
787///
788/// SwiftUI has two masks and they are different types: `.mask(alignment:_:)`
789/// gives `_MaskAlignmentEffect` and is what [`mask`] reaches, while the older
790/// `.mask(_:)` gives `_MaskEffect`. The second is deprecated and inlined, so it
791/// exports no function — but `_MaskEffect<Mask>` is a newtype over the mask
792/// itself (measured: both eight bytes for the same mask), its descriptor is
793/// exported, and `View.modifier(_:)` composes anything that conforms to
794/// `ViewModifier`. So the modifier is built rather than called.
795///
796/// Falls back to [`mask`] if the instantiation does not take.
797pub fn mask_plain(view: &ViewHandle, content: &ViewHandle) -> ViewHandle {
798    unsafe {
799        let desc = resolve::mask_effect_desc();
800        if desc.is_null() {
801            return mask(view, content);
802        }
803        let effect = abi::generic_metadata_from_descriptor(desc, &[content.meta, content.wt]);
804        if effect.is_null() {
805            return mask(view, content);
806        }
807        // A newtype over the mask: one stored property, at offset zero. The
808        // metadata says so rather than being inferred from the size matching,
809        // which two different layouts can also do.
810        if abi::struct_field_offsets(effect).as_deref() != Some(&[0][..]) {
811            return mask(view, content);
812        }
813        if abi::type_bytes(effect) != abi::type_bytes(content.meta) {
814            return mask(view, content);
815        }
816        if !crate::diag::type_name(effect).starts_with("SwiftUI._MaskEffect<") {
817            return mask(view, content);
818        }
819        let bytes =
820            core::slice::from_raw_parts(content.value_ptr() as *const u8, abi::type_bytes(effect));
821        super::apply_modifier(view, effect, bytes)
822    }
823}