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