1#![deny(unsafe_code)]
4
5use cranpose_core::{location_key, ApplierGuard, MemoryApplier, NodeError, NodeId, RuntimeHandle};
6pub use cranpose_core::{Composition, Key};
7pub use cranpose_macros::composable;
8use std::ops::{Deref, DerefMut};
9use std::rc::Rc;
10
11pub mod bring_into_view;
12pub mod clipboard_session;
13mod cursor_animation;
14mod debug;
15mod draw;
16pub mod fling_animation;
17mod focus_dispatch;
18pub mod font_scale;
19mod interaction;
20mod key_event;
21pub mod layout;
22mod modifier;
23mod modifier_nodes;
24mod pointer_dispatch;
25mod primitives;
26mod render_state;
27mod renderer;
28pub mod round_scaling_list;
29pub mod round_scroll_indicator;
30pub mod safe_area;
31pub mod scroll;
32mod semantics_dispatch;
33mod subcompose_layout;
34pub mod text;
35pub mod text_field_focus;
36mod text_field_handler;
37mod text_field_input;
38mod text_field_modifier_node;
39pub mod text_input_session;
40pub mod text_layout_result;
41mod text_modifier_node;
42pub mod text_selection;
43pub mod widgets;
44mod word_boundaries;
45pub mod zoom;
46
47pub use text_field_focus::has_focused_field;
49pub use text_field_focus::ImeEditorState;
52pub use text_input_session::PlatformTextInputHandler;
55pub use cursor_animation::{
57 is_cursor_visible, next_cursor_blink_time, reset_cursor_blink, start_cursor_blink,
58 stop_cursor_blink, tick_cursor_blink,
59};
60
61pub use bring_into_view::{
62 local_bring_into_view_responder, scroll_delta_to_reveal, BringIntoViewResponder,
63};
64pub use cranpose_ui_graphics::{BlurredEdgeTreatment, ColorFilter, Dp, ImageBitmap, ImageSampling};
65pub use cranpose_ui_layout::IntrinsicSize;
66pub use draw::{
67 command_draw_scope, command_draw_scope_retained, command_draw_scope_reusing,
68 execute_draw_commands, DrawCacheBuilder, DrawCommand, DrawCommandFn,
69};
70pub use focus_dispatch::{
71 active_focus_target, clear_focus_invalidations, has_pending_focus_invalidations,
72 process_focus_invalidations, schedule_focus_invalidation, set_active_focus_target,
73};
74pub use interaction::{
75 collect_is_pressed_as_state, rememberMutableInteractionSource, Interaction,
76 MutableInteractionSource, PressInteraction, PressInteractionCancel, PressInteractionPress,
77 PressInteractionRelease,
78};
79pub use safe_area::{local_ime_insets, local_safe_area_insets};
80pub use cranpose_foundation::nodes::input::focus::FocusManager;
82pub use cranpose_foundation::{
83 DelegatableNode, ModifierNode, ModifierNodeElement, NodeCapabilities, NodeState,
84};
85pub use layout::{
86 build_layout_tree_from_applier, build_semantics_tree_from_applier,
87 build_semantics_tree_from_layout_tree,
88 core::{
89 Alignment, Arrangement, HorizontalAlignment, LinearArrangement, Measurable, Placeable,
90 VerticalAlignment,
91 },
92 measure_layout, measure_layout_with_options, tree_needs_layout, tree_needs_semantics,
93 LayoutAllocationDebugStats, LayoutBox, LayoutEngine, LayoutMeasurements, LayoutNodeData,
94 LayoutNodeKind, LayoutTree, MeasureLayoutOptions, SemanticsAction, SemanticsCallback,
95 SemanticsNode, SemanticsRole, SemanticsTree,
96};
97pub use cranpose_foundation::{
102 CanvasSemanticsNode, SemanticsConfiguration, SemanticsCustomAction, SemanticsWidgetRole,
103};
104pub use modifier::{
105 collect_modifier_slices, collect_semantics_from_modifier, collect_slices_from_modifier,
106 BlendMode, Brush, Color, CompositingStrategy, CornerRadii, DpOffset, EdgeInsets,
107 FocusDirection, FocusRequester, GlassMaterial, GraphicsLayer, LayerShape, Modifier,
108 ModifierNodeSlices, ModifierNodeSlicesDebugStats, Point, PointerEvent, PointerEventKind,
109 PointerInputScope, PointerSource, Rect, RenderEffect, ResolvedBackground, ResolvedModifiers,
110 RotaryInputModifierNode, RotaryScrollEvent, RoundedCornerShape, RuntimeShader,
111 SemanticsRequester, Shadow, ShadowScope, Size, TransformOrigin,
112};
113pub use modifier_nodes::{
114 AlphaElement, AlphaNode, BackgroundElement, BackgroundNode, ClickableElement, ClickableNode,
115 CornerShapeElement, CornerShapeNode, FillDirection, FillElement, FillNode,
116 FractionalOffsetElement, FractionalOffsetNode, OffsetElement, OffsetNode, PaddingElement,
117 PaddingNode, SizeElement, SizeNode,
118};
119pub use pointer_dispatch::{
120 clear_pointer_repasses, has_pending_pointer_repasses, process_pointer_repasses,
121 schedule_pointer_repass,
122};
123pub use primitives::{
124 fade_in, fade_out, remember_svg, slide_in_vertically, slide_out_vertically, AnimatedVisibility,
125 BasicText, BasicTextField, BasicTextFieldOptions, BasicTextFieldWithOptions,
126 BasicTextWithOptions, BitmapPainter, Box, BoxScope, BoxSpec, BoxWithConstraints,
127 BoxWithConstraintsScope, BoxWithConstraintsScopeImpl, Button, ButtonSpec, Canvas, Column,
128 ColumnSpec, ContentScale, Crossfade, EnterTransition, ExitTransition, ForEach, Image, Layout,
129 LayoutNode, Painter, Row, RowSpec, Spacer, SubcomposeLayout, SvgPainter, SvgPainterError, Text,
130 TextWithOptions, DEFAULT_ALPHA,
131};
132pub use cranpose_foundation::lazy::{LazyListItemInfo, LazyListLayoutInfo, LazyListState};
134pub use font_scale::{FontScaleCurve, MAX_FONT_SCALE_KNOTS};
135pub use key_event::{KeyCode, KeyEvent, KeyEventType, Modifiers};
136#[cfg(any(test, feature = "test-helpers"))]
137#[doc(hidden)]
138pub use render_state::reset_render_state_for_tests;
139pub use render_state::{
140 clear_transient_scroll_motion_contexts, current_density, current_font_scale,
141 current_font_scale_curve, debug_last_fling_velocity, debug_reset_last_fling_velocity,
142 has_current_app_context, has_pending_draw_repasses, has_pending_layout_repasses,
143 has_pending_measure_repasses, peek_focus_invalidation, peek_layout_invalidation,
144 peek_pointer_invalidation, peek_render_invalidation, pending_layout_repass_nodes_snapshot,
145 prune_draw_observations_to_nodes, request_focus_invalidation, request_layout_invalidation,
146 request_pointer_invalidation, request_render_invalidation, scale_sp, schedule_draw_repass,
147 schedule_layout_repass, schedule_measure_repass, set_density, set_font_scale,
148 set_font_scale_curve, take_draw_repass_nodes, take_focus_invalidation,
149 take_layout_invalidation, take_layout_repass_nodes, take_measure_repass_nodes,
150 take_pointer_invalidation, take_render_invalidation, AppContext, AppContextScope,
151 MAX_FONT_SCALE, MIN_FONT_SCALE,
152};
153pub use renderer::{HeadlessRenderer, PaintLayer, RecordedRenderScene, RenderOp};
154pub use scroll::{ScrollElement, ScrollNode, ScrollSettlePolicy, ScrollState};
155pub use semantics_dispatch::{
156 clear_semantics_invalidations, has_pending_semantics_invalidations,
157 process_semantics_invalidations, schedule_semantics_invalidation,
158};
159pub use zoom::ZoomState;
160#[cfg(feature = "test-helpers")]
162pub use modifier::{last_fling_velocity, reset_last_fling_velocity};
163pub use subcompose_layout::{
164 Constraints, MeasureResult, Placement, SubcomposeLayoutNode, SubcomposeLayoutScope,
165 SubcomposeMeasureScope, SubcomposeMeasureScopeImpl,
166};
167pub use text::{
168 get_cursor_x_for_offset, get_offset_for_position, layout_text, measure_text,
169 measure_text_for_node, measure_text_with_options, measure_text_with_options_for_node,
170 prepare_text_layout, prepare_text_layout_for_node, set_text_measurer, LinkAnnotation,
171 ParagraphStyle, PlatformParagraphStyle, PlatformSpanStyle, PlatformTextStyle,
172 PreparedTextLayout, SpanStyle, StringAnnotation, TextDrawStyle, TextLayoutOptions,
173 TextLayoutResult, TextLinePrefixWidths, TextMeasurer, TextMetrics, TextOptions, TextOverflow,
174 TextShaping, TextStyle,
175};
176pub use text_field_modifier_node::{TextFieldElement, TextFieldModifierNode, TextPanResolver};
177pub use text_modifier_node::{TextModifierElement, TextModifierNode};
178pub use widgets::clickable_text::ClickableText;
179pub use widgets::lazy_list::{LazyColumn, LazyColumnSpec, LazyRow, LazyRowSpec};
180pub use widgets::linked_text::LinkedText;
181pub use widgets::swipe_to_dismiss::{SwipeDismissSide, SwipeToDismiss, SwipeToDismissSpec};
182pub use widgets::text_selection_menu::local_on_light_surface;
183
184pub use debug::{
186 format_layout_tree, format_modifier_chain, format_render_scene, format_screen_summary,
187 install_modifier_chain_trace, log_layout_tree, log_modifier_chain, log_render_scene,
188 log_screen_summary, ModifierChainTraceGuard,
189};
190
191pub struct TestComposition {
193 _scope: render_state::AppContextScope,
194 app_context: Rc<AppContext>,
195 composition: Composition<MemoryApplier>,
196}
197
198impl TestComposition {
199 pub fn root(&self) -> Option<NodeId> {
200 self.app_context.enter(|| self.composition.root())
201 }
202
203 pub fn runtime_handle(&self) -> RuntimeHandle {
204 self.app_context.enter(|| self.composition.runtime_handle())
205 }
206
207 pub fn should_render(&self) -> bool {
208 self.app_context.enter(|| self.composition.should_render())
209 }
210
211 pub fn take_root_render_request(&mut self) -> bool {
212 let app_context = Rc::clone(&self.app_context);
213 app_context.enter(|| self.composition.take_root_render_request())
214 }
215
216 pub fn flush_pending_node_updates(&mut self) -> Result<(), NodeError> {
217 let app_context = Rc::clone(&self.app_context);
218 app_context.enter(|| self.composition.flush_pending_node_updates())
219 }
220
221 pub fn process_invalid_scopes(&mut self) -> Result<bool, NodeError> {
222 let app_context = Rc::clone(&self.app_context);
223 app_context.enter(|| self.composition.process_invalid_scopes())
224 }
225
226 pub fn render(&mut self, root_key: Key, content: impl FnMut()) -> Result<(), NodeError> {
227 let app_context = Rc::clone(&self.app_context);
228 app_context.enter(|| self.composition.render(root_key, content))
229 }
230
231 pub fn applier_mut(&mut self) -> TestApplierGuard<'_> {
232 let scope = self.app_context.enter_scope();
233 let applier = self.composition.applier_mut();
234 TestApplierGuard {
235 _scope: scope,
236 applier,
237 }
238 }
239
240 pub fn with_app_context<R>(&self, block: impl FnOnce() -> R) -> R {
241 self.app_context.enter(block)
242 }
243}
244
245pub struct TestApplierGuard<'a> {
246 _scope: render_state::AppContextScope,
247 applier: ApplierGuard<'a, MemoryApplier>,
248}
249
250impl Deref for TestApplierGuard<'_> {
251 type Target = MemoryApplier;
252
253 fn deref(&self) -> &Self::Target {
254 &self.applier
255 }
256}
257
258impl DerefMut for TestApplierGuard<'_> {
259 fn deref_mut(&mut self) -> &mut Self::Target {
260 &mut self.applier
261 }
262}
263
264pub fn run_test_composition(build: impl FnMut()) -> TestComposition {
266 let app_context = AppContext::new();
267 app_context.enter(|| {
268 #[cfg(test)]
269 reset_render_state_for_tests();
270 });
271 let mut test_composition = TestComposition {
272 _scope: app_context.enter_scope(),
273 app_context,
274 composition: Composition::new(MemoryApplier::new()),
275 };
276 test_composition
277 .render(location_key(file!(), line!(), column!()), build)
278 .expect("initial render succeeds");
279 test_composition
280}
281
282pub use cranpose_core::MutableState as SnapshotState;
283
284#[cfg(test)]
285#[path = "tests/anchor_async_tests.rs"]
286mod anchor_async_tests;
287
288#[cfg(test)]
289#[path = "tests/animated_visibility_tests.rs"]
290mod animated_visibility_tests;
291
292#[cfg(test)]
293#[path = "tests/lazy_recycle_effect_tests.rs"]
294mod lazy_recycle_effect_tests;
295
296#[cfg(test)]
297#[path = "tests/animation_frame_pump_tests.rs"]
298mod animation_frame_pump_tests;
299
300#[cfg(test)]
301#[path = "tests/crossfade_tests.rs"]
302mod crossfade_tests;
303
304#[cfg(test)]
305#[path = "tests/async_runtime_full_layout_test.rs"]
306mod async_runtime_full_layout_test;
307
308#[cfg(test)]
309#[path = "tests/cursor_position_tests.rs"]
310mod cursor_position_tests;
311
312#[cfg(test)]
313#[path = "tests/popup_tests.rs"]
314mod popup_tests;
315
316#[cfg(test)]
317#[path = "tests/selection_handle_tests.rs"]
318mod selection_handle_tests;
319
320#[cfg(test)]
321#[path = "tests/tab_switching_tests.rs"]
322mod tab_switching_tests;
323
324#[cfg(test)]
325#[path = "tests/lazy_list_viewport_tests.rs"]
326mod lazy_list_viewport_tests;
327
328#[cfg(test)]
329#[path = "tests/swipe_to_dismiss_lazy_tests.rs"]
330mod swipe_to_dismiss_lazy_tests;
331
332#[cfg(test)]
333#[path = "tests/swipe_to_dismiss_render_tests.rs"]
334mod swipe_to_dismiss_render_tests;
335
336#[cfg(test)]
337#[path = "tests/lazy_list_recompose_tests.rs"]
338mod lazy_list_recompose_tests;
339
340#[cfg(test)]
341#[path = "tests/wear_widget_tests.rs"]
342mod wear_widget_tests;