Skip to main content

fission_core/
lib.rs

1//! # fission-core
2//!
3//! The runtime, widget system, and action/reducer architecture for the Fission UI
4//! framework.
5//!
6//! `fission-core` provides:
7//!
8//! - A **declarative widget tree** built from composable primitives ([`Widget`]).
9//! - A **unidirectional data-flow** pipeline: [`Action`] -> [`Runtime::dispatch`] -> reducer
10//!   -> mutated [`GlobalState`].
11//! - An **effect system** for async side-effects ([`Effect`], [`RuntimeEffect`]).
12//! - Built-in widgets: [`ui::Button`], [`ui::Text`], [`ui::TextInput`],
13//!   [`ui::Container`], [`ui::Row`], [`ui::Column`], [`ui::Scroll`],
14//!   [`ui::ZStack`], [`ui::Grid`], [`ui::LazyColumn`], and more.
15//!
16//! ## Getting started
17//!
18//! ```rust,ignore
19//! use fission_core::*;
20//! use fission_core::ui::*;
21//!
22//! // Define application state
23//! #[derive(Debug, Default)]
24//! struct MyState { value: String }
25//! impl GlobalState for MyState {}
26//!
27//! // Build a widget tree value
28//! struct MyWidget;
29//! impl From<MyWidget> for Widget {
30//!     fn from(_: MyWidget) -> Widget {
31//!         let (_, view) = fission_core::build::current::<MyState>();
32//!         Text::new(&*view.state().value).into()
33//!     }
34//! }
35//! ```
36
37use anyhow::Result;
38use lazy_static::lazy_static;
39use std::any::TypeId;
40use std::collections::HashMap;
41use std::sync::{Arc, Mutex};
42
43extern crate self as fission_core;
44
45pub mod action;
46pub mod async_runtime;
47pub mod build;
48mod build_context;
49pub mod capability; // New
50pub mod context; // New
51pub mod data_stream;
52pub mod diff;
53pub mod effect; // New
54pub mod env;
55pub mod event;
56pub mod hit_test;
57pub mod input;
58pub(crate) mod lowering;
59pub mod media;
60pub mod motion;
61pub mod platform;
62pub mod platform_barcode;
63pub mod platform_biometric;
64pub mod platform_bluetooth;
65pub mod platform_camera;
66pub mod platform_clipboard;
67pub mod platform_geolocation;
68pub mod platform_haptics;
69pub mod platform_microphone;
70pub mod platform_nfc;
71pub mod platform_passkey;
72pub mod platform_volume;
73pub mod platform_wifi;
74pub mod registry;
75pub mod runtime;
76pub mod scoped_action_handlers;
77pub mod scrollbar;
78pub mod state;
79pub mod time;
80pub mod ui;
81
82pub mod view;
83
84#[doc(hidden)]
85/// Framework integration boundary for first-party shells, renderers, test
86/// harnesses, and generated widget implementations.
87///
88/// This module is not part of the application authoring API. Application code
89/// should construct `Widget` values from widget structs and components instead
90/// of calling lowering helpers directly.
91pub mod internal {
92    pub use crate::build_context::BuildCtx;
93    pub use crate::lowering::{
94        build_layout_tree, wrap_zstack_child, InternalIrBuilder, InternalLoweringCx,
95    };
96    use crate::Widget;
97    use fission_ir::WidgetId;
98
99    pub fn custom_render_widget(node: InternalRenderNode) -> Widget {
100        Widget::custom(node)
101    }
102
103    pub fn lower_widget(widget: &Widget, cx: &mut InternalLoweringCx) -> WidgetId {
104        let root = cx.next_widget_root();
105        lower_widget_with_root(widget, cx, root)
106    }
107
108    pub fn lower_widget_with_root(
109        widget: &Widget,
110        cx: &mut InternalLoweringCx,
111        root: WidgetId,
112    ) -> WidgetId {
113        widget.clone().resolve_identities(root).lower(cx)
114    }
115
116    pub fn lower_widget_to_ir(widget: &Widget) -> fission_ir::CoreIR {
117        lower_widget_to_ir_with_root(widget, WidgetId::app_root())
118    }
119
120    pub fn lower_widget_to_ir_with_root(widget: &Widget, root: WidgetId) -> fission_ir::CoreIR {
121        let env = crate::Env::default();
122        let runtime_state = crate::RuntimeState::default();
123        let mut cx = InternalLoweringCx::new(&env, &runtime_state, None, None);
124        let root_id = widget.clone().resolve_identities(root).lower(&mut cx);
125        cx.ir.root = Some(root_id);
126        cx.ir
127    }
128
129    pub fn resolve_widget_identities(widget: &Widget, root: WidgetId) -> Widget {
130        widget.clone().resolve_identities(root)
131    }
132
133    pub fn shell_root_id(authored_root: WidgetId) -> WidgetId {
134        WidgetId::derived(authored_root.as_u128(), &[0xF155_5E11])
135    }
136
137    pub fn widget_id(widget: &Widget) -> Option<WidgetId> {
138        widget.declared_id()
139    }
140
141    pub fn widget_kind_name(widget: &Widget) -> &'static str {
142        widget.kind_name()
143    }
144
145    pub fn widget_as_row(widget: &Widget) -> Option<&crate::ui::Row> {
146        widget.as_row()
147    }
148
149    pub fn widget_as_column(widget: &Widget) -> Option<&crate::ui::Column> {
150        widget.as_column()
151    }
152
153    pub fn widget_as_container(widget: &Widget) -> Option<&crate::ui::Container> {
154        widget.as_container()
155    }
156
157    pub fn widget_as_scroll(widget: &Widget) -> Option<&crate::ui::Scroll> {
158        widget.as_scroll()
159    }
160
161    pub fn widget_as_rich_text(widget: &Widget) -> Option<&crate::ui::RichText> {
162        widget.as_rich_text()
163    }
164
165    pub fn widget_as_text(widget: &Widget) -> Option<&crate::ui::Text> {
166        widget.as_text()
167    }
168
169    pub fn widget_as_text_input(widget: &Widget) -> Option<&crate::ui::TextInput> {
170        widget.as_text_input()
171    }
172
173    pub fn widget_as_button(widget: &Widget) -> Option<&crate::ui::Button> {
174        widget.as_button()
175    }
176
177    pub fn widget_as_gesture_detector(widget: &Widget) -> Option<&crate::ui::GestureDetector> {
178        widget.as_gesture_detector()
179    }
180
181    #[cfg(feature = "interactive-canvas")]
182    pub fn widget_as_interactive_viewer(widget: &Widget) -> Option<&crate::ui::InteractiveViewer> {
183        widget.as_interactive_viewer()
184    }
185
186    pub fn widget_as_zstack(widget: &Widget) -> Option<&crate::ui::ZStack> {
187        widget.as_zstack()
188    }
189
190    pub use crate::ui::custom_render::{
191        downcast_render_object, CustomEventResult, CustomHitResult, CustomRender,
192        CustomRenderObject,
193    };
194    pub use crate::ui::node::{CustomWidget, InternalRenderNode};
195    pub use crate::ui::traits::{InternalLower, InternalLowerer};
196}
197
198pub mod public {
199    pub mod action {
200        pub use crate::action::*;
201    }
202    pub mod env {
203        pub use crate::env::*;
204    }
205    pub mod event {
206        pub use crate::event::*;
207    }
208    pub mod hit_test {
209        pub use crate::hit_test::*;
210    }
211    pub mod registry {
212        pub use crate::registry::*;
213    }
214    pub mod scoped_action_handlers {
215        pub use crate::scoped_action_handlers::*;
216    }
217    pub mod ui {
218        pub use crate::ui::widgets::*;
219        pub use crate::ui::Widget;
220
221        pub mod widgets {
222            pub use crate::ui::widgets::*;
223        }
224    }
225    pub mod view {
226        pub use crate::view::*;
227    }
228
229    pub use crate::action::{
230        Action, ActionEnvelope, ActionId, ActionScopeId, GlobalState, UpdateTextInput,
231    };
232    pub use crate::async_runtime::{
233        BoxFuture, JobCtx, JobRef, JobSpec, ResourceExecutionContext, ServiceBindings, ServiceCtx,
234        ServiceRunner, ServiceSlot, ServiceSpec, ServiceType,
235    };
236    pub use crate::capability::{
237        CapabilityCtx, CapabilityInvocationPayload, CapabilityType, OpenUrlCapability,
238        OpenUrlRequest, OperationCapability, PickOpenFilesCapability, PickOpenFilesError,
239        PickOpenFilesRequest, PickOpenFilesResult, PickedFile, OPEN_URL, PICK_OPEN_FILES,
240    };
241    pub use crate::context::{
242        BarcodeScannerEffects, BiometricEffects, BluetoothEffects, CameraEffects, ClipboardEffects,
243        Effects, GeolocationEffects, HapticEffects, MicrophoneEffects, NfcEffects,
244        NotificationEffects, PasskeyEffects, ReducerContext, VolumeEffects, WifiEffects,
245    }; // New
246    pub use crate::data_stream::{
247        collect_data_stream, empty_data_stream, single_chunk_data_stream, BoxFissionDataStream,
248        DataStreamId, DataStreamRegistry, FissionDataStream, FissionDataStreamError,
249        FissionDataStreamErrorKind,
250    };
251    pub use crate::effect::{
252        ActionInput, ActionInputCodecError, Effect, EffectEnvelope, RuntimeEffect, ScrollAlignment,
253        ScrollAxis, ScrollBehavior, ScrollIntoViewRequest,
254    };
255    pub use crate::env::{
256        Clipboard, DragSessionPayload, DragSessionState, Env, ImeHandler, InteractionStateMap,
257        RuntimeState, ScrollStateMap, WindowEnv, WindowTitle,
258    };
259    pub use crate::runtime::Runtime;
260    pub use crate::state::{LocalStateKey, LocalStateStore, StateField};
261    pub use bytes::Bytes;
262
263    pub use crate::build::{BuildCtxHandle, ViewHandle};
264    pub use crate::event::{
265        ExternalDragEvent, InputEvent, KeyCode, KeyEvent, LifecycleEvent, PointerButton,
266        PointerEvent, PointerId, PointerKind, PointerPhase, ScrollDeltaMode,
267    };
268    pub use crate::input::canvas::{
269        CanvasInteraction, CanvasInteractionKind, CanvasInteractionPhase,
270    };
271    pub use crate::input::viewport::{
272        ViewportInputKind, ViewportInteraction, ViewportInteractionPhase, ViewportStateMap,
273    };
274    pub use crate::motion::*;
275    pub use crate::platform::{
276        CancelAllNotificationsCapability, CancelNotificationCapability, CancelNotificationRequest,
277        DeepLink, DeepLinkConfig, DeepLinkReceived, DeepLinkSource,
278        GetNotificationSettingsCapability, NotificationActionButton, NotificationError,
279        NotificationId, NotificationPermission, NotificationPermissionRequest, NotificationReceipt,
280        NotificationRequest, NotificationResponse, NotificationResponseReceived,
281        NotificationSchedule, NotificationSettings, NotificationSound, PushPlatform,
282        PushRegistration, PushRegistrationRequest, RegisterPushNotificationsCapability,
283        RequestNotificationPermissionCapability, ScheduleNotificationCapability,
284        SetBadgeCountCapability, SetBadgeCountRequest, ShowNotificationCapability,
285        UnregisterPushNotificationsCapability, CANCEL_ALL_NOTIFICATIONS, CANCEL_NOTIFICATION,
286        GET_NOTIFICATION_SETTINGS, REGISTER_PUSH_NOTIFICATIONS, REQUEST_NOTIFICATION_PERMISSION,
287        SCHEDULE_NOTIFICATION, SET_BADGE_COUNT, SHOW_NOTIFICATION, UNREGISTER_PUSH_NOTIFICATIONS,
288    };
289    pub use crate::platform_barcode::{
290        BarcodeFormat, BarcodeImageDecodeRequest, BarcodePoint, BarcodeScanRequest,
291        BarcodeScanResult, BarcodeScanResults, BarcodeScannerError, CancelBarcodeScanCapability,
292        DecodeBarcodeImageCapability, ScanBarcodeCapability, CANCEL_BARCODE_SCAN,
293        DECODE_BARCODE_IMAGE, SCAN_BARCODE,
294    };
295    pub use crate::platform_biometric::{
296        AuthenticateBiometricCapability, BiometricAuthenticateRequest, BiometricAuthenticateResult,
297        BiometricAvailability, BiometricError, BiometricKind, BiometricStrength,
298        CancelBiometricAuthenticationCapability, GetBiometricAvailabilityCapability,
299        AUTHENTICATE_BIOMETRIC, CANCEL_BIOMETRIC_AUTHENTICATION, GET_BIOMETRIC_AVAILABILITY,
300    };
301    pub use crate::platform_bluetooth::{
302        BluetoothAdvertiseReceipt, BluetoothAdvertiseRequest, BluetoothAvailability,
303        BluetoothConnectRequest, BluetoothConnection, BluetoothDevice, BluetoothDisconnectRequest,
304        BluetoothError, BluetoothMode, BluetoothPermission, BluetoothPermissionRequest,
305        BluetoothReadRequest, BluetoothReadResult, BluetoothScanRequest, BluetoothScanResult,
306        BluetoothStopAdvertiseRequest, BluetoothWriteRequest, ConnectBluetoothDeviceCapability,
307        DisconnectBluetoothDeviceCapability, GetBluetoothAvailabilityCapability,
308        ReadBluetoothCharacteristicCapability, RequestBluetoothPermissionCapability,
309        ScanBluetoothDevicesCapability, StartBluetoothAdvertisingCapability,
310        StopBluetoothAdvertisingCapability, WriteBluetoothCharacteristicCapability,
311        CONNECT_BLUETOOTH_DEVICE, DISCONNECT_BLUETOOTH_DEVICE, GET_BLUETOOTH_AVAILABILITY,
312        READ_BLUETOOTH_CHARACTERISTIC, REQUEST_BLUETOOTH_PERMISSION, SCAN_BLUETOOTH_DEVICES,
313        START_BLUETOOTH_ADVERTISING, STOP_BLUETOOTH_ADVERTISING, WRITE_BLUETOOTH_CHARACTERISTIC,
314    };
315    pub use crate::platform_camera::{
316        CameraAvailability, CameraCapture, CameraCaptureRequest, CameraDevice, CameraError,
317        CameraFacing, CameraFlashMode, CameraFlashlightRequest, CameraImageFormat,
318        CameraPermission, CameraPermissionRequest, CameraResolution, CancelCameraCaptureCapability,
319        CapturePhotoCapability, GetCameraAvailabilityCapability, RequestCameraPermissionCapability,
320        SetCameraFlashlightCapability, CANCEL_CAMERA_CAPTURE, CAPTURE_PHOTO,
321        GET_CAMERA_AVAILABILITY, REQUEST_CAMERA_PERMISSION, SET_CAMERA_FLASHLIGHT,
322    };
323    pub use crate::platform_clipboard::{
324        ClearClipboardCapability, ClipboardContent, ClipboardError, ClipboardItem, ClipboardText,
325        ClipboardWriteTextRequest, ReadClipboardContentCapability, ReadClipboardTextCapability,
326        WriteClipboardContentCapability, WriteClipboardTextCapability, CLEAR_CLIPBOARD,
327        READ_CLIPBOARD_CONTENT, READ_CLIPBOARD_TEXT, WRITE_CLIPBOARD_CONTENT, WRITE_CLIPBOARD_TEXT,
328    };
329    pub use crate::platform_geolocation::{
330        GeolocationError, GeolocationPermission, GeolocationPermissionRequest, GeolocationPosition,
331        GeolocationPositionRequest, GetCurrentPositionCapability,
332        GetGeolocationPermissionCapability, RequestGeolocationPermissionCapability,
333        GET_CURRENT_POSITION, GET_GEOLOCATION_PERMISSION, REQUEST_GEOLOCATION_PERMISSION,
334    };
335    pub use crate::platform_haptics::{
336        HapticError, HapticImpactCapability, HapticImpactRequest, HapticImpactStyle,
337        HapticNotificationCapability, HapticNotificationKind, HapticNotificationRequest,
338        HapticPatternCapability, HapticPatternRequest, HapticPatternStep,
339        HapticSelectionCapability, HAPTIC_IMPACT, HAPTIC_NOTIFICATION, HAPTIC_PATTERN,
340        HAPTIC_SELECTION,
341    };
342    pub use crate::platform_microphone::{
343        AudioSampleFormat, CancelMicrophoneCaptureCapability, CaptureMicrophoneAudioCapability,
344        GetMicrophoneAvailabilityCapability, MicrophoneAvailability, MicrophoneCapture,
345        MicrophoneCaptureRequest, MicrophoneDevice, MicrophoneError, MicrophonePermission,
346        MicrophonePermissionRequest, RequestMicrophonePermissionCapability,
347        CANCEL_MICROPHONE_CAPTURE, CAPTURE_MICROPHONE_AUDIO, GET_MICROPHONE_AVAILABILITY,
348        REQUEST_MICROPHONE_PERMISSION,
349    };
350    pub use crate::platform_nfc::{
351        CancelNfcSessionCapability, EmulateNfcTagCapability, GetNfcAvailabilityCapability,
352        NfcAvailability, NfcEmulationRequest, NfcError, NfcRecord, NfcRecordTypeNameFormat,
353        NfcScanRequest, NfcSessionReceipt, NfcTag, NfcTagDiscovered, NfcTechnology,
354        NfcWriteRequest, ScanNfcTagCapability, WriteNfcTagCapability, CANCEL_NFC_SESSION,
355        EMULATE_NFC_TAG, GET_NFC_AVAILABILITY, SCAN_NFC_TAG, WRITE_NFC_TAG,
356    };
357    pub use crate::platform_passkey::{
358        AuthenticatePasskeyCapability, CancelPasskeyOperationCapability,
359        GetPasskeyAvailabilityCapability, PasskeyAlgorithm, PasskeyAttestationConveyance,
360        PasskeyAuthenticationRequest, PasskeyAuthenticationResult, PasskeyAuthenticatorAttachment,
361        PasskeyAuthenticatorSelection, PasskeyAvailability, PasskeyCredentialDescriptor,
362        PasskeyError, PasskeyMediation, PasskeyRegistrationRequest, PasskeyRegistrationResult,
363        PasskeyRelyingParty, PasskeyResidentKeyRequirement, PasskeyTransport, PasskeyUser,
364        PasskeyUserVerification, RegisterPasskeyCapability, AUTHENTICATE_PASSKEY,
365        CANCEL_PASSKEY_OPERATION, GET_PASSKEY_AVAILABILITY, REGISTER_PASSKEY,
366    };
367    pub use crate::platform_volume::{
368        AdjustVolumeLevelCapability, GetVolumeLevelCapability, SetVolumeLevelCapability,
369        VolumeAdjustDirection, VolumeAdjustRequest, VolumeError, VolumeLevel, VolumeSetRequest,
370        VolumeStream, ADJUST_VOLUME_LEVEL, GET_VOLUME_LEVEL, SET_VOLUME_LEVEL,
371    };
372    pub use crate::platform_wifi::{
373        ConnectWifiNetworkCapability, DisconnectWifiNetworkCapability,
374        GetWifiAvailabilityCapability, RequestWifiPermissionCapability, ScanWifiNetworksCapability,
375        WifiAvailability, WifiConnectRequest, WifiConnection, WifiDisconnectRequest, WifiError,
376        WifiNetwork, WifiPermission, WifiPermissionRequest, WifiScanRequest, WifiScanResult,
377        WifiSecurity, CONNECT_WIFI_NETWORK, DISCONNECT_WIFI_NETWORK, GET_WIFI_AVAILABILITY,
378        REQUEST_WIFI_PERMISSION, SCAN_WIFI_NETWORKS,
379    };
380    pub use crate::registry::{
381        ActionRegistry, Handler, JobResource, PortalLayer, ResourceKey, ResourcePolicy,
382        ResourceRegistry, RuntimeResourceDeclaration, RuntimeResourceKind, ServiceResource,
383        TimerResource, VideoRegistration,
384    };
385    pub use crate::scoped_action_handlers::ScopedActionResolution;
386    pub use crate::time::{Clock, CurrentTime};
387    pub use crate::ui::{
388        provider, ActionScope, BadgeTone, Button, ButtonHierarchy, ButtonMotion, CardPattern,
389        Column, ComponentSize, ComponentState, CustomWidget, IosAudioSessionCategory,
390        IosAudioSessionCategoryOption, IosAudioSessionMode, IosVideoAudioOptions, Pressable,
391        PressableRole, PressableStyle, Provider, Responsive, ResponsiveCase, ResponsiveQuery, Row,
392        Text, Video, VideoAudioActivation, VideoAudioOptions, VideoAudioPolicy, VideoSource,
393        Widget, WidgetIdExt, WidgetKind,
394    };
395    pub use crate::view::{ComputedView, FissionViewField, Selector, ValueView, View};
396    pub use crate::{
397        reduce, reduce_with, video_asset, video_file, video_network, widgets, with_reducer,
398    };
399    pub use fission_ir::op;
400    pub use fission_ir::op::{
401        BoxAlignment, BoxGridPlacement, BoxPosition, BoxStyle, GridPlacement, GridTrack, Length,
402        Overflow,
403    };
404    pub use fission_ir::{EmbedKind, FocusPolicy, Op, Role, Semantics, WidgetId};
405    pub use fission_layout::{
406        BoxConstraints, FlexDirection, LayoutEngine, LayoutInspection, LayoutNodeGeometry,
407        LayoutOp, LayoutPoint, LayoutRect, LayoutSize, LayoutSnapshot, LayoutUnit, TextMeasurer,
408    };
409}
410
411#[cfg(test)]
412mod tests;
413
414pub use action::{
415    Action, ActionEnvelope, ActionId, ActionScopeId, GlobalState, ShellRouteChanged,
416    UpdateTextInput,
417};
418pub use async_runtime::{
419    BoxFuture, JobCtx, JobRef, JobSpec, ResourceExecutionContext, ServiceBindings, ServiceCtx,
420    ServiceRunner, ServiceSlot, ServiceSpec, ServiceType,
421};
422pub use bytes::Bytes;
423pub use capability::{
424    CapabilityCtx, CapabilityInvocationPayload, CapabilityType, OpenUrlCapability, OpenUrlRequest,
425    OperationCapability, PickOpenFilesCapability, PickOpenFilesError, PickOpenFilesRequest,
426    PickOpenFilesResult, PickedFile, OPEN_URL, PICK_OPEN_FILES,
427};
428pub use context::{
429    BarcodeScannerEffects, BiometricEffects, BluetoothEffects, CameraEffects, ClipboardEffects,
430    Effects, GeolocationEffects, HapticEffects, MicrophoneEffects, NfcEffects, NotificationEffects,
431    PasskeyEffects, ReducerContext, VolumeEffects, WifiEffects,
432}; // New
433pub use data_stream::{
434    collect_data_stream, empty_data_stream, single_chunk_data_stream, BoxFissionDataStream,
435    DataStreamId, DataStreamRegistry, FissionDataStream, FissionDataStreamError,
436    FissionDataStreamErrorKind,
437};
438pub use effect::{
439    ActionInput, ActionInputCodecError, Effect, EffectEnvelope, RuntimeEffect, ScrollAlignment,
440    ScrollAxis, ScrollBehavior, ScrollIntoViewRequest,
441};
442pub use env::{
443    Clipboard, DragSessionPayload, DragSessionState, Env, ImeHandler, InteractionStateMap,
444    RouteLocation, RuntimeState, ScrollStateMap, WindowEnv, WindowTitle,
445};
446pub use motion::*;
447pub use runtime::Runtime;
448pub use state::{LocalStateKey, LocalStateStore, StateField};
449
450pub use build::{BuildCtxHandle, ViewHandle};
451pub use event::{
452    ExternalDragEvent, InputEvent, KeyCode, KeyEvent, LifecycleEvent, PointerButton, PointerEvent,
453    PointerId, PointerKind, PointerPhase, ScrollDeltaMode,
454};
455pub use fission_ir::op;
456pub use fission_ir::op::{
457    BoxAlignment, BoxGridPlacement, BoxPosition, BoxStyle, GridPlacement, GridTrack, Length,
458    Overflow,
459};
460pub use fission_ir::{EmbedKind, FocusPolicy, Op, Role, Semantics, WidgetId};
461pub use fission_layout::{
462    BoxConstraints, FlexDirection, LayoutEngine, LayoutInspection, LayoutNodeGeometry, LayoutOp,
463    LayoutPoint, LayoutRect, LayoutSize, LayoutSnapshot, LayoutUnit, TextMeasurer,
464};
465pub use input::canvas::{CanvasInteraction, CanvasInteractionKind, CanvasInteractionPhase};
466pub use input::viewport::{
467    ViewportInputKind, ViewportInteraction, ViewportInteractionPhase, ViewportStateMap,
468};
469pub use platform::{
470    CancelAllNotificationsCapability, CancelNotificationCapability, CancelNotificationRequest,
471    DeepLink, DeepLinkConfig, DeepLinkReceived, DeepLinkSource, GetNotificationSettingsCapability,
472    NotificationActionButton, NotificationError, NotificationId, NotificationPermission,
473    NotificationPermissionRequest, NotificationReceipt, NotificationRequest, NotificationResponse,
474    NotificationResponseReceived, NotificationSchedule, NotificationSettings, NotificationSound,
475    PushPlatform, PushRegistration, PushRegistrationRequest, RegisterPushNotificationsCapability,
476    RequestNotificationPermissionCapability, ScheduleNotificationCapability,
477    SetBadgeCountCapability, SetBadgeCountRequest, ShowNotificationCapability,
478    UnregisterPushNotificationsCapability, CANCEL_ALL_NOTIFICATIONS, CANCEL_NOTIFICATION,
479    GET_NOTIFICATION_SETTINGS, REGISTER_PUSH_NOTIFICATIONS, REQUEST_NOTIFICATION_PERMISSION,
480    SCHEDULE_NOTIFICATION, SET_BADGE_COUNT, SHOW_NOTIFICATION, UNREGISTER_PUSH_NOTIFICATIONS,
481};
482pub use platform_barcode::{
483    BarcodeFormat, BarcodeImageDecodeRequest, BarcodePoint, BarcodeScanRequest, BarcodeScanResult,
484    BarcodeScanResults, BarcodeScannerError, CancelBarcodeScanCapability,
485    DecodeBarcodeImageCapability, ScanBarcodeCapability, CANCEL_BARCODE_SCAN, DECODE_BARCODE_IMAGE,
486    SCAN_BARCODE,
487};
488pub use platform_biometric::{
489    AuthenticateBiometricCapability, BiometricAuthenticateRequest, BiometricAuthenticateResult,
490    BiometricAvailability, BiometricError, BiometricKind, BiometricStrength,
491    CancelBiometricAuthenticationCapability, GetBiometricAvailabilityCapability,
492    AUTHENTICATE_BIOMETRIC, CANCEL_BIOMETRIC_AUTHENTICATION, GET_BIOMETRIC_AVAILABILITY,
493};
494pub use platform_bluetooth::{
495    BluetoothAdvertiseReceipt, BluetoothAdvertiseRequest, BluetoothAvailability,
496    BluetoothConnectRequest, BluetoothConnection, BluetoothDevice, BluetoothDisconnectRequest,
497    BluetoothError, BluetoothMode, BluetoothPermission, BluetoothPermissionRequest,
498    BluetoothReadRequest, BluetoothReadResult, BluetoothScanRequest, BluetoothScanResult,
499    BluetoothStopAdvertiseRequest, BluetoothWriteRequest, ConnectBluetoothDeviceCapability,
500    DisconnectBluetoothDeviceCapability, GetBluetoothAvailabilityCapability,
501    ReadBluetoothCharacteristicCapability, RequestBluetoothPermissionCapability,
502    ScanBluetoothDevicesCapability, StartBluetoothAdvertisingCapability,
503    StopBluetoothAdvertisingCapability, WriteBluetoothCharacteristicCapability,
504    CONNECT_BLUETOOTH_DEVICE, DISCONNECT_BLUETOOTH_DEVICE, GET_BLUETOOTH_AVAILABILITY,
505    READ_BLUETOOTH_CHARACTERISTIC, REQUEST_BLUETOOTH_PERMISSION, SCAN_BLUETOOTH_DEVICES,
506    START_BLUETOOTH_ADVERTISING, STOP_BLUETOOTH_ADVERTISING, WRITE_BLUETOOTH_CHARACTERISTIC,
507};
508pub use platform_camera::{
509    CameraAvailability, CameraCapture, CameraCaptureRequest, CameraDevice, CameraError,
510    CameraFacing, CameraFlashMode, CameraFlashlightRequest, CameraImageFormat, CameraPermission,
511    CameraPermissionRequest, CameraResolution, CancelCameraCaptureCapability,
512    CapturePhotoCapability, GetCameraAvailabilityCapability, RequestCameraPermissionCapability,
513    SetCameraFlashlightCapability, CANCEL_CAMERA_CAPTURE, CAPTURE_PHOTO, GET_CAMERA_AVAILABILITY,
514    REQUEST_CAMERA_PERMISSION, SET_CAMERA_FLASHLIGHT,
515};
516pub use platform_clipboard::{
517    ClearClipboardCapability, ClipboardContent, ClipboardError, ClipboardItem, ClipboardText,
518    ClipboardWriteTextRequest, ReadClipboardContentCapability, ReadClipboardTextCapability,
519    WriteClipboardContentCapability, WriteClipboardTextCapability, CLEAR_CLIPBOARD,
520    READ_CLIPBOARD_CONTENT, READ_CLIPBOARD_TEXT, WRITE_CLIPBOARD_CONTENT, WRITE_CLIPBOARD_TEXT,
521};
522pub use platform_geolocation::{
523    GeolocationError, GeolocationPermission, GeolocationPermissionRequest, GeolocationPosition,
524    GeolocationPositionRequest, GetCurrentPositionCapability, GetGeolocationPermissionCapability,
525    RequestGeolocationPermissionCapability, GET_CURRENT_POSITION, GET_GEOLOCATION_PERMISSION,
526    REQUEST_GEOLOCATION_PERMISSION,
527};
528pub use platform_haptics::{
529    HapticError, HapticImpactCapability, HapticImpactRequest, HapticImpactStyle,
530    HapticNotificationCapability, HapticNotificationKind, HapticNotificationRequest,
531    HapticPatternCapability, HapticPatternRequest, HapticPatternStep, HapticSelectionCapability,
532    HAPTIC_IMPACT, HAPTIC_NOTIFICATION, HAPTIC_PATTERN, HAPTIC_SELECTION,
533};
534pub use platform_microphone::{
535    AudioSampleFormat, CancelMicrophoneCaptureCapability, CaptureMicrophoneAudioCapability,
536    GetMicrophoneAvailabilityCapability, MicrophoneAvailability, MicrophoneCapture,
537    MicrophoneCaptureRequest, MicrophoneDevice, MicrophoneError, MicrophonePermission,
538    MicrophonePermissionRequest, RequestMicrophonePermissionCapability, CANCEL_MICROPHONE_CAPTURE,
539    CAPTURE_MICROPHONE_AUDIO, GET_MICROPHONE_AVAILABILITY, REQUEST_MICROPHONE_PERMISSION,
540};
541pub use platform_nfc::{
542    CancelNfcSessionCapability, EmulateNfcTagCapability, GetNfcAvailabilityCapability,
543    NfcAvailability, NfcEmulationRequest, NfcError, NfcRecord, NfcRecordTypeNameFormat,
544    NfcScanRequest, NfcSessionReceipt, NfcTag, NfcTagDiscovered, NfcTechnology, NfcWriteRequest,
545    ScanNfcTagCapability, WriteNfcTagCapability, CANCEL_NFC_SESSION, EMULATE_NFC_TAG,
546    GET_NFC_AVAILABILITY, SCAN_NFC_TAG, WRITE_NFC_TAG,
547};
548pub use platform_passkey::{
549    AuthenticatePasskeyCapability, CancelPasskeyOperationCapability,
550    GetPasskeyAvailabilityCapability, PasskeyAlgorithm, PasskeyAttestationConveyance,
551    PasskeyAuthenticationRequest, PasskeyAuthenticationResult, PasskeyAuthenticatorAttachment,
552    PasskeyAuthenticatorSelection, PasskeyAvailability, PasskeyCredentialDescriptor, PasskeyError,
553    PasskeyMediation, PasskeyRegistrationRequest, PasskeyRegistrationResult, PasskeyRelyingParty,
554    PasskeyResidentKeyRequirement, PasskeyTransport, PasskeyUser, PasskeyUserVerification,
555    RegisterPasskeyCapability, AUTHENTICATE_PASSKEY, CANCEL_PASSKEY_OPERATION,
556    GET_PASSKEY_AVAILABILITY, REGISTER_PASSKEY,
557};
558pub use platform_volume::{
559    AdjustVolumeLevelCapability, GetVolumeLevelCapability, SetVolumeLevelCapability,
560    VolumeAdjustDirection, VolumeAdjustRequest, VolumeError, VolumeLevel, VolumeSetRequest,
561    VolumeStream, ADJUST_VOLUME_LEVEL, GET_VOLUME_LEVEL, SET_VOLUME_LEVEL,
562};
563pub use platform_wifi::{
564    ConnectWifiNetworkCapability, DisconnectWifiNetworkCapability, GetWifiAvailabilityCapability,
565    RequestWifiPermissionCapability, ScanWifiNetworksCapability, WifiAvailability,
566    WifiConnectRequest, WifiConnection, WifiDisconnectRequest, WifiError, WifiNetwork,
567    WifiPermission, WifiPermissionRequest, WifiScanRequest, WifiScanResult, WifiSecurity,
568    CONNECT_WIFI_NETWORK, DISCONNECT_WIFI_NETWORK, GET_WIFI_AVAILABILITY, REQUEST_WIFI_PERMISSION,
569    SCAN_WIFI_NETWORKS,
570};
571pub use registry::{
572    ActionRegistry, Handler, JobResource, PortalLayer, ResourceKey, ResourcePolicy,
573    ResourceRegistry, RuntimeResourceDeclaration, RuntimeResourceKind, ServiceResource,
574    TimerResource, VideoRegistration,
575};
576pub use scoped_action_handlers::ScopedActionResolution;
577pub use time::{Clock, CurrentTime};
578pub use ui::{
579    provider, ActionScope, BadgeTone, Button, ButtonHierarchy, ButtonMotion, CardPattern, Column,
580    ComponentSize, ComponentState, CustomWidget, IosAudioSessionCategory,
581    IosAudioSessionCategoryOption, IosAudioSessionMode, IosVideoAudioOptions, Pressable,
582    PressableRole, PressableStyle, Provider, Responsive, ResponsiveCase, ResponsiveQuery, Row,
583    Text, Video, VideoAudioActivation, VideoAudioOptions, VideoAudioPolicy, VideoSource, Widget,
584    WidgetIdExt, WidgetKind,
585};
586pub use view::{ComputedView, FissionViewField, Selector, ValueView, View};
587
588/// Coerces a reducer function item or non-capturing closure to the handler
589/// function-pointer type Rust can infer from the surrounding `ctx.bind(...)`
590/// call.
591///
592/// ```rust,ignore
593/// use fission::prelude::*;
594///
595/// let on_press = with_reducer!(ctx, Increment, on_increment);
596/// ```
597#[macro_export]
598macro_rules! reduce_with {
599    ($handler:expr $(,)?) => {
600        $handler as $crate::Handler<_, _>
601    };
602}
603
604/// Short alias for [`reduce_with!`].
605#[macro_export]
606macro_rules! reduce {
607    ($handler:expr $(,)?) => {
608        $crate::reduce_with!($handler)
609    };
610}
611
612/// Builds a `Vec<Widget>` from widget expressions without repeated `.into()` calls.
613///
614/// Dynamic children may still be produced with normal iterators and
615/// `collect::<Vec<Widget>>()`. In both forms, children without explicit IDs
616/// receive deterministic position-based identity. Set an explicit ID on a
617/// logical collection item when retained state should follow the item through
618/// reordering.
619#[macro_export]
620macro_rules! widgets {
621    ($($widget:expr),* $(,)?) => {
622        {
623            let mut widgets = ::std::vec::Vec::<$crate::Widget>::new();
624            let collection_scope = $crate::build::collection_scope(file!(), line!(), column!());
625            $(
626                let child_index = widgets.len() as u32;
627                let child_scope = $crate::WidgetId::derived(
628                    collection_scope.as_u128(),
629                    &[child_index],
630                );
631                widgets.push($crate::build::with_implicit_widget_id(child_scope, || {
632                    $crate::Widget::from($widget)
633                }));
634            )*
635            widgets
636        }
637    };
638}
639
640/// Creates a [`Video`](crate::ui::Video) from an app asset literal and fails
641/// compilation when the asset does not exist under `CARGO_MANIFEST_DIR`.
642///
643/// Use [`Video::asset`](crate::ui::Video::asset) when the path is computed at
644/// runtime and cannot be checked by the compiler.
645#[macro_export]
646macro_rules! video_asset {
647    ($path:literal $(,)?) => {{
648        const _: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path));
649        $crate::ui::Video::asset($path)
650    }};
651}
652
653/// Creates a [`Video`](crate::ui::Video) from a compile-time local file path
654/// and fails compilation when that path cannot be resolved by `include_bytes!`.
655///
656/// Relative paths are resolved the same way as `include_bytes!`: relative to the
657/// source file that invokes this macro.
658#[macro_export]
659macro_rules! video_file {
660    ($path:expr $(,)?) => {{
661        const _: &[u8] = include_bytes!($path);
662        $crate::ui::Video::file($path)
663    }};
664}
665
666/// Creates a [`Video`](crate::ui::Video) from a network URL literal.
667///
668/// Network playback support is shell-specific; use this helper for literal URLs
669/// and [`Video::network`](crate::ui::Video::network) when the URL is computed at
670/// runtime.
671#[macro_export]
672macro_rules! video_network {
673    ($url:literal $(,)?) => {{
674        $crate::ui::Video::network($url)
675    }};
676}
677
678/// Binds an action to a reducer in one expression.
679///
680/// ```rust,ignore
681/// use fission::prelude::*;
682///
683/// let on_press = with_reducer!(ctx, Increment, on_increment);
684/// ```
685#[macro_export]
686macro_rules! with_reducer {
687    ($ctx:expr, $action:expr, $handler:expr $(,)?) => {
688        $ctx.bind($action, $crate::reduce_with!($handler))
689    };
690}
691
692/// A frame-tick action that advances the runtime clock by a delta.
693///
694/// The platform shell dispatches `Tick` once per frame so that animations,
695/// timers, and other time-dependent logic can progress.
696///
697/// # Example
698///
699/// ```rust,ignore
700/// // Advance the runtime by 16 ms (~60 fps)
701/// runtime.tick(16)?;
702/// ```
703#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
704pub struct Tick {
705    /// Delta time in milliseconds since the last tick.
706    pub dt: CurrentTime,
707}
708
709impl Action for Tick {
710    fn static_id() -> ActionId {
711        *TICK_ACTION_ID
712    }
713}
714
715lazy_static! {
716    pub static ref TICK_ACTION_ID: ActionId = ActionId::from_name("fission_core::Tick");
717}
718
719/// An action that sets the runtime clock to an absolute timestamp.
720///
721/// Unlike [`Tick`] which advances by a delta, `AdvanceTo` jumps directly to
722/// the given time. Useful for testing and deterministic replay.
723///
724/// # Example
725///
726/// ```rust,ignore
727/// let envelope: ActionEnvelope = AdvanceTo { time: 5000 }.into();
728/// runtime.dispatch(envelope, WidgetId::from_u128(0))?;
729/// ```
730#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
731pub struct AdvanceTo {
732    /// The absolute time (in milliseconds) to set the clock to.
733    pub time: CurrentTime,
734}
735
736impl Action for AdvanceTo {
737    fn static_id() -> ActionId {
738        *ADVANCE_TO_ACTION_ID
739    }
740}
741
742lazy_static! {
743    pub static ref ADVANCE_TO_ACTION_ID: ActionId = ActionId::from_name("fission_core::AdvanceTo");
744}
745
746/// A type-erased reducer function stored in the [`Runtime`].
747///
748/// `BoxedReducer` is the internal representation used by the runtime to invoke
749/// reducers without knowing the concrete `GlobalState` or `Action` types.
750pub(crate) type BoxedReducer = Box<
751    dyn FnMut(
752            &mut HashMap<TypeId, Box<dyn GlobalState>>,
753            &ActionEnvelope,
754            WidgetId,
755            &mut Vec<EffectEnvelope>,
756            &ActionInput,
757            &Arc<EffectCallbackRegistry>,
758        ) -> Result<()>
759        + Send
760        + Sync,
761>;
762
763/// One-shot reducers bound to async effect completion actions.
764///
765/// These reducers are separate from the per-frame widget registry so a
766/// completion remains deliverable after the frame that issued the effect.
767pub(crate) struct EffectCallbackRegistry {
768    reducers: Mutex<HashMap<ActionId, Vec<BoxedReducer>>>,
769}
770
771impl EffectCallbackRegistry {
772    pub(crate) fn new() -> Self {
773        Self {
774            reducers: Mutex::new(HashMap::new()),
775        }
776    }
777
778    pub(crate) fn register(&self, action_id: ActionId, reducer: BoxedReducer) {
779        self.reducers
780            .lock()
781            .unwrap_or_else(std::sync::PoisonError::into_inner)
782            .entry(action_id)
783            .or_default()
784            .push(reducer);
785    }
786
787    pub(crate) fn take(&self, action_id: ActionId) -> Vec<BoxedReducer> {
788        self.reducers
789            .lock()
790            .unwrap_or_else(std::sync::PoisonError::into_inner)
791            .remove(&action_id)
792            .unwrap_or_default()
793    }
794
795    pub(crate) fn clear(&self) -> usize {
796        let mut reducers = self
797            .reducers
798            .lock()
799            .unwrap_or_else(std::sync::PoisonError::into_inner);
800        let callback_count = reducers.values().map(Vec::len).sum();
801        reducers.clear();
802        callback_count
803    }
804}