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