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, LinearArrangement, Measurable, Placeable,
179        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, AnimatedVisibility, BasicSecureTextField, BasicText,
232    BasicTextField, BasicTextFieldDecorated, BasicTextFieldDecorationScope, BasicTextFieldOptions,
233    BasicTextFieldWithOptions, BasicTextWithOptions, BitmapPainter, BitmapRegionPainter, Box,
234    BoxScope, BoxSpec, BoxWithConstraints, BoxWithConstraintsScope, BoxWithConstraintsScopeImpl,
235    Button, ButtonSpec, Canvas, Column, ColumnSpec, ContentScale, ContentSeek, ContentTransform,
236    Crossfade, DEFAULT_ALPHA, EnterTransition, ExitTransition, ForEach, HorizontalPager, Image,
237    Layout, LayoutNode, PageSize, PagerSpec, PagerState, Painter, Row, RowSpec,
238    SecureTextFieldOptions, SeekableAnimatedContent, SeekableCrossfade, SharedElement,
239    SharedTransitionLayout, SizeTransform, Spacer, SubcomposeLayout, SvgPainter, SvgPainterError,
240    Text, TextObfuscationMode, TextWithOptions, VerticalPager, fade_in, fade_out,
241    rememberPagerState, rememberSvg, scale_in, scale_out, slide_in_horizontally,
242    slide_in_vertically, slide_out_horizontally, slide_out_vertically,
243};
244#[cfg(any(test, feature = "test-helpers"))]
245#[doc(hidden)]
246pub use render_state::reset_render_state_for_tests;
247pub use render_state::{
248    AppContext, AppContextScope, MAX_FONT_SCALE, MIN_FONT_SCALE, current_density,
249    current_font_scale, current_font_scale_curve, scale_sp, set_density, set_font_scale,
250    set_font_scale_curve,
251};
252#[doc(hidden)]
253pub use render_state::{
254    GeometrySceneNodes, append_z_order_changes, clear_transient_scroll_motion_contexts,
255    debug_last_fling_velocity, debug_reset_last_fling_velocity, has_current_app_context,
256    has_pending_draw_repasses, has_pending_layer_property_repasses, has_pending_layout_repasses,
257    has_pending_measure_repasses, peek_focus_invalidation, peek_layout_invalidation,
258    peek_pointer_invalidation, peek_render_invalidation, pending_layout_repass_nodes_snapshot,
259    pending_measure_repass_nodes_snapshot, prune_draw_observations_to_nodes,
260    request_current_draw_redraw, request_focus_invalidation, request_layout_invalidation,
261    request_pointer_invalidation, request_render_invalidation, schedule_draw_repass,
262    schedule_layout_repass, schedule_measure_repass, schedule_z_order_change,
263    take_draw_repass_nodes, take_draw_repass_nodes_into, take_focus_invalidation,
264    take_geometry_scene_nodes, take_geometry_scene_nodes_into,
265    take_layer_property_repass_nodes_into, take_layout_invalidation, take_layout_repass_nodes,
266    take_measure_repass_nodes, take_pointer_invalidation, take_render_invalidation,
267};
268pub use renderer::{HeadlessRenderer, PaintLayer, RecordedRenderScene, RenderOp};
269pub use safe_area::{WindowInsets, local_ime_insets, local_safe_area_insets, window_insets};
270pub use scroll::{ScrollElement, ScrollMetrics, ScrollNode, ScrollSettlePolicy, ScrollState};
271pub use scrollbar::{ThumbBounds, ThumbGeometry, content_delta_for_thumb_drag, thumb_geometry};
272#[doc(hidden)]
273pub use semantics_dispatch::{
274    clear_semantics_invalidations, has_pending_semantics_invalidations,
275    process_semantics_invalidations, schedule_semantics_invalidation,
276};
277pub use subcompose_layout::{
278    Constraints, MeasureResult, Placement, SubcomposeLayoutNode, SubcomposeLayoutScope,
279    SubcomposeMeasureScope, SubcomposeMeasureScopeImpl, clean_slot_skip_count,
280};
281pub use text::{
282    LinkAnnotation, ParagraphStyle, PlatformParagraphStyle, PlatformSpanStyle, PlatformTextStyle,
283    PreparedTextLayout, SpanStyle, StringAnnotation, TextDrawStyle, TextLayoutOptions,
284    TextLayoutResult, TextLinePrefixWidths, TextMeasurer, TextMetrics, TextOptions, TextOverflow,
285    TextShaping, TextStyle, get_cursor_x_for_offset, get_offset_for_position, layout_text,
286    measure_text, measure_text_for_node, measure_text_with_options,
287    measure_text_with_options_for_node, prepare_text_layout, prepare_text_layout_for_node,
288    set_text_measurer,
289};
290pub use text_field_focus::{ImeEditorState, has_focused_field};
291pub use text_field_modifier_node::{TextFieldElement, TextFieldModifierNode, TextPanResolver};
292pub use text_input_session::PlatformTextInputHandler;
293pub use text_modifier_node::{TextModifierElement, TextModifierNode};
294pub use unhandled_keys::{UnhandledKeyEvents, dispatch_unhandled_key_event};
295pub use widgets::{
296    clickable_text::ClickableText,
297    lazy_list::{
298        GridCells, LazyColumn, LazyColumnSpec, LazyGridState, LazyHorizontalGrid,
299        LazyHorizontalGridSpec, LazyHorizontalStaggeredGrid, LazyHorizontalStaggeredGridSpec,
300        LazyRow, LazyRowSpec, LazyStaggeredGridState, LazyVerticalGrid, LazyVerticalGridSpec,
301        LazyVerticalStaggeredGrid, LazyVerticalStaggeredGridSpec, rememberLazyGridState,
302        rememberLazyStaggeredGridState,
303    },
304    linked_text::LinkedText,
305    padding_values::PaddingValues,
306    popup::{dismiss_top_popup, dismissable_popup_open},
307    slider::{Slider, SliderOrientation, SliderScope, SliderSpec},
308    swipe_to_dismiss::{
309        SwipeDismissDirection, SwipeDismissSide, SwipeDismissState, SwipeToDismiss,
310        SwipeToDismissBox, SwipeToDismissSpec, rememberSwipeDismissState,
311    },
312    text_selection_menu::local_on_light_surface,
313};
314pub use zoom::ZoomState;
315
316/// In-memory composition helper used by tests.
317pub struct TestComposition {
318    _scope: render_state::AppContextScope,
319    app_context: Rc<AppContext>,
320    composition: Composition<MemoryApplier>,
321}
322
323impl TestComposition {
324    /// Returns the id of the root node, or `None` when the composition has no root node.
325    pub fn root(&self) -> Option<NodeId> {
326        self.app_context.enter(|| self.composition.root())
327    }
328
329    /// Returns the runtime handle of the composition.
330    pub fn runtime_handle(&self) -> RuntimeHandle {
331        self.app_context.enter(|| self.composition.runtime_handle())
332    }
333
334    /// Returns true when state changes or animation callbacks have work pending.
335    pub fn should_render(&self) -> bool {
336        self.app_context.enter(|| self.composition.should_render())
337    }
338
339    /// Returns true once if a root render was requested since the last call, then resets the
340    /// request.
341    pub fn take_root_render_request(&mut self) -> bool {
342        let app_context = Rc::clone(&self.app_context);
343        app_context.enter(|| self.composition.take_root_render_request())
344    }
345
346    /// Applies the node updates that state changes queued.
347    pub fn flush_pending_node_updates(&mut self) -> Result<(), NodeError> {
348        let app_context = Rc::clone(&self.app_context);
349        app_context.enter(|| self.composition.flush_pending_node_updates())
350    }
351
352    /// Recomposes the scopes that state changes invalidated. Returns true when any scope
353    /// recomposed.
354    pub fn process_invalid_scopes(&mut self) -> Result<bool, NodeError> {
355        let app_context = Rc::clone(&self.app_context);
356        app_context.enter(|| self.composition.process_invalid_scopes())
357    }
358
359    /// Renders `content` as the root composition under `root_key`, then recomposes invalidated
360    /// scopes.
361    pub fn render(&mut self, root_key: Key, content: impl FnMut()) -> Result<(), NodeError> {
362        let app_context = Rc::clone(&self.app_context);
363        app_context.enter(|| self.composition.render(root_key, content))
364    }
365
366    /// Borrows the applier that holds the node tree. The guard keeps this composition's app context
367    /// active.
368    pub fn applier_mut(&mut self) -> TestApplierGuard<'_> {
369        let scope = self.app_context.enter_scope();
370        let applier = self.composition.applier_mut();
371        TestApplierGuard {
372            _scope: scope,
373            applier,
374        }
375    }
376
377    /// Runs `block` with this composition's app context active.
378    pub fn with_app_context<R>(&self, block: impl FnOnce() -> R) -> R {
379        self.app_context.enter(block)
380    }
381}
382
383/// Mutable access to the applier of a [`TestComposition`], with the app context of that composition
384/// active while the guard lives.
385pub struct TestApplierGuard<'a> {
386    _scope: render_state::AppContextScope,
387    applier: ApplierGuard<'a, MemoryApplier>,
388}
389
390impl Deref for TestApplierGuard<'_> {
391    type Target = MemoryApplier;
392
393    fn deref(&self) -> &Self::Target {
394        &self.applier
395    }
396}
397
398impl DerefMut for TestApplierGuard<'_> {
399    fn deref_mut(&mut self) -> &mut Self::Target {
400        &mut self.applier
401    }
402}
403
404/// Build a composition with a simple in-memory applier and run the provided closure once.
405pub fn run_test_composition(build: impl FnMut()) -> TestComposition {
406    let app_context = AppContext::new();
407    app_context.enter(|| {
408        #[cfg(test)]
409        reset_render_state_for_tests();
410    });
411    let mut test_composition = TestComposition {
412        _scope: app_context.enter_scope(),
413        app_context,
414        composition: Composition::new(MemoryApplier::new()),
415    };
416    test_composition
417        .render(location_key(file!(), line!(), column!()), build)
418        .expect("initial render succeeds");
419    test_composition
420}
421
422pub use cranpose_core::MutableState as SnapshotState;
423
424#[cfg(test)]
425#[path = "tests/anchor_async_tests.rs"]
426mod anchor_async_tests;
427
428#[cfg(test)]
429#[path = "tests/animated_visibility_tests.rs"]
430mod animated_visibility_tests;
431
432#[cfg(test)]
433#[path = "tests/lazy_recycle_effect_tests.rs"]
434mod lazy_recycle_effect_tests;
435
436#[cfg(test)]
437#[path = "tests/measure_time_ui_task_tests.rs"]
438mod measure_time_ui_task_tests;
439
440#[cfg(test)]
441#[path = "tests/animation_frame_pump_tests.rs"]
442mod animation_frame_pump_tests;
443
444#[cfg(test)]
445#[path = "tests/crossfade_tests.rs"]
446mod crossfade_tests;
447
448#[cfg(test)]
449#[path = "tests/async_runtime_full_layout_test.rs"]
450mod async_runtime_full_layout_test;
451
452#[cfg(test)]
453#[path = "tests/cursor_position_tests.rs"]
454mod cursor_position_tests;
455
456#[cfg(test)]
457#[path = "tests/popup_tests.rs"]
458mod popup_tests;
459
460#[cfg(test)]
461#[path = "tests/selection_handle_tests.rs"]
462mod selection_handle_tests;
463
464#[cfg(test)]
465#[path = "tests/tab_switching_tests.rs"]
466mod tab_switching_tests;
467
468#[cfg(test)]
469#[path = "tests/layout_keep_tests.rs"]
470mod layout_keep_tests;
471
472#[cfg(test)]
473#[path = "tests/lazy_list_viewport_tests.rs"]
474mod lazy_list_viewport_tests;
475
476#[cfg(test)]
477#[path = "tests/swipe_to_dismiss_lazy_tests.rs"]
478mod swipe_to_dismiss_lazy_tests;
479
480#[cfg(test)]
481#[path = "tests/top_modal_tests.rs"]
482mod top_modal_tests;
483
484#[cfg(test)]
485#[path = "tests/swipe_to_dismiss_render_tests.rs"]
486mod swipe_to_dismiss_render_tests;
487
488#[cfg(test)]
489#[path = "tests/lazy_list_recompose_tests.rs"]
490mod lazy_list_recompose_tests;
491
492#[cfg(test)]
493#[path = "tests/lazy_row_tests.rs"]
494mod lazy_row_tests;
495
496#[cfg(test)]
497#[path = "tests/wear_widget_tests.rs"]
498mod wear_widget_tests;
499
500#[cfg(test)]
501#[path = "tests/ios_fling_measurement.rs"]
502mod ios_fling_measurement;