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