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