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