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