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        widget.lower(cx)
105    }
106
107    pub fn lower_widget_to_ir(widget: &Widget) -> fission_ir::CoreIR {
108        let env = crate::Env::default();
109        let runtime_state = crate::RuntimeState::default();
110        let mut cx = InternalLoweringCx::new(&env, &runtime_state, None, None);
111        widget.lower(&mut cx);
112        cx.ir
113    }
114
115    pub fn widget_kind_name(widget: &Widget) -> &'static str {
116        widget.kind_name()
117    }
118
119    pub fn widget_as_row(widget: &Widget) -> Option<&crate::ui::Row> {
120        widget.as_row()
121    }
122
123    pub fn widget_as_column(widget: &Widget) -> Option<&crate::ui::Column> {
124        widget.as_column()
125    }
126
127    pub fn widget_as_container(widget: &Widget) -> Option<&crate::ui::Container> {
128        widget.as_container()
129    }
130
131    pub fn widget_as_scroll(widget: &Widget) -> Option<&crate::ui::Scroll> {
132        widget.as_scroll()
133    }
134
135    pub fn widget_as_rich_text(widget: &Widget) -> Option<&crate::ui::RichText> {
136        widget.as_rich_text()
137    }
138
139    pub fn widget_as_text(widget: &Widget) -> Option<&crate::ui::Text> {
140        widget.as_text()
141    }
142
143    pub fn widget_as_text_input(widget: &Widget) -> Option<&crate::ui::TextInput> {
144        widget.as_text_input()
145    }
146
147    pub fn widget_as_button(widget: &Widget) -> Option<&crate::ui::Button> {
148        widget.as_button()
149    }
150
151    pub fn widget_as_gesture_detector(widget: &Widget) -> Option<&crate::ui::GestureDetector> {
152        widget.as_gesture_detector()
153    }
154
155    pub fn widget_as_zstack(widget: &Widget) -> Option<&crate::ui::ZStack> {
156        widget.as_zstack()
157    }
158
159    pub use crate::ui::custom_render::{
160        downcast_render_object, CustomEventResult, CustomHitResult, CustomRender,
161        CustomRenderObject,
162    };
163    pub use crate::ui::node::{CustomWidget, InternalRenderNode};
164    pub use crate::ui::traits::{InternalLower, InternalLowerer};
165}
166
167pub mod public {
168    pub mod action {
169        pub use crate::action::*;
170    }
171    pub mod env {
172        pub use crate::env::*;
173    }
174    pub mod event {
175        pub use crate::event::*;
176    }
177    pub mod hit_test {
178        pub use crate::hit_test::*;
179    }
180    pub mod registry {
181        pub use crate::registry::*;
182    }
183    pub mod scoped_action_handlers {
184        pub use crate::scoped_action_handlers::*;
185    }
186    pub mod ui {
187        pub use crate::ui::widgets::*;
188        pub use crate::ui::Widget;
189
190        pub mod widgets {
191            pub use crate::ui::widgets::*;
192        }
193    }
194    pub mod view {
195        pub use crate::view::*;
196    }
197
198    pub use crate::action::{Action, ActionEnvelope, ActionId, ActionScopeId, GlobalState};
199    pub use crate::async_runtime::{
200        BoxFuture, JobCtx, JobRef, JobSpec, ResourceExecutionContext, ServiceBindings, ServiceCtx,
201        ServiceRunner, ServiceSlot, ServiceSpec, ServiceType,
202    };
203    pub use crate::capability::{
204        CapabilityCtx, CapabilityInvocationPayload, CapabilityType, OpenUrlCapability,
205        OpenUrlRequest, OperationCapability, PickOpenFilesCapability, PickOpenFilesError,
206        PickOpenFilesRequest, PickOpenFilesResult, PickedFile, OPEN_URL, PICK_OPEN_FILES,
207    };
208    pub use crate::context::{
209        BarcodeScannerEffects, BiometricEffects, BluetoothEffects, CameraEffects, ClipboardEffects,
210        Effects, GeolocationEffects, HapticEffects, MicrophoneEffects, NfcEffects,
211        NotificationEffects, PasskeyEffects, ReducerContext, VolumeEffects, WifiEffects,
212    }; // New
213    pub use crate::data_stream::{
214        collect_data_stream, empty_data_stream, single_chunk_data_stream, BoxFissionDataStream,
215        DataStreamId, DataStreamRegistry, FissionDataStream, FissionDataStreamError,
216        FissionDataStreamErrorKind,
217    };
218    pub use crate::effect::{
219        ActionInput, Effect, EffectEnvelope, RuntimeEffect, ScrollAlignment, ScrollAxis,
220        ScrollBehavior, ScrollIntoViewRequest,
221    };
222    pub use crate::env::{
223        Clipboard, DragSessionPayload, DragSessionState, Env, ImeHandler, InteractionStateMap,
224        RuntimeState, ScrollStateMap, WindowEnv, WindowTitle,
225    };
226    pub use crate::runtime::Runtime;
227    pub use crate::state::{LocalStateKey, LocalStateStore, StateField};
228    pub use bytes::Bytes;
229
230    pub use crate::build::{BuildCtxHandle, ViewHandle};
231    pub use crate::event::{
232        ExternalDragEvent, InputEvent, KeyCode, KeyEvent, LifecycleEvent, PointerButton,
233        PointerEvent,
234    };
235    pub use crate::motion::*;
236    pub use crate::platform::{
237        CancelAllNotificationsCapability, CancelNotificationCapability, CancelNotificationRequest,
238        DeepLink, DeepLinkConfig, DeepLinkReceived, DeepLinkSource,
239        GetNotificationSettingsCapability, NotificationActionButton, NotificationError,
240        NotificationId, NotificationPermission, NotificationPermissionRequest, NotificationReceipt,
241        NotificationRequest, NotificationResponse, NotificationResponseReceived,
242        NotificationSchedule, NotificationSettings, NotificationSound, PushPlatform,
243        PushRegistration, PushRegistrationRequest, RegisterPushNotificationsCapability,
244        RequestNotificationPermissionCapability, ScheduleNotificationCapability,
245        SetBadgeCountCapability, SetBadgeCountRequest, ShowNotificationCapability,
246        UnregisterPushNotificationsCapability, CANCEL_ALL_NOTIFICATIONS, CANCEL_NOTIFICATION,
247        GET_NOTIFICATION_SETTINGS, REGISTER_PUSH_NOTIFICATIONS, REQUEST_NOTIFICATION_PERMISSION,
248        SCHEDULE_NOTIFICATION, SET_BADGE_COUNT, SHOW_NOTIFICATION, UNREGISTER_PUSH_NOTIFICATIONS,
249    };
250    pub use crate::platform_barcode::{
251        BarcodeFormat, BarcodeImageDecodeRequest, BarcodePoint, BarcodeScanRequest,
252        BarcodeScanResult, BarcodeScanResults, BarcodeScannerError, CancelBarcodeScanCapability,
253        DecodeBarcodeImageCapability, ScanBarcodeCapability, CANCEL_BARCODE_SCAN,
254        DECODE_BARCODE_IMAGE, SCAN_BARCODE,
255    };
256    pub use crate::platform_biometric::{
257        AuthenticateBiometricCapability, BiometricAuthenticateRequest, BiometricAuthenticateResult,
258        BiometricAvailability, BiometricError, BiometricKind, BiometricStrength,
259        CancelBiometricAuthenticationCapability, GetBiometricAvailabilityCapability,
260        AUTHENTICATE_BIOMETRIC, CANCEL_BIOMETRIC_AUTHENTICATION, GET_BIOMETRIC_AVAILABILITY,
261    };
262    pub use crate::platform_bluetooth::{
263        BluetoothAdvertiseReceipt, BluetoothAdvertiseRequest, BluetoothAvailability,
264        BluetoothConnectRequest, BluetoothConnection, BluetoothDevice, BluetoothDisconnectRequest,
265        BluetoothError, BluetoothMode, BluetoothPermission, BluetoothPermissionRequest,
266        BluetoothReadRequest, BluetoothReadResult, BluetoothScanRequest, BluetoothScanResult,
267        BluetoothStopAdvertiseRequest, BluetoothWriteRequest, ConnectBluetoothDeviceCapability,
268        DisconnectBluetoothDeviceCapability, GetBluetoothAvailabilityCapability,
269        ReadBluetoothCharacteristicCapability, RequestBluetoothPermissionCapability,
270        ScanBluetoothDevicesCapability, StartBluetoothAdvertisingCapability,
271        StopBluetoothAdvertisingCapability, WriteBluetoothCharacteristicCapability,
272        CONNECT_BLUETOOTH_DEVICE, DISCONNECT_BLUETOOTH_DEVICE, GET_BLUETOOTH_AVAILABILITY,
273        READ_BLUETOOTH_CHARACTERISTIC, REQUEST_BLUETOOTH_PERMISSION, SCAN_BLUETOOTH_DEVICES,
274        START_BLUETOOTH_ADVERTISING, STOP_BLUETOOTH_ADVERTISING, WRITE_BLUETOOTH_CHARACTERISTIC,
275    };
276    pub use crate::platform_camera::{
277        CameraAvailability, CameraCapture, CameraCaptureRequest, CameraDevice, CameraError,
278        CameraFacing, CameraFlashMode, CameraFlashlightRequest, CameraImageFormat,
279        CameraPermission, CameraPermissionRequest, CameraResolution, CancelCameraCaptureCapability,
280        CapturePhotoCapability, GetCameraAvailabilityCapability, RequestCameraPermissionCapability,
281        SetCameraFlashlightCapability, CANCEL_CAMERA_CAPTURE, CAPTURE_PHOTO,
282        GET_CAMERA_AVAILABILITY, REQUEST_CAMERA_PERMISSION, SET_CAMERA_FLASHLIGHT,
283    };
284    pub use crate::platform_clipboard::{
285        ClearClipboardCapability, ClipboardContent, ClipboardError, ClipboardItem, ClipboardText,
286        ClipboardWriteTextRequest, ReadClipboardContentCapability, ReadClipboardTextCapability,
287        WriteClipboardContentCapability, WriteClipboardTextCapability, CLEAR_CLIPBOARD,
288        READ_CLIPBOARD_CONTENT, READ_CLIPBOARD_TEXT, WRITE_CLIPBOARD_CONTENT, WRITE_CLIPBOARD_TEXT,
289    };
290    pub use crate::platform_geolocation::{
291        GeolocationError, GeolocationPermission, GeolocationPermissionRequest, GeolocationPosition,
292        GeolocationPositionRequest, GetCurrentPositionCapability,
293        GetGeolocationPermissionCapability, RequestGeolocationPermissionCapability,
294        GET_CURRENT_POSITION, GET_GEOLOCATION_PERMISSION, REQUEST_GEOLOCATION_PERMISSION,
295    };
296    pub use crate::platform_haptics::{
297        HapticError, HapticImpactCapability, HapticImpactRequest, HapticImpactStyle,
298        HapticNotificationCapability, HapticNotificationKind, HapticNotificationRequest,
299        HapticPatternCapability, HapticPatternRequest, HapticPatternStep,
300        HapticSelectionCapability, HAPTIC_IMPACT, HAPTIC_NOTIFICATION, HAPTIC_PATTERN,
301        HAPTIC_SELECTION,
302    };
303    pub use crate::platform_microphone::{
304        AudioSampleFormat, CancelMicrophoneCaptureCapability, CaptureMicrophoneAudioCapability,
305        GetMicrophoneAvailabilityCapability, MicrophoneAvailability, MicrophoneCapture,
306        MicrophoneCaptureRequest, MicrophoneDevice, MicrophoneError, MicrophonePermission,
307        MicrophonePermissionRequest, RequestMicrophonePermissionCapability,
308        CANCEL_MICROPHONE_CAPTURE, CAPTURE_MICROPHONE_AUDIO, GET_MICROPHONE_AVAILABILITY,
309        REQUEST_MICROPHONE_PERMISSION,
310    };
311    pub use crate::platform_nfc::{
312        CancelNfcSessionCapability, EmulateNfcTagCapability, GetNfcAvailabilityCapability,
313        NfcAvailability, NfcEmulationRequest, NfcError, NfcRecord, NfcRecordTypeNameFormat,
314        NfcScanRequest, NfcSessionReceipt, NfcTag, NfcTagDiscovered, NfcTechnology,
315        NfcWriteRequest, ScanNfcTagCapability, WriteNfcTagCapability, CANCEL_NFC_SESSION,
316        EMULATE_NFC_TAG, GET_NFC_AVAILABILITY, SCAN_NFC_TAG, WRITE_NFC_TAG,
317    };
318    pub use crate::platform_passkey::{
319        AuthenticatePasskeyCapability, CancelPasskeyOperationCapability,
320        GetPasskeyAvailabilityCapability, PasskeyAlgorithm, PasskeyAttestationConveyance,
321        PasskeyAuthenticationRequest, PasskeyAuthenticationResult, PasskeyAuthenticatorAttachment,
322        PasskeyAuthenticatorSelection, PasskeyAvailability, PasskeyCredentialDescriptor,
323        PasskeyError, PasskeyMediation, PasskeyRegistrationRequest, PasskeyRegistrationResult,
324        PasskeyRelyingParty, PasskeyResidentKeyRequirement, PasskeyTransport, PasskeyUser,
325        PasskeyUserVerification, RegisterPasskeyCapability, AUTHENTICATE_PASSKEY,
326        CANCEL_PASSKEY_OPERATION, GET_PASSKEY_AVAILABILITY, REGISTER_PASSKEY,
327    };
328    pub use crate::platform_volume::{
329        AdjustVolumeLevelCapability, GetVolumeLevelCapability, SetVolumeLevelCapability,
330        VolumeAdjustDirection, VolumeAdjustRequest, VolumeError, VolumeLevel, VolumeSetRequest,
331        VolumeStream, ADJUST_VOLUME_LEVEL, GET_VOLUME_LEVEL, SET_VOLUME_LEVEL,
332    };
333    pub use crate::platform_wifi::{
334        ConnectWifiNetworkCapability, DisconnectWifiNetworkCapability,
335        GetWifiAvailabilityCapability, RequestWifiPermissionCapability, ScanWifiNetworksCapability,
336        WifiAvailability, WifiConnectRequest, WifiConnection, WifiDisconnectRequest, WifiError,
337        WifiNetwork, WifiPermission, WifiPermissionRequest, WifiScanRequest, WifiScanResult,
338        WifiSecurity, CONNECT_WIFI_NETWORK, DISCONNECT_WIFI_NETWORK, GET_WIFI_AVAILABILITY,
339        REQUEST_WIFI_PERMISSION, SCAN_WIFI_NETWORKS,
340    };
341    pub use crate::registry::{
342        ActionRegistry, Handler, JobResource, PortalLayer, ResourceKey, ResourcePolicy,
343        ResourceRegistry, RuntimeResourceDeclaration, RuntimeResourceKind, ServiceResource,
344        TimerResource, VideoRegistration,
345    };
346    pub use crate::time::{Clock, CurrentTime};
347    pub use crate::ui::{
348        provider, ActionScope, BadgeTone, Button, ButtonHierarchy, ButtonMotion, CardPattern,
349        Column, ComponentSize, ComponentState, CustomWidget, IosAudioSessionCategory,
350        IosAudioSessionCategoryOption, IosAudioSessionMode, IosVideoAudioOptions, Provider, Row,
351        Text, Video, VideoAudioActivation, VideoAudioOptions, VideoAudioPolicy, VideoSource,
352        Widget, WidgetIdExt,
353    };
354    pub use crate::view::{ComputedView, FissionViewField, Selector, ValueView, View};
355    pub use crate::{
356        reduce, reduce_with, video_asset, video_file, video_network, widgets, with_reducer,
357    };
358    pub use fission_ir::op;
359    pub use fission_ir::{EmbedKind, FocusPolicy, Op, Role, Semantics, WidgetId};
360    pub use fission_layout::{
361        BoxConstraints, FlexDirection, LayoutEngine, LayoutOp, LayoutPoint, LayoutRect, LayoutSize,
362        LayoutSnapshot, LayoutUnit, TextMeasurer,
363    };
364}
365
366#[cfg(test)]
367mod tests;
368
369pub use action::{Action, ActionEnvelope, ActionId, ActionScopeId, GlobalState, ShellRouteChanged};
370pub use async_runtime::{
371    BoxFuture, JobCtx, JobRef, JobSpec, ResourceExecutionContext, ServiceBindings, ServiceCtx,
372    ServiceRunner, ServiceSlot, ServiceSpec, ServiceType,
373};
374pub use bytes::Bytes;
375pub use capability::{
376    CapabilityCtx, CapabilityInvocationPayload, CapabilityType, OpenUrlCapability, OpenUrlRequest,
377    OperationCapability, PickOpenFilesCapability, PickOpenFilesError, PickOpenFilesRequest,
378    PickOpenFilesResult, PickedFile, OPEN_URL, PICK_OPEN_FILES,
379};
380pub use context::{
381    BarcodeScannerEffects, BiometricEffects, BluetoothEffects, CameraEffects, ClipboardEffects,
382    Effects, GeolocationEffects, HapticEffects, MicrophoneEffects, NfcEffects, NotificationEffects,
383    PasskeyEffects, ReducerContext, VolumeEffects, WifiEffects,
384}; // New
385pub use data_stream::{
386    collect_data_stream, empty_data_stream, single_chunk_data_stream, BoxFissionDataStream,
387    DataStreamId, DataStreamRegistry, FissionDataStream, FissionDataStreamError,
388    FissionDataStreamErrorKind,
389};
390pub use effect::{
391    ActionInput, Effect, EffectEnvelope, RuntimeEffect, ScrollAlignment, ScrollAxis,
392    ScrollBehavior, ScrollIntoViewRequest,
393};
394pub use env::{
395    Clipboard, DragSessionPayload, DragSessionState, Env, ImeHandler, InteractionStateMap,
396    RouteLocation, RuntimeState, ScrollStateMap, WindowEnv, WindowTitle,
397};
398pub use motion::*;
399pub use runtime::Runtime;
400pub use state::{LocalStateKey, LocalStateStore, StateField};
401
402pub use build::{BuildCtxHandle, ViewHandle};
403pub use event::{
404    ExternalDragEvent, InputEvent, KeyCode, KeyEvent, LifecycleEvent, PointerButton, PointerEvent,
405};
406pub use fission_ir::op;
407pub use fission_ir::{EmbedKind, FocusPolicy, Op, Role, Semantics, WidgetId};
408pub use fission_layout::{
409    BoxConstraints, FlexDirection, LayoutEngine, LayoutOp, LayoutPoint, LayoutRect, LayoutSize,
410    LayoutSnapshot, LayoutUnit, TextMeasurer,
411};
412pub use platform::{
413    CancelAllNotificationsCapability, CancelNotificationCapability, CancelNotificationRequest,
414    DeepLink, DeepLinkConfig, DeepLinkReceived, DeepLinkSource, GetNotificationSettingsCapability,
415    NotificationActionButton, NotificationError, NotificationId, NotificationPermission,
416    NotificationPermissionRequest, NotificationReceipt, NotificationRequest, NotificationResponse,
417    NotificationResponseReceived, NotificationSchedule, NotificationSettings, NotificationSound,
418    PushPlatform, PushRegistration, PushRegistrationRequest, RegisterPushNotificationsCapability,
419    RequestNotificationPermissionCapability, ScheduleNotificationCapability,
420    SetBadgeCountCapability, SetBadgeCountRequest, ShowNotificationCapability,
421    UnregisterPushNotificationsCapability, CANCEL_ALL_NOTIFICATIONS, CANCEL_NOTIFICATION,
422    GET_NOTIFICATION_SETTINGS, REGISTER_PUSH_NOTIFICATIONS, REQUEST_NOTIFICATION_PERMISSION,
423    SCHEDULE_NOTIFICATION, SET_BADGE_COUNT, SHOW_NOTIFICATION, UNREGISTER_PUSH_NOTIFICATIONS,
424};
425pub use platform_barcode::{
426    BarcodeFormat, BarcodeImageDecodeRequest, BarcodePoint, BarcodeScanRequest, BarcodeScanResult,
427    BarcodeScanResults, BarcodeScannerError, CancelBarcodeScanCapability,
428    DecodeBarcodeImageCapability, ScanBarcodeCapability, CANCEL_BARCODE_SCAN, DECODE_BARCODE_IMAGE,
429    SCAN_BARCODE,
430};
431pub use platform_biometric::{
432    AuthenticateBiometricCapability, BiometricAuthenticateRequest, BiometricAuthenticateResult,
433    BiometricAvailability, BiometricError, BiometricKind, BiometricStrength,
434    CancelBiometricAuthenticationCapability, GetBiometricAvailabilityCapability,
435    AUTHENTICATE_BIOMETRIC, CANCEL_BIOMETRIC_AUTHENTICATION, GET_BIOMETRIC_AVAILABILITY,
436};
437pub use platform_bluetooth::{
438    BluetoothAdvertiseReceipt, BluetoothAdvertiseRequest, BluetoothAvailability,
439    BluetoothConnectRequest, BluetoothConnection, BluetoothDevice, BluetoothDisconnectRequest,
440    BluetoothError, BluetoothMode, BluetoothPermission, BluetoothPermissionRequest,
441    BluetoothReadRequest, BluetoothReadResult, BluetoothScanRequest, BluetoothScanResult,
442    BluetoothStopAdvertiseRequest, BluetoothWriteRequest, ConnectBluetoothDeviceCapability,
443    DisconnectBluetoothDeviceCapability, GetBluetoothAvailabilityCapability,
444    ReadBluetoothCharacteristicCapability, RequestBluetoothPermissionCapability,
445    ScanBluetoothDevicesCapability, StartBluetoothAdvertisingCapability,
446    StopBluetoothAdvertisingCapability, WriteBluetoothCharacteristicCapability,
447    CONNECT_BLUETOOTH_DEVICE, DISCONNECT_BLUETOOTH_DEVICE, GET_BLUETOOTH_AVAILABILITY,
448    READ_BLUETOOTH_CHARACTERISTIC, REQUEST_BLUETOOTH_PERMISSION, SCAN_BLUETOOTH_DEVICES,
449    START_BLUETOOTH_ADVERTISING, STOP_BLUETOOTH_ADVERTISING, WRITE_BLUETOOTH_CHARACTERISTIC,
450};
451pub use platform_camera::{
452    CameraAvailability, CameraCapture, CameraCaptureRequest, CameraDevice, CameraError,
453    CameraFacing, CameraFlashMode, CameraFlashlightRequest, CameraImageFormat, CameraPermission,
454    CameraPermissionRequest, CameraResolution, CancelCameraCaptureCapability,
455    CapturePhotoCapability, GetCameraAvailabilityCapability, RequestCameraPermissionCapability,
456    SetCameraFlashlightCapability, CANCEL_CAMERA_CAPTURE, CAPTURE_PHOTO, GET_CAMERA_AVAILABILITY,
457    REQUEST_CAMERA_PERMISSION, SET_CAMERA_FLASHLIGHT,
458};
459pub use platform_clipboard::{
460    ClearClipboardCapability, ClipboardContent, ClipboardError, ClipboardItem, ClipboardText,
461    ClipboardWriteTextRequest, ReadClipboardContentCapability, ReadClipboardTextCapability,
462    WriteClipboardContentCapability, WriteClipboardTextCapability, CLEAR_CLIPBOARD,
463    READ_CLIPBOARD_CONTENT, READ_CLIPBOARD_TEXT, WRITE_CLIPBOARD_CONTENT, WRITE_CLIPBOARD_TEXT,
464};
465pub use platform_geolocation::{
466    GeolocationError, GeolocationPermission, GeolocationPermissionRequest, GeolocationPosition,
467    GeolocationPositionRequest, GetCurrentPositionCapability, GetGeolocationPermissionCapability,
468    RequestGeolocationPermissionCapability, GET_CURRENT_POSITION, GET_GEOLOCATION_PERMISSION,
469    REQUEST_GEOLOCATION_PERMISSION,
470};
471pub use platform_haptics::{
472    HapticError, HapticImpactCapability, HapticImpactRequest, HapticImpactStyle,
473    HapticNotificationCapability, HapticNotificationKind, HapticNotificationRequest,
474    HapticPatternCapability, HapticPatternRequest, HapticPatternStep, HapticSelectionCapability,
475    HAPTIC_IMPACT, HAPTIC_NOTIFICATION, HAPTIC_PATTERN, HAPTIC_SELECTION,
476};
477pub use platform_microphone::{
478    AudioSampleFormat, CancelMicrophoneCaptureCapability, CaptureMicrophoneAudioCapability,
479    GetMicrophoneAvailabilityCapability, MicrophoneAvailability, MicrophoneCapture,
480    MicrophoneCaptureRequest, MicrophoneDevice, MicrophoneError, MicrophonePermission,
481    MicrophonePermissionRequest, RequestMicrophonePermissionCapability, CANCEL_MICROPHONE_CAPTURE,
482    CAPTURE_MICROPHONE_AUDIO, GET_MICROPHONE_AVAILABILITY, REQUEST_MICROPHONE_PERMISSION,
483};
484pub use platform_nfc::{
485    CancelNfcSessionCapability, EmulateNfcTagCapability, GetNfcAvailabilityCapability,
486    NfcAvailability, NfcEmulationRequest, NfcError, NfcRecord, NfcRecordTypeNameFormat,
487    NfcScanRequest, NfcSessionReceipt, NfcTag, NfcTagDiscovered, NfcTechnology, NfcWriteRequest,
488    ScanNfcTagCapability, WriteNfcTagCapability, CANCEL_NFC_SESSION, EMULATE_NFC_TAG,
489    GET_NFC_AVAILABILITY, SCAN_NFC_TAG, WRITE_NFC_TAG,
490};
491pub use platform_passkey::{
492    AuthenticatePasskeyCapability, CancelPasskeyOperationCapability,
493    GetPasskeyAvailabilityCapability, PasskeyAlgorithm, PasskeyAttestationConveyance,
494    PasskeyAuthenticationRequest, PasskeyAuthenticationResult, PasskeyAuthenticatorAttachment,
495    PasskeyAuthenticatorSelection, PasskeyAvailability, PasskeyCredentialDescriptor, PasskeyError,
496    PasskeyMediation, PasskeyRegistrationRequest, PasskeyRegistrationResult, PasskeyRelyingParty,
497    PasskeyResidentKeyRequirement, PasskeyTransport, PasskeyUser, PasskeyUserVerification,
498    RegisterPasskeyCapability, AUTHENTICATE_PASSKEY, CANCEL_PASSKEY_OPERATION,
499    GET_PASSKEY_AVAILABILITY, REGISTER_PASSKEY,
500};
501pub use platform_volume::{
502    AdjustVolumeLevelCapability, GetVolumeLevelCapability, SetVolumeLevelCapability,
503    VolumeAdjustDirection, VolumeAdjustRequest, VolumeError, VolumeLevel, VolumeSetRequest,
504    VolumeStream, ADJUST_VOLUME_LEVEL, GET_VOLUME_LEVEL, SET_VOLUME_LEVEL,
505};
506pub use platform_wifi::{
507    ConnectWifiNetworkCapability, DisconnectWifiNetworkCapability, GetWifiAvailabilityCapability,
508    RequestWifiPermissionCapability, ScanWifiNetworksCapability, WifiAvailability,
509    WifiConnectRequest, WifiConnection, WifiDisconnectRequest, WifiError, WifiNetwork,
510    WifiPermission, WifiPermissionRequest, WifiScanRequest, WifiScanResult, WifiSecurity,
511    CONNECT_WIFI_NETWORK, DISCONNECT_WIFI_NETWORK, GET_WIFI_AVAILABILITY, REQUEST_WIFI_PERMISSION,
512    SCAN_WIFI_NETWORKS,
513};
514pub use registry::{
515    ActionRegistry, Handler, JobResource, PortalLayer, ResourceKey, ResourcePolicy,
516    ResourceRegistry, RuntimeResourceDeclaration, RuntimeResourceKind, ServiceResource,
517    TimerResource, VideoRegistration,
518};
519pub use time::{Clock, CurrentTime};
520pub use ui::{
521    provider, ActionScope, BadgeTone, Button, ButtonHierarchy, ButtonMotion, CardPattern, Column,
522    ComponentSize, ComponentState, CustomWidget, IosAudioSessionCategory,
523    IosAudioSessionCategoryOption, IosAudioSessionMode, IosVideoAudioOptions, Provider, Row, Text,
524    Video, VideoAudioActivation, VideoAudioOptions, VideoAudioPolicy, VideoSource, Widget,
525    WidgetIdExt,
526};
527pub use view::{ComputedView, FissionViewField, Selector, ValueView, View};
528
529/// Coerces a reducer function item or non-capturing closure to the handler
530/// function-pointer type Rust can infer from the surrounding `ctx.bind(...)`
531/// call.
532///
533/// ```rust,ignore
534/// use fission::prelude::*;
535///
536/// let on_press = with_reducer!(ctx, Increment, on_increment);
537/// ```
538#[macro_export]
539macro_rules! reduce_with {
540    ($handler:expr $(,)?) => {
541        $handler as $crate::Handler<_, _>
542    };
543}
544
545/// Short alias for [`reduce_with!`].
546#[macro_export]
547macro_rules! reduce {
548    ($handler:expr $(,)?) => {
549        $crate::reduce_with!($handler)
550    };
551}
552
553/// Builds a `Vec<Widget>` from widget expressions without repeated `.into()` calls.
554///
555/// Dynamic children may still be produced with normal iterators and
556/// `collect::<Vec<Widget>>()`; this macro is only syntax sugar for
557/// literal child lists.
558#[macro_export]
559macro_rules! widgets {
560    ($($widget:expr),* $(,)?) => {
561        {
562            let mut widgets = ::std::vec::Vec::<$crate::Widget>::new();
563            $(
564                widgets.push($crate::Widget::from($widget));
565            )*
566            widgets
567        }
568    };
569}
570
571/// Creates a [`Video`](crate::ui::Video) from an app asset literal and fails
572/// compilation when the asset does not exist under `CARGO_MANIFEST_DIR`.
573///
574/// Use [`Video::asset`](crate::ui::Video::asset) when the path is computed at
575/// runtime and cannot be checked by the compiler.
576#[macro_export]
577macro_rules! video_asset {
578    ($path:literal $(,)?) => {{
579        const _: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path));
580        $crate::ui::Video::asset($path)
581    }};
582}
583
584/// Creates a [`Video`](crate::ui::Video) from a compile-time local file path
585/// and fails compilation when that path cannot be resolved by `include_bytes!`.
586///
587/// Relative paths are resolved the same way as `include_bytes!`: relative to the
588/// source file that invokes this macro.
589#[macro_export]
590macro_rules! video_file {
591    ($path:expr $(,)?) => {{
592        const _: &[u8] = include_bytes!($path);
593        $crate::ui::Video::file($path)
594    }};
595}
596
597/// Creates a [`Video`](crate::ui::Video) from a network URL literal.
598///
599/// Network playback support is shell-specific; use this helper for literal URLs
600/// and [`Video::network`](crate::ui::Video::network) when the URL is computed at
601/// runtime.
602#[macro_export]
603macro_rules! video_network {
604    ($url:literal $(,)?) => {{
605        $crate::ui::Video::network($url)
606    }};
607}
608
609/// Binds an action to a reducer in one expression.
610///
611/// ```rust,ignore
612/// use fission::prelude::*;
613///
614/// let on_press = with_reducer!(ctx, Increment, on_increment);
615/// ```
616#[macro_export]
617macro_rules! with_reducer {
618    ($ctx:expr, $action:expr, $handler:expr $(,)?) => {
619        $ctx.bind($action, $crate::reduce_with!($handler))
620    };
621}
622
623/// A frame-tick action that advances the runtime clock by a delta.
624///
625/// The platform shell dispatches `Tick` once per frame so that animations,
626/// timers, and other time-dependent logic can progress.
627///
628/// # Example
629///
630/// ```rust,ignore
631/// // Advance the runtime by 16 ms (~60 fps)
632/// runtime.tick(16)?;
633/// ```
634#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
635pub struct Tick {
636    /// Delta time in milliseconds since the last tick.
637    pub dt: CurrentTime,
638}
639
640impl Action for Tick {
641    fn static_id() -> ActionId {
642        *TICK_ACTION_ID
643    }
644}
645
646lazy_static! {
647    pub static ref TICK_ACTION_ID: ActionId = ActionId::from_name("fission_core::Tick");
648}
649
650/// An action that sets the runtime clock to an absolute timestamp.
651///
652/// Unlike [`Tick`] which advances by a delta, `AdvanceTo` jumps directly to
653/// the given time. Useful for testing and deterministic replay.
654///
655/// # Example
656///
657/// ```rust,ignore
658/// let envelope: ActionEnvelope = AdvanceTo { time: 5000 }.into();
659/// runtime.dispatch(envelope, WidgetId::from_u128(0))?;
660/// ```
661#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
662pub struct AdvanceTo {
663    /// The absolute time (in milliseconds) to set the clock to.
664    pub time: CurrentTime,
665}
666
667impl Action for AdvanceTo {
668    fn static_id() -> ActionId {
669        *ADVANCE_TO_ACTION_ID
670    }
671}
672
673lazy_static! {
674    pub static ref ADVANCE_TO_ACTION_ID: ActionId = ActionId::from_name("fission_core::AdvanceTo");
675}
676
677/// A type-erased reducer function stored in the [`Runtime`].
678///
679/// `BoxedReducer` is the internal representation used by the runtime to invoke
680/// reducers without knowing the concrete `GlobalState` or `Action` types.
681pub(crate) type BoxedReducer = Box<
682    dyn FnMut(
683            &mut HashMap<TypeId, Box<dyn GlobalState>>,
684            &ActionEnvelope,
685            WidgetId,
686            &mut Vec<EffectEnvelope>,
687            &ActionInput,
688            &Arc<EffectCallbackRegistry>,
689        ) -> Result<()>
690        + Send
691        + Sync,
692>;
693
694/// One-shot reducers bound to async effect completion actions.
695///
696/// These reducers are separate from the per-frame widget registry so a
697/// completion remains deliverable after the frame that issued the effect.
698pub(crate) struct EffectCallbackRegistry {
699    reducers: Mutex<HashMap<ActionId, Vec<BoxedReducer>>>,
700}
701
702impl EffectCallbackRegistry {
703    pub(crate) fn new() -> Self {
704        Self {
705            reducers: Mutex::new(HashMap::new()),
706        }
707    }
708
709    pub(crate) fn register(&self, action_id: ActionId, reducer: BoxedReducer) {
710        self.reducers
711            .lock()
712            .unwrap_or_else(std::sync::PoisonError::into_inner)
713            .entry(action_id)
714            .or_default()
715            .push(reducer);
716    }
717
718    pub(crate) fn take(&self, action_id: ActionId) -> Vec<BoxedReducer> {
719        self.reducers
720            .lock()
721            .unwrap_or_else(std::sync::PoisonError::into_inner)
722            .remove(&action_id)
723            .unwrap_or_default()
724    }
725}