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, EdgeInsets,
134    FocusDirection, FocusRequestError, FocusRequester, GlassMaterial, GraphicsLayer, LayerShape,
135    Modifier, ModifierLocalKey, ModifierLocalReadScope, ModifierNodeSlices,
136    ModifierNodeSlicesDebugStats, Point, PointerEvent, PointerEventKind, PointerInputScope,
137    PointerSource, Rect, RenderEffect, ResolvedBackground, ResolvedModifiers,
138    RotaryInputModifierNode, RotaryScrollEvent, RoundedCornerShape, RuntimeShader,
139    SemanticsRequester, Shadow, ShadowScope, Size, TransformOrigin, collect_modifier_slices,
140    collect_semantics_from_modifier, collect_slices_from_modifier,
141};
142#[cfg(feature = "test-helpers")]
143pub use modifier::{last_fling_velocity, reset_last_fling_velocity};
144pub use modifier_nodes::{
145    AlphaElement, AlphaNode, BackgroundElement, BackgroundNode, ClickableElement, ClickableNode,
146    CornerShapeElement, CornerShapeNode, FillDirection, FillElement, FillNode,
147    FractionalOffsetElement, FractionalOffsetNode, OffsetElement, OffsetNode, PaddingElement,
148    PaddingNode, SizeElement, SizeNode,
149};
150pub use nine_patch::{
151    NinePatchInsets, PatchFill, PatchQuad, nine_patch_quads, tile_count, tile_quads,
152};
153#[doc(hidden)]
154pub use pointer_dispatch::{
155    clear_pointer_repasses, has_pending_pointer_repasses, process_pointer_repasses,
156    schedule_pointer_repass,
157};
158pub use primitives::{
159    AnimatedVisibility, BasicText, BasicTextField, BasicTextFieldDecorated,
160    BasicTextFieldDecorationScope, BasicTextFieldOptions, BasicTextFieldWithOptions,
161    BasicTextWithOptions, BitmapPainter, BitmapRegionPainter, Box, BoxScope, BoxSpec,
162    BoxWithConstraints, BoxWithConstraintsScope, BoxWithConstraintsScopeImpl, Button, ButtonSpec,
163    Canvas, Column, ColumnSpec, ContentScale, Crossfade, DEFAULT_ALPHA, EnterTransition,
164    ExitTransition, ForEach, Image, Layout, LayoutNode, Painter, Row, RowSpec, Spacer,
165    SubcomposeLayout, SvgPainter, SvgPainterError, Text, TextWithOptions, fade_in, fade_out,
166    rememberSvg, slide_in_vertically, slide_out_vertically,
167};
168#[cfg(any(test, feature = "test-helpers"))]
169#[doc(hidden)]
170pub use render_state::reset_render_state_for_tests;
171pub use render_state::{
172    AppContext, AppContextScope, MAX_FONT_SCALE, MIN_FONT_SCALE, current_density,
173    current_font_scale, current_font_scale_curve, scale_sp, set_density, set_font_scale,
174    set_font_scale_curve,
175};
176#[doc(hidden)]
177pub use render_state::{
178    clear_transient_scroll_motion_contexts, debug_last_fling_velocity,
179    debug_reset_last_fling_velocity, has_current_app_context, has_pending_draw_repasses,
180    has_pending_layout_repasses, has_pending_measure_repasses, peek_focus_invalidation,
181    peek_layout_invalidation, peek_pointer_invalidation, peek_render_invalidation,
182    pending_layout_repass_nodes_snapshot, pending_measure_repass_nodes_snapshot,
183    prune_draw_observations_to_nodes, request_current_draw_redraw, request_focus_invalidation,
184    request_layout_invalidation, request_pointer_invalidation, request_render_invalidation,
185    schedule_draw_repass, schedule_layout_repass, schedule_measure_repass, take_draw_repass_nodes,
186    take_focus_invalidation, take_geometry_scene_nodes, take_layout_invalidation,
187    take_layout_repass_nodes, take_measure_repass_nodes, take_pointer_invalidation,
188    take_render_invalidation,
189};
190pub use renderer::{HeadlessRenderer, PaintLayer, RecordedRenderScene, RenderOp};
191pub use safe_area::{WindowInsets, local_ime_insets, local_safe_area_insets, window_insets};
192pub use scroll::{ScrollElement, ScrollMetrics, ScrollNode, ScrollSettlePolicy, ScrollState};
193pub use scrollbar::{ThumbBounds, ThumbGeometry, content_delta_for_thumb_drag, thumb_geometry};
194#[doc(hidden)]
195pub use semantics_dispatch::{
196    clear_semantics_invalidations, has_pending_semantics_invalidations,
197    process_semantics_invalidations, schedule_semantics_invalidation,
198};
199pub use subcompose_layout::{
200    Constraints, MeasureResult, Placement, SubcomposeLayoutNode, SubcomposeLayoutScope,
201    SubcomposeMeasureScope, SubcomposeMeasureScopeImpl, clean_slot_skip_count,
202};
203pub use text::{
204    LinkAnnotation, ParagraphStyle, PlatformParagraphStyle, PlatformSpanStyle, PlatformTextStyle,
205    PreparedTextLayout, SpanStyle, StringAnnotation, TextDrawStyle, TextLayoutOptions,
206    TextLayoutResult, TextLinePrefixWidths, TextMeasurer, TextMetrics, TextOptions, TextOverflow,
207    TextShaping, TextStyle, get_cursor_x_for_offset, get_offset_for_position, layout_text,
208    measure_text, measure_text_for_node, measure_text_with_options,
209    measure_text_with_options_for_node, prepare_text_layout, prepare_text_layout_for_node,
210    set_text_measurer,
211};
212pub use text_field_focus::{ImeEditorState, has_focused_field};
213pub use text_field_modifier_node::{TextFieldElement, TextFieldModifierNode, TextPanResolver};
214pub use text_input_session::PlatformTextInputHandler;
215pub use text_modifier_node::{TextModifierElement, TextModifierNode};
216pub use widgets::{
217    clickable_text::ClickableText,
218    lazy_list::{LazyColumn, LazyColumnSpec, LazyRow, LazyRowSpec},
219    linked_text::LinkedText,
220    popup::{dismiss_top_popup, dismissable_popup_open},
221    slider::{Slider, SliderOrientation, SliderScope, SliderSpec},
222    swipe_to_dismiss::{
223        SwipeDismissDirection, SwipeDismissSide, SwipeDismissState, SwipeToDismiss,
224        SwipeToDismissBox, SwipeToDismissSpec, rememberSwipeDismissState,
225    },
226    text_selection_menu::local_on_light_surface,
227};
228pub use zoom::ZoomState;
229
230/// In-memory composition helper used by tests.
231pub struct TestComposition {
232    _scope: render_state::AppContextScope,
233    app_context: Rc<AppContext>,
234    composition: Composition<MemoryApplier>,
235}
236
237impl TestComposition {
238    pub fn root(&self) -> Option<NodeId> {
239        self.app_context.enter(|| self.composition.root())
240    }
241
242    pub fn runtime_handle(&self) -> RuntimeHandle {
243        self.app_context.enter(|| self.composition.runtime_handle())
244    }
245
246    pub fn should_render(&self) -> bool {
247        self.app_context.enter(|| self.composition.should_render())
248    }
249
250    pub fn take_root_render_request(&mut self) -> bool {
251        let app_context = Rc::clone(&self.app_context);
252        app_context.enter(|| self.composition.take_root_render_request())
253    }
254
255    pub fn flush_pending_node_updates(&mut self) -> Result<(), NodeError> {
256        let app_context = Rc::clone(&self.app_context);
257        app_context.enter(|| self.composition.flush_pending_node_updates())
258    }
259
260    pub fn process_invalid_scopes(&mut self) -> Result<bool, NodeError> {
261        let app_context = Rc::clone(&self.app_context);
262        app_context.enter(|| self.composition.process_invalid_scopes())
263    }
264
265    pub fn render(&mut self, root_key: Key, content: impl FnMut()) -> Result<(), NodeError> {
266        let app_context = Rc::clone(&self.app_context);
267        app_context.enter(|| self.composition.render(root_key, content))
268    }
269
270    pub fn applier_mut(&mut self) -> TestApplierGuard<'_> {
271        let scope = self.app_context.enter_scope();
272        let applier = self.composition.applier_mut();
273        TestApplierGuard {
274            _scope: scope,
275            applier,
276        }
277    }
278
279    pub fn with_app_context<R>(&self, block: impl FnOnce() -> R) -> R {
280        self.app_context.enter(block)
281    }
282}
283
284pub struct TestApplierGuard<'a> {
285    _scope: render_state::AppContextScope,
286    applier: ApplierGuard<'a, MemoryApplier>,
287}
288
289impl Deref for TestApplierGuard<'_> {
290    type Target = MemoryApplier;
291
292    fn deref(&self) -> &Self::Target {
293        &self.applier
294    }
295}
296
297impl DerefMut for TestApplierGuard<'_> {
298    fn deref_mut(&mut self) -> &mut Self::Target {
299        &mut self.applier
300    }
301}
302
303/// Build a composition with a simple in-memory applier and run the provided closure once.
304pub fn run_test_composition(build: impl FnMut()) -> TestComposition {
305    let app_context = AppContext::new();
306    app_context.enter(|| {
307        #[cfg(test)]
308        reset_render_state_for_tests();
309    });
310    let mut test_composition = TestComposition {
311        _scope: app_context.enter_scope(),
312        app_context,
313        composition: Composition::new(MemoryApplier::new()),
314    };
315    test_composition
316        .render(location_key(file!(), line!(), column!()), build)
317        .expect("initial render succeeds");
318    test_composition
319}
320
321pub use cranpose_core::MutableState as SnapshotState;
322
323#[cfg(test)]
324#[path = "tests/anchor_async_tests.rs"]
325mod anchor_async_tests;
326
327#[cfg(test)]
328#[path = "tests/animated_visibility_tests.rs"]
329mod animated_visibility_tests;
330
331#[cfg(test)]
332#[path = "tests/lazy_recycle_effect_tests.rs"]
333mod lazy_recycle_effect_tests;
334
335#[cfg(test)]
336#[path = "tests/animation_frame_pump_tests.rs"]
337mod animation_frame_pump_tests;
338
339#[cfg(test)]
340#[path = "tests/crossfade_tests.rs"]
341mod crossfade_tests;
342
343#[cfg(test)]
344#[path = "tests/async_runtime_full_layout_test.rs"]
345mod async_runtime_full_layout_test;
346
347#[cfg(test)]
348#[path = "tests/cursor_position_tests.rs"]
349mod cursor_position_tests;
350
351#[cfg(test)]
352#[path = "tests/popup_tests.rs"]
353mod popup_tests;
354
355#[cfg(test)]
356#[path = "tests/selection_handle_tests.rs"]
357mod selection_handle_tests;
358
359#[cfg(test)]
360#[path = "tests/tab_switching_tests.rs"]
361mod tab_switching_tests;
362
363#[cfg(test)]
364#[path = "tests/lazy_list_viewport_tests.rs"]
365mod lazy_list_viewport_tests;
366
367#[cfg(test)]
368#[path = "tests/swipe_to_dismiss_lazy_tests.rs"]
369mod swipe_to_dismiss_lazy_tests;
370
371#[cfg(test)]
372#[path = "tests/swipe_to_dismiss_render_tests.rs"]
373mod swipe_to_dismiss_render_tests;
374
375#[cfg(test)]
376#[path = "tests/lazy_list_recompose_tests.rs"]
377mod lazy_list_recompose_tests;
378
379#[cfg(test)]
380#[path = "tests/lazy_row_tests.rs"]
381mod lazy_row_tests;
382
383#[cfg(test)]
384#[path = "tests/wear_widget_tests.rs"]
385mod wear_widget_tests;
386
387#[cfg(test)]
388#[path = "tests/ios_fling_measurement.rs"]
389mod ios_fling_measurement;