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