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