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