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