T.Molodoy.Compose.Net.Navigation 0.0.13

There is a newer version of this package available.
See the version list below for details.
dotnet add package T.Molodoy.Compose.Net.Navigation --version 0.0.13
                    
NuGet\Install-Package T.Molodoy.Compose.Net.Navigation -Version 0.0.13
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="T.Molodoy.Compose.Net.Navigation" Version="0.0.13" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="T.Molodoy.Compose.Net.Navigation" Version="0.0.13" />
                    
Directory.Packages.props
<PackageReference Include="T.Molodoy.Compose.Net.Navigation" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add T.Molodoy.Compose.Net.Navigation --version 0.0.13
                    
#r "nuget: T.Molodoy.Compose.Net.Navigation, 0.0.13"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package T.Molodoy.Compose.Net.Navigation@0.0.13
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=T.Molodoy.Compose.Net.Navigation&version=0.0.13
                    
Install as a Cake Addin
#tool nuget:?package=T.Molodoy.Compose.Net.Navigation&version=0.0.13
                    
Install as a Cake Tool

Compose.Net

.Net implementation of Compose.

ORBIT application demo

ORBIT is a complete seven-screen creative workspace: projects, search, favorites, task milestones, activity, focus timer, settings, and a motion playground with up to 144 particles. It combines shared-element navigation, lazy grids/lists, a pager, animated controls, custom drawing and gesture input. Open Tools → Compose.Net → ORBIT — Full App Demo in the companion Unity project after following the build/install instructions.

By delegates a C# local to IMutableState<T>, following the Compose delegated-state pattern:

public partial class CounterScreen
{
    [Composable]
    public void Content()
    {
        var count = By(Remember(() => MutableStateOf(1)));
        Text($"Count: {count}");
        Box(modifier: Modifier.Clickable(() => count++),
            content: () => Text("Increment"));
    }
}

The local has the value's type in source code. Compose.Net.CodeGen rewrites its reads and writes (including ++, compound assignments, local functions and captured callbacks) to the state's Value. State reads subscribe the current composition; equal writes use the existing MutableStateOf equality behavior. Remember retains the holder; By itself does not remember or copy it. var value = By(existingState) also delegates to an externally owned holder.

Use one var local per declaration in a method or property accessor in a partial type; enclosing types must also be partial. By needs both the Compose generator and assembly patcher, including for ordinary methods outside composition. ref, out and explicit in arguments are rejected because the delegated value is a property. For struct values, assign a replacement value instead of mutating its fields. nameof, inferred tuple/object member names and independent locals with the same spelling are preserved. Lambda captures retain the state holder; a method group such as count.ToString binds the current value when created.

Unity Edit Mode tests are in Assets/Tests/Editor/DelegatedState, assembly Compose.Net.Unity.DelegatedState.Tests. Run generator diagnostics with dotnet run --project Compose.Net.CodeGen.Tests -c Release; runtime regressions are included in Compose.Net.Modifiers.Tests.

Library builds leave the generated composable implementations in the DLL without patching it. The Unity integration applies Compose.Net.Patcher inside the Editor:

  • Imported managed plug-ins are checked after import and on domain reload. DLLs with generated composable members are patched in place, together with their PDBs, and reimported before Play Mode. Unrelated and already patched DLLs are unchanged.
  • Player builds patch the staged managed assemblies in IPostBuildPlayerScriptDLLs, before managed stripping and IL2CPP conversion.
  • Scripts compiled by Unity continue to use ComposeILPostProcessor.

Install the patcher and Mono.Cecil as Editor-only plug-ins. These are build tools, not runtime references of Compose.Net. Imported DLLs must be writable; replacing or restoring a package causes Unity to apply the patch again. Source generation still runs when the original library is compiled.

For a non-Unity consumer that needs a patched library directly, opt in with dotnet build -p:ComposePatchAfterBuild=true, or run dotnet run --project Compose.Net.Patch -- path/to/library.dll. The .NET regression executable patches only its own output copies.

Unity plug-in platform settings

AnimatedContent follows the AndroidX AnimatedContent API. Use the state passed to the content lambda: outgoing and incoming content coexist during a transition.

AnimatedContent(
    targetState: page,
    contentKey: value => value.Id,
    label: "Pages",
    contentAlignment: Alignment.Center,
    transitionSpec: scope => scope.SlideIntoContainer(SlideDirection.Left)
        .TogetherWith(scope.SlideOutOfContainer(SlideDirection.Left))
        .Using(SizeTransform(clip: false,
            sizeAnimationSpec: (initial, target) => Tween(500))),
    content: (scope, value) => Text(value.Title,
        modifier: Modifier.AnimateEnterExit(scope, enter: FadeIn(), exit: FadeOut())));

The state-only content overload is also available. UpdateTransition(targetState, label) returns a transition with CurrentState, TargetState, and IsRunning; call transition.AnimatedContent(...) to share it between containers. Each content scope exposes its own Transition<EnterExitState> and supports child AnimateEnterExit animations. Child animations and KeepUntilTransitionsFinished participate in content disposal.

ContentTransform exposes TargetContentEnter, InitialContentExit, TargetContentZIndex and SizeTransform (Enter/Exit remain aliases). .Using(null) disables size animation and clipping; .Using(SizeTransform(clip: false)) preserves animation without clipping. The default is a 90 ms fade-out followed by a 220 ms fade/scale-in delayed by 90 ms, with initial scale 0.92 and a critically damped size spring (stiffness 200). Retargeting preserves the rendered values, remembered content for surviving keys, and the size spring's velocity. Equal content keys update data without starting another transition.

C# keeps the required content delegate second, and uses the existing Size (UI units), AnimationSpec, and SlideDirection types. Start/End follow this library's LTR alignment aliases. Adapters provide compound composition through IComposeLayouts; Unity owns its internal animated-content layout/item nodes. Adapters implement ITransitionSnapshotNode for uninterrupted visual transitions. The Unity adapter includes these implementations, AnimatedContentDemo (21 examples), and Edit Mode UI tests under Assets/Tests/Editor/AnimatedContent.

Run the platform-independent regressions with dotnet run --project Compose.Net.Modifiers.Tests -c Release.

VerticalScroll and HorizontalScroll follow the parameter order and edge semantics of AndroidX Scroll.kt:

var scroll = RememberScrollState(initial: 120);
Column(modifier: Modifier.Height(240.Dp()).VerticalScroll(
    scroll, enabled: true, flingBehavior: null, reverseScrolling: false), content: Items);
// Explicit null disables elastic overscroll; omitting it uses the adapter default.
var horizontalScroll = RememberScrollState();
Row(modifier: Modifier.Width(320.Dp()).HorizontalScroll(
    horizontalScroll, overscrollEffect: null, enabled: false), content: Cards);
scroll.ScrollTo(80); // Works even when user input is disabled; returns consumed distance.
await scroll.AnimateScrollTo(scroll.MaxValue, Tween(500));

Use a separate state for each viewport. Zero means top/left, or bottom/right when reversed; reversing does not reorder children. enabled controls user input, while measurement, clipping and programmatic scrolling remain active. RememberScrollState(initial) retains its first initial value. MaxValue is float.MaxValue before layout; ViewportSize is initially zero. Bounds, scrollability, direction and IsScrollInProgress are observable. New commands and native input cancel pending programmatic animations; forgetting remembered state cancels its animation. Drag interactions are available through state.InteractionSource.CollectIsDraggedAsState().

The Unity adapter uses UI Toolkit ScrollView for input, layout, clipping, native inertia, overscroll and nested boundary forwarding. State updates are subscribed directly, without recomposing the scrolling content. Both native scrollbars are hidden. C# positions remain fractional UI units; horizontal Start/End use the library's existing LTR conventions. FlingBehavior(decelerationRate) and OverscrollEffect(elasticity) configure native physics; they do not reproduce Android's custom coroutine-based fling or effect-node interfaces. Programmatic animations use the existing AnimationSpec system.

Migration: replace userScrollEnabled: with enabled:. The old scrollMultiplier and modifier interactionSource arguments and SmoothScrollBy are removed; wheel sensitivity comes from UI Toolkit, interactions from the state, and animation uses AnimateScrollBy/AnimateScrollTo. Adapters implement IScrollStateAdapter to publish atomic measurements and native input, and to subscribe/unsubscribe position changes and programmatic-scroll requests.

The Unity project contains Tools > Compose.Net > Scroll Demo, an add-to-scene menu, 18 interactive examples, and Edit Mode UI tests in Assets/Tests/Editor/Scroll. Run those tests with Unity Test Runner (assembly Compose.Net.Unity.Scroll.Tests). Platform-independent scroll regressions run with the existing Compose.Net.Modifiers.Tests executable.

HorizontalPager and VerticalPager follow the AndroidX Foundation Pager contract. The existing C# argument order is retained (required pageContent comes second). Both support content padding, spacing, PageSize.Fill, PageSize.Fixed and custom PageSize implementations, cross-axis alignment, stable keys, reverse layout, userScrollEnabled, beyondViewportPageCount, and Start/Center/End snapping. A fixed size is independent of content padding. Page offsets include spacing and are constrained to [-0.5, 0.5]; out-of-range page requests clamp to the data set.

var pager = RememberPagerState(() => items.Count, initialPage: 2);
HorizontalPager(pager,
    pageContent: page => Text(items[page].Title),
    modifier: Modifier.FillMaxWidth().Height(180.Dp()),
    contentPadding: new PaddingValues(Horizontal: 16.Dp()),
    pageSpacing: 8.Dp(),
    key: page => items[page].Id);
// Use ScrollToPage for a jump, or await AnimateScrollToPage for an animation.

CurrentPage is the nearest page during motion, SettledPage changes when motion stops, and TargetPage describes the running animation. The state also exposes CurrentPageOffsetFraction, LayoutInfo.VisiblePagesInfo, scroll availability, and GetOffsetDistanceInPages. Recomposition updates the page-count provider; resizing retains the selected page and offset, and keyed insertions retain the selected item. A new scroll request cancels the previous animation. Only visible pages and the requested extra pages are composed; leaving the window disposes page effects and remembered state, as with Compose's lazy layouts.

The Unity adapter uses UI Toolkit ScrollView for touch gestures, wheel input, nested-scroll forwarding, clipping, and content translation. It adds lazy page placement, keyboard paging and settling through the existing animation/frame clock. PagerDefaults.FlingBehavior configures the page limit, positional threshold and snap animation. Platform adaptation: input physics and nested scroll routing follow UI Toolkit; Android-specific NestedScrollConnection, OverscrollEffect and coroutine ScrollableState types are not exposed as empty compatibility parameters. The library's alignment aliases currently use LTR.

The Unity project includes PagerDemo (21 interactive examples) in SampleScene, a repeatable Tools → Compose.Net → Add Pager Demo to Scene command, and real UI Toolkit panel tests under Assets/Tests/Editor/Pager. Platform-independent pager regressions run with the existing Compose.Net.Modifiers.Tests executable.

PointerInput follows the keyed lifetime and three-pass event model of AndroidX pointer input and SuspendingPointerInputFilter:

Modifier.PointerInput(key1: item.Id, key2: enabled, block: async input =>
{
    await input.AwaitEachGesture(async events =>
    {
        var down = await events.AwaitFirstDown();
        down.Consume();
        var up = await events.WaitForUpOrCancellation();
        if (up != null) { up.Consume(); HandleTap(up.Position); }
    });
});

Modifier.PointerInput(keys: new object?[] { item.Id, enabled, mode }, block: input =>
    input.DetectTapGestures(onTap: HandleTap, onPress: async (press, position) =>
    {
        ShowPressed(position);
        var released = await press.TryAwaitRelease(); // false on cancellation
        HidePressed(released);
    }));

One key (including null), two keys and a nonempty key array are supported. Keys use value comparison; key arrays are copied and compared element by element. A block starts on its first input event. Changing a key or the delegate's method cancels it and starts a fresh block on the next event. Recreating a closure at the same call site does not restart an active block. Captured values therefore stay captured: use a stable remembered state holder to read the latest value. Removing the modifier or detaching its element cancels its waits and releases native capture. A completed block stays completed until reset. Each run has its own CancellationToken.

AwaitPointerEvent() returns a durable PointerEvent with Changes, Type, Buttons and KeyboardModifiers. Initial and Final travel parent to child; Main travels child to parent, including multiple modifiers on one element. One snapshot is reused across the passes. Consume() shares its flag with other handlers without hiding the event from them. CurrentEvent, Size, ExtendedTouchPadding, ViewConfiguration, generic result scopes, WithTimeout and WithTimeoutOrNull are available. Concurrent listeners need separate AwaitPointerEventScope blocks. Events are not queued while a block is doing unrelated work. Inside an event scope, consume before awaiting again; C# cannot enforce Kotlin's restricted suspension rules. Use the run's Token for any additional asynchronous work.

Tap detection supports release/cancel-aware asynchronous onPress, double-tap minimum and maximum intervals, long press, bounds cancellation and consumption in the Final pass. Drag supports touch/mouse slop, over-slop first deltas, horizontal/vertical axes, pointer handoff, cancellation and long-press drag. Transform detection supports pan, zoom, weighted rotation, touch slop and panZoomLock. CalculateCentroid, CalculateCentroidSize, CalculatePan, CalculateZoom and CalculateRotation operate on a whole event and exclude entering/leaving pointers. The required C# gesture callback comes first; optional callbacks use named arguments.

The Unity adapter uses UI Toolkit picking, pointer capture, panel coordinates and native mouse/touch/pen/wheel events. A small PointerManipulator bridge supplies Compose's extra Final pass and independent consumption state. Native trickle-down guards run first; consumed gestures are withheld from native controls only after every Compose pass. No polling, custom raycaster, gesture event queue, reflection into Unity, or retained pooled Unity events is used. Positions remain fractional UI units. Desktop defaults are 8 UI units of touch slop, 0.5 mouse slop, 500 ms long press and 40–300 ms between taps. Extended touch padding is zero: the native hit target is authoritative. Raw pointer input does not add keyboard activation, accessibility semantics or an indication; use a higher-level control when those interactions are needed.

Migration: prefer AwaitPointerEvent().Changes and ChangedToDown(); the old single-change AwaitPointerInputChange() and ChangedToPressed() remain compatibility helpers. onPress now accepts (IPressGestureScope, Offset) => Task; gesture thresholds come from scope.ViewConfiguration instead of global constants. The Unity project includes 18 live examples in PointerInputDemo, an Editor window under Tools > Compose.Net > Pointer Input Demo, Assets/Scenes/PointerInputDemo.unity, and Edit Mode UI tests in assembly Compose.Net.Unity.PointerInput.Tests.

SharedBounds and SharedElement follow the parameter order and matching semantics of AndroidX SharedTransitionScope. Create a separate remembered state at each modifier site; equal keys match only within one scope.

SharedTransitionLayout(shared => AnimatedContent(page, (visibility, value) =>
    Box(content: () => Text(value.Title), modifier: Modifier
        .Size(value.Width.Dp(), value.Height.Dp())
        .SharedBounds(shared.RememberSharedContentState("card"), visibility,
            boundsTransform: (_, _) => Tween(700),
            resizeMode: ResizeMode.ScaleToBounds(ContentScale.Fit, Alignment.Center),
            placeholderSize: PlaceholderSize.ContentSize,
            clipInOverlayDuringTransition: new OverlayClip(RoundedCornerShape(16.Dp()))))));

SharedElement draws only the incoming content and remeasures it at the animated bounds. SharedBounds draws both contents with independent enter/exit timing; its default resize mode is ScaleToBounds(FillWidth, Center). RemeasureToBounds uses native layout on every size change. ScaleToBounds also supports FillHeight, FillBounds, Fit, Crop, Inside and None with all alignments. PlaceholderSize.ContentSize, AnimatedSize, or a custom size function controls the original layout slot. The default bounds spring is critically damped with stiffness 400 and animates rectangle edges. Custom bounds transforms use the existing finite AnimationSpec and receive root-relative rectangles. An interrupted tween continues with a spring (stiffness 1500), carrying its current velocity; the bounds transform is initialized once per transition, not called again on every retarget. Subpixel float noise does not restart the animation. A target change on the completion frame still animates to the new bounds.

The Unity adapter moves the live VisualElement into a scope overlay, preserving its logical slot. UI Toolkit provides layout, transforms, native picking, sibling ordering and rounded overflow clipping; there are no cloned draw commands, screenshot textures or custom hit testing. The overlay escapes parent alpha, scale and clipping. renderInOverlayDuringTransition: false keeps those parent effects. zIndexInOverlay controls ordering and OverlayClip.ParentClip inherits the nearest explicit shared ancestor SharedBounds clip. OverlayClip.None disables inherited clipping. Ordinary modifier clipping still applies. SharedContentState.ClipPathInOverlay exposes the resolved root-relative path during drawing; it is borrowed from the host and must not be disposed. ParentSharedContentState refers to the nearest SharedBounds, skipping intermediate shared elements.

IsMatchFound and IsTransitionActive are observable. Unmatched or disabled modifiers do not animate. SharedContentConfig controls enabling and whether disabling interrupts an ongoing transition. Override IsEnabledFor(SharedContentState) for AndroidX's state-receiver property, or the existing IsEnabled property when the state is not needed. State read by the config is observed even while idle. AlternativeTargetBoundsInTransitionScopeAfterRemoval(state, previousBounds, scopeSize) may supply a replacement destination when the target is disposed. Returning null (the default) cancels the transition. Removing the source alone allows the destination to finish. SharedTransitionDefaults.SharedContentConfig is the immutable default configuration. Bounds are measured again after layout changes; reversal and retargeting start at the rendered bounds. Shared animations retain outgoing AnimatedContent/scoped AnimatedVisibility content even when the parent transition is shorter or instantaneous. SharedElementWithCallerManagedVisibility keeps layout ownership with the caller; remove inactive content after IsTransitionActive becomes false.

SharedTransitionScope((shared, rootModifier) => ...) creates a scope without an extra container. Apply rootModifier to exactly one topmost layout before composing shared children. Ordinary content can use Modifier.RenderInSharedTransitionScopeOverlay(shared, zIndexInOverlay, renderInOverlay); the observed predicate defaults to shared.IsTransitionActive. Root shared layers at the default z-index draw below ordinary overlay content, allowing controls such as a floating action button to stay above the transition.

Call state.PrepareTransitionWithInitialVelocity(new Velocity(x, y)) when releasing a gesture, before changing the destination. Velocity is in logical layout units per second. The next transition adds a spring momentum offset to its bounds (also for custom tweens); disabled states ignore the request.

C# uses top-level ResizeMode/PlaceholderSize/OverlayClip types and the existing Shape and AnimationSpec. Overlay clips support rectangles and rounded rectangles, rather than arbitrary Android Path callbacks. Unity has no Compose lookahead measurement pass: scale mode retains the measured native content size, while remeasure mode constrains the live native subtree. Intrinsic content changes during remeasure mode should supply explicit target dimensions. SkipToLookaheadSize, SkipToLookaheadPosition and interactive DeferredTransition require a lookahead/approach-layout engine and are not implemented. Use one shared or overlay modifier per native layout; nest layouts to combine them. Ancestor transforms are excluded from target coordinates, as are parent enter/exit transforms; native ScrollView offsets are included.

The Unity project includes SharedLayoutDemo in Assets/Scenes/SampleScene.unity, Tools > Compose.Net > Shared Layout Demo, and 27 interactive examples. Run Compose.Net.Unity.SharedLayout.Tests in the Edit Mode Test Runner, plus the normal Compose.Net.Modifiers.Tests executable for platform-independent regressions.

Draggable, Scrollable and Hoverable follow the state and interaction lifecycle of Compose Draggable, Scrollable and Hoverable.

var interactions = Remember(MutableInteractionSource);
var dragged = interactions.CollectIsDraggedAsState().Value;
var drag = RememberDraggableState(delta => offset.Value += delta);
var modifier = Modifier.Draggable(drag, Orientation.Horizontal,
    enabled: true, interactionSource: interactions, startDragImmediately: false,
    onDragStarted: position => Log(position),
    onDragStopped: velocity => Log(velocity), reverseDirection: false);

var scroll = RememberScrollableState(delta =>
{
    var previous = offset.Value;
    offset.Value = Math.Clamp(previous + delta, 0, 500);
    return offset.Value - previous; // Return only the consumed distance.
});
modifier = Modifier.Scrollable(scroll, Orientation.Vertical,
    enabled: true, reverseDirection: false, flingBehavior: new FlingBehavior(0.135f),
    interactionSource: interactions);
modifier = Modifier.Hoverable(interactions, enabled: true);

These low-level modifiers do not move, measure, clip or focus the element. The application applies the reported delta to its own content. Drag deltas follow pointer travel; wheel scrolling has the opposite sign. reverseDirection changes input and release velocity, not programmatic state operations. Draggable supports mouse, pen and touch, touch slop, immediate capture, local start coordinates, velocity in UI units/second, and touch pointer handoff. Scrollable accepts touch/pen drags, wheel/trackpad input and PageUp/PageDown when focused; mouse dragging belongs to Draggable. It forwards unconsumed input to ancestors, including native VerticalScroll/HorizontalScroll containers. A tap produces no drag interaction. Hoverable observes mouse/pen enter/leave without consuming input.

RememberDraggableState and RememberScrollableState retain identity while updating their callbacks. Drag/Scroll blocks expose DragBy/ScrollBy and a cancellation token. Equal or higher MutatePriority cancels the previous operation; a lower priority is rejected. Use the scope token for asynchronous work and finish cancellation promptly; native input that arrives while asynchronous cleanup still owns the state is not replayed later. A completed or cancelled scope rejects further deltas. DispatchRawDelta deliberately bypasses priority, reversal and nested scrolling. ScrollableState exposes progress, availability and last consumed direction, with ScrollBy, AnimateScrollBy and StopScroll helpers. ScrollState also implements IScrollableState; its programmatic animations and low-level mutations share the same priority mechanism. The old callback-only Draggable overload remains obsolete for migration.

Drag starts have matching Stop/Cancel instances; hover enters have matching Exit instances. Disable, removal, panel detach and capture loss clean up active interactions. Callback updates preserve an active gesture; replacing its interaction source closes the old pair. Hover source replacement waits for the next enter event, as in Compose. Shared sources track all active interactions by reference, so ending one does not clear another's state.

Unity uses PointerManipulator, native picking/transforms/capture, pooled bubbling events, and UI Toolkit value animations for fling. It does not insert a hidden ScrollView or run an additional frame loop. Only the missing state/consumption bridge, axis recognition and velocity sampling live in the adapter. Platform adaptation: FlingBehavior retains the library's native deceleration configuration; drag callbacks are synchronous C# delegates. The common Scrollable(state, orientation, enabled, reverseDirection, flingBehavior, interactionSource) overload is supported. Android effect-node/overscroll and BringIntoViewSpec overloads are not represented by ignored parameters.

The Unity project includes InteractionDemo in SampleScene and Tools > Compose.Net > Interaction Demo, with 24 examples, live values, event counters and controls for disabling, removal, callback/source replacement and programmatic changes. Its UI tests are in Assets/Tests/Editor/Interaction (Compose.Net.Unity.Interaction.Tests), including all demo cards, nested input, click arbitration, capture lifecycle, source sharing and touch handoff. State, priority and collection regressions run with Compose.Net.Modifiers.Tests.

Box follows the AndroidX Box contract:

Box(modifier: Modifier.SizeIn(minWidth: 120.Dp(), minHeight: 80.Dp()),
    contentAlignment: Alignment.Center, propagateMinConstraints: false,
    content: () =>
    {
        Box(Modifier.Size(160.Dp(), 50.Dp()).Background(Color.DodgerBlue));
        Box(Modifier.Size(40.Dp(), 90.Dp()).Align(Alignment.BottomEnd));
        Box(Modifier.MatchParentSize().TestTag("overlay"));
    });

Ordinary children determine the maximum width and height independently and overlap in composition order. Empty content uses the minimum constraints. Box(modifier) is the empty layout overload. contentAlignment defaults to TopStart; a child's Align overrides it. propagateMinConstraints defaults to false, while incoming maximum constraints still apply. MatchParentSize excludes a child from measurement and sizes it to the resulting inner Box. FillMaxSize participates in measurement and can make the Box fill the available space. As in Compose, the first Align or MatchParentSize in a modifier chain supplies the complete Box parent data. Parent data only affects direct children of a Box.

The Unity adapter uses one native CSS Grid cell for measurement, overlap and all nine alignments, with absolute native insets for matching overlays. It no longer uses a synthetic spacer, negative margins or translation to stack children. A constraint bridge handles minimum propagation, bounded fractional fill and unbounded native scroll content; animated shells retain their natural target measurement. Picking, drawing order, z-index, text wrapping and scrolling remain native UI Toolkit operations. Unity 7's Project Settings > UI Toolkit > Enable Grid Layout must be enabled (the sample project includes this setting).

C# keeps required content first. Layout functions and node factories no longer accept a string type. Start/End retain the library's existing LTR conventions; this change does not introduce a layout-direction API. When migrating an adapter, add propagateMinConstraints to IBoxNodeFactory.Apply and implement IAlignmentModifiersFactory.MatchParentSize. IBoxNodeFactory only creates ordinary boxes. AnimatedContent, AnimatedVisibility, lazy lists, grids and pagers delegate through IComposeAdapter.Layouts (IComposeLayouts). Their composition and native wrapper creation live in Unity's UnityComposeLayouts; state and public APIs remain in Compose.Net. Custom hosts must provide or forward Layouts along with NodeFactory. Unity provides factory overloads in ComposeUnityFunctions: ReusableComposeNode accepts any VisualElement factory; Box, Row and Column accept factories of their corresponding Unity view classes, and Spacer accepts a VisualElement factory. Factories run when the node is created; modifiers, layout parameters and initializers update on recomposition. Float and Position remain Unity-specific extensions.

The Unity project includes Tools > Compose.Net > Box Demo, an idempotent add-to-scene command, and active Samples/BoxDemo in Assets/Scenes/SampleScene.unity. Its 32 examples use a responsive native Flexbox, native buttons, live measurements and a ScrollView. Run Compose.Net.Unity.Box.Tests for layout, update, input, demo and scene coverage; run Compose.Net.Modifiers.Tests for API/default/adapter forwarding regressions.

LazyVerticalGrid and LazyHorizontalGrid support fixed/adaptive/fixed-size tracks, custom spans, keyed content, sticky headers, reverse layout, arrangements and observable item-based scrolling. Unity uses native ListView row virtualization and a bounded horizontal ScrollView window. GridDemo includes 24 interactive examples and Edit Mode UI coverage. See lazy-grid API, behavior and adapter details.

LazyColumn and LazyRow

LazyColumn and LazyRow compose a visible window, with stable keyed item identity and disposal when items leave that window. Items(count, ...) stores an interval; declaring a million items does not invoke a million content/key delegates. This follows the Compose lazy-list DSL.

var state = RememberLazyListState(initialFirstVisibleItemIndex: 20,
    initialFirstVisibleItemScrollOffset: 8);
LazyColumn(
    state: state,
    modifier: Modifier.Size(320.Dp(), 240.Dp()),
    contentPadding: new PaddingValues(12.Dp()),
    verticalArrangement: Arrangement.SpacedBy(8.Dp()),
    content: list =>
    {
        list.StickyHeader(index => Text("Section"), key: "section");
        list.Items(100_000, index => Text($"Item {index}"),
            key: index => index, contentType: _ => "row");
    });
state.ScrollToItem(50_000, scrollOffset: 12);

Both lists accept contentPadding, reverseLayout, flingBehavior, userScrollEnabled, and an optional overscrollEffect (null explicitly disables it). Existing C# parameter order remains compatible. reverseLayout reverses item order; index zero starts at the bottom/right by default. Explicit main-axis arrangements still apply to short content. Arrangement.SpacedBy supports spacing, overlap through negative spacing, and optional alignment. Cross-axis size can wrap the visible content, or be constrained by modifiers. The viewport needs a finite size on its scroll axis.

ILazyListScope provides Item, Items(count, ...), collection Items / ItemsIndexed, and StickyHeader; keys must be unique and stable. Inserting before the viewport retains the first visible key and its offset. RequestScrollToItem overrides that anchor for the next layout. Item content inherits composition locals. FillParentMaxSize, FillParentMaxWidth, and FillParentMaxHeight fill a fraction (0–1) of the lazy viewport after content padding; use them inside lazy item content, where ordinary FillMax* cannot fill an unbounded scroll axis.

LazyListState is publicly constructible. RememberLazyListState(index, offset) consumes its initial arguments once. State exposes FirstVisibleItemIndex, FirstVisibleItemScrollOffset, LayoutInfo.VisibleItemsInfo, total count, orientation, padding, spacing, viewport and reverse flag. ScrollToItem, RequestScrollToItem, and AnimateScrollToItem work before measurement and for uncomposed targets. A positive offset moves the target further out of the leading edge; negative offsets are allowed. Negative indices and non-finite offsets throw; indices beyond the data clamp. The existing ScrollToItemVisibility helpers and IScrollState pixel operations remain available. Pixel range/total size are estimates until items are measured; use index-based navigation for unmeasured items. Animation completion waits for target measurement and alignment. A new request or native gesture cancels the old animation. Disabling user input keeps programmatic scrolling, measurement, clipping and child controls available.

Unity uses native ListView dynamic-height virtualization for columns. UI Toolkit only supplies vertical collection virtualization, so rows use a sparse measured-size index and a bounded window on native ScrollView. Toolkit owns wheel/touch/keyboard handling, capture, nested scrolling, inertia and overscroll on both axes. No reflection or custom gesture/physics implementation is used. The native overscan strategy replaces Compose-specific coroutine prefetch/cache configuration. C# FlingBehavior and OverscrollEffect configure Toolkit physics, as for the scroll modifiers.

The Unity project includes LazyListDemo (18 interactive cards), a standalone scene, an entry in SampleScene → Demos, editor menu commands, and the Compose.Net.Unity.Lazy.Tests panel suite. Core regression coverage lives in Compose.Net.Modifiers.Tests/LazyListTests.cs.

NavDisplay uses NavEntry.ContentKey to identify screen content. Replacing route arguments with the same content key updates that screen while preserving its Remember state. Give separate screen instances distinct keys. By default, entries still present in the back stack retain their composition; popped entries are disposed after their exit animation. Set retainEntries: false to dispose covered screens as well. Decorators receive OnPop once per removed content key, including when the stack is cleared.

Run dotnet run --project Compose.Net.Modifiers.Tests -c Release for navigation regressions covering stack operations, providers, push/pop, interrupted transitions, retention, decorators, empty stacks, hash collisions and content-key updates. The companion Unity project's Compose.Net.Unity.Navigation.Tests Edit Mode assembly tests the actual NavigationSample, including button clicks, labels, counter retention, settings reset, product routes and background/text draw order.

The shared-transition audit and regression coverage are described in docs/shared-transitions.md.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.14 49 11/8/2026
0.0.13 90 11/4/2026