swiftui_native/views/customshape.rs
1//! A `Shape` written in Rust — a path computed from the rect it is given.
2//!
3//! [`crate::views::Shape::path`] already builds a fixed `Path` and renders it. A
4//! *conformance* is a different thing: SwiftUI asks the shape for its path each
5//! time the layout changes, so the path can be a function of the space available,
6//! and the result is a real `Shape` — so `.fill`, `.stroke`, `clipShape` and
7//! shape animation all apply to it.
8//!
9//! ```no_run
10//! # use swiftui_native::views;
11//! // A chevron that always spans its frame.
12//! let s = views::shape_fn(|p: &mut views::ShapePath, r: views::Rect| {
13//! p.move_to(r.x, r.y);
14//! p.line_to(r.x + r.width * 0.5, r.y + r.height);
15//! p.line_to(r.x + r.width, r.y);
16//! });
17//! let v = views::custom_shape(&s);
18//! ```
19//!
20//! # The knot
21//!
22//! `Shape: Animatable, View`, and a shape's `View.Body` is
23//! `_ShapeView<Self, ForegroundStyle>` — whose metadata the runtime will only
24//! build once it can find `Self: Shape`. So neither witness table can be
25//! completed before the other one exists. Swift ties this off with *lazy* witness
26//! tables; here the laziness is written out:
27//!
28//! 1. the `Shape` conformance is built and registered with its `View` slot empty;
29//! 2. `_ShapeView<RustShape, ForegroundStyle>` is resolved by name, which the
30//! runtime can now do;
31//! 3. a `View` conformance is built whose `Body` is that type and whose `body`
32//! getter is SwiftUI's own `Shape` extension, reached through a thunk that
33//! swaps the `View` witness table it is handed for the `Shape` one it wants;
34//! 4. the empty slot is filled in.
35//!
36//! `Animatable` is borrowed rather than synthesised — see
37//! [`resolve::rectangle_animatable_wt`].
38
39use super::{wrap_owned, PathBuilder, ViewHandle};
40use crate::abi;
41use crate::conformance::{synthesize, Conformance, Protocol, Witness};
42use crate::resolve;
43use core::ffi::c_void;
44use std::sync::{Mutex, OnceLock};
45
46// The rect a shape is asked to fill is [`super::Rect`] — the same one a custom
47// `Layout` places subviews in. Two identical structs with the same name in the
48// same module is an ambiguity, and there is no reason for a shape's rect to be a
49// different type from a layout's.
50pub use super::Rect;
51
52/// The path a shape is building.
53///
54/// A thin wrapper over [`PathBuilder`] so a shape closure reads like drawing
55/// rather than like building a value.
56pub struct ShapePath {
57 builder: PathBuilder,
58}
59
60impl ShapePath {
61 /// Start a new subpath at a point.
62 pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
63 self.builder.move_to(x, y);
64 self
65 }
66
67 /// Draw a straight line to a point.
68 pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
69 self.builder.line_to(x, y);
70 self
71 }
72
73 /// Draw a quadratic curve to a point, bending towards a control point.
74 pub fn quad_curve_to(&mut self, x: f64, y: f64, cx: f64, cy: f64) -> &mut Self {
75 self.builder.quad_curve_to(x, y, cx, cy);
76 self
77 }
78
79 /// Close the current subpath.
80 pub fn close(&mut self) -> &mut Self {
81 self.builder.close();
82 self
83 }
84}
85
86/// How a shape draws itself into a rect.
87pub trait Shaping: Send + Sync + 'static {
88 /// Add this shape's path for `rect`.
89 fn path(&self, path: &mut ShapePath, rect: Rect);
90}
91
92impl<F> Shaping for F
93where
94 F: Fn(&mut ShapePath, Rect) + Send + Sync + 'static,
95{
96 fn path(&self, path: &mut ShapePath, rect: Rect) {
97 self(path, rect)
98 }
99}
100
101/// A registered shape, as the one word SwiftUI stores.
102pub struct CustomShape {
103 index: i64,
104}
105
106fn registry() -> &'static Mutex<Vec<&'static dyn Shaping>> {
107 static R: OnceLock<Mutex<Vec<&'static dyn Shaping>>> = OnceLock::new();
108 R.get_or_init(|| Mutex::new(Vec::new()))
109}
110
111/// Register a shape. A closure taking the path and the rect is one.
112pub fn shape_fn<S: Shaping>(shape: S) -> CustomShape {
113 let leaked: &'static dyn Shaping = Box::leak(Box::new(shape));
114 let mut r = registry().lock().expect("shape registry");
115 r.push(leaked);
116 CustomShape { index: r.len() as i64 - 1 }
117}
118
119// `path(in:)` arrives with the `Path` result through x8, the `CGRect` in d0–d3
120// and `self` in x20. Only the two integer registers need shuffling; the rect is
121// already where a Rust `fn(_, _, f64, f64, f64, f64)` expects it.
122core::arch::global_asm!(
123 ".p2align 2",
124 ".globl _rswift_shape_path_thunk",
125 "_rswift_shape_path_thunk:",
126 "mov x1, x20",
127 "mov x0, x8",
128 "b _rswift_shape_path",
129);
130
131unsafe extern "C" {
132 #[link_name = "rswift_shape_path_thunk"]
133 fn shape_path_thunk();
134}
135
136/// `path(in:) -> Path`.
137///
138/// # Safety
139/// Reached through `rswift_shape_path_thunk`.
140#[unsafe(no_mangle)]
141unsafe extern "C" fn rswift_shape_path(
142 out: *mut u8,
143 this: *const u8,
144 x: f64,
145 y: f64,
146 width: f64,
147 height: f64,
148) {
149 let index = unsafe { *(this as *const i64) } as usize;
150 let shape = {
151 let r = registry().lock().expect("shape registry");
152 r.get(index).copied().expect("shape index out of range")
153 };
154 let mut path = ShapePath { builder: super::Shape::path() };
155 shape.path(&mut path, Rect { x, y, width, height });
156 // The `Path` the builder holds is the result, moved into SwiftUI's slot.
157 unsafe { path.builder.move_into(out) };
158}
159
160// The `View.body` witness for a shape is SwiftUI's own — the `Shape` extension's
161// `body`, which returns `_ShapeView<Self, ForegroundStyle>`. It has the same
162// convention as a `View.body` witness (result through x8, `self` in x20,
163// `Self.Type` in x0, a witness table in x1) and differs in *which* table: it
164// wants `Self: Shape` where the witness is handed `Self: View`. So the thunk
165// swaps x1 for the Shape table and tail-calls.
166#[unsafe(no_mangle)]
167static mut RSWIFT_SHAPE_WT: *const c_void = core::ptr::null();
168
169crate::thunk::runtime_thunk! {
170 /// `View.body` for a shape: the Shape witness table replaces the View one.
171 fn shape_body_thunk = "rswift_shape_body" via RSWIFT_SHAPE_BODY {
172 prologue: [
173 "adrp x1, _RSWIFT_SHAPE_WT@PAGE",
174 "add x1, x1, _RSWIFT_SHAPE_WT@PAGEOFF",
175 "ldr x1, [x1]",
176 ],
177 branch: "br",
178 epilogue: [],
179 }
180}
181
182/// The carrier type a shape value *is*: one `Int`, its index.
183fn carrier() -> (*const c_void, *const c_void) {
184 static C: OnceLock<(usize, usize)> = OnceLock::new();
185 let (d, m) = *C.get_or_init(|| unsafe {
186 let (d, m) = crate::state::emit_int_carrier(b"RustShape\0", b"6rswift9RustShapeV\0");
187 (d as usize, m as usize)
188 });
189 (d as *const c_void, m as *const c_void)
190}
191
192/// The `Shape` conformance, and the `View` one it needs.
193///
194/// Four steps, in this order because each needs the one before:
195///
196/// 1. `RustShape: Animatable`, registered with `Rectangle`'s own witnesses. It
197/// has to be *registered* and not merely referenced: instantiating
198/// `_ShapeView<RustShape, …>` checks that every conformance `Shape` requires
199/// can be found, and one that only exists inside another table cannot be.
200/// 2. the carrier's `View` table, which [`crate::state`] already built and
201/// registered when the type was minted — with a stub `body`.
202/// 3. `RustShape: Shape`, whose two base slots are those two tables.
203/// 4. `_ShapeView<RustShape, ForegroundStyle>`, which the runtime can build now,
204/// and whose metadata and `View` conformance are patched into the carrier's
205/// `View` table along with the real `body`. Patching rather than registering a
206/// second table: two conformances for the same type and protocol is a
207/// conflict, and this is the one the runtime already hands out.
208fn shape_conformance() -> &'static Conformance {
209 static C: OnceLock<Conformance> = OnceLock::new();
210 C.get_or_init(|| unsafe {
211 let carrier_meta = carrier().1;
212
213 // ── 1. Animatable, borrowed and registered ──
214 let animatable = Protocol::from_symbol(c"$s7SwiftUI10AnimatableMp");
215 assert_eq!(
216 animatable.requirement_count(),
217 6,
218 "Animatable changed shape — it used to be AnimatableData, its \
219 conformance, a getter, a setter, a modify coroutine and one default",
220 );
221 // `Rectangle`'s table, word by word. Every shape whose `AnimatableData` is
222 // `EmptyAnimatableData` has the same one — the getter returns a zero-size
223 // value and the setter ignores it — so there is nothing in these
224 // implementations that could depend on which shape they came from.
225 let borrowed = resolve::rectangle_animatable_wt() as *const *const c_void;
226 let witnesses: Vec<Witness> = (0..animatable.requirement_count())
227 .map(|i| Witness::Metadata(*borrowed.add(1 + i)))
228 .collect();
229 let animatable_wt = Box::leak(Box::new(synthesize(
230 &animatable,
231 carrier().0,
232 &witnesses,
233 )))
234 .witness_table();
235
236 // ── 2. the View table the type was minted with ──
237 let view_wt = abi::get_view_wt(carrier_meta);
238 assert!(
239 !view_wt.is_null(),
240 "the carrier type has no View conformance — `emit_int_carrier` used to \
241 register one",
242 );
243
244 // ── 3. Shape ──
245 let protocol = Protocol::from_symbol(c"$s7SwiftUI5ShapeMp");
246 assert_eq!(
247 protocol.requirement_count(),
248 6,
249 "Shape changed shape — it used to be Animatable, View, `path(in:)` and \
250 three defaulted requirements",
251 );
252 let shape = synthesize(
253 &protocol,
254 carrier().0,
255 &[
256 Witness::Metadata(animatable_wt),
257 Witness::Metadata(view_wt),
258 Witness::Function(shape_path_thunk as *const c_void),
259 Witness::Default,
260 Witness::Default,
261 Witness::Default,
262 ],
263 );
264 RSWIFT_SHAPE_WT = shape.witness_table();
265
266 // ── 4. the body, and the knot tied ──
267 // Built from `_ShapeView`'s descriptor and its four key arguments, not
268 // from a mangled name. A name cannot work here: the content type was
269 // minted at run time, and `swift_getTypeByMangledName` resolves a nominal
270 // name by searching the images' metadata sections — where a synthesised
271 // descriptor does not appear. Registering a *conformance* makes the type
272 // findable as a conformer, which is a different question and is why the
273 // lookups above succeed while a name lookup returns nothing.
274 let style_meta = abi::metadata0(resolve::foreground_style_ma());
275 assert!(!style_meta.is_null(), "ForegroundStyle metadata");
276 let body_meta = abi::generic_metadata_from_descriptor(
277 resolve::shape_view_desc(),
278 &[carrier_meta, style_meta, shape.witness_table(), resolve::foreground_style_wt()],
279 );
280 assert!(
281 !body_meta.is_null(),
282 "_ShapeView<RustShape, ForegroundStyle> could not be instantiated",
283 );
284 let body_wt = abi::get_view_wt(body_meta);
285 assert!(!body_wt.is_null(), "{} !: View", crate::diag::type_name(body_meta));
286
287 RSWIFT_SHAPE_BODY = resolve::shape_extension_body();
288 // The carrier's `View` table, as `crate::state` laid it out:
289 // [0] conformance descriptor
290 // [1] Body: View [2] Body
291 // [3..5] the three `_make…` defaults
292 // [6] body
293 let words = view_wt as *mut *const c_void;
294 *words.add(1) = body_wt;
295 *words.add(2) = body_meta;
296 *words.add(6) = shape_body_thunk as *const c_void;
297
298 shape
299 })
300}
301
302/// A shape written in Rust, as a view.
303///
304/// Filled with the foreground style, which is what a bare `Shape` in a view
305/// builder means — `Circle()` on its own draws a filled circle.
306pub fn custom_shape(shape: &CustomShape) -> ViewHandle {
307 unsafe {
308 let conformance = shape_conformance();
309 let meta = carrier().1;
310 let wt = abi::get_view_wt(meta);
311 assert!(
312 !wt.is_null(),
313 "{} !: View — the synthesised conformance was not found",
314 crate::diag::type_name(meta),
315 );
316 let _ = conformance;
317 let mut bytes = vec![0u8; abi::type_bytes(meta)];
318 bytes[..8].copy_from_slice(&shape.index.to_le_bytes());
319 wrap_owned(bytes, meta, wt)
320 }
321}
322
323/// The shape as one of the crate's [`super::Shape`] values.
324///
325/// `.fill(_:)` and `.fill_style(…)` work: they reach `_ShapeView` through its
326/// *descriptor* and four key arguments, so no name is involved. The methods that
327/// compose a result *name* — `.inset`, `.rotation`, `.stroked` and the rest —
328/// cannot: they build a mangled name for the result type, and a type minted at
329/// run time has none that `swift_getTypeByMangledName` will resolve. Those fail
330/// with this shape's name in the message rather than silently.
331pub fn custom_shape_value(shape: &CustomShape) -> super::Shape {
332 unsafe {
333 let conformance = shape_conformance();
334 let meta = carrier().1;
335 let mut bytes = vec![0u8; abi::type_bytes(meta)];
336 bytes[..8].copy_from_slice(&shape.index.to_le_bytes());
337 super::Shape::of_metadata(
338 bytes,
339 meta,
340 conformance.witness_table(),
341 crate::mangle::Type::Verbatim("6rswift9RustShapeV".into()),
342 )
343 }
344}