cranpose-ui 0.1.118

UI primitives for Cranpose
Documentation
//! High level UI primitives built on top of the Compose core runtime.

use std::{
    ops::{Deref, DerefMut},
    rc::Rc,
};

use cranpose_core::{ApplierGuard, MemoryApplier, NodeError, NodeId, RuntimeHandle, location_key};
pub use cranpose_core::{Composition, Key};
pub use cranpose_macros::composable;

pub mod bring_into_view;
pub mod clipboard_session;
mod cursor_animation;
mod debug;
pub mod density;
pub mod draggable;
mod draw;
pub mod fling_animation;
mod focus_dispatch;
pub mod font_scale;
mod interaction;
mod key_event;
pub mod layout;
pub mod layout_direction;
pub mod lazy_item;
pub mod modal;
mod modifier;
mod modifier_nodes;
pub mod nine_patch;
mod pointer_dispatch;
mod primitives;
mod render_state;
mod renderer;
pub mod round_scaling_list;
pub mod round_scroll_indicator;
pub mod safe_area;
pub mod scroll;
pub mod scrollbar;
mod semantics_dispatch;
mod subcompose_layout;
pub mod text;
pub mod text_field_focus;
mod text_field_handler;
mod text_field_input;
mod text_field_modifier_node;
pub mod text_input_session;
pub mod text_layout_result;
mod text_modifier_node;
pub mod text_selection;
pub mod widgets;
mod word_boundaries;
pub mod zoom;

pub use bring_into_view::{
    BringIntoViewResponder, local_bring_into_view_responder, scroll_delta_to_reveal,
};
pub use cranpose_foundation::{
    CanvasSemanticsNode, DelegatableNode, ModifierNode, ModifierNodeElement, NodeCapabilities,
    NodeState, SemanticsConfiguration, SemanticsCustomAction, SemanticsWidgetRole,
    lazy::{
        LazyItems, LazyListItemInfo, LazyListLayoutInfo, LazyListScope, LazyListState,
        rememberLazyListState, rememberLazyListStateWithPosition,
    },
    text::{TextFieldBuffer, TextFieldLineLimits, TextFieldState, TextFieldValue, TextRange},
};
pub use cranpose_ui_graphics::{BlurredEdgeTreatment, ColorFilter, Dp, ImageBitmap, ImageSampling};
pub use cranpose_ui_layout::IntrinsicSize;
pub use cursor_animation::{
    is_cursor_visible, next_cursor_blink_time, reset_cursor_blink, start_cursor_blink,
    stop_cursor_blink, tick_cursor_blink,
};
pub use debug::{
    ModifierChainTraceGuard, format_layout_tree, format_modifier_chain, format_render_scene,
    format_screen_summary, install_modifier_chain_trace, log_layout_tree, log_modifier_chain,
    log_render_scene, log_screen_summary,
};
pub use density::{Density, DensityMeasureScope, density, local_density};
pub use draggable::{DragDeltaHandler, DraggableState, rememberDraggableState};
pub use draw::{
    DrawCacheBuilder, DrawCommand, DrawCommandFn, command_draw_scope, command_draw_scope_reusing,
    execute_draw_commands,
};
#[doc(hidden)]
pub use focus_dispatch::{
    active_focus_target, clear_focus_invalidations, has_pending_focus_invalidations,
    process_focus_invalidations, schedule_focus_invalidation, set_active_focus_target,
};
pub use font_scale::{FontScaleCurve, MAX_FONT_SCALE_KNOTS};
pub use interaction::{
    Interaction, MutableInteractionSource, PressInteraction, PressInteractionCancel,
    PressInteractionPress, PressInteractionRelease, collect_is_pressed_as_state,
    rememberMutableInteractionSource,
};
pub use key_event::{KeyCode, KeyEvent, KeyEventType, Modifiers};
pub use layout::{
    LayoutAllocationDebugStats, LayoutBox, LayoutEngine, LayoutMeasurements, LayoutNodeData,
    LayoutNodeKind, LayoutTree, MeasureLayoutOptions, SemanticsAction, SemanticsCallback,
    SemanticsNode, SemanticsRole, SemanticsTree, build_layout_tree_from_applier,
    build_semantics_tree_from_applier, build_semantics_tree_from_layout_tree,
    core::{
        Alignment, Arrangement, HorizontalAlignment, LinearArrangement, Measurable, Placeable,
        VerticalAlignment,
    },
    measure_layout, measure_layout_with_options, tree_needs_layout, tree_needs_semantics,
};
pub use layout_direction::{
    LayoutDirection, ProvideLayoutDirection, layout_direction, local_layout_direction,
};
pub use lazy_item::{ProvideLazyItemKey, lazy_item_key, local_lazy_item_key};
pub use modal::{
    ModalRegistration, clear_modals, dispatch_modal_back, local_modal_depth, modal_depth,
};
pub use modifier::{
    BlendMode, Brush, Color, CompositingStrategy, CornerRadii, DpOffset, EdgeInsets,
    FocusDirection, FocusRequestError, FocusRequester, GlassMaterial, GraphicsLayer, LayerShape,
    Modifier, ModifierLocalKey, ModifierLocalReadScope, ModifierNodeSlices,
    ModifierNodeSlicesDebugStats, Point, PointerEvent, PointerEventKind, PointerInputScope,
    PointerSource, Rect, RenderEffect, ResolvedBackground, ResolvedModifiers,
    RotaryInputModifierNode, RotaryScrollEvent, RoundedCornerShape, RuntimeShader,
    SemanticsRequester, Shadow, ShadowScope, Size, TransformOrigin, collect_modifier_slices,
    collect_semantics_from_modifier, collect_slices_from_modifier,
};
#[cfg(feature = "test-helpers")]
pub use modifier::{last_fling_velocity, reset_last_fling_velocity};
pub use modifier_nodes::{
    AlphaElement, AlphaNode, BackgroundElement, BackgroundNode, ClickableElement, ClickableNode,
    CornerShapeElement, CornerShapeNode, FillDirection, FillElement, FillNode,
    FractionalOffsetElement, FractionalOffsetNode, OffsetElement, OffsetNode, PaddingElement,
    PaddingNode, SizeElement, SizeNode,
};
pub use nine_patch::{
    NinePatchInsets, PatchFill, PatchQuad, nine_patch_quads, tile_count, tile_quads,
};
#[doc(hidden)]
pub use pointer_dispatch::{
    clear_pointer_repasses, has_pending_pointer_repasses, process_pointer_repasses,
    schedule_pointer_repass,
};
pub use primitives::{
    AnimatedVisibility, BasicText, BasicTextField, BasicTextFieldDecorated,
    BasicTextFieldDecorationScope, BasicTextFieldOptions, BasicTextFieldWithOptions,
    BasicTextWithOptions, BitmapPainter, BitmapRegionPainter, Box, BoxScope, BoxSpec,
    BoxWithConstraints, BoxWithConstraintsScope, BoxWithConstraintsScopeImpl, Button, ButtonSpec,
    Canvas, Column, ColumnSpec, ContentScale, Crossfade, DEFAULT_ALPHA, EnterTransition,
    ExitTransition, ForEach, Image, Layout, LayoutNode, Painter, Row, RowSpec, Spacer,
    SubcomposeLayout, SvgPainter, SvgPainterError, Text, TextWithOptions, fade_in, fade_out,
    rememberSvg, slide_in_vertically, slide_out_vertically,
};
#[cfg(any(test, feature = "test-helpers"))]
#[doc(hidden)]
pub use render_state::reset_render_state_for_tests;
pub use render_state::{
    AppContext, AppContextScope, MAX_FONT_SCALE, MIN_FONT_SCALE, current_density,
    current_font_scale, current_font_scale_curve, scale_sp, set_density, set_font_scale,
    set_font_scale_curve,
};
#[doc(hidden)]
pub use render_state::{
    clear_transient_scroll_motion_contexts, debug_last_fling_velocity,
    debug_reset_last_fling_velocity, has_current_app_context, has_pending_draw_repasses,
    has_pending_layout_repasses, has_pending_measure_repasses, peek_focus_invalidation,
    peek_layout_invalidation, peek_pointer_invalidation, peek_render_invalidation,
    pending_layout_repass_nodes_snapshot, pending_measure_repass_nodes_snapshot,
    prune_draw_observations_to_nodes, request_current_draw_redraw, request_focus_invalidation,
    request_layout_invalidation, request_pointer_invalidation, request_render_invalidation,
    schedule_draw_repass, schedule_layout_repass, schedule_measure_repass, take_draw_repass_nodes,
    take_focus_invalidation, take_geometry_scene_nodes, take_layout_invalidation,
    take_layout_repass_nodes, take_measure_repass_nodes, take_pointer_invalidation,
    take_render_invalidation,
};
pub use renderer::{HeadlessRenderer, PaintLayer, RecordedRenderScene, RenderOp};
pub use safe_area::{WindowInsets, local_ime_insets, local_safe_area_insets, window_insets};
pub use scroll::{ScrollElement, ScrollMetrics, ScrollNode, ScrollSettlePolicy, ScrollState};
pub use scrollbar::{ThumbBounds, ThumbGeometry, content_delta_for_thumb_drag, thumb_geometry};
#[doc(hidden)]
pub use semantics_dispatch::{
    clear_semantics_invalidations, has_pending_semantics_invalidations,
    process_semantics_invalidations, schedule_semantics_invalidation,
};
pub use subcompose_layout::{
    Constraints, MeasureResult, Placement, SubcomposeLayoutNode, SubcomposeLayoutScope,
    SubcomposeMeasureScope, SubcomposeMeasureScopeImpl, clean_slot_skip_count,
};
pub use text::{
    LinkAnnotation, ParagraphStyle, PlatformParagraphStyle, PlatformSpanStyle, PlatformTextStyle,
    PreparedTextLayout, SpanStyle, StringAnnotation, TextDrawStyle, TextLayoutOptions,
    TextLayoutResult, TextLinePrefixWidths, TextMeasurer, TextMetrics, TextOptions, TextOverflow,
    TextShaping, TextStyle, get_cursor_x_for_offset, get_offset_for_position, layout_text,
    measure_text, measure_text_for_node, measure_text_with_options,
    measure_text_with_options_for_node, prepare_text_layout, prepare_text_layout_for_node,
    set_text_measurer,
};
pub use text_field_focus::{ImeEditorState, has_focused_field};
pub use text_field_modifier_node::{TextFieldElement, TextFieldModifierNode, TextPanResolver};
pub use text_input_session::PlatformTextInputHandler;
pub use text_modifier_node::{TextModifierElement, TextModifierNode};
pub use widgets::{
    clickable_text::ClickableText,
    lazy_list::{LazyColumn, LazyColumnSpec, LazyRow, LazyRowSpec},
    linked_text::LinkedText,
    slider::{Slider, SliderOrientation, SliderScope, SliderSpec},
    swipe_to_dismiss::{
        SwipeDismissDirection, SwipeDismissSide, SwipeDismissState, SwipeToDismiss,
        SwipeToDismissBox, SwipeToDismissSpec, rememberSwipeDismissState,
    },
    text_selection_menu::local_on_light_surface,
};
pub use zoom::ZoomState;

/// In-memory composition helper used by tests.
pub struct TestComposition {
    _scope: render_state::AppContextScope,
    app_context: Rc<AppContext>,
    composition: Composition<MemoryApplier>,
}

impl TestComposition {
    pub fn root(&self) -> Option<NodeId> {
        self.app_context.enter(|| self.composition.root())
    }

    pub fn runtime_handle(&self) -> RuntimeHandle {
        self.app_context.enter(|| self.composition.runtime_handle())
    }

    pub fn should_render(&self) -> bool {
        self.app_context.enter(|| self.composition.should_render())
    }

    pub fn take_root_render_request(&mut self) -> bool {
        let app_context = Rc::clone(&self.app_context);
        app_context.enter(|| self.composition.take_root_render_request())
    }

    pub fn flush_pending_node_updates(&mut self) -> Result<(), NodeError> {
        let app_context = Rc::clone(&self.app_context);
        app_context.enter(|| self.composition.flush_pending_node_updates())
    }

    pub fn process_invalid_scopes(&mut self) -> Result<bool, NodeError> {
        let app_context = Rc::clone(&self.app_context);
        app_context.enter(|| self.composition.process_invalid_scopes())
    }

    pub fn render(&mut self, root_key: Key, content: impl FnMut()) -> Result<(), NodeError> {
        let app_context = Rc::clone(&self.app_context);
        app_context.enter(|| self.composition.render(root_key, content))
    }

    pub fn applier_mut(&mut self) -> TestApplierGuard<'_> {
        let scope = self.app_context.enter_scope();
        let applier = self.composition.applier_mut();
        TestApplierGuard {
            _scope: scope,
            applier,
        }
    }

    pub fn with_app_context<R>(&self, block: impl FnOnce() -> R) -> R {
        self.app_context.enter(block)
    }
}

pub struct TestApplierGuard<'a> {
    _scope: render_state::AppContextScope,
    applier: ApplierGuard<'a, MemoryApplier>,
}

impl Deref for TestApplierGuard<'_> {
    type Target = MemoryApplier;

    fn deref(&self) -> &Self::Target {
        &self.applier
    }
}

impl DerefMut for TestApplierGuard<'_> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.applier
    }
}

/// Build a composition with a simple in-memory applier and run the provided closure once.
pub fn run_test_composition(build: impl FnMut()) -> TestComposition {
    let app_context = AppContext::new();
    app_context.enter(|| {
        #[cfg(test)]
        reset_render_state_for_tests();
    });
    let mut test_composition = TestComposition {
        _scope: app_context.enter_scope(),
        app_context,
        composition: Composition::new(MemoryApplier::new()),
    };
    test_composition
        .render(location_key(file!(), line!(), column!()), build)
        .expect("initial render succeeds");
    test_composition
}

pub use cranpose_core::MutableState as SnapshotState;

#[cfg(test)]
#[path = "tests/anchor_async_tests.rs"]
mod anchor_async_tests;

#[cfg(test)]
#[path = "tests/animated_visibility_tests.rs"]
mod animated_visibility_tests;

#[cfg(test)]
#[path = "tests/lazy_recycle_effect_tests.rs"]
mod lazy_recycle_effect_tests;

#[cfg(test)]
#[path = "tests/animation_frame_pump_tests.rs"]
mod animation_frame_pump_tests;

#[cfg(test)]
#[path = "tests/crossfade_tests.rs"]
mod crossfade_tests;

#[cfg(test)]
#[path = "tests/async_runtime_full_layout_test.rs"]
mod async_runtime_full_layout_test;

#[cfg(test)]
#[path = "tests/cursor_position_tests.rs"]
mod cursor_position_tests;

#[cfg(test)]
#[path = "tests/popup_tests.rs"]
mod popup_tests;

#[cfg(test)]
#[path = "tests/selection_handle_tests.rs"]
mod selection_handle_tests;

#[cfg(test)]
#[path = "tests/tab_switching_tests.rs"]
mod tab_switching_tests;

#[cfg(test)]
#[path = "tests/lazy_list_viewport_tests.rs"]
mod lazy_list_viewport_tests;

#[cfg(test)]
#[path = "tests/swipe_to_dismiss_lazy_tests.rs"]
mod swipe_to_dismiss_lazy_tests;

#[cfg(test)]
#[path = "tests/swipe_to_dismiss_render_tests.rs"]
mod swipe_to_dismiss_render_tests;

#[cfg(test)]
#[path = "tests/lazy_list_recompose_tests.rs"]
mod lazy_list_recompose_tests;

#[cfg(test)]
#[path = "tests/lazy_row_tests.rs"]
mod lazy_row_tests;

#[cfg(test)]
#[path = "tests/wear_widget_tests.rs"]
mod wear_widget_tests;

#[cfg(test)]
#[path = "tests/ios_fling_measurement.rs"]
mod ios_fling_measurement;