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