use super::*;
use crate::abi;
use crate::resolve;
use core::ffi::c_void;
use crate::mangle::{self, swift_type};
pub type Persist<T> = std::sync::Arc<dyn Fn(&T) + Send + Sync>;
pub struct BoundState<T> {
value: std::sync::Arc<std::sync::Mutex<T>>,
persist: Option<Persist<T>>,
}
impl<T: Clone> BoundState<T> {
pub fn new(initial: T) -> Self {
Self {
value: std::sync::Arc::new(std::sync::Mutex::new(initial)),
persist: None,
}
}
pub fn get(&self) -> T {
self.value.lock().unwrap().clone()
}
pub fn set(&self, v: T) {
if let Some(write) = &self.persist {
write(&v);
}
*self.value.lock().unwrap() = v;
}
pub fn is_persistent(&self) -> bool {
self.persist.is_some()
}
}
impl<T: crate::defaults::Persisted> BoundState<T> {
pub fn persistent(key: &str, default: T) -> Self {
let initial = <T as crate::defaults::Persisted>::load(key, default);
let owned = key.to_owned();
Self {
value: std::sync::Arc::new(std::sync::Mutex::new(initial)),
persist: Some(std::sync::Arc::new(move |v: &T| {
<T as crate::defaults::Persisted>::store(&owned, v);
})),
}
}
}
impl<T> Clone for BoundState<T> {
fn clone(&self) -> Self {
Self { value: self.value.clone(), persist: self.persist.clone() }
}
}
pub(crate) struct BindingHooks {
get: Box<dyn Fn(*mut u8)>,
set: Box<dyn Fn(*const u8)>,
}
pub(crate) unsafe extern "C" fn binding_get_body(out: *mut u8, payload: *mut c_void) {
let hooks = unsafe { &*(payload as *const BindingHooks) };
(hooks.get)(out);
}
pub(crate) unsafe extern "C" fn binding_set_body(value: *const u8, payload: *mut c_void) {
let hooks = unsafe { &*(payload as *const BindingHooks) };
(hooks.set)(value);
if let Some(f) = ON_WRITE.get() {
f();
}
}
static ON_WRITE: std::sync::OnceLock<Box<dyn Fn() + Send + Sync>> =
std::sync::OnceLock::new();
pub fn on_write(f: impl Fn() + Send + Sync + 'static) {
let _ = ON_WRITE.set(Box::new(f));
}
pub(crate) unsafe fn binding_get_set(
value_meta: *const c_void,
binding_mangled: &[u8],
get: Box<dyn Fn(*mut u8)>,
set: Box<dyn Fn(*const u8)>,
) -> Vec<u8> {
unsafe {
let hooks = Box::leak(Box::new(BindingHooks { get, set })) as *mut BindingHooks;
let get_ctx = Box::leak(Box::new(abi::ClosureCtx::new(
binding_get_body,
hooks as *mut c_void,
))) as *mut abi::ClosureCtx;
let set_ctx = Box::leak(Box::new(abi::ValueClosureCtx::new(
binding_set_body,
hooks as *mut c_void,
))) as *mut abi::ValueClosureCtx;
let binding_meta = abi::resolve_type_by_mangled_name(binding_mangled);
assert!(!binding_meta.is_null(), "Binding metadata");
let n = abi::type_stride(binding_meta).max(abi::type_size(binding_meta));
let mut out = vec![0u8; n];
core::arch::asm!("blr {f}", f = in(reg) resolve::binding_get_set(),
in("x0") abi::swift_closure_thunk as *const c_void,
in("x1") get_ctx,
in("x2") abi::value_closure_thunk as *const c_void,
in("x3") set_ctx,
in("x4") value_meta,
in("x8") out.as_mut_ptr(),
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"),
);
out
}
}
pub fn toggle_bound(label: &str, state: &BoundState<bool>) -> ViewHandle {
unsafe {
let g = state.clone();
let sset = state.clone();
let binding = binding_get_set(
resolve::bool_meta(),
mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::BOOL]).as_bytes(),
Box::new(move |out| *out = u8::from(g.get())),
Box::new(move |v| sset.set(*v != 0)),
);
toggle_with_binding(label, &binding)
}
}
pub fn toggle_app_storage(label: &str, stored: &crate::state::StoredView<bool>) -> ViewHandle {
unsafe { toggle_with_binding(label, &stored.binding()) }
}
pub fn slider_app_storage(
stored: &crate::state::StoredView<f64>,
lower: f64,
upper: f64,
) -> ViewHandle {
unsafe {
let mut binding = stored.binding();
super::slider_with_binding(&mut binding, lower, upper)
}
}
pub fn stepper_app_storage(
label: &str,
stored: &crate::state::StoredView<f64>,
lower: f64,
upper: f64,
step: f64,
) -> ViewHandle {
unsafe {
let mut binding = stored.binding();
super::stepper_with_binding(label, &mut binding, lower, upper, step)
}
}
pub(crate) unsafe fn scalar_binding<T, const N: usize>(
state: &BoundState<T>,
meta: *const c_void,
mangled: &[u8],
to_bytes: fn(&T) -> [u8; N],
from_bytes: fn([u8; N]) -> T,
) -> Vec<u8>
where
T: Clone + Send + 'static,
{
let g = state.clone();
let s = state.clone();
unsafe {
binding_get_set(
meta,
mangled,
Box::new(move |out| {
let v = to_bytes(&g.get());
core::ptr::copy_nonoverlapping(v.as_ptr(), out, N);
}),
Box::new(move |v| {
let mut b = [0u8; N];
core::ptr::copy_nonoverlapping(v, b.as_mut_ptr(), N);
s.set(from_bytes(b));
}),
)
}
}
pub(crate) unsafe fn bool_binding_of(state: &BoundState<bool>) -> Vec<u8> {
unsafe {
scalar_binding::<bool, 1>(
state,
resolve::bool_meta(),
mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::BOOL]).as_bytes(),
|v| [u8::from(*v)],
|b| b[0] != 0,
)
}
}
pub(crate) unsafe fn f64_binding_of(state: &BoundState<f64>) -> Vec<u8> {
unsafe {
scalar_binding::<f64, 8>(
state,
resolve::double_meta(),
mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::DOUBLE]).as_bytes(),
|v| v.to_le_bytes(),
f64::from_le_bytes,
)
}
}
pub(crate) unsafe fn i64_binding_of(state: &BoundState<i64>) -> Vec<u8> {
unsafe {
scalar_binding::<i64, 8>(
state,
resolve::int_meta(),
mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::INT]).as_bytes(),
|v| v.to_le_bytes(),
i64::from_le_bytes,
)
}
}
pub fn slider_bound(state: &BoundState<f64>, lower: f64, upper: f64) -> ViewHandle {
unsafe {
let mut binding = f64_binding_of(state);
super::slider_with_binding(&mut binding, lower, upper)
}
}
pub fn slider_step_bound(
state: &BoundState<f64>,
lower: f64,
upper: f64,
step: f64,
) -> ViewHandle {
unsafe {
let mut binding = f64_binding_of(state);
super::slider_step_with_binding(&mut binding, lower, upper, step)
}
}
pub fn stepper_bound(
label: &str,
state: &BoundState<f64>,
lower: f64,
upper: f64,
step: f64,
) -> ViewHandle {
unsafe {
let mut binding = f64_binding_of(state);
super::stepper_with_binding(label, &mut binding, lower, upper, step)
}
}
pub(crate) unsafe fn toggle_with_binding(label: &str, binding: &[u8]) -> ViewHandle {
unsafe {
let ss = abi::swift_string(label);
let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
let (k0, k1, k2, k3) = localized_string_key(s0, s1);
let b0 = u64::from_le_bytes(binding[0..8].try_into().unwrap());
let b1 = u64::from_le_bytes(binding[8..16].try_into().unwrap());
let b2 = binding[16] as u64;
let (meta, wt, size) = result_type(&TOGGLE_T);
let mut result = vec![0u8; size];
core::arch::asm!("blr {f}", f = in(reg) resolve::toggle_init(),
in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
in("x4") b0, in("x5") b1, in("x6") b2,
in("x8") result.as_mut_ptr(),
in("x20") meta,
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"),
);
wrap_owned(result, meta, wt)
}
}
pub unsafe fn binding_write(binding: &mut [u8], value: &mut [u8], mangled: &[u8]) {
unsafe {
let meta = abi::resolve_type_by_mangled_name(mangled);
assert!(!meta.is_null(), "binding type metadata");
core::arch::asm!("blr {f}", f = in(reg) resolve::binding_wrapped_set(),
in("x0") value.as_mut_ptr(),
in("x1") meta,
in("x20") binding.as_mut_ptr(),
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"),
);
}
}
pub fn binding_write_bool(binding: &mut [u8], value: bool) {
unsafe {
let mut v = [u8::from(value)];
let meta = abi::resolve_type_by_mangled_name(mangle::bound("7SwiftUI7BindingV", &[mangle::BOOL]).as_bytes());
core::arch::asm!("blr {f}", f = in(reg) resolve::binding_wrapped_set(),
in("x0") v.as_mut_ptr(),
in("x1") meta,
in("x20") binding.as_mut_ptr(),
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"),
);
}
}
pub fn text_binding(state: &BoundState<String>) -> Vec<u8> {
let g = state.clone();
let s = state.clone();
unsafe {
binding_get_set(
resolve::string_meta(),
mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::STRING]).as_bytes(),
Box::new(move |out| {
let bytes = abi::swift_string(&g.get());
core::ptr::copy_nonoverlapping(bytes.as_ptr(), out, 16);
}),
Box::new(move |v| {
let mut bytes = [0u8; 16];
core::ptr::copy_nonoverlapping(v, bytes.as_mut_ptr(), 16);
s.set(abi::read_swift_string(bytes));
}),
)
}
}
pub fn f64_binding(state: &BoundState<f64>) -> Vec<u8> {
let g = state.clone();
let s = state.clone();
unsafe {
binding_get_set(
resolve::double_meta(),
mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::DOUBLE]).as_bytes(),
Box::new(move |out| {
let v = g.get();
core::ptr::copy_nonoverlapping(v.to_le_bytes().as_ptr(), out, 8);
}),
Box::new(move |v| {
let mut b = [0u8; 8];
core::ptr::copy_nonoverlapping(v, b.as_mut_ptr(), 8);
s.set(f64::from_le_bytes(b));
}),
)
}
}
pub fn optional_i64_binding(state: &BoundState<Option<i64>>) -> Vec<u8> {
let g = state.clone();
let s = state.clone();
unsafe {
let payload = resolve::int_meta();
let opt = abi::resolve_type_by_mangled_name(b"SiSg");
assert!(!opt.is_null(), "Optional<Int> metadata");
let width = abi::type_bytes(opt);
binding_get_set(
opt,
mangle::bound(&swift_type!(SwiftUI.Binding), &[&mangle::optional(mangle::INT)])
.as_bytes(),
Box::new(move |out| {
let slot = core::slice::from_raw_parts_mut(out, width);
slot.fill(0);
match g.get() {
Some(v) => {
slot[..8].copy_from_slice(&v.to_le_bytes());
abi::store_optional_tag(slot, payload, true);
}
None => abi::store_optional_tag(slot, payload, false),
}
}),
Box::new(move |v| {
let slot = core::slice::from_raw_parts(v, width);
let present = abi::read_optional_tag(slot, payload);
s.set(present.then(|| {
let mut b = [0u8; 8];
b.copy_from_slice(&slot[..8]);
i64::from_le_bytes(b)
}));
}),
)
}
}
pub fn i64_binding(state: &BoundState<i64>) -> Vec<u8> {
let g = state.clone();
let s = state.clone();
unsafe {
binding_get_set(
resolve::int_meta(),
mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::INT]).as_bytes(),
Box::new(move |out| {
let v = g.get();
core::ptr::copy_nonoverlapping(v.to_le_bytes().as_ptr(), out, 8);
}),
Box::new(move |v| {
let mut b = [0u8; 8];
core::ptr::copy_nonoverlapping(v, b.as_mut_ptr(), 8);
s.set(i64::from_le_bytes(b));
}),
)
}
}
pub fn bool_binding(state: &BoundState<bool>) -> Vec<u8> {
let g = state.clone();
let s = state.clone();
unsafe {
binding_get_set(
resolve::bool_meta(),
mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::BOOL]).as_bytes(),
Box::new(move |out| *out = u8::from(g.get())),
Box::new(move |v| s.set(*v != 0)),
)
}
}
swift_cases! {
pub enum HorizontalEdge {
Leading = 0,
Trailing = 1,
}
}
unsafe fn presenting_modifier(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
before: &[u64],
state: &BoundState<bool>,
content: &ViewHandle,
) -> ViewHandle {
unsafe {
let mut b = bool_binding(state);
let w = binding_words(&mut b);
let mut ints = before.to_vec();
ints.extend_from_slice(&w);
ints.extend_from_slice(&view_words(content));
generic1_modifier_ints(
func,
oret,
view,
content.meta,
content.wt,
&ints,
[0.0; 4],
)
}
}
pub fn navigation_destination(
view: &ViewHandle,
state: &BoundState<bool>,
destination: &ViewHandle,
) -> ViewHandle {
unsafe {
presenting_modifier(
resolve::navigation_destination_fn(),
resolve::navigation_destination_oret(),
view,
&[],
state,
destination,
)
}
}
pub fn inspector(
view: &ViewHandle,
state: &BoundState<bool>,
content: &ViewHandle,
) -> ViewHandle {
unsafe {
presenting_modifier(
resolve::inspector_fn(),
resolve::inspector_oret(),
view,
&[],
state,
content,
)
}
}
pub fn swipe_actions(
view: &ViewHandle,
edge: HorizontalEdge,
allows_full_swipe: bool,
content: &ViewHandle,
) -> ViewHandle {
unsafe {
let e = abi::enum_argument(abi::metadata0(resolve::horizontal_edge_ma()), edge.tag());
let payload = abi::resolve_type_by_mangled_name(mangle::bound("7SwiftUI7BindingV", &[mangle::BOOL]).as_bytes());
let opt = abi::resolve_type_by_mangled_name(mangle::optional(&mangle::bound("7SwiftUI7BindingV", &[mangle::BOOL])).as_bytes());
let mut nil = vec![0u8; abi::type_bytes(opt).max(24)];
abi::store_optional_tag(&mut nil, payload, false);
let mut ints = vec![e, u64::from(allows_full_swipe)];
ints.extend_from_slice(&binding_words(&mut nil));
ints.extend_from_slice(&view_words(content));
generic1_modifier_ints(
resolve::swipe_actions_fn(),
resolve::swipe_actions_oret(),
view,
content.meta,
content.wt,
&ints,
[0.0; 4],
)
}
}
pub fn popover_anchored(
view: &ViewHandle,
state: &BoundState<bool>,
anchor: (f64, f64),
arrow_edge: Edge,
content: &ViewHandle,
) -> ViewHandle {
unsafe {
let mut b = bool_binding(state);
let w = binding_words(&mut b);
let anchor_meta = abi::metadata0(resolve::popover_anchor_ma());
let mut buf = vec![0u8; abi::type_bytes(anchor_meta)];
buf[..8].copy_from_slice(&anchor.0.to_le_bytes());
buf[8..16].copy_from_slice(&anchor.1.to_le_bytes());
abi::inject_enum_tag(&mut buf, anchor_meta, 1);
let edge = abi::enum_argument(abi::metadata0(resolve::edge_ma()), arrow_edge as u32);
let mut ints = w.to_vec();
ints.push(buf.as_ptr() as u64);
ints.push(edge);
ints.extend_from_slice(&view_words(content));
generic1_modifier_ints(
resolve::popover_fn(),
resolve::popover_oret(),
view,
content.meta,
content.wt,
&ints,
[0.0; 4],
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Edge {
Top = 0,
Leading = 1,
Bottom = 2,
Trailing = 3,
}
pub fn toggle_labelled(label: &ViewHandle, state: &BoundState<bool>) -> ViewHandle {
unsafe {
let binding = bool_binding_of(state);
let b0 = u64::from_le_bytes(binding[0..8].try_into().expect("binding 0"));
let b1 = u64::from_le_bytes(binding[8..16].try_into().expect("binding 1"));
let b2 = binding[16] as u64;
let (known_meta, known_wt, known_size) = super::result_type(&super::TOGGLE_ANY_T);
let typed = super::substitute_args(known_meta, 2, &[(0, label.meta)], &[(1, label.wt)]);
let (meta, wt) = typed.unwrap_or((known_meta, known_wt));
let size = if typed.is_some() { abi::type_bytes(meta) } else { known_size };
let (label_meta, label_wt) = match typed {
Some(_) => (label.meta, label.wt),
None => (resolve::anyview_meta(), resolve::anyview_wt()),
};
let kind = if typed.is_some() { super::Content::Concrete } else { super::Content::Erased };
let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
super::emit_tuple,
super::Child::leak(label, kind),
))) as *mut abi::ClosureCtx;
let mut result = vec![0u8; size];
core::arch::asm!("blr {f}", f = in(reg) resolve::toggle_label_init(),
in("x0") b0, in("x1") b1, in("x2") b2,
in("x3") abi::swift_closure_thunk as *const c_void,
in("x4") ctx,
in("x5") label_meta,
in("x6") label_wt,
in("x8") result.as_mut_ptr(),
in("x20") meta,
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"),
);
wrap_owned(result, meta, wt)
}
}
pub fn toggle_sources(label: &str, states: &[&BoundState<bool>]) -> ViewHandle {
assert!(!states.is_empty(), "a Toggle over sources needs at least one");
unsafe {
let ss = abi::swift_string(label);
let s0 = u64::from_le_bytes(ss[..8].try_into().expect("label lo"));
let s1 = u64::from_le_bytes(ss[8..].try_into().expect("label hi"));
let (k0, k1, k2, k3) = localized_string_key(s0, s1);
let element = abi::resolve_type_by_mangled_name(b"7SwiftUI7BindingVySbG");
assert!(!element.is_null(), "Binding<Bool> metadata");
let stride = abi::type_bytes(element);
let mut bytes = vec![0u8; stride * states.len()];
for (i, state) in states.iter().enumerate() {
let b = bool_binding_of(state);
bytes[i * stride..i * stride + b.len()].copy_from_slice(&b);
}
let array = abi::swift_array(element, states.len(), &bytes);
let array_meta = abi::resolve_type_by_mangled_name(b"Say7SwiftUI7BindingVySbGG");
assert!(!array_meta.is_null(), "Array<Binding<Bool>> metadata");
let rac = super::conformance_wt(array_meta, resolve::random_access_proto());
assert!(!rac.is_null(), "Array<Binding<Bool>>: RandomAccessCollection");
let (meta, wt, size) = super::result_type(&super::TOGGLE_T);
let mut result = vec![0u8; size];
core::arch::asm!("blr {f}", f = in(reg) resolve::toggle_sources_init(),
in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
in("x4") &array as *const u64,
in("x5") array_meta,
in("x6") rac,
in("x8") result.as_mut_ptr(),
in("x20") meta,
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"),
);
wrap_owned(result, meta, wt)
}
}