Skip to main content

cranpose_ui/
lib.rs

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