Skip to main content

cranpose_ui/
lib.rs

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