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