swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
//! A `Shape` written in Rust — a path computed from the rect it is given.
//!
//! [`crate::views::Shape::path`] already builds a fixed `Path` and renders it. A
//! *conformance* is a different thing: SwiftUI asks the shape for its path each
//! time the layout changes, so the path can be a function of the space available,
//! and the result is a real `Shape` — so `.fill`, `.stroke`, `clipShape` and
//! shape animation all apply to it.
//!
//! ```no_run
//! # use swiftui_native::views;
//! // A chevron that always spans its frame.
//! let s = views::shape_fn(|p: &mut views::ShapePath, r: views::Rect| {
//!     p.move_to(r.x, r.y);
//!     p.line_to(r.x + r.width * 0.5, r.y + r.height);
//!     p.line_to(r.x + r.width, r.y);
//! });
//! let v = views::custom_shape(&s);
//! ```
//!
//! # The knot
//!
//! `Shape: Animatable, View`, and a shape's `View.Body` is
//! `_ShapeView<Self, ForegroundStyle>` — whose metadata the runtime will only
//! build once it can find `Self: Shape`. So neither witness table can be
//! completed before the other one exists. Swift ties this off with *lazy* witness
//! tables; here the laziness is written out:
//!
//! 1. the `Shape` conformance is built and registered with its `View` slot empty;
//! 2. `_ShapeView<RustShape, ForegroundStyle>` is resolved by name, which the
//!    runtime can now do;
//! 3. a `View` conformance is built whose `Body` is that type and whose `body`
//!    getter is SwiftUI's own `Shape` extension, reached through a thunk that
//!    swaps the `View` witness table it is handed for the `Shape` one it wants;
//! 4. the empty slot is filled in.
//!
//! `Animatable` is borrowed rather than synthesised — see
//! [`resolve::rectangle_animatable_wt`].

use super::{wrap_owned, PathBuilder, ViewHandle};
use crate::abi;
use crate::conformance::{synthesize, Conformance, Protocol, Witness};
use crate::resolve;
use core::ffi::c_void;
use std::sync::{Mutex, OnceLock};

// The rect a shape is asked to fill is [`super::Rect`] — the same one a custom
// `Layout` places subviews in. Two identical structs with the same name in the
// same module is an ambiguity, and there is no reason for a shape's rect to be a
// different type from a layout's.
pub use super::Rect;

/// The path a shape is building.
///
/// A thin wrapper over [`PathBuilder`] so a shape closure reads like drawing
/// rather than like building a value.
pub struct ShapePath {
    builder: PathBuilder,
}

impl ShapePath {
    /// Start a new subpath at a point.
    pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
        self.builder.move_to(x, y);
        self
    }

    /// Draw a straight line to a point.
    pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
        self.builder.line_to(x, y);
        self
    }

    /// Draw a quadratic curve to a point, bending towards a control point.
    pub fn quad_curve_to(&mut self, x: f64, y: f64, cx: f64, cy: f64) -> &mut Self {
        self.builder.quad_curve_to(x, y, cx, cy);
        self
    }

    /// Close the current subpath.
    pub fn close(&mut self) -> &mut Self {
        self.builder.close();
        self
    }
}

/// How a shape draws itself into a rect.
pub trait Shaping: Send + Sync + 'static {
    /// Add this shape's path for `rect`.
    fn path(&self, path: &mut ShapePath, rect: Rect);
}

impl<F> Shaping for F
where
    F: Fn(&mut ShapePath, Rect) + Send + Sync + 'static,
{
    fn path(&self, path: &mut ShapePath, rect: Rect) {
        self(path, rect)
    }
}

/// A registered shape, as the one word SwiftUI stores.
pub struct CustomShape {
    index: i64,
}

fn registry() -> &'static Mutex<Vec<&'static dyn Shaping>> {
    static R: OnceLock<Mutex<Vec<&'static dyn Shaping>>> = OnceLock::new();
    R.get_or_init(|| Mutex::new(Vec::new()))
}

/// Register a shape. A closure taking the path and the rect is one.
pub fn shape_fn<S: Shaping>(shape: S) -> CustomShape {
    let leaked: &'static dyn Shaping = Box::leak(Box::new(shape));
    let mut r = registry().lock().expect("shape registry");
    r.push(leaked);
    CustomShape { index: r.len() as i64 - 1 }
}

// `path(in:)` arrives with the `Path` result through x8, the `CGRect` in d0–d3
// and `self` in x20. Only the two integer registers need shuffling; the rect is
// already where a Rust `fn(_, _, f64, f64, f64, f64)` expects it.
core::arch::global_asm!(
    ".p2align 2",
    ".globl _rswift_shape_path_thunk",
    "_rswift_shape_path_thunk:",
    "mov x1, x20",
    "mov x0, x8",
    "b _rswift_shape_path",
);

unsafe extern "C" {
    #[link_name = "rswift_shape_path_thunk"]
    fn shape_path_thunk();
}

/// `path(in:) -> Path`.
///
/// # Safety
/// Reached through `rswift_shape_path_thunk`.
#[unsafe(no_mangle)]
unsafe extern "C" fn rswift_shape_path(
    out: *mut u8,
    this: *const u8,
    x: f64,
    y: f64,
    width: f64,
    height: f64,
) {
    let index = unsafe { *(this as *const i64) } as usize;
    let shape = {
        let r = registry().lock().expect("shape registry");
        r.get(index).copied().expect("shape index out of range")
    };
    let mut path = ShapePath { builder: super::Shape::path() };
    shape.path(&mut path, Rect { x, y, width, height });
    // The `Path` the builder holds is the result, moved into SwiftUI's slot.
    unsafe { path.builder.move_into(out) };
}

// The `View.body` witness for a shape is SwiftUI's own — the `Shape` extension's
// `body`, which returns `_ShapeView<Self, ForegroundStyle>`. It has the same
// convention as a `View.body` witness (result through x8, `self` in x20,
// `Self.Type` in x0, a witness table in x1) and differs in *which* table: it
// wants `Self: Shape` where the witness is handed `Self: View`. So the thunk
// swaps x1 for the Shape table and tail-calls.
#[unsafe(no_mangle)]
static mut RSWIFT_SHAPE_WT: *const c_void = core::ptr::null();

crate::thunk::runtime_thunk! {
    /// `View.body` for a shape: the Shape witness table replaces the View one.
    fn shape_body_thunk = "rswift_shape_body" via RSWIFT_SHAPE_BODY {
        prologue: [
            "adrp x1, _RSWIFT_SHAPE_WT@PAGE",
            "add x1, x1, _RSWIFT_SHAPE_WT@PAGEOFF",
            "ldr x1, [x1]",
        ],
        branch: "br",
        epilogue: [],
    }
}

/// The carrier type a shape value *is*: one `Int`, its index.
fn carrier() -> (*const c_void, *const c_void) {
    static C: OnceLock<(usize, usize)> = OnceLock::new();
    let (d, m) = *C.get_or_init(|| unsafe {
        let (d, m) = crate::state::emit_int_carrier(b"RustShape\0", b"6rswift9RustShapeV\0");
        (d as usize, m as usize)
    });
    (d as *const c_void, m as *const c_void)
}

/// The `Shape` conformance, and the `View` one it needs.
///
/// Four steps, in this order because each needs the one before:
///
/// 1. `RustShape: Animatable`, registered with `Rectangle`'s own witnesses. It
///    has to be *registered* and not merely referenced: instantiating
///    `_ShapeView<RustShape, …>` checks that every conformance `Shape` requires
///    can be found, and one that only exists inside another table cannot be.
/// 2. the carrier's `View` table, which [`crate::state`] already built and
///    registered when the type was minted — with a stub `body`.
/// 3. `RustShape: Shape`, whose two base slots are those two tables.
/// 4. `_ShapeView<RustShape, ForegroundStyle>`, which the runtime can build now,
///    and whose metadata and `View` conformance are patched into the carrier's
///    `View` table along with the real `body`. Patching rather than registering a
///    second table: two conformances for the same type and protocol is a
///    conflict, and this is the one the runtime already hands out.
fn shape_conformance() -> &'static Conformance {
    static C: OnceLock<Conformance> = OnceLock::new();
    C.get_or_init(|| unsafe {
        let carrier_meta = carrier().1;

        // ── 1. Animatable, borrowed and registered ──
        let animatable = Protocol::from_symbol(c"$s7SwiftUI10AnimatableMp");
        assert_eq!(
            animatable.requirement_count(),
            6,
            "Animatable changed shape — it used to be AnimatableData, its \
             conformance, a getter, a setter, a modify coroutine and one default",
        );
        // `Rectangle`'s table, word by word. Every shape whose `AnimatableData` is
        // `EmptyAnimatableData` has the same one — the getter returns a zero-size
        // value and the setter ignores it — so there is nothing in these
        // implementations that could depend on which shape they came from.
        let borrowed = resolve::rectangle_animatable_wt() as *const *const c_void;
        let witnesses: Vec<Witness> = (0..animatable.requirement_count())
            .map(|i| Witness::Metadata(*borrowed.add(1 + i)))
            .collect();
        let animatable_wt = Box::leak(Box::new(synthesize(
            &animatable,
            carrier().0,
            &witnesses,
        )))
        .witness_table();

        // ── 2. the View table the type was minted with ──
        let view_wt = abi::get_view_wt(carrier_meta);
        assert!(
            !view_wt.is_null(),
            "the carrier type has no View conformance — `emit_int_carrier` used to \
             register one",
        );

        // ── 3. Shape ──
        let protocol = Protocol::from_symbol(c"$s7SwiftUI5ShapeMp");
        assert_eq!(
            protocol.requirement_count(),
            6,
            "Shape changed shape — it used to be Animatable, View, `path(in:)` and \
             three defaulted requirements",
        );
        let shape = synthesize(
            &protocol,
            carrier().0,
            &[
                Witness::Metadata(animatable_wt),
                Witness::Metadata(view_wt),
                Witness::Function(shape_path_thunk as *const c_void),
                Witness::Default,
                Witness::Default,
                Witness::Default,
            ],
        );
        RSWIFT_SHAPE_WT = shape.witness_table();

        // ── 4. the body, and the knot tied ──
        // Built from `_ShapeView`'s descriptor and its four key arguments, not
        // from a mangled name. A name cannot work here: the content type was
        // minted at run time, and `swift_getTypeByMangledName` resolves a nominal
        // name by searching the images' metadata sections — where a synthesised
        // descriptor does not appear. Registering a *conformance* makes the type
        // findable as a conformer, which is a different question and is why the
        // lookups above succeed while a name lookup returns nothing.
        let style_meta = abi::metadata0(resolve::foreground_style_ma());
        assert!(!style_meta.is_null(), "ForegroundStyle metadata");
        let body_meta = abi::generic_metadata_from_descriptor(
            resolve::shape_view_desc(),
            &[carrier_meta, style_meta, shape.witness_table(), resolve::foreground_style_wt()],
        );
        assert!(
            !body_meta.is_null(),
            "_ShapeView<RustShape, ForegroundStyle> could not be instantiated",
        );
        let body_wt = abi::get_view_wt(body_meta);
        assert!(!body_wt.is_null(), "{} !: View", crate::diag::type_name(body_meta));

        RSWIFT_SHAPE_BODY = resolve::shape_extension_body();
        // The carrier's `View` table, as `crate::state` laid it out:
        //   [0] conformance descriptor
        //   [1] Body: View        [2] Body
        //   [3..5] the three `_make…` defaults
        //   [6] body
        let words = view_wt as *mut *const c_void;
        *words.add(1) = body_wt;
        *words.add(2) = body_meta;
        *words.add(6) = shape_body_thunk as *const c_void;

        shape
    })
}

/// A shape written in Rust, as a view.
///
/// Filled with the foreground style, which is what a bare `Shape` in a view
/// builder means — `Circle()` on its own draws a filled circle.
pub fn custom_shape(shape: &CustomShape) -> ViewHandle {
    unsafe {
        let conformance = shape_conformance();
        let meta = carrier().1;
        let wt = abi::get_view_wt(meta);
        assert!(
            !wt.is_null(),
            "{} !: View — the synthesised conformance was not found",
            crate::diag::type_name(meta),
        );
        let _ = conformance;
        let mut bytes = vec![0u8; abi::type_bytes(meta)];
        bytes[..8].copy_from_slice(&shape.index.to_le_bytes());
        wrap_owned(bytes, meta, wt)
    }
}

/// The shape as one of the crate's [`super::Shape`] values.
///
/// `.fill(_:)` and `.fill_style(…)` work: they reach `_ShapeView` through its
/// *descriptor* and four key arguments, so no name is involved. The methods that
/// compose a result *name* — `.inset`, `.rotation`, `.stroked` and the rest —
/// cannot: they build a mangled name for the result type, and a type minted at
/// run time has none that `swift_getTypeByMangledName` will resolve. Those fail
/// with this shape's name in the message rather than silently.
pub fn custom_shape_value(shape: &CustomShape) -> super::Shape {
    unsafe {
        let conformance = shape_conformance();
        let meta = carrier().1;
        let mut bytes = vec![0u8; abi::type_bytes(meta)];
        bytes[..8].copy_from_slice(&shape.index.to_le_bytes());
        super::Shape::of_metadata(
            bytes,
            meta,
            conformance.witness_table(),
            crate::mangle::Type::Verbatim("6rswift9RustShapeV".into()),
        )
    }
}