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