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