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