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