1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4extern crate self as cranpose_ui;
5
6use std::{
7 ops::{Deref, DerefMut},
8 rc::Rc,
9};
10
11use cranpose_core::{ApplierGuard, MemoryApplier, NodeError, NodeId, RuntimeHandle, location_key};
12pub use cranpose_core::{Composition, Key};
13pub use cranpose_macros::composable;
14#[cfg(feature = "localization")]
15pub use cranpose_macros::{message, tr, translation_messages, translations};
16#[cfg(feature = "localization")]
17pub use localization::{
18 Catalog, Language, LanguagePreference, Locale, LocalizedText, PreviewMode, ProvideLocalization,
19 ProvideTranslator,
20};
21pub use text::SharedText;
22#[cfg(feature = "localization")]
23mod ui_text;
24#[cfg(feature = "localization")]
25pub use ui_text::UiText;
26#[cfg(feature = "localization")]
27mod language_preferences;
28#[cfg(feature = "localization")]
29pub use language_preferences::{
30 LocalizationController, ProvideLanguagePreferences, local_localization,
31};
32pub use ui_strings::UiString;
33
34pub mod announce;
36pub mod bring_into_view;
37pub mod clipboard_session;
38mod cursor_animation;
39mod debug;
40pub mod density;
41pub mod draggable;
42mod draw;
43mod environment_locals;
44pub mod fling_animation;
45mod focus_dispatch;
46pub mod focus_manager;
48mod focus_navigation;
49pub mod focus_order;
51mod focus_within;
52pub mod font_scale;
53mod interaction;
54mod key_event;
55mod keyboard_options;
56pub mod layout;
58pub mod layout_direction;
59pub mod lazy_item;
60mod lazy_prefetch;
61#[cfg(feature = "localization")]
62pub mod localization;
63pub mod modal;
64mod modal_nodes;
65mod modifier;
66mod modifier_nodes;
67pub mod mouse_input;
68mod nested_scroll;
69pub mod nine_patch;
70mod pointer_dispatch;
71pub mod pointer_icon_session;
72mod primitives;
73mod render_state;
74mod renderer;
75pub mod round_scaling_list;
76pub mod round_scroll_indicator;
77pub mod safe_area;
78pub mod scroll;
79pub mod scrollbar;
80pub mod selection_container;
81mod semantics_dispatch;
82mod semantics_layout_log;
83mod subcompose_layout;
84pub mod text;
86mod text_field_decorator_node;
87pub mod text_field_focus;
88mod text_field_handler;
89mod text_field_input;
90mod text_field_modifier_node;
91mod text_field_presentation;
92pub mod text_input_session;
93pub mod text_layout_result;
94mod text_modifier_node;
95pub mod text_selection;
96mod thread_state;
97mod ui_strings;
98mod unhandled_keys;
99pub mod widgets;
100mod word_boundaries;
101pub mod zoom;
102
103#[doc(hidden)]
104pub use announce::{Announcement, Announcer, announce, drain_announcements, local_announcer};
105pub use bring_into_view::{
106 BringIntoViewResponder, local_bring_into_view_responder, scroll_delta_to_reveal,
107};
108pub use cranpose_foundation::{
109 CanvasSemanticsNode, CollectionInfo, DelegatableNode, LiveRegionMode, ModifierNode,
110 ModifierNodeElement, NodeCapabilities, NodeState, ProgressBarRangeInfo, ScrollAxisRange,
111 SemanticsConfiguration, SemanticsCustomAction, SemanticsDismiss, SemanticsExpand,
112 SemanticsLongClick, SemanticsMagicTap, SemanticsScrollBy, SemanticsScrollToIndex,
113 SemanticsSetProgress, SemanticsSetSelection, SemanticsSetText, SemanticsSpec,
114 SemanticsWidgetRole, ToggleableState,
115 lazy::{
116 LazyItems, LazyListItemInfo, LazyListLayoutInfo, LazyListScope, LazyListState,
117 rememberLazyListState, rememberLazyListStateWithPosition,
118 },
119 text::{TextFieldBuffer, TextFieldLineLimits, TextFieldState, TextFieldValue, TextRange},
120};
121pub use cranpose_ui_graphics::{
122 BlurredEdgeTreatment, ColorFilter, CursorIcon, CustomPointerIcon, DashPathEffect, Dp,
123 DrawScope, DrawSpecialization, DrawStyle, ExternalTexture, ImageBitmap, ImageBitmapError,
124 ImagePixelFormat, ImageSampling, Path, PlaceholderShape, PointerIcon, PointerIconError,
125 RuntimeShaderUniformError, ShaderPlaceholder, Stroke, StrokeCap, StrokeJoin, SubstrateSpec,
126 TileMode, WindowCoordinates, glass_material_folds_enabled, set_glass_material_folds,
127};
128pub use cranpose_ui_layout::{AlignmentLines, IntrinsicSize, Measurement};
129pub use cursor_animation::{
130 is_cursor_visible, next_cursor_blink_time, reset_cursor_blink, start_cursor_blink,
131 stop_cursor_blink, tick_cursor_blink,
132};
133pub use debug::{
134 ModifierChainTraceGuard, format_layout_tree, format_modifier_chain, format_render_scene,
135 format_screen_summary, install_modifier_chain_trace, log_layout_tree, log_modifier_chain,
136 log_render_scene, log_screen_summary,
137};
138pub use density::{Density, DensityMeasureScope, density, local_density};
139pub use draggable::{
140 DragDeltaHandler, DraggableState, ScrollableState, rememberDraggableState,
141 rememberScrollableState,
142};
143pub use draw::{
144 DrawCacheBuilder, DrawCommand, DrawCommandFn, command_draw_scope, command_draw_scope_reusing,
145 execute_draw_commands,
146};
147pub use focus_dispatch::{
148 active_focus_target, clear_focus_invalidations, has_pending_focus_invalidations,
149 keyboard_focus_visible, process_focus_invalidations, schedule_focus_invalidation,
150 set_active_focus_target, set_keyboard_focus_visible,
151};
152pub use focus_manager::{FocusManager, local_focus_manager, request_focus_from_platform};
153pub use focus_navigation::{KeyboardFocusTarget, keyboard_focus_target};
154pub use focus_order::{
155 FocusEntry, collect_focus_order, collect_focus_order_under, focus_order_len,
156 selectable_group_of, set_focus_order, take_requested_focus_move,
157};
158pub use focus_within::FocusWithin;
159pub use font_scale::{FontScaleCurve, MAX_FONT_SCALE_KNOTS};
160pub use interaction::{
161 HoverInteraction, HoverInteractionEnter, HoverInteractionExit, Interaction,
162 MutableInteractionSource, PressInteraction, PressInteractionCancel, PressInteractionPress,
163 PressInteractionRelease, collect_is_hovered_as_state, collect_is_pressed_as_state,
164 rememberMutableInteractionSource,
165};
166pub use key_event::{KeyCode, KeyEvent, KeyEventType, Modifiers};
167pub use keyboard_options::{
168 ContentType, ImeAction, KeyboardActionHandler, KeyboardActionScope, KeyboardCapitalization,
169 KeyboardOptions, KeyboardType,
170};
171pub use layout::{
172 LayoutAllocationDebugStats, LayoutBox, LayoutEngine, LayoutMeasurements, LayoutNodeData,
173 LayoutNodeKind, LayoutTree, MeasureLayoutOptions, SemanticsAction, SemanticsCallback,
174 SemanticsDetails, SemanticsNode, SemanticsPlacement, SemanticsRole, SemanticsText,
175 SemanticsTree, build_layout_tree_from_applier, build_semantics_tree_from_applier,
176 build_semantics_tree_from_layout_tree,
177 core::{
178 Alignment, Arrangement, HorizontalAlignment, LinearArrangement, Measurable, Placeable,
179 VerticalAlignment,
180 },
181 has_placed_content, measure_layout, measure_layout_with_options, placed_content_extent,
182 top_modal_from_applier, tree_needs_layout, tree_needs_semantics,
183 update_semantics_tree_from_applier,
184};
185pub use layout_direction::{
186 LayoutDirection, ProvideLayoutDirection, layout_direction, local_layout_direction,
187};
188pub use lazy_item::{ProvideLazyItemKey, lazy_item_key, local_lazy_item_key};
189pub use lazy_prefetch::{
190 drain_lazy_prefetch_requests, has_lazy_prefetch_requests, lazy_prefetch_pass_cost,
191 set_idle_prefetch_passes, warm_prefetched_slices, with_lazy_prefetch_pass,
192};
193pub use modal::{
194 ModalRegistration, clear_modals, dispatch_modal_back, local_modal_depth, modal_depth,
195};
196pub use modal_nodes::modal_node_may_be_live;
197pub use modifier::{
198 BlendMode, Brush, Color, CompositingStrategy, CornerRadii, DpOffset, DragAndDropEvent,
199 DragAndDropOutcome, DragAndDropPayload, DragAndDropPoint, DragAndDropSource,
200 DragAndDropSourceElement, DragAndDropSourceNode, DragAndDropState, DragAndDropTarget,
201 DragAndDropTargetElement, DragAndDropTargetNode, EdgeInsets, FocusDirection, FocusRequestError,
202 FocusRequester, GlassMaterial, GraphicsLayer, KeyEventRoute, LayerShape, Modifier,
203 ModifierLocalKey, ModifierLocalReadScope, ModifierNodeSlices, ModifierNodeSlicesDebugStats,
204 Point, PointerEvent, PointerEventKind, PointerInputScope, PointerSource, Rect, RenderEffect,
205 ResolvedBackground, ResolvedModifiers, RotaryInputModifierNode, RotaryScrollEvent,
206 RoundedCornerShape, RuntimeShader, SemanticsRequester, Shadow, ShadowScope, Size, SliceTextRef,
207 TransformOrigin, WindowRootDescriptor, WindowRootElement, WindowRootEntry, WindowRootNode,
208 WindowRootRegistry, WindowRootRoutingScratch, collect_modifier_slices,
209 collect_semantics_from_modifier, collect_slices_from_modifier, is_window_root,
210 nearest_window_root, nearest_window_roots, nearest_window_roots_into, window_roots,
211 window_roots_revision, z_index_in_use,
212};
213#[cfg(feature = "test-helpers")]
214pub use modifier::{last_fling_velocity, reset_last_fling_velocity};
215pub use modifier_nodes::{
216 AlphaElement, AlphaNode, BackgroundElement, BackgroundNode, ClickableElement, ClickableNode,
217 CornerShapeElement, CornerShapeNode, FillDirection, FillElement, FillNode,
218 FractionalOffsetElement, FractionalOffsetNode, OffsetElement, OffsetNode, PaddingElement,
219 PaddingNode, SizeElement, SizeNode,
220};
221pub use nested_scroll::{NestedScroll, NestedScrollConnection, NestedScrollSource, ScrollAxis};
222pub use nine_patch::{
223 NinePatchInsets, PatchFill, PatchQuad, nine_patch_quads, tile_count, tile_quads,
224};
225#[doc(hidden)]
226pub use pointer_dispatch::{
227 clear_pointer_repasses, has_pending_pointer_repasses, process_pointer_repasses,
228 schedule_pointer_repass,
229};
230pub use primitives::{
231 AnimateScroll, AnimatedContent, AnimatedVisibility, BasicSecureTextField, BasicText,
232 BasicTextField, BasicTextFieldDecorated, BasicTextFieldDecorationScope, BasicTextFieldOptions,
233 BasicTextFieldWithOptions, BasicTextWithOptions, BitmapPainter, BitmapRegionPainter, Box,
234 BoxScope, BoxSpec, BoxWithConstraints, BoxWithConstraintsScope, BoxWithConstraintsScopeImpl,
235 Button, ButtonSpec, Canvas, Column, ColumnSpec, ContentScale, ContentSeek, ContentTransform,
236 Crossfade, DEFAULT_ALPHA, EnterTransition, ExitTransition, ForEach, HorizontalPager, Image,
237 Layout, LayoutNode, PageSize, PagerSpec, PagerState, Painter, Row, RowSpec,
238 SecureTextFieldOptions, SeekableAnimatedContent, SeekableCrossfade, SharedElement,
239 SharedTransitionLayout, SizeTransform, Spacer, SubcomposeLayout, SvgPainter, SvgPainterError,
240 Text, TextObfuscationMode, TextWithOptions, VerticalPager, fade_in, fade_out,
241 rememberPagerState, rememberSvg, scale_in, scale_out, slide_in_horizontally,
242 slide_in_vertically, slide_out_horizontally, slide_out_vertically,
243};
244#[cfg(any(test, feature = "test-helpers"))]
245#[doc(hidden)]
246pub use render_state::reset_render_state_for_tests;
247pub use render_state::{
248 AppContext, AppContextScope, MAX_FONT_SCALE, MIN_FONT_SCALE, current_density,
249 current_font_scale, current_font_scale_curve, scale_sp, set_density, set_font_scale,
250 set_font_scale_curve,
251};
252#[doc(hidden)]
253pub use render_state::{
254 GeometrySceneNodes, append_z_order_changes, clear_transient_scroll_motion_contexts,
255 debug_last_fling_velocity, debug_reset_last_fling_velocity, has_current_app_context,
256 has_pending_draw_repasses, has_pending_layer_property_repasses, has_pending_layout_repasses,
257 has_pending_measure_repasses, peek_focus_invalidation, peek_layout_invalidation,
258 peek_pointer_invalidation, peek_render_invalidation, pending_layout_repass_nodes_snapshot,
259 pending_measure_repass_nodes_snapshot, prune_draw_observations_to_nodes,
260 request_current_draw_redraw, request_focus_invalidation, request_layout_invalidation,
261 request_pointer_invalidation, request_render_invalidation, schedule_draw_repass,
262 schedule_layout_repass, schedule_measure_repass, schedule_z_order_change,
263 take_draw_repass_nodes, take_draw_repass_nodes_into, take_focus_invalidation,
264 take_geometry_scene_nodes, take_geometry_scene_nodes_into,
265 take_layer_property_repass_nodes_into, take_layout_invalidation, take_layout_repass_nodes,
266 take_measure_repass_nodes, take_pointer_invalidation, take_render_invalidation,
267};
268pub use renderer::{HeadlessRenderer, PaintLayer, RecordedRenderScene, RenderOp};
269pub use safe_area::{WindowInsets, local_ime_insets, local_safe_area_insets, window_insets};
270pub use scroll::{ScrollElement, ScrollMetrics, ScrollNode, ScrollSettlePolicy, ScrollState};
271pub use scrollbar::{ThumbBounds, ThumbGeometry, content_delta_for_thumb_drag, thumb_geometry};
272#[doc(hidden)]
273pub use semantics_dispatch::{
274 clear_semantics_invalidations, has_pending_semantics_invalidations,
275 process_semantics_invalidations, schedule_semantics_invalidation,
276};
277pub use subcompose_layout::{
278 Constraints, MeasureResult, Placement, SubcomposeLayoutNode, SubcomposeLayoutScope,
279 SubcomposeMeasureScope, SubcomposeMeasureScopeImpl, clean_slot_skip_count,
280};
281pub use text::{
282 LinkAnnotation, ParagraphStyle, PlatformParagraphStyle, PlatformSpanStyle, PlatformTextStyle,
283 PreparedTextLayout, SpanStyle, StringAnnotation, TextDrawStyle, TextLayoutOptions,
284 TextLayoutResult, TextLinePrefixWidths, TextMeasurer, TextMetrics, TextOptions, TextOverflow,
285 TextShaping, TextStyle, get_cursor_x_for_offset, get_offset_for_position, layout_text,
286 measure_text, measure_text_for_node, measure_text_with_options,
287 measure_text_with_options_for_node, prepare_text_layout, prepare_text_layout_for_node,
288 set_text_measurer,
289};
290pub use text_field_focus::{ImeEditorState, has_focused_field};
291pub use text_field_modifier_node::{TextFieldElement, TextFieldModifierNode, TextPanResolver};
292pub use text_input_session::PlatformTextInputHandler;
293pub use text_modifier_node::{TextModifierElement, TextModifierNode};
294pub use unhandled_keys::{UnhandledKeyEvents, dispatch_unhandled_key_event};
295pub use widgets::{
296 clickable_text::ClickableText,
297 lazy_list::{
298 GridCells, LazyColumn, LazyColumnSpec, LazyGridState, LazyHorizontalGrid,
299 LazyHorizontalGridSpec, LazyHorizontalStaggeredGrid, LazyHorizontalStaggeredGridSpec,
300 LazyRow, LazyRowSpec, LazyStaggeredGridState, LazyVerticalGrid, LazyVerticalGridSpec,
301 LazyVerticalStaggeredGrid, LazyVerticalStaggeredGridSpec, rememberLazyGridState,
302 rememberLazyStaggeredGridState,
303 },
304 linked_text::LinkedText,
305 padding_values::PaddingValues,
306 popup::{dismiss_top_popup, dismissable_popup_open},
307 slider::{Slider, SliderOrientation, SliderScope, SliderSpec},
308 swipe_to_dismiss::{
309 SwipeDismissDirection, SwipeDismissSide, SwipeDismissState, SwipeToDismiss,
310 SwipeToDismissBox, SwipeToDismissSpec, rememberSwipeDismissState,
311 },
312 text_selection_menu::local_on_light_surface,
313};
314pub use zoom::ZoomState;
315
316pub struct TestComposition {
318 _scope: render_state::AppContextScope,
319 app_context: Rc<AppContext>,
320 composition: Composition<MemoryApplier>,
321}
322
323impl TestComposition {
324 pub fn root(&self) -> Option<NodeId> {
326 self.app_context.enter(|| self.composition.root())
327 }
328
329 pub fn runtime_handle(&self) -> RuntimeHandle {
331 self.app_context.enter(|| self.composition.runtime_handle())
332 }
333
334 pub fn should_render(&self) -> bool {
336 self.app_context.enter(|| self.composition.should_render())
337 }
338
339 pub fn take_root_render_request(&mut self) -> bool {
342 let app_context = Rc::clone(&self.app_context);
343 app_context.enter(|| self.composition.take_root_render_request())
344 }
345
346 pub fn flush_pending_node_updates(&mut self) -> Result<(), NodeError> {
348 let app_context = Rc::clone(&self.app_context);
349 app_context.enter(|| self.composition.flush_pending_node_updates())
350 }
351
352 pub fn process_invalid_scopes(&mut self) -> Result<bool, NodeError> {
355 let app_context = Rc::clone(&self.app_context);
356 app_context.enter(|| self.composition.process_invalid_scopes())
357 }
358
359 pub fn render(&mut self, root_key: Key, content: impl FnMut()) -> Result<(), NodeError> {
362 let app_context = Rc::clone(&self.app_context);
363 app_context.enter(|| self.composition.render(root_key, content))
364 }
365
366 pub fn applier_mut(&mut self) -> TestApplierGuard<'_> {
369 let scope = self.app_context.enter_scope();
370 let applier = self.composition.applier_mut();
371 TestApplierGuard {
372 _scope: scope,
373 applier,
374 }
375 }
376
377 pub fn with_app_context<R>(&self, block: impl FnOnce() -> R) -> R {
379 self.app_context.enter(block)
380 }
381}
382
383pub struct TestApplierGuard<'a> {
386 _scope: render_state::AppContextScope,
387 applier: ApplierGuard<'a, MemoryApplier>,
388}
389
390impl Deref for TestApplierGuard<'_> {
391 type Target = MemoryApplier;
392
393 fn deref(&self) -> &Self::Target {
394 &self.applier
395 }
396}
397
398impl DerefMut for TestApplierGuard<'_> {
399 fn deref_mut(&mut self) -> &mut Self::Target {
400 &mut self.applier
401 }
402}
403
404pub fn run_test_composition(build: impl FnMut()) -> TestComposition {
406 let app_context = AppContext::new();
407 app_context.enter(|| {
408 #[cfg(test)]
409 reset_render_state_for_tests();
410 });
411 let mut test_composition = TestComposition {
412 _scope: app_context.enter_scope(),
413 app_context,
414 composition: Composition::new(MemoryApplier::new()),
415 };
416 test_composition
417 .render(location_key(file!(), line!(), column!()), build)
418 .expect("initial render succeeds");
419 test_composition
420}
421
422pub use cranpose_core::MutableState as SnapshotState;
423
424#[cfg(test)]
425#[path = "tests/anchor_async_tests.rs"]
426mod anchor_async_tests;
427
428#[cfg(test)]
429#[path = "tests/animated_visibility_tests.rs"]
430mod animated_visibility_tests;
431
432#[cfg(test)]
433#[path = "tests/lazy_recycle_effect_tests.rs"]
434mod lazy_recycle_effect_tests;
435
436#[cfg(test)]
437#[path = "tests/measure_time_ui_task_tests.rs"]
438mod measure_time_ui_task_tests;
439
440#[cfg(test)]
441#[path = "tests/animation_frame_pump_tests.rs"]
442mod animation_frame_pump_tests;
443
444#[cfg(test)]
445#[path = "tests/crossfade_tests.rs"]
446mod crossfade_tests;
447
448#[cfg(test)]
449#[path = "tests/async_runtime_full_layout_test.rs"]
450mod async_runtime_full_layout_test;
451
452#[cfg(test)]
453#[path = "tests/cursor_position_tests.rs"]
454mod cursor_position_tests;
455
456#[cfg(test)]
457#[path = "tests/popup_tests.rs"]
458mod popup_tests;
459
460#[cfg(test)]
461#[path = "tests/selection_handle_tests.rs"]
462mod selection_handle_tests;
463
464#[cfg(test)]
465#[path = "tests/tab_switching_tests.rs"]
466mod tab_switching_tests;
467
468#[cfg(test)]
469#[path = "tests/layout_keep_tests.rs"]
470mod layout_keep_tests;
471
472#[cfg(test)]
473#[path = "tests/lazy_list_viewport_tests.rs"]
474mod lazy_list_viewport_tests;
475
476#[cfg(test)]
477#[path = "tests/swipe_to_dismiss_lazy_tests.rs"]
478mod swipe_to_dismiss_lazy_tests;
479
480#[cfg(test)]
481#[path = "tests/top_modal_tests.rs"]
482mod top_modal_tests;
483
484#[cfg(test)]
485#[path = "tests/swipe_to_dismiss_render_tests.rs"]
486mod swipe_to_dismiss_render_tests;
487
488#[cfg(test)]
489#[path = "tests/lazy_list_recompose_tests.rs"]
490mod lazy_list_recompose_tests;
491
492#[cfg(test)]
493#[path = "tests/lazy_row_tests.rs"]
494mod lazy_row_tests;
495
496#[cfg(test)]
497#[path = "tests/wear_widget_tests.rs"]
498mod wear_widget_tests;
499
500#[cfg(test)]
501#[path = "tests/ios_fling_measurement.rs"]
502mod ios_fling_measurement;