swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
//! Gestures, drag and drop, commands, scrolling and focus.
//!
//! Part of [`crate::view::View`]'s chainable API; Rust allows a type's
//! inherent methods to be split across modules, and 400 of them in one file
//! was a file nobody could find anything in.

use super::{View};
use crate::views;

impl View {
    // ── Drag and drop ──
    //
    // Distinct from the drag *gesture*, which is a finger moving on screen:
    // this is handing an item to another view, another window, or another app.

    /// `.dragConfiguration(within:outside:)` — which operations a drag permits
    /// inside the app and outside it, which need not be the same.
    pub fn drag_configuration(
        self,
        within_app: views::DragOperations,
        outside_app: views::DragOperations,
    ) -> Self {
        Self::new(views::drag_configuration(&self.handle, within_app, outside_app))
    }

    /// `.dragContainer(for:)` — the items a multi-item drag carries.
    pub fn drag_container(self, items: &[&str]) -> Self {
        Self::new(views::drag_container(&self.handle, items))
    }

    /// `.dragContainerSelection(_:)` — which of them are selected, so dragging
    /// one drags the selection rather than just the item under the finger.
    pub fn drag_container_selection(self, selected: &[&str]) -> Self {
        Self::new(views::drag_container_selection(&self.handle, selected))
    }

    /// `.draggableContainerItem(id:)` — mark this view as one of the container's
    /// items.
    pub fn draggable_container_item(self, id: views::HashableValue<'_>) -> Self {
        Self::new(views::draggable_container_item(&self.handle, id))
    }

    /// `.dragPreviewsFormation(_:)` — how several dragged previews stack up.
    pub fn drag_previews_formation(self, value: views::DragDropPreviewsFormation) -> Self {
        Self::new(views::drag_previews_formation(&self.handle, value))
    }

    /// `.dropPreviewsFormation(_:)`
    pub fn drop_previews_formation(self, value: views::DragDropPreviewsFormation) -> Self {
        Self::new(views::drop_previews_formation(&self.handle, value))
    }

    /// `.dropConfiguration(_:)` — decide what a drop over this view would do,
    /// which is what sets the cursor before anything is released.
    pub fn drop_configuration(self, decide: impl Fn() -> views::DropOperation + 'static) -> Self {
        Self::new(views::drop_configuration(&self.handle, decide))
    }

    /// `.dropDestination(for:action:isTargeted:)` — `accept` returns whether the
    /// drop was taken; `targeted` fires as the drag enters and leaves.
    pub fn drop_destination(
        self,
        accept: impl Fn(&[String]) -> bool + 'static,
        targeted: impl Fn(bool) + 'static,
    ) -> Self {
        Self::new(views::drop_destination(&self.handle, accept, targeted))
    }

    /// `.onDragSessionUpdated(_:)`
    pub fn on_drag_session_updated(self, action: impl Fn() + 'static) -> Self {
        Self::new(views::on_drag_session_updated(&self.handle, action))
    }

    /// `.onDropSessionUpdated(_:)`
    pub fn on_drop_session_updated(self, action: impl Fn() + 'static) -> Self {
        Self::new(views::on_drop_session_updated(&self.handle, action))
    }

    // ── Editing commands ──
    //
    // What the standard Copy, Cut, Paste and Delete menu items do when this
    // view has focus — the keyboard and menu half of an app, which a
    // touch-first API leaves out.

    /// `.onCopyCommand(perform:)` — what Copy takes from this view.
    pub fn on_copy_command(self, items: &[&str]) -> Self {
        Self::new(views::on_copy_command(&self.handle, items))
    }

    /// `.onCutCommand(perform:)`
    pub fn on_cut_command(self, items: &[&str]) -> Self {
        Self::new(views::on_cut_command(&self.handle, items))
    }

    /// `.onPasteCommand(of:perform:)`
    pub fn on_paste_command(
        self,
        kinds: &[views::ContentType],
        paste: impl Fn(&[views::ItemProvider]) + 'static,
    ) -> Self {
        Self::new(views::on_paste_command(&self.handle, kinds, paste))
    }

    /// `.onDeleteCommand(perform:)`
    pub fn on_delete_command(self, action: impl Fn() + 'static) -> Self {
        Self::new(views::on_delete_command(&self.handle, action))
    }

    /// `.onMoveCommand(perform:)` — arrow keys, with the direction as an `Int`.
    pub fn on_move_command(self, action: impl Fn(i64) + 'static) -> Self {
        Self::new(views::on_move_command(&self.handle, action))
    }

    /// `.onExitCommand(perform:)` — escape.
    pub fn on_exit_command(self, action: impl Fn() + 'static) -> Self {
        Self::new(views::on_exit_command(&self.handle, action))
    }

    /// `.onCommand(_:perform:)` — any other selector by name, which is the way
    /// out when a command has no modifier of its own.
    pub fn on_command(self, selector: &'static core::ffi::CStr, action: impl Fn() + 'static) -> Self {
        Self::new(views::on_command(&self.handle, selector, action))
    }

    /// `.cuttable(for:action:)` — what Cut takes, as distinct from
    /// [`View::copyable`], which Copy takes.
    pub fn cuttable(self, items: &[&str]) -> Self {
        Self::new(views::cuttable(&self.handle, items))
    }

    /// `.pasteDestination(for:action:)`
    pub fn paste_destination(self, paste: impl Fn(&[String]) + 'static) -> Self {
        Self::new(views::paste_destination(&self.handle, paste))
    }

    /// `.renameAction(_:)` — what the Rename command does, which a list row
    /// needs for in-place renaming to work at all.
    pub fn rename_action(self, action: impl Fn() + 'static) -> Self {
        Self::new(views::rename_action(&self.handle, action))
    }

    // ── Pointer and keyboard ──

    /// `.pointerStyle(_:)` — `None` restores the default cursor.
    pub fn pointer_style(self, style: Option<views::PointerStyle>) -> Self {
        Self::new(views::pointer_style(&self.handle, style))
    }

    /// `.pointerVisibility(_:)`
    pub fn pointer_visibility(self, value: views::Visibility) -> Self {
        Self::new(views::pointer_visibility(&self.handle, value))
    }

    /// `.onModifierKeysChanged(mask:initial:_:)`
    pub fn on_modifier_keys_changed(
        self,
        mask: views::EventModifier,
        initial: bool,
        action: impl Fn(u64, u64) + 'static,
    ) -> Self {
        Self::new(views::on_modifier_keys_changed(&self.handle, mask, initial, action))
    }

    /// `.modifierKeyAlternate(_:_:)` — a different view while a key is held.
    pub fn modifier_key_alternate(self, modifiers: views::EventModifier, content: View) -> Self {
        Self::new(views::modifier_key_alternate(&self.handle, modifiers, content.handle()))
    }

    /// `.onInteractiveResizeChange(_:)`
    pub fn on_interactive_resize_change(self, action: impl Fn(bool) + 'static) -> Self {
        Self::new(views::on_interactive_resize_change(&self.handle, action))
    }

    /// `.userActivity(_:isActive:_:)` — advertise what this view is doing, for
    /// Handoff and Spotlight.
    pub fn user_activity(self, kind: &str, active: bool, action: impl Fn() + 'static) -> Self {
        Self::new(views::user_activity(&self.handle, kind, active, action))
    }

    /// `.onContinueUserActivity(_:perform:)` — the other end of Handoff.
    pub fn on_continue_user_activity(self, kind: &str, action: impl Fn() + 'static) -> Self {
        Self::new(views::on_continue_user_activity(&self.handle, kind, action))
    }

    /// `.handlesExternalEvents(preferring:allowing:)`
    pub fn handles_external_events(self, preferring: &[&str], allowing: &[&str]) -> Self {
        Self::new(views::handles_external_events(&self.handle, preferring, allowing))
    }

    /// `.allowedDynamicRange(.high)`
    pub fn allowed_dynamic_range_high(self) -> Self {
        Self::new(views::allowed_dynamic_range_high(&self.handle))
    }

    // ── Gestures ──

    /// `.tabItem { … }` — the label a `TabView` shows for this page.
    pub fn tab_item(self, label: View) -> Self {
        Self::new(views::tab_item(&self.handle, label.handle()))
    }

    /// `.highPriorityGesture(_:)` — take the gesture ahead of the children,
    /// where `.gesture` would lose to them.
    pub fn high_priority_gesture(self, minimum_distance: f64, action: impl Fn() + 'static) -> Self {
        Self::new(views::high_priority_gesture(&self.handle, minimum_distance, action))
    }

    /// `.simultaneousGesture(_:)` — recognise alongside the children rather than
    /// instead of them.
    pub fn simultaneous_gesture(self, minimum_distance: f64, action: impl Fn() + 'static) -> Self {
        Self::new(views::simultaneous_gesture(&self.handle, minimum_distance, action))
    }

    /// `.onOpenURL(perform:)` — deep links.
    pub fn on_open_url(self, action: impl Fn(&str) + 'static) -> Self {
        Self::new(views::on_open_url(&self.handle, action))
    }

    /// `.copyable(_:)` — what a copy command takes from this view.
    pub fn copyable(self, items: &[&str]) -> Self {
        Self::new(views::copyable(&self.handle, items))
    }

    // ── Scrolling ──

    /// `.scrollPosition(id:anchor:)` bound to Rust state — the id of the item
    /// at the anchor lands in `state`, and writing it scrolls there.
    pub fn scroll_position_id(
        self,
        state: &views::BoundState<String>,
        anchor: Option<(f64, f64)>,
    ) -> Self {
        Self::new(views::scroll_position_id(&self.handle, state, anchor))
    }

    /// `.defaultScrollAnchor(_:for:)` — `None` restores the default.
    pub fn default_scroll_anchor(
        self,
        anchor: Option<(f64, f64)>,
        role: views::ScrollAnchorRole,
    ) -> Self {
        Self::new(views::default_scroll_anchor(&self.handle, anchor, role))
    }

    /// `.scrollTargetLayout()` — mark the stack whose children are the targets
    /// a paging scroll snaps to.
    pub fn scroll_target_layout(self, enabled: bool) -> Self {
        Self::new(views::scroll_target_layout(&self.handle, enabled))
    }

    /// `.scrollBounceBehavior(_:axes:)`
    pub fn scroll_bounce_behavior(
        self,
        behavior: views::ScrollBounceBehavior,
        axes: views::AxisSet,
    ) -> Self {
        Self::new(views::scroll_bounce_behavior(&self.handle, behavior, axes))
    }

    /// `.scrollIndicatorsFlash(onAppear:)`
    pub fn scroll_indicators_flash(self, on_appear: bool) -> Self {
        Self::new(views::scroll_indicators_flash(&self.handle, on_appear))
    }

    /// `.scrollEdgeEffectHidden(_:for:)`
    pub fn scroll_edge_effect_hidden(self, hidden: bool, edges: views::EdgeSet) -> Self {
        Self::new(views::scroll_edge_effect_hidden(&self.handle, hidden, edges))
    }

    /// `.scrollEdgeEffectStyle(_:for:)` — `None` restores the default.
    pub fn scroll_edge_effect_style(
        self,
        style: Option<views::ScrollEdgeEffectStyle>,
        edges: &[views::EdgeSetMember],
    ) -> Self {
        Self::new(views::scroll_edge_effect_style(&self.handle, style, edges))
    }

    /// `.scrollTransition(_:axis:)` — how a child changes as it crosses the
    /// viewport.
    pub fn scroll_transition(
        self,
        config: views::ScrollTransitionConfiguration,
        axis: Option<views::Axis>,
    ) -> Self {
        Self::new(views::scroll_transition(&self.handle, config, axis))
    }

    /// `.scrollTransition(topLeading:bottomTrailing:axis:)` — the two ends of
    /// the scroll configured separately.
    ///
    /// The single-configuration [`scroll_transition`](Self::scroll_transition)
    /// applies the same one at both ends; this is what lets a view animate on
    /// the way in and do nothing on the way out.
    pub fn scroll_transition_ends(
        self,
        top_leading: views::ScrollTransitionConfiguration,
        bottom_trailing: views::ScrollTransitionConfiguration,
        axis: Option<views::Axis>,
    ) -> Self {
        Self::new(views::scroll_transition_ends(
            &self.handle,
            top_leading,
            bottom_trailing,
            axis,
        ))
    }

    /// `.onScrollPhaseChange(_:)` — the handler sees the old and new phase.
    pub fn on_scroll_phase_change(self, action: impl Fn(i64, i64) + 'static) -> Self {
        Self::new(views::on_scroll_phase_change(&self.handle, action))
    }

    /// `.onScrollVisibilityChange(threshold:_:)`
    pub fn on_scroll_visibility_change(self, threshold: f64, action: impl Fn(bool) + 'static) -> Self {
        Self::new(views::on_scroll_visibility_change(&self.handle, threshold, action))
    }

    // ── Focus ──

    /// `.defaultFocus(_:_:)` — which control starts focused.
    pub fn default_focus(self, value: bool, priority: views::DefaultFocusPriority) -> Self {
        Self::new(views::default_focus(&self.handle, value, priority))
    }

    /// `.prefersDefaultFocus(_:in:)`
    pub fn prefers_default_focus(self, prefers: bool) -> Self {
        Self::new(views::prefers_default_focus(&self.handle, prefers))
    }

    /// `.focusScope(_:)` — a boundary focus moves within.
    pub fn focus_scope(self) -> Self {
        Self::new(views::focus_scope(&self.handle))
    }

    /// `.accessibilityFocused(_:)`
    pub fn a11y_focused(self) -> Self {
        Self::new(views::accessibility_focused(&self.handle))
    }

    /// `.accessibilityDefaultFocus(_:_:)`
    pub fn a11y_default_focus(self, value: bool) -> Self {
        Self::new(views::accessibility_default_focus(&self.handle, value))
    }

    /// `.focused(_:)`
    pub fn focused(self) -> Self {
        Self::new(views::focused(&self.handle))
    }

    /// `.matchedGeometryEffect(id:in:properties:anchor:isSource:)` — the pair of
    /// views that share an id are interpolated between rather than crossfaded.
    pub fn matched_geometry_effect(
        self,
        id: views::HashableValue<'_>,
        properties: &[views::GeometryProperties],
        anchor: (f64, f64),
        is_source: bool,
    ) -> Self {
        Self::new(views::matched_geometry_effect(
            &self.handle,
            id,
            properties,
            anchor,
            is_source,
        ))
    }

    /// `.matchedGeometryEffect(id:in:properties:anchor:isSource:)` in a
    /// *shared* namespace.
    ///
    /// The pairing spelling. [`matched_geometry_effect`](Self::matched_geometry_effect)
    /// mints a namespace per call, so the views it makes cannot pair with
    /// anything.
    pub fn matched_geometry_effect_in(
        self,
        id: views::HashableValue<'_>,
        namespace: views::Namespace,
        properties: &[views::GeometryProperties],
        anchor: (f64, f64),
        is_source: bool,
    ) -> Self {
        Self::new(views::matched_geometry_effect_in(
            &self.handle,
            id,
            namespace,
            properties,
            anchor,
            is_source,
        ))
    }

    /// `.onDrag { NSItemProvider(object: item) }` with a custom preview.
    ///
    /// Named apart from [`View::on_drag`], which is the drag *gesture* — a
    /// finger moving on screen — where this is drag and drop, which hands an
    /// item to another view or another app.
    pub fn on_drag_item(self, item: &str, preview: View) -> Self {
        Self::new(views::on_drag(&self.handle, item, preview.handle()))
    }

    /// `.onDrop(of:isTargeted:perform:)` — the handler returns whether it took
    /// the drop.
    pub fn on_drop(
        self,
        kinds: &[views::ContentType],
        accept: impl Fn(&[views::ItemProvider]) -> bool + 'static,
    ) -> Self {
        Self::new(views::on_drop(&self.handle, kinds, accept))
    }

    /// `.onKeyPress(_:phases:action:)`
    ///
    /// The action is handed the [`views::KeyPress`] — what was typed, which
    /// modifiers were held, and which phase of the press it is — and answers
    /// whether it handled it. It used to receive nothing at all, which left a
    /// key handler unable to branch on the key.
    ///
    /// ```no_run
    /// # use swiftui_native::prelude::*;
    /// # use swiftui_native::views::{CharacterSetKind, EventModifier, KeyPressPhases, KeyPressResult};
    /// let v = text("editor").on_key_press(
    ///     CharacterSetKind::Alphanumerics,
    ///     KeyPressPhases::Down,
    ///     |press| {
    ///         if press.characters == "s" && press.holding(EventModifier::Command) {
    ///             KeyPressResult::Handled
    ///         } else {
    ///             KeyPressResult::Ignored
    ///         }
    ///     },
    /// );
    /// ```
    pub fn on_key_press(
        self,
        characters: views::CharacterSetKind,
        phases: views::KeyPressPhases,
        action: impl Fn(views::KeyPress) -> views::KeyPressResult + 'static,
    ) -> Self {
        Self::new(views::on_key_press(&self.handle, characters, phases, action))
    }

    /// `.onKeyPress(_:action:)` — one named key.
    ///
    /// [`on_key_press`](Self::on_key_press) watches a whole character set and
    /// hands the action the press; this watches *one* key, which is how a
    /// shortcut is usually written — "Escape closes this". Only the
    /// character-set form was bound, so a view that cared about Escape had to
    /// accept every key and filter.
    ///
    /// ```no_run
    /// # use swiftui_native::prelude::*;
    /// # use swiftui_native::views::{Key, KeyPressResult};
    /// let sheet = text("editor").on_key(Key::Escape, || KeyPressResult::Handled);
    /// ```
    pub fn on_key(
        self,
        key: views::Key,
        action: impl Fn() -> views::KeyPressResult + 'static,
    ) -> Self {
        Self::new(views::on_key(&self.handle, key, action))
    }

    /// `.sensoryFeedback(_:trigger:condition:)` — haptics, fired when the
    /// condition says the trigger's change deserves one.
    pub fn sensory_feedback(
        self,
        feedback: views::SensoryFeedback,
        trigger: i64,
        condition: impl Fn(i64, i64) -> bool + 'static,
    ) -> Self {
        Self::new(views::sensory_feedback(&self.handle, feedback, trigger, condition))
    }

    // ── visionOS: hover effects ──

    /// Apply `.hoverEffect(.lift)` — visionOS native gaze-driven lift.
    ///
    /// Constructs the `LiftHoverEffect` value, resolves witness tables, and
    /// calls the modifier entirely at runtime via arm64 asm. No Swift bridge.
    /// No-op on platforms where the symbol is absent.
    #[cfg(not(target_os = "macos"))]
    pub fn hover_effect_lift(self) -> Self {
        Self::new(views::hover_effect_lift(&self.handle))
    }

    /// Apply `.hoverEffect(.highlight)` — visionOS native gaze-driven highlight.
    ///
    /// Same runtime mechanism as `hover_effect_lift`.
    #[cfg(not(target_os = "macos"))]
    pub fn hover_effect_highlight(self) -> Self {
        Self::new(views::hover_effect_highlight(&self.handle))
    }

    /// Apply `.onHover(perform:)` — fires callback on gaze enter/exit (visionOS)
    /// or mouse enter/exit (macOS).
    pub fn on_hover(self, callback: impl Fn(bool) + 'static) -> Self {
        Self::new(views::on_hover(&self.handle, callback))
    }
}