Skip to main content

cranpose_ui/
lib.rs

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