1use super::*;
4use crate::abi;
5use crate::resolve;
6use core::ffi::c_void;
7use crate::mangle::{self, swift_type};
8
9pub(crate) unsafe fn modifier_indirect(
18 f: *const c_void,
19 o: *const c_void,
20 v: &ViewHandle,
21 bytes: &[u8],
22) -> ViewHandle {
23 unsafe { opaque_modifier(f, o, v, &[bytes.as_ptr() as u64], [0.0; 4]) }
24}
25
26pub(crate) unsafe fn enum_bytes(ma: *const c_void, tag: u32) -> Vec<u8> {
28 unsafe {
29 let meta = abi::metadata0(ma);
30 let mut buf = vec![0u8; abi::type_bytes(meta)];
31 abi::inject_enum_tag(&mut buf, meta, tag);
32 buf
33 }
34}
35
36pub(crate) unsafe fn opt_enum_bytes(ma: *const c_void, opt_mangled: &[u8], tag: Option<u32>) -> Vec<u8> {
38 unsafe {
39 let payload = abi::metadata0(ma);
40 let opt = abi::resolve_type_by_mangled_name(opt_mangled);
41 assert!(!opt.is_null(), "{}", crate::diag::demangle_type(opt_mangled));
42 let mut buf = vec![0u8; abi::type_bytes(opt)];
43 if let Some(t) = tag {
44 abi::inject_enum_tag(&mut buf[..abi::type_bytes(payload)], payload, t);
45 }
46 abi::store_optional_tag(&mut buf, payload, tag.is_some());
47 buf
48 }
49}
50
51pub(crate) unsafe fn optional_words(
64 payload_meta: *const c_void,
65 opt_meta: *const c_void,
66 bytes: &[u8],
67 is_some: bool,
68) -> Vec<u64> {
69 unsafe {
70 let stride = abi::type_bytes(opt_meta);
71 let mut buf = vec![0u8; stride.max(8)];
72 if is_some {
73 buf[..bytes.len()].copy_from_slice(bytes);
74 }
75 abi::store_optional_tag(&mut buf, payload_meta, is_some);
76 let words = abi::type_size(opt_meta).div_ceil(8);
77 (0..words)
78 .map(|i| u64::from_le_bytes(buf[i * 8..i * 8 + 8].try_into().expect("word")))
79 .collect()
80 }
81}
82
83pub(crate) unsafe fn opt_f64_words(v: Option<f64>) -> Vec<u64> {
85 unsafe {
86 let payload = resolve::double_meta();
87 let opt = abi::resolve_type_by_mangled_name(mangle::optional(mangle::DOUBLE).as_bytes());
88 optional_words(payload, opt, &v.unwrap_or(0.0).to_le_bytes(), v.is_some())
89 }
90}
91
92pub(crate) unsafe fn opt_unit_point_words(v: Option<(f64, f64)>) -> Vec<u64> {
94 unsafe {
95 let payload = crate::type_by_name!(swift_type!(SwiftUI.UnitPoint).as_bytes());
96 let opt = crate::type_by_name!(swift_type!(opt SwiftUI.UnitPoint).as_bytes());
97 let (x, y) = v.unwrap_or((0.0, 0.0));
98 let mut bytes = [0u8; 16];
99 bytes[..8].copy_from_slice(&x.to_le_bytes());
100 bytes[8..].copy_from_slice(&y.to_le_bytes());
101 optional_words(payload, opt, &bytes, v.is_some())
102 }
103}
104
105pub(crate) unsafe fn opt_string_words(v: Option<&str>) -> [u64; 2] {
110 unsafe {
111 let payload = resolve::string_meta();
112 let mut buf = [0u8; 16];
113 if let Some(s) = v {
114 buf.copy_from_slice(&abi::swift_string(s));
115 }
116 abi::store_optional_tag(&mut buf, payload, v.is_some());
117 [
118 u64::from_le_bytes(buf[..8].try_into().expect("string lo")),
119 u64::from_le_bytes(buf[8..].try_into().expect("string hi")),
120 ]
121 }
122}
123
124swift_cases! {
125 pub enum ImageScale {
127 Small = 0,
128 Medium = 1,
129 Large = 2,
130 }
131}
132
133swift_cases! {
134 pub enum TruncationMode {
136 Head = 0,
137 Tail = 1,
138 Middle = 2,
139 }
140}
141
142swift_cases! {
143 pub enum TextCase {
145 Uppercase = 0,
146 Lowercase = 1,
147 }
148}
149
150swift_statics! {
151 pub enum HorizontalAlignment {
153 Leading = c"$s7SwiftUI19HorizontalAlignmentV7leadingACvgZ",
154 Center = c"$s7SwiftUI19HorizontalAlignmentV6centerACvgZ",
155 Trailing = c"$s7SwiftUI19HorizontalAlignmentV8trailingACvgZ",
156 ListRowSeparatorLeading = c"$s7SwiftUI19HorizontalAlignmentV23listRowSeparatorLeadingACvgZ",
157 ListRowSeparatorTrailing = c"$s7SwiftUI19HorizontalAlignmentV24listRowSeparatorTrailingACvgZ",
158 }
159}
160
161swift_statics! {
162 pub enum VerticalAlignment {
170 Top = c"$s7SwiftUI17VerticalAlignmentV3topACvgZ",
171 Center = c"$s7SwiftUI17VerticalAlignmentV6centerACvgZ",
172 Bottom = c"$s7SwiftUI17VerticalAlignmentV6bottomACvgZ",
173 FirstTextBaseline = c"$s7SwiftUI17VerticalAlignmentV17firstTextBaselineACvgZ",
174 LastTextBaseline = c"$s7SwiftUI17VerticalAlignmentV16lastTextBaselineACvgZ",
175 }
176}
177
178swift_statics! {
179 pub enum ScrollAnchorRole {
181 Alignment = c"$s7SwiftUI16ScrollAnchorRoleV9alignmentACvgZ",
182 SizeChanges = c"$s7SwiftUI16ScrollAnchorRoleV11sizeChangesACvgZ",
183 InitialOffset = c"$s7SwiftUI16ScrollAnchorRoleV13initialOffsetACvgZ",
184 }
185}
186
187pub fn accent_color(view: &ViewHandle, color: Option<crate::Color>) -> ViewHandle {
189 unsafe {
190 modifier_opt_color(resolve::accent_color_fn(), resolve::accent_color_oret(), view, color)
191 }
192}
193
194pub fn foreground_color_value(view: &ViewHandle, color: u64) -> ViewHandle {
204 unsafe {
205 opaque_modifier(
206 resolve::foreground_color_fn(),
207 resolve::foreground_color_oret(),
208 view,
209 &[color],
210 [0.0; 4],
211 )
212 }
213}
214
215pub fn foreground_color(view: &ViewHandle, color: Option<crate::Color>) -> ViewHandle {
217 unsafe {
218 modifier_opt_color(
219 resolve::foreground_color_fn(),
220 resolve::foreground_color_oret(),
221 view,
222 color,
223 )
224 }
225}
226
227pub fn color_multiply(view: &ViewHandle, color: crate::Color) -> ViewHandle {
229 unsafe {
230 let word = color_value(color.r, color.g, color.b, color.a);
231 opaque_modifier(
232 resolve::color_multiply_fn(),
233 resolve::color_multiply_oret(),
234 view,
235 &[word],
236 [0.0; 4],
237 )
238 }
239}
240
241pub fn allows_window_activation_events(view: &ViewHandle, allows: Option<bool>) -> ViewHandle {
243 unsafe {
244 modifier_opt_bool(
245 resolve::allows_window_activation_fn(),
246 resolve::allows_window_activation_oret(),
247 view,
248 allows,
249 )
250 }
251}
252
253pub fn dialog_prevents_app_termination(view: &ViewHandle, prevents: Option<bool>) -> ViewHandle {
255 unsafe {
256 modifier_opt_bool(
257 resolve::dialog_prevents_termination_fn(),
258 resolve::dialog_prevents_termination_oret(),
259 view,
260 prevents,
261 )
262 }
263}
264
265pub fn disable_autocorrection(view: &ViewHandle, disable: Option<bool>) -> ViewHandle {
267 unsafe {
268 modifier_opt_bool(
269 resolve::disable_autocorrection_fn(),
270 resolve::disable_autocorrection_oret(),
271 view,
272 disable,
273 )
274 }
275}
276
277pub fn presentation_prevents_app_termination(
279 view: &ViewHandle,
280 prevents: Option<bool>,
281) -> ViewHandle {
282 unsafe {
283 modifier_opt_bool(
284 resolve::presentation_prevents_termination_fn(),
285 resolve::presentation_prevents_termination_oret(),
286 view,
287 prevents,
288 )
289 }
290}
291
292pub fn preview_display_name(view: &ViewHandle, name: Option<&str>) -> ViewHandle {
294 unsafe {
295 let w = opt_string_words(name);
296 opaque_modifier(
297 resolve::preview_display_name_fn(),
298 resolve::preview_display_name_oret(),
299 view,
300 &w,
301 [0.0; 4],
302 )
303 }
304}
305
306pub fn hue_rotation(view: &ViewHandle, radians: f64) -> ViewHandle {
311 unsafe {
312 modifier_f64(resolve::hue_rotation_fn(), resolve::hue_rotation_oret(), view, radians)
313 }
314}
315
316pub fn grid_cell_anchor(view: &ViewHandle, x: f64, y: f64) -> ViewHandle {
318 unsafe {
319 opaque_modifier(
320 resolve::grid_cell_anchor_fn(),
321 resolve::grid_cell_anchor_oret(),
322 view,
323 &[],
324 [x, y, 0.0, 0.0],
325 )
326 }
327}
328
329pub fn presentation_corner_radius(view: &ViewHandle, radius: Option<f64>) -> ViewHandle {
331 unsafe {
332 let w = opt_f64_words(radius);
333 opaque_modifier(
334 resolve::presentation_corner_radius_fn(),
335 resolve::presentation_corner_radius_oret(),
336 view,
337 &w,
338 [0.0; 4],
339 )
340 }
341}
342
343pub fn image_scale(view: &ViewHandle, scale: ImageScale) -> ViewHandle {
345 unsafe {
346 let b = enum_bytes(resolve::image_scale_ma(), scale.tag());
347 modifier_indirect(resolve::image_scale_fn(), resolve::image_scale_oret(), view, &b)
348 }
349}
350
351pub fn truncation_mode(view: &ViewHandle, mode: TruncationMode) -> ViewHandle {
353 unsafe {
354 let b = enum_bytes(resolve::truncation_mode_ma(), mode.tag());
355 modifier_indirect(resolve::truncation_mode_fn(), resolve::truncation_mode_oret(), view, &b)
356 }
357}
358
359pub fn text_case(view: &ViewHandle, case: Option<TextCase>) -> ViewHandle {
361 unsafe {
362 let b = opt_enum_bytes(
363 resolve::text_case_ma(),
364 swift_type!(opt enum SwiftUI.Text.Case).as_bytes(),
365 case.map(TextCase::tag),
366 );
367 modifier_indirect(resolve::text_case_fn(), resolve::text_case_oret(), view, &b)
368 }
369}
370
371pub fn grid_column_alignment(view: &ViewHandle, alignment: HorizontalAlignment) -> ViewHandle {
373 unsafe {
374 let meta = abi::metadata0(resolve::horizontal_alignment_ma());
375 let a = frozen_static(meta, alignment.getter());
376 opaque_modifier(
377 resolve::grid_column_alignment_fn(),
378 resolve::grid_column_alignment_oret(),
379 view,
380 &[a],
381 [0.0; 4],
382 )
383 }
384}
385
386pub fn transform_effect(
388 view: &ViewHandle,
389 a: f64,
390 b: f64,
391 c: f64,
392 d: f64,
393 tx: f64,
394 ty: f64,
395) -> ViewHandle {
396 unsafe {
397 let mut bytes = [0u8; 48];
398 for (i, v) in [a, b, c, d, tx, ty].iter().enumerate() {
399 bytes[i * 8..(i + 1) * 8].copy_from_slice(&v.to_le_bytes());
400 }
401 modifier_indirect(
402 resolve::transform_effect_fn(),
403 resolve::transform_effect_oret(),
404 view,
405 &bytes,
406 )
407 }
408}
409
410pub fn projection_effect(view: &ViewHandle, m: [f64; 9]) -> ViewHandle {
412 unsafe {
413 let mut bytes = [0u8; 72];
414 for (i, v) in m.iter().enumerate() {
415 bytes[i * 8..(i + 1) * 8].copy_from_slice(&v.to_le_bytes());
416 }
417 modifier_indirect(
418 resolve::projection_effect_fn(),
419 resolve::projection_effect_oret(),
420 view,
421 &bytes,
422 )
423 }
424}
425
426pub fn list_row_insets(view: &ViewHandle, insets: Option<[f64; 4]>) -> ViewHandle {
428 unsafe {
429 let payload = abi::metadata0(resolve::edge_insets_ma());
430 let opt = crate::type_by_name!(swift_type!(opt SwiftUI.EdgeInsets).as_bytes());
431 let mut bytes = vec![0u8; abi::type_bytes(opt)];
432 if let Some(v) = insets {
433 for (i, x) in v.iter().enumerate() {
434 bytes[i * 8..(i + 1) * 8].copy_from_slice(&x.to_le_bytes());
435 }
436 }
437 abi::store_optional_tag(&mut bytes, payload, insets.is_some());
438 modifier_indirect(resolve::list_row_insets_fn(), resolve::list_row_insets_oret(), view, &bytes)
439 }
440}
441
442pub fn window_resize_anchor(view: &ViewHandle, anchor: Option<(f64, f64)>) -> ViewHandle {
444 unsafe {
445 let w = opt_unit_point_words(anchor);
446 opaque_modifier(
447 resolve::window_resize_anchor_fn(),
448 resolve::window_resize_anchor_oret(),
449 view,
450 &w,
451 [0.0; 4],
452 )
453 }
454}
455
456pub fn default_scroll_anchor(
458 view: &ViewHandle,
459 anchor: Option<(f64, f64)>,
460 role: ScrollAnchorRole,
461) -> ViewHandle {
462 unsafe {
463 let mut w = opt_unit_point_words(anchor);
464 let meta = abi::metadata0(resolve::scroll_anchor_role_ma());
465 let r = resilient_static(meta, role.getter());
466 w.push(r.as_ptr() as u64);
467 opaque_modifier(
468 resolve::default_scroll_anchor_fn(),
469 resolve::default_scroll_anchor_oret(),
470 view,
471 &w,
472 [0.0; 4],
473 )
474 }
475}
476
477#[cfg(test)]
478mod optional_abi_tests {
479 use super::*;
480
481 #[test]
488 fn optional_payloads_travel_as_raw_words() {
489 unsafe {
490 let w = opt_unit_point_words(Some((0.3, 0.6)));
491 assert_eq!(w.len(), 3, "Optional<UnitPoint> is three words");
492 assert_eq!(w[0], 0.3f64.to_bits(), "x is raw bits, not a float register");
493 assert_eq!(w[1], 0.6f64.to_bits(), "y is raw bits, not a float register");
494 assert_eq!(w[2] & 0xff, 0, "some");
495
496 let n = opt_unit_point_words(None);
497 assert_eq!(n.len(), 3);
498 assert_ne!(n[2] & 0xff, 0, "none");
499
500 let d = opt_f64_words(Some(12.0));
501 assert_eq!(d.len(), 2, "Optional<Double> is two words");
502 assert_eq!(d[0], 12.0f64.to_bits());
503 }
504 }
505
506 #[test]
509 fn optional_bool_is_one_byte() {
510 unsafe {
511 let m = abi::resolve_type_by_mangled_name(mangle::optional(mangle::BOOL).as_bytes());
512 assert_eq!(abi::type_bytes(m), 1);
513 let mut buf = [0u8; 1];
514 buf[0] = 1;
515 abi::store_optional_tag(&mut buf, resolve::bool_meta(), true);
516 assert_eq!(buf[0], 1, "Some(true)");
517 abi::store_optional_tag(&mut buf, resolve::bool_meta(), false);
518 assert!(buf[0] > 1, "nil is a value a Bool cannot hold");
519 }
520 }
521
522 #[test]
525 fn color_is_one_frozen_word() {
526 unsafe {
527 let m = crate::type_by_name!(swift_type!(SwiftUI.Color).as_bytes());
528 assert_eq!(abi::type_bytes(m), 8);
529 let o = crate::type_by_name!(swift_type!(opt SwiftUI.Color).as_bytes());
530 assert_eq!(abi::type_bytes(o), 8, "a null reference is already nil");
531 }
532 }
533}