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