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