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