Skip to main content

cranpose_ui/
lib.rs

1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4extern crate self as cranpose_ui;
5
6use std::{
7    ops::{Deref, DerefMut},
8    rc::Rc,
9};
10
11use cranpose_core::{ApplierGuard, MemoryApplier, NodeError, NodeId, RuntimeHandle, location_key};
12pub use cranpose_core::{Composition, Key};
13pub use cranpose_macros::composable;
14#[cfg(feature = "localization")]
15pub use cranpose_macros::{message, tr, translation_messages, translations};
16#[cfg(feature = "localization")]
17pub use localization::{
18    Catalog, Language, LanguagePreference, Locale, LocalizedText, PreviewMode, ProvideLocalization,
19    ProvideTranslator,
20};
21pub use text::SharedText;
22#[cfg(feature = "localization")]
23mod ui_text;
24#[cfg(feature = "localization")]
25pub use ui_text::UiText;
26#[cfg(feature = "localization")]
27mod language_preferences;
28#[cfg(feature = "localization")]
29pub use language_preferences::{
30    LocalizationController, ProvideLanguagePreferences, local_localization,
31};
32pub use ui_strings::UiString;
33
34/// Screen reader announcements for events that have no control on screen.
35pub mod announce;
36pub mod bring_into_view;
37pub mod clipboard_session;
38mod cursor_animation;
39mod debug;
40pub mod density;
41pub mod draggable;
42mod draw;
43mod environment_locals;
44pub mod fling_animation;
45mod focus_dispatch;
46/// Moving focus between focus targets, as `LocalFocusManager` does in Compose.
47pub mod focus_manager;
48mod focus_navigation;
49/// The ordered list of focus targets that layout publishes for keyboard traversal.
50pub mod focus_order;
51mod focus_within;
52pub mod font_scale;
53mod interaction;
54mod key_event;
55mod keyboard_options;
56/// Layout tree, measurement and placement of nodes.
57pub mod layout;
58pub mod layout_direction;
59pub mod lazy_item;
60mod lazy_prefetch;
61#[cfg(feature = "localization")]
62pub mod localization;
63pub mod modal;
64mod modal_nodes;
65mod modifier;
66mod modifier_nodes;
67pub mod mouse_input;
68mod nested_scroll;
69pub mod nine_patch;
70mod pointer_dispatch;
71pub mod pointer_icon_session;
72mod primitives;
73mod render_state;
74mod renderer;
75pub mod round_scaling_list;
76pub mod round_scroll_indicator;
77pub mod safe_area;
78pub mod scroll;
79pub mod scrollbar;
80pub mod selection_container;
81mod semantics_dispatch;
82mod semantics_layout_log;
83mod subcompose_layout;
84/// Text styling, measurement and layout.
85pub mod text;
86mod text_field_decorator_node;
87pub mod text_field_focus;
88mod text_field_handler;
89mod text_field_input;
90mod text_field_modifier_node;
91mod text_field_presentation;
92pub mod text_input_session;
93pub mod text_layout_result;
94mod text_modifier_node;
95pub mod text_selection;
96mod thread_state;
97mod ui_strings;
98mod unhandled_keys;
99pub mod widgets;
100mod word_boundaries;
101pub mod zoom;
102
103#[doc(hidden)]
104pub use announce::{Announcement, Announcer, announce, drain_announcements, local_announcer};
105pub use bring_into_view::{
106    BringIntoViewResponder, local_bring_into_view_responder, scroll_delta_to_reveal,
107};
108pub use cranpose_foundation::{
109    CanvasSemanticsNode, CollectionInfo, DelegatableNode, LiveRegionMode, ModifierNode,
110    ModifierNodeElement, NodeCapabilities, NodeState, ProgressBarRangeInfo, ScrollAxisRange,
111    SemanticsConfiguration, SemanticsCustomAction, SemanticsDismiss, SemanticsExpand,
112    SemanticsLongClick, SemanticsMagicTap, SemanticsScrollBy, SemanticsScrollToIndex,
113    SemanticsSetProgress, SemanticsSetSelection, SemanticsSetText, SemanticsSpec,
114    SemanticsWidgetRole, ToggleableState,
115    lazy::{
116        LazyItems, LazyListItemInfo, LazyListLayoutInfo, LazyListScope, LazyListState,
117        rememberLazyListState, rememberLazyListStateWithPosition,
118    },
119    text::{TextFieldBuffer, TextFieldLineLimits, TextFieldState, TextFieldValue, TextRange},
120};
121pub use cranpose_ui_graphics::{
122    BlurredEdgeTreatment, ColorFilter, CursorIcon, CustomPointerIcon, DashPathEffect, Dp,
123    DrawScope, DrawSpecialization, DrawStyle, ExternalTexture, ImageBitmap, ImageBitmapError,
124    ImagePixelFormat, ImageSampling, Path, PlaceholderShape, PointerIcon, PointerIconError,
125    RuntimeShaderUniformError, ShaderPlaceholder, Stroke, StrokeCap, StrokeJoin, SubstrateSpec,
126    TileMode, WindowCoordinates, glass_material_folds_enabled, set_glass_material_folds,
127};
128pub use cranpose_ui_layout::{AlignmentLines, IntrinsicSize, Measurement};
129pub use cursor_animation::{
130    is_cursor_visible, next_cursor_blink_time, reset_cursor_blink, start_cursor_blink,
131    stop_cursor_blink, tick_cursor_blink,
132};
133pub use debug::{
134    ModifierChainTraceGuard, format_layout_tree, format_modifier_chain, format_render_scene,
135    format_screen_summary, install_modifier_chain_trace, log_layout_tree, log_modifier_chain,
136    log_render_scene, log_screen_summary,
137};
138pub use density::{Density, DensityMeasureScope, density, local_density};
139pub use draggable::{
140    DragDeltaHandler, DraggableState, ScrollableState, rememberDraggableState,
141    rememberScrollableState,
142};
143pub use draw::{
144    DrawCacheBuilder, DrawCommand, DrawCommandFn, command_draw_scope, command_draw_scope_reusing,
145    execute_draw_commands,
146};
147pub use focus_dispatch::{
148    active_focus_target, clear_focus_invalidations, has_pending_focus_invalidations,
149    keyboard_focus_visible, process_focus_invalidations, schedule_focus_invalidation,
150    set_active_focus_target, set_keyboard_focus_visible,
151};
152pub use focus_manager::{FocusManager, local_focus_manager, request_focus_from_platform};
153pub use focus_navigation::{KeyboardFocusTarget, keyboard_focus_target};
154pub use focus_order::{
155    FocusEntry, collect_focus_order, collect_focus_order_under, focus_order_len,
156    selectable_group_of, set_focus_order, take_requested_focus_move,
157};
158pub use focus_within::FocusWithin;
159pub use font_scale::{FontScaleCurve, MAX_FONT_SCALE_KNOTS};
160pub use interaction::{
161    HoverInteraction, HoverInteractionEnter, HoverInteractionExit, Interaction,
162    MutableInteractionSource, PressInteraction, PressInteractionCancel, PressInteractionPress,
163    PressInteractionRelease, collect_is_hovered_as_state, collect_is_pressed_as_state,
164    rememberMutableInteractionSource,
165};
166pub use key_event::{KeyCode, KeyEvent, KeyEventType, Modifiers};
167pub use keyboard_options::{
168    ContentType, ImeAction, KeyboardActionHandler, KeyboardActionScope, KeyboardCapitalization,
169    KeyboardOptions, KeyboardType,
170};
171pub use layout::{
172    LayoutAllocationDebugStats, LayoutBox, LayoutEngine, LayoutMeasurements, LayoutNodeData,
173    LayoutNodeKind, LayoutTree, MeasureLayoutOptions, SemanticsAction, SemanticsCallback,
174    SemanticsDetails, SemanticsNode, SemanticsPlacement, SemanticsRole, SemanticsText,
175    SemanticsTree, build_layout_tree_from_applier, build_semantics_tree_from_applier,
176    build_semantics_tree_from_layout_tree,
177    core::{
178        Alignment, Arrangement, HorizontalAlignment, LayoutData, LinearArrangement, Measurable,
179        ParentData, Placeable, VerticalAlignment,
180    },
181    has_placed_content, measure_layout, measure_layout_with_options, placed_content_extent,
182    top_modal_from_applier, tree_needs_layout, tree_needs_semantics,
183    update_semantics_tree_from_applier,
184};
185pub use layout_direction::{
186    LayoutDirection, ProvideLayoutDirection, layout_direction, local_layout_direction,
187};
188pub use lazy_item::{ProvideLazyItemKey, lazy_item_key, local_lazy_item_key};
189pub use lazy_prefetch::{
190    drain_lazy_prefetch_requests, has_lazy_prefetch_requests, lazy_prefetch_pass_cost,
191    set_idle_prefetch_passes, warm_prefetched_slices, with_lazy_prefetch_pass,
192};
193pub use modal::{
194    ModalRegistration, clear_modals, dispatch_modal_back, local_modal_depth, modal_depth,
195};
196pub use modal_nodes::modal_node_may_be_live;
197pub use modifier::{
198    BlendMode, Brush, Color, CompositingStrategy, CornerRadii, DpOffset, DragAndDropEvent,
199    DragAndDropOutcome, DragAndDropPayload, DragAndDropPoint, DragAndDropSource,
200    DragAndDropSourceElement, DragAndDropSourceNode, DragAndDropState, DragAndDropTarget,
201    DragAndDropTargetElement, DragAndDropTargetNode, EdgeInsets, FocusDirection, FocusRequestError,
202    FocusRequester, GlassMaterial, GraphicsLayer, KeyEventRoute, LayerShape, Modifier,
203    ModifierLocalKey, ModifierLocalReadScope, ModifierNodeSlices, ModifierNodeSlicesDebugStats,
204    Point, PointerEvent, PointerEventKind, PointerInputScope, PointerSource, Rect, RenderEffect,
205    ResolvedBackground, ResolvedModifiers, RotaryInputModifierNode, RotaryScrollEvent,
206    RoundedCornerShape, RuntimeShader, SemanticsRequester, Shadow, ShadowScope, Size, SliceTextRef,
207    TransformOrigin, WindowRootDescriptor, WindowRootElement, WindowRootEntry, WindowRootNode,
208    WindowRootRegistry, WindowRootRoutingScratch, collect_modifier_slices,
209    collect_semantics_from_modifier, collect_slices_from_modifier, is_window_root,
210    nearest_window_root, nearest_window_roots, nearest_window_roots_into, window_roots,
211    window_roots_revision, z_index_in_use,
212};
213#[cfg(feature = "test-helpers")]
214pub use modifier::{last_fling_velocity, reset_last_fling_velocity};
215pub use modifier_nodes::{
216    AlphaElement, AlphaNode, BackgroundElement, BackgroundNode, ClickableElement, ClickableNode,
217    CornerShapeElement, CornerShapeNode, FillDirection, FillElement, FillNode,
218    FractionalOffsetElement, FractionalOffsetNode, OffsetElement, OffsetNode, PaddingElement,
219    PaddingNode, SizeElement, SizeNode,
220};
221pub use nested_scroll::{NestedScroll, NestedScrollConnection, NestedScrollSource, ScrollAxis};
222pub use nine_patch::{
223    NinePatchInsets, PatchFill, PatchQuad, nine_patch_quads, tile_count, tile_quads,
224};
225#[doc(hidden)]
226pub use pointer_dispatch::{
227    clear_pointer_repasses, has_pending_pointer_repasses, process_pointer_repasses,
228    schedule_pointer_repass,
229};
230pub use primitives::{
231    AnimateScroll, AnimatedContent, AnimatedContentWithKey, AnimatedVisibility,
232    BasicSecureTextField, BasicText, BasicTextField, BasicTextFieldDecorated,
233    BasicTextFieldDecorationScope, BasicTextFieldOptions, BasicTextFieldWithOptions,
234    BasicTextWithOptions, BitmapPainter, BitmapRegionPainter, Box, BoxScope, BoxSpec,
235    BoxWithConstraints, BoxWithConstraintsScope, BoxWithConstraintsScopeImpl, Button, ButtonSpec,
236    Canvas, Column, ColumnSpec, ContentScale, ContentSeek, ContentTransform, Crossfade,
237    DEFAULT_ALPHA, EnterTransition, ExitTransition, ForEach, HorizontalPager, Image, Layout,
238    LayoutNode, PageSize, PagerFling, PagerSpec, PagerState, Painter, Row, RowSpec,
239    SecureTextFieldOptions, SeekableAnimatedContent, SeekableCrossfade, SharedBounds,
240    SharedElement, SharedTransitionLayout, SizeTransform, SlideDirection, SnapPosition, Spacer,
241    SubcomposeLayout, SvgPainter, SvgPainterError, Text, TextObfuscationMode, TextWithOptions,
242    VerticalPager, fade_in, fade_out, rememberPagerState, rememberSvg, scale_in, scale_out,
243    slide_in_horizontally, slide_in_vertically, slide_into_container, slide_out_horizontally,
244    slide_out_of_container, slide_out_vertically,
245};
246#[cfg(any(test, feature = "test-helpers"))]
247#[doc(hidden)]
248pub use render_state::reset_render_state_for_tests;
249pub use render_state::{
250    AppContext, AppContextScope, MAX_FONT_SCALE, MIN_FONT_SCALE, current_density,
251    current_font_scale, current_font_scale_curve, scale_sp, set_density, set_font_scale,
252    set_font_scale_curve,
253};
254#[doc(hidden)]
255pub use render_state::{
256    GeometrySceneNodes, append_z_order_changes, clear_transient_scroll_motion_contexts,
257    debug_last_fling_velocity, debug_reset_last_fling_velocity, has_current_app_context,
258    has_pending_draw_repasses, has_pending_layer_property_repasses, has_pending_layout_repasses,
259    has_pending_measure_repasses, peek_focus_invalidation, peek_layout_invalidation,
260    peek_pointer_invalidation, peek_render_invalidation, pending_layout_repass_nodes_snapshot,
261    pending_measure_repass_nodes_snapshot, prune_draw_observations_to_nodes,
262    request_current_draw_redraw, request_focus_invalidation, request_layout_invalidation,
263    request_pointer_invalidation, request_render_invalidation, schedule_draw_repass,
264    schedule_layout_repass, schedule_measure_repass, schedule_z_order_change,
265    take_draw_repass_nodes, take_draw_repass_nodes_into, take_focus_invalidation,
266    take_geometry_scene_nodes, take_geometry_scene_nodes_into,
267    take_layer_property_repass_nodes_into, take_layout_invalidation, take_layout_repass_nodes,
268    take_measure_repass_nodes, take_pointer_invalidation, take_render_invalidation,
269};
270pub use renderer::{HeadlessRenderer, PaintLayer, RecordedRenderScene, RenderOp};
271pub use safe_area::{WindowInsets, local_ime_insets, local_safe_area_insets, window_insets};
272pub use scroll::{ScrollElement, ScrollMetrics, ScrollNode, ScrollSettlePolicy, ScrollState};
273pub use scrollbar::{ThumbBounds, ThumbGeometry, content_delta_for_thumb_drag, thumb_geometry};
274#[doc(hidden)]
275pub use semantics_dispatch::{
276    clear_semantics_invalidations, has_pending_semantics_invalidations,
277    process_semantics_invalidations, schedule_semantics_invalidation,
278};
279pub use subcompose_layout::{
280    Constraints, MeasureResult, Placement, SubcomposeLayoutNode, SubcomposeLayoutScope,
281    SubcomposeMeasureScope, SubcomposeMeasureScopeImpl, clean_slot_skip_count,
282};
283pub use text::{
284    LinkAnnotation, ParagraphStyle, PlatformParagraphStyle, PlatformSpanStyle, PlatformTextStyle,
285    PreparedTextLayout, SpanStyle, StringAnnotation, TextDrawStyle, TextLayoutOptions,
286    TextLayoutResult, TextLinePrefixWidths, TextMeasurer, TextMetrics, TextOptions, TextOverflow,
287    TextShaping, TextStyle, get_cursor_x_for_offset, get_offset_for_position, layout_text,
288    measure_text, measure_text_for_node, measure_text_with_options,
289    measure_text_with_options_for_node, prepare_text_layout, prepare_text_layout_for_node,
290    set_text_measurer,
291};
292pub use text_field_focus::{ImeEditorState, has_focused_field};
293pub use text_field_modifier_node::{TextFieldElement, TextFieldModifierNode, TextPanResolver};
294pub use text_input_session::PlatformTextInputHandler;
295pub use text_modifier_node::{TextModifierElement, TextModifierNode};
296pub use unhandled_keys::{UnhandledKeyEvents, dispatch_unhandled_key_event};
297pub use widgets::{
298    clickable_text::ClickableText,
299    lazy_list::{
300        GridCells, LazyColumn, LazyColumnSpec, LazyGridState, LazyHorizontalGrid,
301        LazyHorizontalGridSpec, LazyHorizontalStaggeredGrid, LazyHorizontalStaggeredGridSpec,
302        LazyRow, LazyRowSpec, LazyStaggeredGridState, LazyVerticalGrid, LazyVerticalGridSpec,
303        LazyVerticalStaggeredGrid, LazyVerticalStaggeredGridSpec, rememberLazyGridState,
304        rememberLazyStaggeredGridState,
305    },
306    linked_text::LinkedText,
307    padding_values::PaddingValues,
308    popup::{dismiss_top_popup, dismissable_popup_open},
309    slider::{Slider, SliderOrientation, SliderScope, SliderSpec},
310    swipe_to_dismiss::{
311        SwipeDismissDirection, SwipeDismissSide, SwipeDismissState, SwipeToDismiss,
312        SwipeToDismissBox, SwipeToDismissSpec, rememberSwipeDismissState,
313    },
314    text_selection_menu::local_on_light_surface,
315};
316pub use zoom::ZoomState;
317
318/// In-memory composition helper used by tests.
319pub struct TestComposition {
320    _scope: render_state::AppContextScope,
321    app_context: Rc<AppContext>,
322    composition: Composition<MemoryApplier>,
323}
324
325impl TestComposition {
326    /// Returns the id of the root node, or `None` when the composition has no root node.
327    pub fn root(&self) -> Option<NodeId> {
328        self.app_context.enter(|| self.composition.root())
329    }
330
331    /// Returns the runtime handle of the composition.
332    pub fn runtime_handle(&self) -> RuntimeHandle {
333        self.app_context.enter(|| self.composition.runtime_handle())
334    }
335
336    /// Returns true when state changes or animation callbacks have work pending.
337    pub fn should_render(&self) -> bool {
338        self.app_context.enter(|| self.composition.should_render())
339    }
340
341    /// Returns true once if a root render was requested since the last call, then resets the
342    /// request.
343    pub fn take_root_render_request(&mut self) -> bool {
344        let app_context = Rc::clone(&self.app_context);
345        app_context.enter(|| self.composition.take_root_render_request())
346    }
347
348    /// Applies the node updates that state changes queued.
349    pub fn flush_pending_node_updates(&mut self) -> Result<(), NodeError> {
350        let app_context = Rc::clone(&self.app_context);
351        app_context.enter(|| self.composition.flush_pending_node_updates())
352    }
353
354    /// Recomposes the scopes that state changes invalidated. Returns true when any scope
355    /// recomposed.
356    pub fn process_invalid_scopes(&mut self) -> Result<bool, NodeError> {
357        let app_context = Rc::clone(&self.app_context);
358        app_context.enter(|| self.composition.process_invalid_scopes())
359    }
360
361    /// Renders `content` as the root composition under `root_key`, then recomposes invalidated
362    /// scopes.
363    pub fn render(&mut self, root_key: Key, content: impl FnMut()) -> Result<(), NodeError> {
364        let app_context = Rc::clone(&self.app_context);
365        app_context.enter(|| self.composition.render(root_key, content))
366    }
367
368    /// Borrows the applier that holds the node tree. The guard keeps this composition's app context
369    /// active.
370    pub fn applier_mut(&mut self) -> TestApplierGuard<'_> {
371        let scope = self.app_context.enter_scope();
372        let applier = self.composition.applier_mut();
373        TestApplierGuard {
374            _scope: scope,
375            applier,
376        }
377    }
378
379    /// Runs `block` with this composition's app context active.
380    pub fn with_app_context<R>(&self, block: impl FnOnce() -> R) -> R {
381        self.app_context.enter(block)
382    }
383}
384
385/// Mutable access to the applier of a [`TestComposition`], with the app context of that composition
386/// active while the guard lives.
387pub struct TestApplierGuard<'a> {
388    _scope: render_state::AppContextScope,
389    applier: ApplierGuard<'a, MemoryApplier>,
390}
391
392impl Deref for TestApplierGuard<'_> {
393    type Target = MemoryApplier;
394
395    fn deref(&self) -> &Self::Target {
396        &self.applier
397    }
398}
399
400impl DerefMut for TestApplierGuard<'_> {
401    fn deref_mut(&mut self) -> &mut Self::Target {
402        &mut self.applier
403    }
404}
405
406/// Build a composition with a simple in-memory applier and run the provided closure once.
407pub fn run_test_composition(build: impl FnMut()) -> TestComposition {
408    let app_context = AppContext::new();
409    app_context.enter(|| {
410        #[cfg(test)]
411        reset_render_state_for_tests();
412    });
413    let mut test_composition = TestComposition {
414        _scope: app_context.enter_scope(),
415        app_context,
416        composition: Composition::new(MemoryApplier::new()),
417    };
418    test_composition
419        .render(location_key(file!(), line!(), column!()), build)
420        .expect("initial render succeeds");
421    test_composition
422}
423
424pub use cranpose_core::MutableState as SnapshotState;
425
426#[cfg(test)]
427#[path = "tests/anchor_async_tests.rs"]
428mod anchor_async_tests;
429
430#[cfg(test)]
431#[path = "tests/animated_visibility_tests.rs"]
432mod animated_visibility_tests;
433
434#[cfg(test)]
435#[path = "tests/lazy_recycle_effect_tests.rs"]
436mod lazy_recycle_effect_tests;
437
438#[cfg(test)]
439#[path = "tests/measure_time_ui_task_tests.rs"]
440mod measure_time_ui_task_tests;
441
442#[cfg(test)]
443#[path = "tests/animation_frame_pump_tests.rs"]
444mod animation_frame_pump_tests;
445
446#[cfg(test)]
447#[path = "tests/crossfade_tests.rs"]
448mod crossfade_tests;
449
450#[cfg(test)]
451#[path = "tests/async_runtime_full_layout_test.rs"]
452mod async_runtime_full_layout_test;
453
454#[cfg(test)]
455#[path = "tests/cursor_position_tests.rs"]
456mod cursor_position_tests;
457
458#[cfg(test)]
459#[path = "tests/popup_tests.rs"]
460mod popup_tests;
461
462#[cfg(test)]
463#[path = "tests/selection_handle_tests.rs"]
464mod selection_handle_tests;
465
466#[cfg(test)]
467#[path = "tests/tab_switching_tests.rs"]
468mod tab_switching_tests;
469
470#[cfg(test)]
471#[path = "tests/layout_keep_tests.rs"]
472mod layout_keep_tests;
473
474#[cfg(test)]
475#[path = "tests/lazy_list_viewport_tests.rs"]
476mod lazy_list_viewport_tests;
477
478#[cfg(test)]
479#[path = "tests/swipe_to_dismiss_lazy_tests.rs"]
480mod swipe_to_dismiss_lazy_tests;
481
482#[cfg(test)]
483#[path = "tests/top_modal_tests.rs"]
484mod top_modal_tests;
485
486#[cfg(test)]
487#[path = "tests/swipe_to_dismiss_render_tests.rs"]
488mod swipe_to_dismiss_render_tests;
489
490#[cfg(test)]
491#[path = "tests/lazy_list_recompose_tests.rs"]
492mod lazy_list_recompose_tests;
493
494#[cfg(test)]
495#[path = "tests/lazy_row_tests.rs"]
496mod lazy_row_tests;
497
498#[cfg(test)]
499#[path = "tests/wear_widget_tests.rs"]
500mod wear_widget_tests;
501
502#[cfg(test)]
503#[path = "tests/ios_fling_measurement.rs"]
504mod ios_fling_measurement;