use super::*;
use crate::abi;
use crate::resolve;
use core::ffi::c_void;
use crate::mangle::{self, swift_type};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InsettableShape {
Circle,
Ellipse,
Rectangle,
ContainerRelativeShape,
}
impl InsettableShape {
pub(crate) fn type_mangling(self) -> &'static str {
match self {
Self::Circle => "7SwiftUI6CircleV",
Self::Ellipse => "7SwiftUI7EllipseV",
Self::Rectangle => "7SwiftUI9RectangleV",
Self::ContainerRelativeShape => "7SwiftUI22ContainerRelativeShapeV",
}
}
pub(crate) fn parts(self) -> (*const c_void, *const c_void, *const c_void) {
match self {
Self::Circle => (
resolve::circle_ma(),
resolve::circle_insettable_wt(),
resolve::circle_init(),
),
Self::Ellipse => (
resolve::ellipse_ma(),
resolve::ellipse_insettable_wt(),
resolve::ellipse_init(),
),
Self::Rectangle => (
resolve::rectangle_ma(),
resolve::rectangle_insettable_wt(),
resolve::rectangle_init(),
),
Self::ContainerRelativeShape => (
resolve::container_relative_shape_ma(),
resolve::container_relative_shape_insettable_wt(),
resolve::container_relative_shape_init(),
),
}
}
}
pub fn find_navigator(view: &ViewHandle, state: &BoundState<bool>) -> ViewHandle {
unsafe {
let mut b = bool_binding(state);
let w = binding_words(&mut b);
opaque_modifier(
resolve::find_navigator_fn(),
resolve::find_navigator_oret(),
view,
&w,
[0.0; 4],
)
}
}
pub fn dialog_suppression_toggle(view: &ViewHandle, state: &BoundState<bool>) -> ViewHandle {
unsafe {
let mut b = bool_binding(state);
let w = binding_words(&mut b);
opaque_modifier(
resolve::dialog_suppression_toggle_fn(),
resolve::dialog_suppression_toggle_oret(),
view,
&w,
[0.0; 4],
)
}
}
pub fn dialog_suppression_toggle_labelled(
view: &ViewHandle,
label: &str,
state: &BoundState<bool>,
) -> ViewHandle {
unsafe {
let ss = abi::swift_string(label);
let k = localized_string_key(
u64::from_le_bytes(ss[..8].try_into().expect("lo")),
u64::from_le_bytes(ss[8..].try_into().expect("hi")),
);
let mut b = bool_binding(state);
let w = binding_words(&mut b);
opaque_modifier(
resolve::dialog_suppression_toggle_labelled_fn(),
resolve::dialog_suppression_toggle_labelled_oret(),
view,
&[k.0, k.1, k.2, k.3, w[0], w[1], w[2]],
[0.0; 4],
)
}
}
pub fn high_priority_gesture(view: &ViewHandle, minimum_distance: f64, action: impl Fn() + 'static) -> ViewHandle {
unsafe {
gesture_variant(
resolve::high_priority_gesture_fn(),
resolve::high_priority_gesture_oret(),
view,
minimum_distance,
action,
)
}
}
pub fn simultaneous_gesture(view: &ViewHandle, minimum_distance: f64, action: impl Fn() + 'static) -> ViewHandle {
unsafe {
gesture_variant(
resolve::simultaneous_gesture_fn(),
resolve::simultaneous_gesture_oret(),
view,
minimum_distance,
action,
)
}
}
pub fn container_shape(view: &ViewHandle, shape: InsettableShape) -> ViewHandle {
unsafe {
let (meta, wt, init) = shape.parts();
style_modifier(
resolve::container_shape_fn(),
resolve::container_shape_oret(),
view,
meta,
wt,
init,
)
}
}
pub(crate) unsafe fn generic1_modifier(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
arg_meta: *const c_void,
arg_wt: *const c_void,
arg: &[u8],
extra: &[u64],
) -> ViewHandle {
unsafe {
let subst = [view.meta, arg_meta, view.wt, arg_wt];
let (meta, wt, size) = opaque_result_type(oret, &subst);
let mut ints = Vec::with_capacity(5 + extra.len());
ints.push(arg.as_ptr() as u64);
ints.extend_from_slice(extra);
ints.push(view.meta as u64);
ints.push(arg_meta as u64);
ints.push(view.wt as u64);
ints.push(arg_wt as u64);
let r = abi::call_modifier_generic(func, view.value_ptr(), &ints, [0.0; 4], size);
wrap_owned(r, meta, wt)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HashableValue<'a> {
Str(&'a str),
Int(i64),
Bool(bool),
Double(f64),
}
impl HashableValue<'_> {
pub(crate) unsafe fn parts(self) -> (Vec<u8>, *const c_void, *const c_void) {
unsafe {
match self {
Self::Str(s) => (
abi::swift_string(s).to_vec(),
resolve::string_meta(),
resolve::string_hashable_wt(),
),
Self::Int(i) => {
(i.to_le_bytes().to_vec(), resolve::int_meta(), resolve::int_hashable_wt())
}
Self::Bool(b) => {
(vec![u8::from(b)], resolve::bool_meta(), resolve::bool_hashable_wt())
}
Self::Double(d) => {
(d.to_le_bytes().to_vec(), resolve::double_meta(), resolve::double_hashable_wt())
}
}
}
}
}
pub trait IntoHashable<'a> {
fn into_hashable(self) -> HashableValue<'a>;
}
impl<'a> IntoHashable<'a> for HashableValue<'a> {
fn into_hashable(self) -> HashableValue<'a> {
self
}
}
impl<'a> IntoHashable<'a> for &'a str {
fn into_hashable(self) -> HashableValue<'a> {
HashableValue::Str(self)
}
}
impl<'a> IntoHashable<'a> for &'a String {
fn into_hashable(self) -> HashableValue<'a> {
HashableValue::Str(self)
}
}
impl IntoHashable<'_> for i64 {
fn into_hashable(self) -> HashableValue<'static> {
HashableValue::Int(self)
}
}
impl IntoHashable<'_> for i32 {
fn into_hashable(self) -> HashableValue<'static> {
HashableValue::Int(i64::from(self))
}
}
impl IntoHashable<'_> for usize {
fn into_hashable(self) -> HashableValue<'static> {
HashableValue::Int(self as i64)
}
}
impl IntoHashable<'_> for bool {
fn into_hashable(self) -> HashableValue<'static> {
HashableValue::Bool(self)
}
}
impl IntoHashable<'_> for f64 {
fn into_hashable(self) -> HashableValue<'static> {
HashableValue::Double(self)
}
}
pub fn id(view: &ViewHandle, value: HashableValue<'_>) -> ViewHandle {
unsafe {
let (bytes, meta, wt) = value.parts();
generic1_modifier(resolve::id_fn(), resolve::id_oret(), view, meta, wt, &bytes, &[])
}
}
pub unsafe fn apply_modifier(
view: &ViewHandle,
modifier_meta: *const c_void,
bytes: &[u8],
) -> ViewHandle {
unsafe {
let content = abi::generic_metadata2(
resolve::modified_content_ma(),
view.meta,
modifier_meta,
);
let wt = abi::get_view_wt(content);
assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(content));
let ints = [
bytes.as_ptr() as u64,
view.meta as u64,
modifier_meta as u64,
view.wt as u64,
];
let r = abi::call_modifier_generic(resolve::view_modifier_fn(), view.value_ptr(),
&ints,
[0.0; 4],
abi::type_bytes(content),
);
wrap_owned(r, content, wt)
}
}
pub fn tag(view: &ViewHandle, value: HashableValue<'_>) -> ViewHandle {
unsafe {
let (bytes, meta, wt) = value.parts();
let modifier = abi::generic_metadata2(resolve::tag_trait_writing_modifier_ma(), meta, wt);
let size = abi::type_size(modifier);
let at = abi::type_size(meta);
assert_eq!(size, at + 1, "_TagTraitWritingModifier is not tag + Bool");
let mut v = vec![0u8; abi::type_bytes(modifier)];
v[..bytes.len()].copy_from_slice(&bytes);
v[at] = 1;
apply_modifier(view, modifier, &v)
}
}
impl InsettableShape {
pub(crate) fn shape_parts(self) -> (*const c_void, *const c_void, *const c_void) {
match self {
Self::Circle => (resolve::circle_ma(), resolve::circle_shape_wt(), resolve::circle_init()),
Self::Ellipse => (resolve::ellipse_ma(), resolve::ellipse_shape_wt(), resolve::ellipse_init()),
Self::Rectangle => (resolve::rectangle_ma(), resolve::rectangle_shape_wt(), resolve::rectangle_init()),
Self::ContainerRelativeShape => (
resolve::container_relative_shape_ma(),
resolve::container_relative_shape_shape_wt(),
resolve::container_relative_shape_init(),
),
}
}
}
pub fn content_shape(view: &ViewHandle, shape: InsettableShape, eo_fill: bool) -> ViewHandle {
unsafe {
let (ma, wt, init) = shape.shape_parts();
let meta = abi::metadata0(ma);
let mut value = vec![0u8; abi::type_bytes(meta)];
abi::call_out(init, [0; 8], [0.0; 4], meta as u64, value.as_mut_ptr());
generic1_modifier(
resolve::content_shape_fn(),
resolve::content_shape_oret(),
view,
meta,
wt,
&value,
&[u64::from(eo_fill)],
)
}
}
pub(crate) unsafe fn focus_binding(state_type: &[u8], init: *const c_void, projected: *const c_void) -> [u64; 3] {
unsafe {
let state_meta = abi::resolve_type_by_mangled_name(state_type);
assert!(!state_meta.is_null(), "focus state metadata");
let mut this = [0u64; 3];
core::arch::asm!("blr {f}", f = in(reg) init,
in("x8") this.as_mut_ptr(), in("x20") state_meta,
lateout("x0") _, 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"),
);
let mut out = vec![0u64; 3];
core::arch::asm!("blr {f}", f = in(reg) projected,
in("x0") state_meta, in("x1") resolve::bool_hashable_wt(),
in("x8") out.as_mut_ptr(), in("x20") this.as_mut_ptr(),
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[0], out[1], out[2]]
}
}
pub fn focused(view: &ViewHandle) -> ViewHandle {
unsafe {
let b = focus_binding(
mangle::bound(&swift_type!(SwiftUI.FocusState), &[mangle::BOOL]).as_bytes(),
resolve::focus_state_bool_init(),
resolve::focus_state_projected(),
);
opaque_modifier(resolve::focused_fn(), resolve::focused_oret(), view, &b, [0.0; 4])
}
}
pub fn focused_on(view: &ViewHandle, projection: [u64; 3]) -> ViewHandle {
unsafe {
opaque_modifier(
resolve::focused_fn(),
resolve::focused_oret(),
view,
&projection,
[0.0; 4],
)
}
}
pub fn search_focused(view: &ViewHandle) -> ViewHandle {
unsafe {
let b = focus_binding(
mangle::bound(&swift_type!(SwiftUI.FocusState), &[mangle::BOOL]).as_bytes(),
resolve::focus_state_bool_init(),
resolve::focus_state_projected(),
);
opaque_modifier(
resolve::search_focused_fn(),
resolve::search_focused_oret(),
view,
&b,
[0.0; 4],
)
}
}
pub fn accessibility_focused(view: &ViewHandle) -> ViewHandle {
unsafe {
let b = focus_binding(
mangle::bound(&swift_type!(SwiftUI.AccessibilityFocusState), &[mangle::BOOL]).as_bytes(),
resolve::a11y_focus_state_bool_init(),
resolve::a11y_focus_state_projected(),
);
opaque_modifier(
resolve::a11y_focused_fn(),
resolve::a11y_focused_oret(),
view,
&b,
[0.0; 4],
)
}
}
pub fn coordinate_space(view: &ViewHandle, name: HashableValue<'_>) -> ViewHandle {
unsafe {
let (bytes, meta, wt) = name.parts();
generic1_modifier(
resolve::coordinate_space_name_fn(),
resolve::coordinate_space_name_oret(),
view,
meta,
wt,
&bytes,
&[],
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HashableKind {
Str,
Int,
}
pub fn scroll_position_id(
view: &ViewHandle,
state: &BoundState<String>,
anchor: Option<(f64, f64)>,
) -> ViewHandle {
unsafe {
let g = state.clone();
let st = state.clone();
let value_meta = abi::resolve_type_by_mangled_name(mangle::optional(mangle::STRING).as_bytes());
let binding = binding_get_set(
value_meta,
mangle::bound(&swift_type!(SwiftUI.Binding), &[&mangle::optional(mangle::STRING)]).as_bytes(),
Box::new(move |out| {
let s = abi::swift_string(&g.get());
core::ptr::copy_nonoverlapping(s.as_ptr(), out, 16);
}),
Box::new(move |v| {
let mut raw = [0u8; 16];
core::ptr::copy_nonoverlapping(v, raw.as_mut_ptr(), 16);
st.set(abi::read_swift_string(raw));
}),
);
let mut ints = vec![binding.as_ptr() as u64];
ints.extend(opt_unit_point_words(anchor));
generic1_modifier_ints(
resolve::scroll_position_id_fn(),
resolve::scroll_position_id_oret(),
view,
resolve::string_meta(),
resolve::string_hashable_wt(),
&ints,
[0.0; 4],
)
}
}
pub fn preference_int(view: &ViewHandle, value: i64) -> ViewHandle {
unsafe {
let wt = crate::conformance::preference_key().witness_table();
let meta = resolve::spacer_meta();
let mut boxed = value;
generic1_modifier_ints(
resolve::preference_fn(),
resolve::preference_oret(),
view,
meta,
wt,
&[meta as u64, &mut boxed as *mut i64 as u64],
[0.0; 4],
)
}
}
unsafe fn generic2_modifier_ints(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
arg_meta: *const c_void,
wt_a: *const c_void,
wt_b: *const c_void,
leading: &[u64],
) -> ViewHandle {
unsafe {
let subst = [view.meta, arg_meta, view.wt, wt_a, wt_b];
let (meta, wt, size) = opaque_result_type(oret, &subst);
let mut ints = leading.to_vec();
ints.push(view.meta as u64);
ints.push(arg_meta as u64);
ints.push(view.wt as u64);
ints.push(wt_a as u64);
ints.push(wt_b as u64);
let r = abi::call_modifier_generic(func, view.value_ptr(), &ints, [0.0; 4], size);
wrap_owned(r, meta, wt)
}
}
pub(crate) static OBSERVED_PREFERENCE: std::sync::atomic::AtomicI64 =
std::sync::atomic::AtomicI64::new(i64::MIN);
pub(crate) unsafe extern "C" fn record_preference(
a0: u64,
_a1: u64,
_a2: u64,
_payload: *mut c_void,
) {
let v = unsafe { *(a0 as *const i64) };
OBSERVED_PREFERENCE.store(v, std::sync::atomic::Ordering::SeqCst);
}
pub fn on_preference_change_int(view: &ViewHandle) -> ViewHandle {
unsafe {
let wt = crate::conformance::preference_key().witness_table();
let meta = resolve::spacer_meta();
let c = wide_words(record_preference, core::ptr::null_mut());
generic2_modifier_ints(
resolve::on_preference_change_fn(),
resolve::on_preference_change_oret(),
view,
meta,
wt,
resolve::int_equatable_wt(),
&[meta as u64, c[0], c[1]],
)
}
}
pub fn observed_preference() -> Option<i64> {
let v = OBSERVED_PREFERENCE.load(std::sync::atomic::Ordering::SeqCst);
(v != i64::MIN).then_some(v)
}
pub(crate) unsafe extern "C" fn double_preference(
a0: u64,
_a1: u64,
_a2: u64,
_payload: *mut c_void,
) {
unsafe {
let p = a0 as *mut i64;
*p *= 2;
}
}
pub fn transform_preference_double(view: &ViewHandle) -> ViewHandle {
unsafe {
let wt = crate::conformance::preference_key().witness_table();
let meta = resolve::spacer_meta();
let c = wide_words(double_preference, core::ptr::null_mut());
generic1_modifier_ints(
resolve::transform_preference_fn(),
resolve::transform_preference_oret(),
view,
meta,
wt,
&[meta as u64, c[0], c[1]],
[0.0; 4],
)
}
}
unsafe fn string_transferable() -> (*const c_void, *const c_void) {
unsafe {
let meta = resolve::string_meta();
let wt = abi::get_view_wt_for_proto(meta, resolve::transferable_proto());
assert!(!wt.is_null(), "String: Transferable");
(meta, wt)
}
}
pub(crate) unsafe fn swift_string_array(items: &[&str]) -> u64 {
unsafe {
let meta = resolve::string_meta();
let stride = abi::type_bytes(meta);
let mut bytes = vec![0u8; stride * items.len()];
for (i, s) in items.iter().enumerate() {
bytes[i * stride..i * stride + 16].copy_from_slice(&abi::swift_string(s));
}
abi::swift_array(meta, items.len(), &bytes)
}
}
pub(crate) unsafe fn read_string_array(array: u64) -> Vec<String> {
unsafe {
if array == 0 {
return Vec::new();
}
let stride = abi::type_bytes(resolve::string_meta());
let (base, count) = abi::array_parts(array);
(0..count)
.map(|i| {
let mut raw = [0u8; 16];
core::ptr::copy_nonoverlapping(base.add(i * stride), raw.as_mut_ptr(), 16);
abi::read_swift_string(raw)
})
.collect()
}
}
pub(crate) type StringsDecide = Box<dyn Fn(&[String]) -> bool>;
pub(crate) type StringsObserve = Box<dyn Fn(&[String])>;
pub(crate) struct StringsPayload {
pub(crate) array: u64,
pub(crate) decide: Option<StringsDecide>,
pub(crate) observe: Option<StringsObserve>,
}
pub(crate) unsafe extern "C" fn emit_strings(payload: *mut c_void, _arg: u64) -> u64 {
unsafe {
let p = &*(payload as *const StringsPayload);
swift_runtime_sys::RuntimeRaw::swift_retain(p.array as *mut c_void);
p.array
}
}
pub(crate) unsafe extern "C" fn decide_on_strings(payload: *mut c_void, arg: u64) -> u64 {
unsafe {
let p = &*(payload as *const StringsPayload);
let items = read_string_array(arg);
u64::from(p.decide.as_ref().map(|f| f(&items)).unwrap_or(false))
}
}
pub(crate) unsafe extern "C" fn observe_strings(payload: *mut c_void, arg: u64) -> u64 {
unsafe {
let p = &*(payload as *const StringsPayload);
if let Some(f) = p.observe.as_ref() {
f(&read_string_array(arg));
}
swift_runtime_sys::RuntimeRaw::swift_retain(arg as *mut c_void);
arg
}
}
fn strings_payload(array: u64) -> *mut c_void {
Box::leak(Box::new(StringsPayload { array, decide: None, observe: None }))
as *mut StringsPayload as *mut c_void
}
fn callback_payload(
decide: Option<StringsDecide>,
observe: Option<StringsObserve>,
) -> *mut c_void {
Box::leak(Box::new(StringsPayload { array: 0, decide, observe }))
as *mut StringsPayload as *mut c_void
}
pub fn copyable(view: &ViewHandle, items: &[&str]) -> ViewHandle {
unsafe {
let (meta, wt) = string_transferable();
let c = word_closure(emit_strings, strings_payload(swift_string_array(items)));
generic1_modifier_ints(
resolve::copyable_fn(),
resolve::copyable_oret(),
view,
meta,
wt,
&[c[0], c[1]],
[0.0; 4],
)
}
}
pub fn cuttable(view: &ViewHandle, items: &[&str]) -> ViewHandle {
unsafe {
let (meta, wt) = string_transferable();
let c = word_closure(emit_strings, strings_payload(swift_string_array(items)));
generic1_modifier_ints(
resolve::cuttable_fn(),
resolve::cuttable_oret(),
view,
meta,
wt,
&[meta as u64, c[0], c[1]],
[0.0; 4],
)
}
}
pub fn exportable_to_services(view: &ViewHandle, items: &[&str]) -> ViewHandle {
unsafe {
let (meta, wt) = string_transferable();
let c = word_closure(emit_strings, strings_payload(swift_string_array(items)));
generic1_modifier_ints(
resolve::exportable_to_services_fn(),
resolve::exportable_to_services_oret(),
view,
meta,
wt,
&[c[0], c[1]],
[0.0; 4],
)
}
}
pub fn importable_from_services(view: &ViewHandle, accept: impl Fn(&[String]) -> bool + 'static) -> ViewHandle {
unsafe {
let (meta, wt) = string_transferable();
let c = word_closure(decide_on_strings, callback_payload(Some(Box::new(accept)), None));
generic1_modifier_ints(
resolve::importable_from_services_fn(),
resolve::importable_from_services_oret(),
view,
meta,
wt,
&[meta as u64, c[0], c[1]],
[0.0; 4],
)
}
}
pub fn drop_destination(
view: &ViewHandle,
accept: impl Fn(&[String]) -> bool + 'static,
targeted: impl Fn(bool) + 'static,
) -> ViewHandle {
unsafe {
let (meta, wt) = string_transferable();
let action = word_closure(decide_on_strings, callback_payload(Some(Box::new(accept)), None));
let is_targeted = bool_words(targeted);
generic1_modifier_ints(
resolve::drop_destination_fn(),
resolve::drop_destination_oret(),
view,
meta,
wt,
&[meta as u64, action[0], action[1], is_targeted[0], is_targeted[1]],
[0.0; 4],
)
}
}
pub fn paste_destination(view: &ViewHandle, paste: impl Fn(&[String]) + 'static) -> ViewHandle {
unsafe {
let (meta, wt) = string_transferable();
let action = wide_words(invoke_string_array, callback_payload(None, Some(Box::new(paste))));
let validator = word_closure(observe_strings, callback_payload(None, None));
generic1_modifier_ints(
resolve::paste_destination_fn(),
resolve::paste_destination_oret(),
view,
meta,
wt,
&[meta as u64, action[0], action[1], validator[0], validator[1]],
[0.0; 4],
)
}
}
pub(crate) unsafe extern "C" fn invoke_string_array(
a0: u64,
_a1: u64,
_a2: u64,
payload: *mut c_void,
) {
unsafe {
let p = &*(payload as *const StringsPayload);
if let Some(f) = p.observe.as_ref() {
f(&read_string_array(a0));
}
}
}
pub fn scroll_position_edge(view: &ViewHandle, edge: super::Edge) -> ViewHandle {
unsafe {
let meta = abi::metadata0(resolve::scroll_position_ma());
assert!(!meta.is_null(), "ScrollPosition metadata");
let mut initial = vec![0u8; abi::type_bytes(meta)];
core::arch::asm!("blr {f}", f = in(reg) resolve::scroll_position_edge_init(),
in("x0") resolve::never_meta(),
in("x1") edge as u64,
in("x2") resolve::never_meta(),
in("x3") resolve::never_hashable_wt(),
in("x8") initial.as_mut_ptr(),
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 cell: &'static std::sync::Mutex<Vec<u8>> =
Box::leak(Box::new(std::sync::Mutex::new(initial)));
let get_cell = cell;
let binding = super::binding_get_set(
meta,
b"7SwiftUI7BindingVyAA14ScrollPositionVG",
Box::new(move |out| {
let v = get_cell.lock().expect("scroll position");
core::ptr::copy_nonoverlapping(v.as_ptr(), out, v.len());
}),
Box::new(move |inp| {
let mut v = cell.lock().expect("scroll position");
let n = v.len();
core::ptr::copy_nonoverlapping(inp, v.as_mut_ptr(), n);
}),
);
let subst = [view.meta, view.wt];
let (out_meta, out_wt, size) =
opaque_result_type(resolve::scroll_position_oret(), &subst);
let mut result = vec![0u8; size];
let this = view.value_ptr();
core::arch::asm!("blr {f}", f = in(reg) resolve::scroll_position_fn(),
in("x0") binding.as_ptr(),
in("x1") 0u64,
in("x2") 0u64,
in("x3") 1u64,
in("x4") view.meta,
in("x5") view.wt,
in("x8") result.as_mut_ptr(),
in("x20") this,
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"),
);
wrap_owned(result, out_meta, out_wt)
}
}
struct DragText {
text: String,
}
unsafe extern "C" fn emit_drag_text(payload: *mut c_void) -> abi::Pair {
unsafe {
let d = &*(payload as *const DragText);
let s = abi::swift_string(&d.text);
abi::Pair(
u64::from_le_bytes(s[..8].try_into().expect("lo")),
u64::from_le_bytes(s[8..].try_into().expect("hi")),
)
}
}
pub fn draggable_text(view: &ViewHandle, text: &str) -> ViewHandle {
unsafe {
let ctx = Box::leak(Box::new(abi::PairReturnClosureCtx::new(
emit_drag_text,
Box::into_raw(Box::new(DragText { text: text.to_string() })) as *mut c_void,
))) as *mut abi::PairReturnClosureCtx;
generic1_modifier_ints(
resolve::draggable_fn(),
resolve::draggable_oret(),
view,
resolve::string_meta(),
resolve::string_transferable_wt(),
&[abi::pair_return_closure_thunk as *const () as u64, ctx as u64],
[0.0; 4],
)
}
}
pub fn draggable_container_item(view: &ViewHandle, id: HashableValue<'_>) -> ViewHandle {
unsafe {
let (bytes, meta, wt) = id.parts();
let ns = crate::type_by_name!(swift_type!(SwiftUI.Namespace.ID).as_bytes());
let opt = crate::type_by_name!(swift_type!(opt SwiftUI.Namespace.ID).as_bytes());
let mut buf = vec![0u8; abi::type_bytes(opt)];
abi::store_optional_tag(&mut buf, ns, false);
let words: Vec<u64> = (0..abi::type_bytes(opt) / 8)
.map(|i| u64::from_le_bytes(buf[i * 8..i * 8 + 8].try_into().expect("word")))
.collect();
let mut ints = vec![bytes.as_ptr() as u64];
ints.extend(words);
generic1_modifier_ints(
resolve::draggable_container_fn(),
resolve::draggable_container_oret(),
view,
meta,
wt,
&ints,
[0.0; 4],
)
}
}
pub(crate) unsafe extern "C" fn emit_measured_int(out: *mut u8, proxy: i64, payload: *mut c_void) {
unsafe {
let f = boxed_ref::<dyn Fn(f64, f64) -> i64>(payload);
let (w, h) = crate::views::geometry_proxy_size(proxy as *const u8);
*(out as *mut i64) = f(w, h);
}
}
pub(crate) unsafe extern "C" fn emit_measured_f64(out: *mut u8, proxy: i64, payload: *mut c_void) {
unsafe {
let f = boxed_ref::<dyn Fn(f64, f64) -> f64>(payload);
let (w, h) = crate::views::geometry_proxy_size(proxy as *const u8);
*(out as *mut f64) = f(w, h);
}
}
pub(crate) unsafe extern "C" fn invoke_f64_pair(
a: *const u8,
b: *const u8,
payload: *mut c_void,
) {
unsafe {
let f = boxed_ref::<dyn Fn(f64, f64)>(payload);
f(*(a as *const f64), *(b as *const f64));
}
}
pub(crate) unsafe extern "C" fn invoke_int_pair(
a: *const u8,
b: *const u8,
payload: *mut c_void,
) {
unsafe {
let f = boxed_ref::<dyn Fn(i64, i64)>(payload);
f(*(a as *const i64), *(b as *const i64));
}
}
pub fn on_geometry_change_int(
view: &ViewHandle,
measure: impl Fn(f64, f64) -> i64 + 'static,
action: impl Fn(i64, i64) + 'static,
) -> ViewHandle {
unsafe {
let meta = resolve::int_meta();
let wt = resolve::int_equatable_wt();
let of = Box::leak(Box::new(abi::IndexClosureCtx::new(
emit_measured_int,
boxed_ctx(Box::new(measure) as Box<dyn Fn(f64, f64) -> i64>),
))) as *mut abi::IndexClosureCtx;
let act = pair_words(invoke_int_pair, boxed_ctx(Box::new(action) as Box<dyn Fn(i64, i64)>));
generic1_modifier_ints(
resolve::on_geometry_change_fn(),
resolve::on_geometry_change_oret(),
view,
meta,
wt,
&[
meta as u64,
abi::index_closure_thunk as *const () as u64,
of as u64,
act[0],
act[1],
],
[0.0; 4],
)
}
}
pub fn on_geometry_change_f64(
view: &ViewHandle,
measure: impl Fn(f64, f64) -> f64 + 'static,
action: impl Fn(f64, f64) + 'static,
) -> ViewHandle {
unsafe {
let meta = resolve::double_meta();
let wt = resolve::double_equatable_wt();
let of = Box::leak(Box::new(abi::IndexClosureCtx::new(
emit_measured_f64,
boxed_ctx(Box::new(measure) as Box<dyn Fn(f64, f64) -> f64>),
))) as *mut abi::IndexClosureCtx;
let act = pair_words(invoke_f64_pair, boxed_ctx(Box::new(action) as Box<dyn Fn(f64, f64)>));
generic1_modifier_ints(
resolve::on_geometry_change_fn(),
resolve::on_geometry_change_oret(),
view,
meta,
wt,
&[
meta as u64,
abi::index_closure_thunk as *const () as u64,
of as u64,
act[0],
act[1],
],
[0.0; 4],
)
}
}
unsafe fn observable_object() -> (u64, *const c_void, *const c_void) {
unsafe {
let conformance = crate::conformance::observable_object();
let meta = abi::metadata0(resolve::json_decoder_ma());
assert!(!meta.is_null(), "JSONDecoder metadata");
let object = abi::call_regs(resolve::json_decoder_init(), abi::ints(&[]), [0.0; 4], meta as u64)[0];
(object, meta, conformance.witness_table())
}
}
unsafe fn object_modifier(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
) -> ViewHandle {
unsafe {
let (object, meta, wt) = observable_object();
generic1_modifier_ints(func, oret, view, meta, wt, &[object], [0.0; 4])
}
}
pub fn environment_object(view: &ViewHandle) -> ViewHandle {
unsafe {
object_modifier(resolve::environment_object_fn(), resolve::environment_object_oret(), view)
}
}
pub fn focused_object(view: &ViewHandle) -> ViewHandle {
unsafe {
object_modifier(resolve::focused_object_fn(), resolve::focused_object_oret(), view)
}
}
pub fn focused_scene_object(view: &ViewHandle) -> ViewHandle {
unsafe {
object_modifier(
resolve::focused_scene_object_fn(),
resolve::focused_scene_object_oret(),
view,
)
}
}
pub fn accessibility_default_focus(view: &ViewHandle, value: bool) -> ViewHandle {
unsafe {
let mut binding = accessibility_focus_binding();
let mut v = u64::from(value);
generic1_modifier_ints(
resolve::a11y_default_focus_fn(),
resolve::a11y_default_focus_oret(),
view,
resolve::bool_meta(),
resolve::bool_hashable_wt(),
&[binding.as_mut_ptr() as u64, &mut v as *mut u64 as u64],
[0.0; 4],
)
}
}
pub(crate) unsafe fn accessibility_focus_binding() -> [u64; 3] {
unsafe {
focus_binding(
mangle::bound(&swift_type!(SwiftUI.AccessibilityFocusState), &[mangle::BOOL]).as_bytes(),
resolve::a11y_focus_state_bool_init(),
resolve::a11y_focus_state_projected(),
)
}
}
pub fn layout_value_int(view: &ViewHandle, value: i64) -> ViewHandle {
unsafe {
let wt = crate::conformance::layout_value_key().witness_table();
let meta = resolve::divider_meta();
let mut boxed = value;
generic1_modifier_ints(
resolve::layout_value_fn(),
resolve::layout_value_oret(),
view,
meta,
wt,
&[meta as u64, &mut boxed as *mut i64 as u64],
[0.0; 4],
)
}
}
unsafe fn synthesized_value_modifier(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
wt: *const c_void,
) -> ViewHandle {
unsafe {
let mut empty = 0u64;
generic1_modifier_ints(
func,
oret,
view,
resolve::empty_meta(),
wt,
&[&mut empty as *mut u64 as u64],
[0.0; 4],
)
}
}
pub fn presentation_sizing(view: &ViewHandle) -> ViewHandle {
unsafe {
synthesized_value_modifier(
resolve::presentation_sizing_fn(),
resolve::presentation_sizing_oret(),
view,
crate::conformance::presentation_sizing().witness_table(),
)
}
}
pub fn preview_context(view: &ViewHandle) -> ViewHandle {
unsafe {
synthesized_value_modifier(
resolve::preview_context_fn(),
resolve::preview_context_oret(),
view,
crate::conformance::preview_context().witness_table(),
)
}
}
unsafe fn image_scale_key_path() -> u64 {
use std::sync::atomic::{AtomicU64, Ordering};
static CACHE: AtomicU64 = AtomicU64::new(0);
unsafe {
let cached = CACHE.load(Ordering::Relaxed);
if cached != 0 {
return cached;
}
let meta = abi::metadata0(resolve::image_scale_ma());
let mut arg = vec![0u8; abi::type_bytes(meta)];
abi::inject_enum_tag(&mut arg, meta, 2);
let host = super::empty_view();
let subst = [host.meta, host.wt];
let (_, _, size) = opaque_result_type(resolve::image_scale_oret(), &subst);
let r = abi::call_modifier(
resolve::image_scale_fn(),
host.value_ptr(),
host.meta,
host.wt,
&[arg.as_ptr() as u64],
[0.0; 4],
size,
);
let kp = u64::from_le_bytes(r[0..8].try_into().expect("key path word"));
swift_runtime_sys::RuntimeRaw::swift_retain(kp as *mut c_void);
CACHE.store(kp, Ordering::Relaxed);
kp
}
}
unsafe fn symbol_variants_key_path() -> u64 {
use std::sync::atomic::{AtomicU64, Ordering};
static CACHE: AtomicU64 = AtomicU64::new(0);
unsafe {
let cached = CACHE.load(Ordering::Relaxed);
if cached != 0 {
return cached;
}
let meta = abi::metadata0(resolve::symbol_variants_ma());
let mut arg = super::static_getter_indirect(
resolve::require(super::SymbolVariants::Fill.getter()),
meta,
0,
);
let host = super::empty_view();
let subst = [host.meta, host.wt];
let (_, _, size) = opaque_result_type(resolve::symbol_variant_oret(), &subst);
let r = abi::call_modifier(
resolve::symbol_variant_fn(),
host.value_ptr(),
host.meta,
host.wt,
&[arg.as_mut_ptr() as u64],
[0.0; 4],
size,
);
let kp = u64::from_le_bytes(r[0..8].try_into().expect("key path word"));
swift_runtime_sys::RuntimeRaw::swift_retain(kp as *mut c_void);
CACHE.store(kp, Ordering::Relaxed);
kp
}
}
pub fn environment_symbol_variants(view: &ViewHandle, value: super::SymbolVariants) -> ViewHandle {
unsafe {
let meta = abi::metadata0(resolve::symbol_variants_ma());
let arg = super::static_getter_indirect(value.resolved(), meta, 0);
generic1_modifier_ints(
resolve::environment_fn(),
resolve::environment_oret(),
view,
meta,
core::ptr::null(),
&[symbol_variants_key_path(), arg.as_ptr() as u64],
[0.0; 4],
)
}
}
pub fn environment_image_scale(view: &ViewHandle, scale: ImageScale) -> ViewHandle {
unsafe {
let meta = abi::metadata0(resolve::image_scale_ma());
let mut value = vec![0u8; abi::type_bytes(meta)];
abi::inject_enum_tag(&mut value, meta, scale.tag());
generic1_modifier_ints(
resolve::environment_fn(),
resolve::environment_oret(),
view,
meta,
core::ptr::null(),
&[image_scale_key_path(), value.as_ptr() as u64],
[0.0; 4],
)
}
}
pub(crate) unsafe extern "C" fn transform_image_scale(
a0: u64,
_a1: u64,
_a2: u64,
payload: *mut c_void,
) {
unsafe {
let f = boxed_ref::<dyn Fn(ImageScale) -> ImageScale>(payload);
let meta = abi::metadata0(resolve::image_scale_ma());
let p = a0 as *mut u8;
let current = match abi::read_enum_tag(p, meta) {
0 => ImageScale::Small,
1 => ImageScale::Medium,
_ => ImageScale::Large,
};
let next = f(current);
let size = abi::type_bytes(meta).max(1);
abi::inject_enum_tag(core::slice::from_raw_parts_mut(p, size), meta, next as u32);
}
}
pub fn transform_environment_image_scale(
view: &ViewHandle,
action: impl Fn(ImageScale) -> ImageScale + 'static,
) -> ViewHandle {
unsafe {
let meta = abi::metadata0(resolve::image_scale_ma());
let c = wide_words(
transform_image_scale,
boxed_ctx(Box::new(action) as Box<dyn Fn(ImageScale) -> ImageScale>),
);
generic1_modifier_ints(
resolve::transform_environment_fn(),
resolve::transform_environment_oret(),
view,
meta,
core::ptr::null(),
&[image_scale_key_path(), c[0], c[1]],
[0.0; 4],
)
}
}
struct TransformCtx {
step: Box<dyn Fn(u8) -> u8>,
}
unsafe extern "C" fn transform_env_byte(a0: u64, _a1: u64, _a2: u64, payload: *mut c_void) {
unsafe {
let ctx = &*(payload as *const TransformCtx);
let p = a0 as *mut u8;
*p = (ctx.step)(*p);
}
}
pub fn transform_environment<K: crate::state::EnvKey>(
view: &ViewHandle,
action: impl Fn(K::Value) -> K::Value + 'static,
) -> ViewHandle {
unsafe {
assert_eq!(
K::buffer_bytes(),
1,
"transform_environment handles one-byte values; this key's is wider"
);
let meta = abi::resolve_type_by_mangled_name(K::value_type());
assert!(!meta.is_null(), "environment value metadata");
let ctx = Box::leak(Box::new(TransformCtx {
step: Box::new(move |b| K::encode(&action(K::decode(&[b])))),
})) as *mut TransformCtx;
let c = wide_words(transform_env_byte, ctx as *mut c_void);
generic1_modifier_ints(
resolve::transform_environment_fn(),
resolve::transform_environment_oret(),
view,
meta,
core::ptr::null(),
&[K::key_path(), c[0], c[1]],
[0.0; 4],
)
}
}
pub fn drag_container_selection(view: &ViewHandle, selected: &[&str]) -> ViewHandle {
unsafe {
let meta = resolve::string_meta();
let wt = resolve::string_hashable_wt();
let selection = word_closure(emit_strings, strings_payload(swift_string_array(selected)));
let ns = crate::type_by_name!(swift_type!(SwiftUI.Namespace.ID).as_bytes());
let opt = crate::type_by_name!(swift_type!(opt SwiftUI.Namespace.ID).as_bytes());
let mut buf = vec![0u8; abi::type_bytes(opt)];
abi::store_optional_tag(&mut buf, ns, false);
let mut ints = vec![selection[0], selection[1]];
ints.extend((0..abi::type_bytes(opt) / 8).map(|i| {
u64::from_le_bytes(buf[i * 8..i * 8 + 8].try_into().expect("word"))
}));
generic1_modifier_ints(
resolve::drag_container_selection_fn(),
resolve::drag_container_selection_oret(),
view,
meta,
wt,
&ints,
[0.0; 4],
)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ScrollGeometry {
pub content_offset: (f64, f64),
pub content_size: (f64, f64),
pub content_insets: (f64, f64, f64, f64),
pub container_size: (f64, f64),
pub visible_rect: (f64, f64, f64, f64),
}
fn scroll_geometry_offsets() -> Option<&'static [usize; 5]> {
static CACHE: std::sync::OnceLock<Option<[usize; 5]>> = std::sync::OnceLock::new();
CACHE
.get_or_init(|| unsafe {
let offs = abi::struct_field_offsets(abi::metadata0(resolve::scroll_geometry_ma()))?;
(offs.len() == 5).then(|| [offs[0], offs[1], offs[2], offs[3], offs[4]])
})
.as_ref()
}
pub(crate) unsafe fn read_scroll_geometry(p: *const u8) -> ScrollGeometry {
let Some(&[offset, size, insets, container, visible]) = scroll_geometry_offsets() else {
return ScrollGeometry::default();
};
unsafe {
let at = |o: usize| *(p.add(o) as *const f64);
let pair = |o: usize| (at(o), at(o + 8));
ScrollGeometry {
content_offset: pair(offset),
content_size: pair(size),
content_insets: (at(insets), at(insets + 8), at(insets + 16), at(insets + 24)),
container_size: pair(container),
visible_rect: (at(visible), at(visible + 8), at(visible + 16), at(visible + 24)),
}
}
}
pub(crate) unsafe extern "C" fn emit_scroll_measure(out: *mut u8, geometry: i64, payload: *mut c_void) {
unsafe {
let f = boxed_ref::<dyn Fn(ScrollGeometry) -> i64>(payload);
*(out as *mut i64) = f(read_scroll_geometry(geometry as *const u8));
}
}
pub fn on_scroll_geometry_change(
view: &ViewHandle,
measure: impl Fn(ScrollGeometry) -> i64 + 'static,
action: impl Fn(i64, i64) + 'static,
) -> ViewHandle {
unsafe {
let meta = resolve::int_meta();
let wt = resolve::int_equatable_wt();
let of = Box::leak(Box::new(abi::IndexClosureCtx::new(
emit_scroll_measure,
boxed_ctx(Box::new(measure) as Box<dyn Fn(ScrollGeometry) -> i64>),
))) as *mut abi::IndexClosureCtx;
let act = pair_words(invoke_int_pair, boxed_ctx(Box::new(action) as Box<dyn Fn(i64, i64)>));
generic1_modifier_ints(
resolve::on_scroll_geometry_change_fn(),
resolve::on_scroll_geometry_change_oret(),
view,
meta,
wt,
&[
meta as u64,
abi::index_closure_thunk as *const () as u64,
of as u64,
act[0],
act[1],
],
[0.0; 4],
)
}
}
pub fn on_scroll_geometry_change_f64(
view: &ViewHandle,
measure: impl Fn(ScrollGeometry) -> f64 + 'static,
action: impl Fn(f64, f64) + 'static,
) -> ViewHandle {
unsafe {
let meta = resolve::double_meta();
let wt = resolve::double_equatable_wt();
let of = Box::leak(Box::new(abi::IndexClosureCtx::new(
emit_scroll_measure_f64,
boxed_ctx(Box::new(measure) as Box<dyn Fn(ScrollGeometry) -> f64>),
))) as *mut abi::IndexClosureCtx;
let act = pair_words(invoke_f64_pair, boxed_ctx(Box::new(action) as Box<dyn Fn(f64, f64)>));
generic1_modifier_ints(
resolve::on_scroll_geometry_change_fn(),
resolve::on_scroll_geometry_change_oret(),
view,
meta,
wt,
&[
meta as u64,
abi::index_closure_thunk as *const () as u64,
of as u64,
act[0],
act[1],
],
[0.0; 4],
)
}
}
pub(crate) unsafe extern "C" fn emit_scroll_measure_f64(
out: *mut u8,
geometry: i64,
payload: *mut c_void,
) {
unsafe {
let f = boxed_ref::<dyn Fn(ScrollGeometry) -> f64>(payload);
*(out as *mut f64) = f(read_scroll_geometry(geometry as *const u8));
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ViewDimensions {
pub width: f64,
pub height: f64,
}
pub(crate) unsafe fn read_view_dimensions(p: *const u8) -> ViewDimensions {
unsafe {
let get = |f: *const c_void| {
let out: f64;
core::arch::asm!("blr {f}", f = in(reg) f,
in("x20") p,
lateout("d0") out,
lateout("x0") _, 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
};
let d = ViewDimensions {
width: get(resolve::view_dimensions_width()),
height: get(resolve::view_dimensions_height()),
};
debug_assert_eq!(Some((d.width, d.height)), stored_view_size(p));
d
}
}
unsafe fn stored_view_size(p: *const u8) -> Option<(f64, f64)> {
unsafe {
let offs = abi::struct_field_offsets(abi::metadata0(resolve::view_dimensions_ma()))?;
let &[_, size] = offs.as_slice() else { return None };
let at = p.add(size) as *const f64;
Some((*at, *at.add(1)))
}
}
pub(crate) unsafe extern "C" fn compute_length(arg: u64, payload: *mut c_void) -> f64 {
unsafe {
let f = boxed_ref::<dyn Fn(ViewDimensions) -> f64>(payload);
f(read_view_dimensions(arg as *const u8))
}
}
pub fn alignment_guide(
view: &ViewHandle,
alignment: HorizontalAlignment,
compute: impl Fn(ViewDimensions) -> f64 + 'static,
) -> ViewHandle {
unsafe {
let meta = abi::metadata0(resolve::horizontal_alignment_ma());
let a = frozen_static(meta, alignment.getter());
let ctx = Box::leak(Box::new(abi::FloatClosureCtx::new(
compute_length,
boxed_ctx(Box::new(compute) as Box<dyn Fn(ViewDimensions) -> f64>),
))) as *mut abi::FloatClosureCtx;
opaque_modifier(
resolve::alignment_guide_fn(),
resolve::alignment_guide_oret(),
view,
&[a, abi::float_closure_thunk as *const () as u64, ctx as u64],
[0.0; 4],
)
}
}
unsafe fn generic_pair_modifier(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
first: (*const c_void, *const c_void),
second: (*const c_void, *const c_void),
leading: &[u64],
) -> ViewHandle {
unsafe {
let subst = [
view.meta,
first.0,
second.0,
view.wt,
first.1,
second.1,
];
let (meta, wt, size) = opaque_result_type(oret, &subst);
let mut ints = leading.to_vec();
ints.extend(subst.iter().map(|p| *p as u64));
let r = abi::call_modifier_generic(func, view.value_ptr(), &ints, [0.0; 4], size);
wrap_owned(r, meta, wt)
}
}
pub fn background_preference_value(
view: &ViewHandle,
alignment: Alignment,
content: &ViewHandle,
) -> ViewHandle {
unsafe { preference_value_modifier(
resolve::background_preference_value_fn(),
resolve::background_preference_value_oret(),
view, alignment, content,
) }
}
pub fn overlay_preference_value(
view: &ViewHandle,
alignment: Alignment,
content: &ViewHandle,
) -> ViewHandle {
unsafe { preference_value_modifier(
resolve::overlay_preference_value_fn(),
resolve::overlay_preference_value_oret(),
view, alignment, content,
) }
}
unsafe fn preference_value_modifier(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
alignment: Alignment,
content: &ViewHandle,
) -> ViewHandle {
unsafe {
let key_meta = resolve::spacer_meta();
let key_wt = crate::conformance::preference_key().witness_table();
let a = alignment.words();
let c = view_words(content);
generic_pair_modifier(
func,
oret,
view,
(key_meta, key_wt),
(content.meta, content.wt),
&[key_meta as u64, a[0], a[1], c[0], c[1]],
)
}
}
unsafe extern "C" fn saw_int_array(value: *const u8, payload: *mut c_void) {
unsafe {
let f = boxed_ref::<dyn Fn(&[i64])>(payload);
let (base, count) = abi::array_parts(value as u64);
f(core::slice::from_raw_parts(base as *const i64, count));
}
}
pub fn on_scroll_target_visibility_change(
view: &ViewHandle,
threshold: f64,
visible: impl Fn(&[i64]) + 'static,
) -> ViewHandle {
unsafe {
let meta = resolve::int_meta();
let wt = resolve::int_hashable_wt();
let ctx = Box::leak(Box::new(abi::ValueClosureCtx::new(
saw_int_array,
boxed_ctx(Box::new(visible) as Box<dyn Fn(&[i64])>),
))) as *mut abi::ValueClosureCtx;
generic1_modifier_ints(
resolve::scroll_target_visibility_fn(),
resolve::scroll_target_visibility_oret(),
view,
meta,
wt,
&[meta as u64, abi::value_closure_thunk as *const () as u64, ctx as u64],
[threshold, 0.0, 0.0, 0.0],
)
}
}
unsafe extern "C" fn identity_effect(_out: *mut u8, _payload: *mut c_void) {}
pub fn visual_effect(view: &ViewHandle) -> ViewHandle {
unsafe {
let meta = crate::type_by_name!(swift_type!(SwiftUI.EmptyVisualEffect).as_bytes());
let wt = resolve::empty_visual_effect_wt();
let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
identity_effect,
core::ptr::null_mut(),
))) as *mut abi::ClosureCtx;
generic1_modifier_ints(
resolve::visual_effect_fn(),
resolve::visual_effect_oret(),
view,
meta,
wt,
&[abi::swift_closure_thunk as *const () as u64, ctx as u64],
[0.0; 4],
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Axis {
Horizontal = 0,
Vertical = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScrollTransitionConfiguration {
Interactive,
Animated,
Identity,
}
impl ScrollTransitionConfiguration {
fn getter(self) -> *const c_void {
match self {
ScrollTransitionConfiguration::Interactive => resolve::scroll_transition_config_interactive(),
ScrollTransitionConfiguration::Animated => resolve::scroll_transition_config_animated(),
ScrollTransitionConfiguration::Identity => resolve::scroll_transition_config_identity(),
}
}
}
unsafe fn scroll_transition_tail(axis: Option<Axis>) -> (Vec<u64>, *const c_void, *const c_void) {
unsafe {
let axis_meta = abi::metadata0(resolve::axis_ma());
let opt_axis = crate::type_by_name!(swift_type!(opt enum SwiftUI.Axis).as_bytes());
let tag = axis.map(|a| a as u32);
let mut payload = vec![0u8; abi::type_bytes(axis_meta).max(1)];
if let Some(t) = tag {
abi::inject_enum_tag(&mut payload, axis_meta, t);
}
let words = optional_words(axis_meta, opt_axis, &payload, tag.is_some());
let meta = crate::type_by_name!(swift_type!(SwiftUI.EmptyVisualEffect).as_bytes());
let wt = resolve::empty_visual_effect_wt();
let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
identity_effect,
core::ptr::null_mut(),
))) as *mut abi::ClosureCtx;
let mut ints = words;
ints.push(abi::swift_closure_thunk as *const () as u64);
ints.push(ctx as u64);
(ints, meta, wt)
}
}
pub fn scroll_transition(
view: &ViewHandle,
config: ScrollTransitionConfiguration,
axis: Option<Axis>,
) -> ViewHandle {
unsafe {
let cfg_meta = abi::metadata0(resolve::scroll_transition_config_ma());
let cfg = abi::static_out(config.getter(), cfg_meta, abi::type_bytes(cfg_meta));
let (tail, meta, wt) = scroll_transition_tail(axis);
let mut ints = vec![cfg.as_ptr() as u64];
ints.extend_from_slice(&tail);
generic1_modifier_ints(
resolve::scroll_transition_fn(),
resolve::scroll_transition_oret(),
view,
meta,
wt,
&ints,
[0.0; 4],
)
}
}
pub fn scroll_transition_ends(
view: &ViewHandle,
top_leading: ScrollTransitionConfiguration,
bottom_trailing: ScrollTransitionConfiguration,
axis: Option<Axis>,
) -> ViewHandle {
unsafe {
let cfg_meta = abi::metadata0(resolve::scroll_transition_config_ma());
let size = abi::type_bytes(cfg_meta);
let lead = abi::static_out(top_leading.getter(), cfg_meta, size);
let trail = abi::static_out(bottom_trailing.getter(), cfg_meta, size);
let (tail, meta, wt) = scroll_transition_tail(axis);
let mut ints = vec![lead.as_ptr() as u64, trail.as_ptr() as u64];
ints.extend_from_slice(&tail);
generic1_modifier_ints(
resolve::scroll_transition_ends_fn(),
resolve::scroll_transition_ends_oret(),
view,
meta,
wt,
&ints,
[0.0; 4],
)
}
}
unsafe fn bool_trait(
key_ma: *const c_void,
key_wt: *const c_void,
view: &ViewHandle,
value: bool,
) -> ViewHandle {
unsafe {
let meta = abi::metadata0(key_ma);
let byte = Box::leak(Box::new(u8::from(value))) as *mut u8;
generic1_modifier_ints(
resolve::view_trait_fn(),
resolve::view_trait_oret(),
view,
meta,
key_wt,
&[meta as u64, byte as u64],
[0.0; 4],
)
}
}
pub fn selection_disabled(view: &ViewHandle, disabled: bool) -> ViewHandle {
unsafe {
bool_trait(
resolve::is_selection_enabled_key_ma(),
resolve::is_selection_enabled_key_wt(),
view,
!disabled,
)
}
}
unsafe extern "C" fn emit_anchor_value(out: *mut u8, payload: *mut c_void) {
unsafe {
let f = boxed_ref::<dyn Fn() -> i64>(payload);
*(out as *mut i64) = f();
}
}
unsafe extern "C" fn fold_anchor_value(value: *const u8, _anchor: *const u8, payload: *mut c_void) {
unsafe {
let f = boxed_ref::<dyn Fn(i64) -> i64>(payload);
let slot = value as *mut i64;
*slot = f(*slot);
}
}
unsafe fn anchor_preference_parts() -> (*const c_void, *const c_void, *const c_void, u64) {
unsafe {
let key_meta = resolve::spacer_meta();
let key_wt = crate::conformance::preference_key().witness_table();
let rect_meta = crate::type_by_name!(swift_type!(objc struct CGRect).as_bytes());
assert!(!rect_meta.is_null(), "CGRect metadata");
let source: u64;
core::arch::asm!("blr {f}", f = in(reg) resolve::anchor_source_bounds(),
lateout("x0") source,
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("x20") _, lateout("x21") _,
clobber_abi("C"),
);
(key_meta, key_wt, rect_meta, source)
}
}
unsafe fn anchor_modifier(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
closure: [u64; 2],
) -> ViewHandle {
unsafe {
let (key_meta, key_wt, rect_meta, source) = anchor_preference_parts();
let av = view.meta;
let aw = view.wt;
let subst = [av, rect_meta, key_meta, aw, key_wt];
let (meta, wt, size) = opaque_result_type(oret, &subst);
let ints = [
key_meta as u64,
source,
closure[0],
closure[1],
av as u64,
rect_meta as u64,
key_meta as u64,
aw as u64,
key_wt as u64,
];
let r = abi::call_modifier_generic(func, view.value_ptr(), &ints, [0.0; 4], size);
wrap_owned(r, meta, wt)
}
}
pub fn anchor_preference(view: &ViewHandle, transform: impl Fn() -> i64 + 'static) -> ViewHandle {
unsafe {
let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
emit_anchor_value,
boxed_ctx(Box::new(transform) as Box<dyn Fn() -> i64>),
))) as *mut abi::ClosureCtx;
anchor_modifier(
resolve::anchor_preference_fn(),
resolve::anchor_preference_oret(),
view,
[abi::swift_closure_thunk as *const () as u64, ctx as u64],
)
}
}
pub fn transform_anchor_preference(view: &ViewHandle, transform: impl Fn(i64) -> i64 + 'static) -> ViewHandle {
unsafe {
anchor_modifier(
resolve::transform_anchor_preference_fn(),
resolve::transform_anchor_preference_oret(),
view,
pair_words(fold_anchor_value, boxed_ctx(Box::new(transform) as Box<dyn Fn(i64) -> i64>)),
)
}
}
swift_statics! {
pub enum DefaultFocusPriority {
Automatic = c"$s7SwiftUI30DefaultFocusEvaluationPriorityV9automaticACvgZ",
UserInitiated = c"$s7SwiftUI30DefaultFocusEvaluationPriorityV13userInitiatedACvgZ",
}
}
pub fn default_focus(
view: &ViewHandle,
value: bool,
priority: DefaultFocusPriority,
) -> ViewHandle {
unsafe {
let b = focus_binding(
mangle::bound(&swift_type!(SwiftUI.FocusState), &[mangle::BOOL]).as_bytes(),
resolve::focus_state_bool_init(),
resolve::focus_state_projected(),
);
let mut binding = [0u8; 24];
for (i, w) in b.iter().enumerate() {
binding[i * 8..i * 8 + 8].copy_from_slice(&w.to_le_bytes());
}
let mut flag = u8::from(value);
let pm = abi::metadata0(resolve::default_focus_priority_ma());
let p = resilient_static(pm, priority.getter());
generic1_modifier_ints(
resolve::default_focus_fn(),
resolve::default_focus_oret(),
view,
resolve::bool_meta(),
resolve::bool_hashable_wt(),
&[
binding.as_ptr() as u64,
&mut flag as *mut u8 as u64,
p.as_ptr() as u64,
],
[0.0; 4],
)
}
}
pub fn text_renderer(view: &ViewHandle) -> ViewHandle {
unsafe {
let renderer = abi::metadata0(resolve::edge_insets_ma());
let wt = crate::conformance::text_renderer().witness_table();
let modifier =
abi::generic_metadata2(resolve::text_renderer_modifier_ma(), renderer, wt);
let bytes = vec![0u8; abi::type_bytes(modifier)];
apply_modifier(view, modifier, &bytes)
}
}
pub fn attributed_text_formatting_definition(view: &ViewHandle) -> ViewHandle {
unsafe {
let meta = abi::metadata0(resolve::swiftui_attributes_ma());
let wt = abi::get_view_wt_for_proto(meta, resolve::attribute_scope_proto());
assert!(!wt.is_null(), "SwiftUIAttributes: AttributeScope");
generic1_modifier_ints(
resolve::attributed_formatting_fn(),
resolve::attributed_formatting_oret(),
view,
meta,
wt,
&[meta as u64],
[0.0; 4],
)
}
}
swift_statics! {
pub enum ContentTransition {
Identity = c"$s7SwiftUI17ContentTransitionV8identityACvgZ",
Opacity = c"$s7SwiftUI17ContentTransitionV7opacityACvgZ",
Interpolate = c"$s7SwiftUI17ContentTransitionV11interpolateACvgZ",
}
}
fn content_transition_key_path() -> u64 {
use std::sync::atomic::{AtomicU64, Ordering};
static CACHE: AtomicU64 = AtomicU64::new(0);
static mut SLOT: crate::keypath::Slot = crate::keypath::Slot::new();
let hit = CACHE.load(Ordering::Relaxed);
if hit != 0 {
return hit;
}
unsafe {
let kp = crate::keypath::build(
crate::static_mut(&raw mut SLOT),
crate::keypath::Property {
getter: resolve::env_content_transition_getter(),
setter: resolve::env_content_transition_setter(),
descriptor: resolve::env_content_transition_descriptor(),
root: swift_type!(SwiftUI.EnvironmentValues).as_bytes(),
value: swift_type!(SwiftUI.ContentTransition).as_bytes(),
},
crate::keypath::next_thunks(),
);
CACHE.store(kp, Ordering::Relaxed);
kp
}
}
pub fn content_transition(view: &ViewHandle, transition: ContentTransition) -> ViewHandle {
unsafe {
let meta = abi::metadata0(resolve::content_transition_ma());
let value = resilient_static(meta, transition.getter());
generic1_modifier_ints(
resolve::environment_fn(),
resolve::environment_oret(),
view,
meta,
core::ptr::null(),
&[content_transition_key_path(), value.as_ptr() as u64],
[0.0; 4],
)
}
}
unsafe extern "C" fn emit_phase_animation(payload: *mut c_void, _arg: u64) -> u64 {
if !payload.is_null() {
unsafe { swift_runtime_sys::RuntimeRaw::swift_retain(payload) };
}
payload as u64
}
pub fn phase_animator(
view: &ViewHandle,
phases: &[i64],
content: &ViewHandle,
animated: bool,
) -> ViewHandle {
unsafe {
let int_meta = resolve::int_meta();
let mut bytes = Vec::with_capacity(phases.len() * 8);
for p in phases {
bytes.extend_from_slice(&p.to_le_bytes());
}
let array = abi::swift_array(int_meta, phases.len(), &bytes);
let array_meta = abi::resolve_type_by_mangled_name(mangle::array(mangle::INT).as_bytes());
assert!(!array_meta.is_null(), "Array<Int> metadata");
let seq_wt = abi::get_view_wt_for_proto(array_meta, resolve::sequence_proto());
assert!(!seq_wt.is_null(), "Array<Int>: Sequence");
let eq_wt = abi::get_view_wt_for_proto(int_meta, resolve::equatable_proto());
assert!(!eq_wt.is_null(), "Int: Equatable");
let anim = if animated {
let meta = abi::metadata0(resolve::animation_ma());
frozen_static(meta, c"$s7SwiftUI9AnimationV7defaultACvgZ")
} else {
0
};
let a = word_closure(emit_phase_animation, anim as *mut c_void);
let av = content.meta;
let aw = content.wt;
let subst = [view.meta, int_meta, array_meta, av, view.wt, eq_wt, seq_wt, aw];
let (meta, wt, size) = opaque_result_type(resolve::phase_animator_oret(), &subst);
let mut ints = vec![&array as *const u64 as u64];
ints.extend_from_slice(&view_words(content));
ints.extend_from_slice(&a);
ints.extend_from_slice(&[view.meta as u64, int_meta as u64, array_meta as u64, av as u64]);
ints.extend_from_slice(&[view.wt as u64, eq_wt as u64, seq_wt as u64, aw as u64]);
let r = abi::call_modifier_generic(resolve::phase_animator_fn(), view.value_ptr(), &ints, [0.0; 4], size);
wrap_owned(r, meta, wt)
}
}
pub fn text_input_suggestions(
view: &ViewHandle,
count: usize,
content: &ViewHandle,
) -> ViewHandle {
unsafe {
let elem = abi::metadata0(resolve::edge_insets_ma());
let stride = abi::type_bytes(elem);
let bytes = vec![0u8; stride * count];
let array = abi::swift_array(elem, count, &bytes);
let array_meta =
crate::type_by_name!(swift_type!([SwiftUI.EdgeInsets]).as_bytes());
assert!(!array_meta.is_null(), "[EdgeInsets] metadata");
let rac = abi::get_view_wt_for_proto(array_meta, resolve::random_access_collection_proto());
assert!(!rac.is_null(), "[EdgeInsets]: RandomAccessCollection");
let ident = crate::conformance::identifiable().witness_table();
let av = content.meta;
let aw = content.wt;
let subst = [view.meta, array_meta, av, view.wt, rac, aw, ident];
let (meta, wt, size) = opaque_result_type(resolve::text_input_suggestions_oret(), &subst);
let mut ints = vec![&array as *const u64 as u64];
ints.extend_from_slice(&view_words(content));
ints.extend_from_slice(&[view.meta as u64, array_meta as u64, av as u64]);
ints.extend_from_slice(&[view.wt as u64, rac as u64, aw as u64, ident as u64]);
let r = abi::call_modifier_generic(resolve::text_input_suggestions_fn(), view.value_ptr(), &ints, [0.0; 4], size);
wrap_owned(r, meta, wt)
}
}
unsafe extern "C" fn emit_drag_items(out: *mut u8, payload: *mut c_void) {
unsafe { *(out as *mut u64) = payload as u64 };
}
pub fn drag_container(view: &ViewHandle, items: &[&str]) -> ViewHandle {
unsafe {
let item = resolve::string_meta();
let transferable = abi::get_view_wt_for_proto(item, resolve::transferable_proto());
assert!(!transferable.is_null(), "String: Transferable");
let identifiable = crate::conformance::string_identifiable().witness_table();
let array = swift_string_array(items);
let array_meta = abi::resolve_type_by_mangled_name(mangle::array(mangle::STRING).as_bytes());
assert!(!array_meta.is_null(), "[String] metadata");
let collection = abi::get_view_wt_for_proto(array_meta, resolve::collection_proto());
assert!(!collection.is_null(), "[String]: Collection");
let ns = abi::metadata0(resolve::namespace_id_ma());
let opt = crate::type_by_name!(swift_type!(opt SwiftUI.Namespace.ID).as_bytes());
assert!(!opt.is_null(), "Optional<Namespace.ID>");
let mut none = vec![0u8; abi::type_bytes(opt)];
abi::store_optional_tag(&mut none, ns, false);
let none_words = optional_words(ns, opt, &[], false);
let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
emit_drag_items,
array as *mut c_void,
))) as *mut abi::ClosureCtx;
let av = view.meta;
let aw = view.wt;
let subst = [av, item, array_meta, aw, transferable, identifiable, collection];
let (meta, wt, size) = opaque_result_type(resolve::drag_container_oret(), &subst);
let mut ints = vec![item as u64];
ints.extend_from_slice(&none_words);
ints.push(abi::swift_closure_thunk as *const () as u64);
ints.push(ctx as u64);
ints.extend_from_slice(&[av as u64, item as u64, array_meta as u64]);
ints.extend_from_slice(&[aw as u64, transferable as u64, identifiable as u64, collection as u64]);
let r = abi::call_modifier_generic(resolve::drag_container_fn(), view.value_ptr(), &ints, [0.0; 4], size);
wrap_owned(r, meta, wt)
}
}
pub(crate) unsafe extern "C" fn saw_published_int(value: *const u8, payload: *mut c_void) {
unsafe {
let f = boxed_ref::<dyn Fn(i64)>(payload);
f(*(value as *const i64));
}
}
pub fn on_receive(view: &ViewHandle, complete_immediately: bool, perform: impl Fn(i64) + 'static) -> ViewHandle {
unsafe {
let publisher = abi::resolve_type_by_mangled_name(mangle::bound("7Combine5EmptyV", &[mangle::INT, mangle::NEVER]).as_bytes());
assert!(!publisher.is_null(), "Empty<Int, Never> metadata");
let wt = abi::get_view_wt_for_proto(publisher, resolve::publisher_proto());
assert!(!wt.is_null(), "Empty<Int, Never>: Publisher");
let value = vec![u8::from(complete_immediately); abi::type_bytes(publisher).max(1)];
let ctx = Box::leak(Box::new(abi::ValueClosureCtx::new(
saw_published_int,
boxed_ctx(Box::new(perform) as Box<dyn Fn(i64)>),
))) as *mut abi::ValueClosureCtx;
generic1_modifier_ints(
resolve::on_receive_fn(),
resolve::on_receive_oret(),
view,
publisher,
wt,
&[
value.as_ptr() as u64,
abi::value_closure_thunk as *const () as u64,
ctx as u64,
],
[0.0; 4],
)
}
}
#[doc(hidden)]
pub unsafe fn apply_modifier_for_test(
view: &ViewHandle,
modifier_meta: *const c_void,
bytes: &[u8],
) -> ViewHandle {
unsafe { apply_modifier(view, modifier_meta, bytes) }
}
pub fn focused_value(view: &ViewHandle, present: bool) -> ViewHandle {
unsafe {
let meta = abi::resolve_type_by_mangled_name(
mangle::Type::Nominal {
module: "Foundation",
path: vec!["JSONDecoder"],
kind: mangle::Kind::Class,
}
.mangle()
.as_bytes(),
);
assert!(!meta.is_null(), "Foundation.JSONDecoder metadata");
let wt = crate::conformance::observable().witness_table();
let object = if present {
let o: u64;
core::arch::asm!("blr {f}", f = in(reg) resolve::json_decoder_init(),
inlateout("x0") meta => o, in("x20") meta,
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"),
);
o
} else {
0
};
generic1_modifier_ints(
resolve::focused_value_fn(),
resolve::focused_value_oret(),
view,
meta,
wt,
&[object],
[0.0; 4],
)
}
}
static FRAME: std::sync::OnceLock<(Vec<u8>, usize)> = std::sync::OnceLock::new();
static TRACK: std::sync::OnceLock<(Vec<u8>, usize)> = std::sync::OnceLock::new();
unsafe extern "C" fn emit_frame(out: *mut u8, _payload: *mut c_void) {
let (bytes, meta) = FRAME.get().expect("keyframe built before the closure runs");
unsafe { abi::copy_value(out, bytes.as_ptr(), *meta as *const c_void) };
}
unsafe extern "C" fn emit_track(out: *mut u8, _payload: *mut c_void) {
let (bytes, meta) = TRACK.get().expect("track built before the closure runs");
unsafe { abi::copy_value(out, bytes.as_ptr(), *meta as *const c_void) };
}
unsafe fn keyframe_track(to: f64) -> (*const c_void, *const c_void) {
unsafe {
let double = resolve::double_meta();
let frame = abi::resolve_type_by_mangled_name(
mangle::ty("MoveKeyframe")
.of(vec![mangle::Type::Verbatim("Sd".into())])
.mangle()
.as_bytes(),
);
assert!(!frame.is_null(), "MoveKeyframe<Double> metadata");
let frame_wt =
abi::get_view_wt_for_proto(frame, resolve::keyframe_track_content_proto());
assert!(!frame_wt.is_null(), "MoveKeyframe<Double>: KeyframeTrackContent");
let mut at = to;
let mut one = vec![0u8; abi::type_bytes(frame)];
abi::call_out(
resolve::move_keyframe_init(),
abi::ints(&[&mut at as *mut f64 as u64, double as u64]),
[0.0; 4],
frame as u64,
one.as_mut_ptr(),
);
let _ = FRAME.set((one, frame as usize));
let track = abi::resolve_type_by_mangled_name(
mangle::ty("KeyframeTrack")
.of(vec![
mangle::Type::Verbatim("Sd".into()),
mangle::Type::Verbatim("Sd".into()),
mangle::ty("MoveKeyframe").of(vec![mangle::Type::Verbatim("Sd".into())]),
])
.mangle()
.as_bytes(),
);
assert!(!track.is_null(), "KeyframeTrack metadata");
let track_wt = abi::get_view_wt_for_proto(track, resolve::keyframes_proto());
assert!(!track_wt.is_null(), "KeyframeTrack: Keyframes");
let ctx = Box::leak(Box::new(abi::ClosureCtx::new(emit_frame, core::ptr::null_mut())))
as *mut abi::ClosureCtx;
let mut bytes = vec![0u8; abi::type_bytes(track)];
abi::call_spilled(
resolve::keyframe_track_init(),
&[
abi::swift_closure_thunk as *const () as u64,
ctx as u64,
double as u64,
frame as u64,
frame_wt as u64,
],
[0.0; 4],
track as u64,
bytes.as_mut_ptr(),
);
let _ = TRACK.set((bytes, track as usize));
(track, track_wt)
}
}
pub fn keyframe_animator(
view: &ViewHandle,
initial: f64,
to: f64,
repeating: bool,
content: &ViewHandle,
) -> ViewHandle {
unsafe {
let (track, track_wt) = keyframe_track(to);
let double = resolve::double_meta();
let av = content.meta;
let aw = content.wt;
let subst = [view.meta, double, av, track, view.wt, aw, track_wt];
let (meta, wt, size) = opaque_result_type(resolve::keyframe_animator_oret(), &subst);
let keyframes = Box::leak(Box::new(abi::ClosureCtx::new(
emit_track,
core::ptr::null_mut(),
))) as *mut abi::ClosureCtx;
let mut start = initial;
let mut ints = vec![&mut start as *mut f64 as u64, u64::from(repeating)];
ints.extend_from_slice(&view_words(content));
ints.extend_from_slice(&[
abi::swift_closure_thunk as *const () as u64,
keyframes as u64,
]);
ints.extend_from_slice(&[view.meta as u64, double as u64, av as u64, track as u64]);
ints.extend_from_slice(&[view.wt as u64, aw as u64, track_wt as u64]);
let r = abi::call_modifier_generic(resolve::keyframe_animator_fn(), view.value_ptr(), &ints, [0.0; 4], size);
wrap_owned(r, meta, wt)
}
}
#[derive(Clone, Copy)]
pub struct EnvironmentValues(*const c_void);
impl EnvironmentValues {
pub unsafe fn bool_at(self, getter: *const c_void) -> bool {
unsafe { abi::call_regs(getter, abi::ints(&[]), [0.0; 4], self.0 as u64)[0] & 1 != 0 }
}
pub fn voice_over_enabled(self) -> bool {
unsafe { self.bool_at(resolve::env_accessibility_voice_over()) }
}
pub fn switch_control_enabled(self) -> bool {
unsafe { self.bool_at(resolve::env_accessibility_switch_control()) }
}
pub fn value_for<T: super::KeyValue>(self, key: &super::CustomEnvKey<T>) -> T {
unsafe {
static GET: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
let f = *GET.get_or_init(|| {
libc::dlsym((-2isize) as *mut c_void, c"swift_getAtKeyPath".as_ptr()) as usize
});
assert!(f != 0, "swift_getAtKeyPath");
let mut out = vec![0u8; abi::type_bytes(T::metadata())];
core::arch::asm!("blr {f}", f = in(reg) f,
in("x0") self.0,
in("x1") key.key_path_for_test(),
in("x8") out.as_mut_ptr(),
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("x20") _, lateout("x21") _,
clobber_abi("C"),
);
let value = T::read(out.as_ptr());
abi::type_destroy(T::metadata(), out.as_mut_ptr());
value
}
}
}
type EnvBuilder = Box<dyn Fn(EnvironmentValues) -> ViewHandle>;
unsafe extern "C" fn emit_environment_view(out: *mut u8, env: i64, payload: *mut c_void) {
unsafe {
let build = &*(payload as *const EnvBuilder);
let view = build(EnvironmentValues(env as *const c_void));
(out as *mut u64).write(leak_retained(&view));
}
}
pub fn environment_reader(
builder: impl Fn(EnvironmentValues) -> ViewHandle + 'static,
) -> ViewHandle {
unsafe {
let boxed: EnvBuilder = Box::new(builder);
let payload = Box::into_raw(Box::new(boxed)) as *mut c_void;
let ctx = Box::leak(Box::new(abi::IndexClosureCtx::new(emit_environment_view, payload)))
as *mut abi::IndexClosureCtx;
let ty = mangle::ty("EnvironmentReader").of(vec![mangle::ty("AnyView")]).mangle();
let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
assert!(!meta.is_null(), "EnvironmentReader<AnyView> metadata");
let mut value = vec![0u8; abi::type_bytes(meta)];
abi::call_out(
resolve::environment_reader_init(),
abi::ints(&[abi::index_closure_thunk as *const c_void as u64, ctx as u64]),
[0.0; 4],
meta as u64,
value.as_mut_ptr().cast(),
);
let wt = abi::get_view_wt(meta);
assert!(!wt.is_null(), "EnvironmentReader !: View");
ViewHandle::wrap(value.as_ptr() as *const c_void, meta, wt)
}
}
#[cfg(test)]
mod geometry_tests {
use super::*;
#[test]
fn scroll_geometry_reader_matches_the_live_layout() {
let &[offset, size, insets, container, visible] =
scroll_geometry_offsets().expect("five stored fields");
let bytes = unsafe { abi::type_bytes(abi::metadata0(resolve::scroll_geometry_ma())) };
let mut buf = vec![0u8; bytes];
let mut put = |at: usize, v: f64| buf[at..at + 8].copy_from_slice(&v.to_le_bytes());
for (base, n) in [(offset, 2), (size, 2), (insets, 4), (container, 2), (visible, 4)] {
for i in 0..n {
put(base + i * 8, (base + i * 8) as f64);
}
}
let g = unsafe { read_scroll_geometry(buf.as_ptr()) };
let f = |o: usize| o as f64;
assert_eq!(g.content_offset, (f(offset), f(offset + 8)));
assert_eq!(g.content_size, (f(size), f(size + 8)));
assert_eq!(
g.content_insets,
(f(insets), f(insets + 8), f(insets + 16), f(insets + 24)),
"top, leading, bottom, trailing — in that order"
);
assert_eq!(g.container_size, (f(container), f(container + 8)));
assert_eq!(
g.visible_rect,
(f(visible), f(visible + 8), f(visible + 16), f(visible + 24)),
"CGRect is origin then size, so x, y, width, height"
);
}
}