use super::{wrap_owned, ViewHandle};
use crate::abi;
use crate::resolve;
use core::ffi::c_void;
use std::sync::{Mutex, OnceLock};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Rect {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Proposal {
pub width: Option<f64>,
pub height: Option<f64>,
}
impl Proposal {
pub fn or(self, fallback: f64) -> (f64, f64) {
(self.width.unwrap_or(fallback), self.height.unwrap_or(fallback))
}
}
pub struct Subviews<'a> {
raw: *mut c_void,
marker: core::marker::PhantomData<&'a ()>,
}
impl Subviews<'_> {
pub fn len(&self) -> usize {
unsafe {
let start = call_index(resolve::layout_subviews_start(), self.raw);
let end = call_index(resolve::layout_subviews_end(), self.raw);
(end - start).max(0) as usize
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn size_that_fits(&self, i: usize, proposal: Proposal) -> (f64, f64) {
unsafe {
let mut sub = self.child(i);
let (w, wt, h, ht) = proposal_words(proposal);
let (rw, rh): (f64, f64);
core::arch::asm!("blr {f}", f = in(reg) resolve::layout_subview_size(),
in("x0") w, in("x1") wt, in("x2") h, in("x3") ht,
in("x20") sub.as_mut_ptr(),
lateout("d0") rw, lateout("d1") rh,
lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
clobber_abi("C"),
);
(rw, rh)
}
}
pub fn place(&self, i: usize, x: f64, y: f64) {
self.place_proposed(i, x, y, Proposal { width: None, height: None });
}
pub fn place_proposed(&self, i: usize, x: f64, y: f64, proposal: Proposal) {
unsafe {
let mut sub = self.child(i);
let (w, wt, h, ht) = proposal_words(proposal);
core::arch::asm!("blr {f}", f = in(reg) resolve::layout_subview_place(),
in("d0") x, in("d1") y, in("d2") 0.0f64, in("d3") 0.0f64,
in("x0") w, in("x1") wt, in("x2") h, in("x3") ht,
in("x20") sub.as_mut_ptr(),
lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
clobber_abi("C"),
);
}
}
unsafe fn child(&self, i: usize) -> Vec<u8> {
unsafe {
let meta = abi::metadata0(resolve::layout_subview_ma());
assert!(!meta.is_null(), "LayoutSubview metadata");
let mut out = vec![0u8; abi::type_bytes(meta)];
core::arch::asm!("blr {f}", f = in(reg) resolve::layout_subviews_subscript(),
in("x0") i as i64,
in("x8") out.as_mut_ptr(),
in("x20") self.raw,
lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
out
}
}
}
unsafe fn call_index(f: *const c_void, raw: *mut c_void) -> i64 {
unsafe {
let out: i64;
core::arch::asm!("blr {f}", f = in(reg) f,
in("x20") raw,
lateout("x0") out,
lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
out
}
}
fn proposal_words(p: Proposal) -> (u64, u64, u64, u64) {
let one = |v: Option<f64>| match v {
Some(x) => (x.to_bits(), 0u64),
None => (0u64, 1u64),
};
let (w, wt) = one(p.width);
let (h, ht) = one(p.height);
(w, wt, h, ht)
}
pub trait LayoutAlgorithm: Send + Sync + 'static {
fn size_that_fits(&self, subviews: &Subviews<'_>, proposal: Proposal) -> (f64, f64);
fn place_subviews(&self, subviews: &Subviews<'_>, bounds: Rect, proposal: Proposal);
}
pub struct FnLayout<S, P> {
size: S,
place: P,
}
impl<S, P> LayoutAlgorithm for FnLayout<S, P>
where
S: Fn(&Subviews<'_>, Proposal) -> (f64, f64) + Send + Sync + 'static,
P: Fn(&Subviews<'_>, Rect, Proposal) + Send + Sync + 'static,
{
fn size_that_fits(&self, subviews: &Subviews<'_>, proposal: Proposal) -> (f64, f64) {
(self.size)(subviews, proposal)
}
fn place_subviews(&self, subviews: &Subviews<'_>, bounds: Rect, proposal: Proposal) {
(self.place)(subviews, bounds, proposal)
}
}
fn registry() -> &'static Mutex<Vec<&'static dyn LayoutAlgorithm>> {
static R: OnceLock<Mutex<Vec<&'static dyn LayoutAlgorithm>>> = OnceLock::new();
R.get_or_init(|| Mutex::new(Vec::new()))
}
pub struct CustomLayout {
index: i64,
}
pub fn layout(
size: impl Fn(&Subviews<'_>, Proposal) -> (f64, f64) + Send + Sync + 'static,
place: impl Fn(&Subviews<'_>, Rect, Proposal) + Send + Sync + 'static,
) -> CustomLayout {
register(FnLayout { size, place })
}
pub fn register<L: LayoutAlgorithm>(algorithm: L) -> CustomLayout {
let leaked: &'static dyn LayoutAlgorithm = Box::leak(Box::new(algorithm));
let mut r = registry().lock().expect("layout registry");
r.push(leaked);
CustomLayout { index: r.len() as i64 - 1 }
}
unsafe fn algorithm_at(this: *const u8) -> &'static dyn LayoutAlgorithm {
let index = unsafe { *(this as *const i64) } as usize;
let r = registry().lock().expect("layout registry");
r.get(index).copied().expect("layout index out of range")
}
core::arch::global_asm!(
".p2align 2",
".globl _rswift_layout_size_thunk",
"_rswift_layout_size_thunk:",
"mov x6, x20",
"b _rswift_layout_size_that_fits",
".p2align 2",
".globl _rswift_layout_place_thunk",
"_rswift_layout_place_thunk:",
"mov x6, x20",
"b _rswift_layout_place_subviews",
);
unsafe extern "C" {
#[link_name = "rswift_layout_size_thunk"]
fn size_that_fits_thunk();
#[link_name = "rswift_layout_place_thunk"]
fn place_subviews_thunk();
}
unsafe extern "C" fn make_cache() {}
#[repr(C)]
struct Size {
width: f64,
height: f64,
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rswift_layout_size_that_fits(
w: u64,
wt: u64,
h: u64,
ht: u64,
subviews: *mut c_void,
_cache: *mut c_void,
this: *const u8,
) -> Size {
let algorithm = unsafe { algorithm_at(this) };
let subviews = Subviews { raw: subviews, marker: core::marker::PhantomData };
let proposal = Proposal {
width: (wt == 0).then(|| f64::from_bits(w)),
height: (ht == 0).then(|| f64::from_bits(h)),
};
let (w, h) = algorithm.size_that_fits(&subviews, proposal);
Size { width: w, height: h }
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rswift_layout_place_subviews(
bx: f64,
by: f64,
bw: f64,
bh: f64,
w: u64,
wt: u64,
h: u64,
ht: u64,
subviews: *mut c_void,
_cache: *mut c_void,
this: *const u8,
) {
let algorithm = unsafe { algorithm_at(this) };
let subviews = Subviews { raw: subviews, marker: core::marker::PhantomData };
let proposal = Proposal {
width: (wt == 0).then(|| f64::from_bits(w)),
height: (ht == 0).then(|| f64::from_bits(h)),
};
algorithm.place_subviews(&subviews, Rect { x: bx, y: by, width: bw, height: bh }, proposal);
}
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"RustLayout\0", b"6rswift10RustLayoutV\0");
(d as usize, m as usize)
});
(d as *const c_void, m as *const c_void)
}
fn conformance() -> &'static crate::conformance::Conformance {
use crate::conformance::{synthesize, Protocol, Witness};
static C: OnceLock<crate::conformance::Conformance> = OnceLock::new();
C.get_or_init(|| unsafe {
let protocol = Protocol::from_symbol(c"$s7SwiftUI6LayoutMp");
assert_eq!(protocol.requirement_count(), 11, "Layout changed shape");
synthesize(
&protocol,
carrier().0,
&[
Witness::Metadata(resolve::hstack_layout_animatable_wt()),
Witness::Default,
Witness::Default,
Witness::Function(make_cache as *const c_void),
Witness::Default,
Witness::Default,
Witness::Function(size_that_fits_thunk as *const c_void),
Witness::Function(place_subviews_thunk as *const c_void),
Witness::Default,
Witness::Default,
Witness::Default,
],
)
})
}
pub fn custom_layout(layout: &CustomLayout, children: &[ViewHandle]) -> ViewHandle {
unsafe {
let conformance = conformance();
let (_, carrier_meta) = carrier();
let mut value = vec![0u8; abi::type_bytes(carrier_meta)];
value[..8].copy_from_slice(&layout.index.to_le_bytes());
let root_meta = abi::generic_metadata2(
resolve::layout_root_ma(),
carrier_meta,
conformance.witness_table(),
);
assert!(!root_meta.is_null(), "_LayoutRoot metadata");
let mut root = vec![0u8; abi::type_bytes(root_meta)];
core::arch::asm!("blr {f}", f = in(reg) resolve::layout_root_init(),
in("x0") value.as_mut_ptr(),
in("x1") carrier_meta,
in("x2") conformance.witness_table(),
in("x8") root.as_mut_ptr(),
lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
lateout("x21") _, clobber_abi("C"),
);
let (content_meta, _content_wt, offsets) = if children.len() == 1 {
(children[0].meta, children[0].wt, vec![0usize])
} else {
let elements: Vec<*const c_void> = children.iter().map(|c| c.meta).collect();
let tuple = abi::tuple_metadata(&elements);
assert!(!tuple.is_null(), "tuple metadata instantiation failed");
let offsets = abi::tuple_element_offsets(tuple, children.len());
let m = abi::generic_metadata1(resolve::tupleview_ma(), tuple);
assert!(!m.is_null(), "TupleView metadata instantiation failed");
let w = abi::get_view_wt(m);
assert!(!w.is_null(), "TupleView !: View");
(m, w, offsets)
};
let kids: Vec<super::TupleChild> = children
.iter()
.zip(offsets.iter())
.map(|(c, &offset)| super::TupleChild {
value: c.value_ptr(),
meta: c.meta,
offset,
})
.collect();
let payload = super::TuplePayload { children: kids.as_ptr(), count: kids.len() };
let mut ctx = abi::ClosureCtx::new(
super::emit_tuple,
&payload as *const super::TuplePayload as *mut c_void,
);
let root_wt = super::conformance_wt(root_meta, resolve::variadic_root_proto());
let meta = abi::generic_metadata_from_descriptor(
resolve::variadic_tree_desc(),
&[root_meta, content_meta, root_wt],
);
assert!(!meta.is_null(), "_VariadicView.Tree metadata");
let wt = abi::get_view_wt(meta);
assert!(!wt.is_null(), "_VariadicView.Tree !: View");
let mut result = vec![0u8; abi::type_bytes(meta)];
core::arch::asm!("blr {f}", f = in(reg) resolve::variadic_tree_init(),
in("x0") root.as_mut_ptr(),
in("x1") abi::swift_closure_thunk as *const c_void,
in("x2") &mut ctx as *mut abi::ClosureCtx,
in("x3") root_meta,
in("x4") content_meta,
in("x5") root_wt,
in("x8") result.as_mut_ptr(),
lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
clobber_abi("C"),
);
wrap_owned(result, meta, wt)
}
}