Skip to content

Add shared UI animation scheduler#4

Merged
ProGraMajster merged 8 commits into
masterfrom
feature/ui-animation-scheduler
Jul 19, 2026
Merged

Add shared UI animation scheduler#4
ProGraMajster merged 8 commits into
masterfrom
feature/ui-animation-scheduler

Conversation

@ProGraMajster

Copy link
Copy Markdown
Owner

Problem

The previous animation foundation used a Task.Delay(16) loop around a shared Stopwatch and mutable animation list. The continuation could leave the UI context, progress and lifecycle behavior were difficult to test deterministically, owner disposal was not part of the contract, and Windows/Android did not share an explicit application-lifecycle policy.

This PR replaces that foundation without implementing ThemeManager, Shape, navigation, or a full animated-layout system.

Architecture

  • One lazily-created, process-wide scheduler for the framework's current single UI dispatcher.
  • One shared, idle-aware pacing source; it starts for runnable work and stops after the final animation.
  • Progress derives from monotonic Stopwatch.GetTimestamp time, not frame counts, so delayed frames catch up without extending duration.
  • Timer requests are coalesced through the existing dispatcher; user updates and callbacks execute on the UI thread and never under the scheduler lock.
  • Start, cancel, state reads, pause/resume, and shutdown are thread-safe.
  • A manual clock, tick source, and dispatcher make tests deterministic without Thread.Sleep.

The design and alternatives are documented in:

  • docs/architecture/ui-animation-scheduler.md
  • docs/architecture/decisions/ADR-UI-Animation-Scheduler.md

Public API

The feature adds:

  • AnimationScheduler
  • AnimationHandle
  • AnimationState
  • AnimationOptions
  • AnimationPolicy
  • AnimationReplacementMode
  • AnimationSchedulerDiagnostics
  • IAnimationInterpolator<T>
  • AnimationInterpolators

Existing FadeToAsync, TranslateToAsync, ScaleToAsync, RotateToAsync, and Switch behavior delegates to the new scheduler rather than maintaining a second timing system.

Cancellation and replacement

Animations are identified by owner reference plus string key. The default behavior replaces and cancels the matching animation; IgnoreNew keeps the existing handle. Cancellation is idempotent, never reports successful completion, removes the entry, and prevents later updates or repaint.

Control-owned work is canceled when the control is detached or disposed. Application shutdown permanently shuts down the default scheduler.

Easing and interpolation

Built-in linear/ease-in/ease-out/ease-in-out functions remain available and custom easing is supported. Finite overshoot is preserved for Back/Elastic-style functions; NaN, infinity, or exceptions fault only the affected animation and are reported through diagnostics/trace.

Typed interpolation covers float, double, int, PointF, SizeF, RectangleF, alpha-aware Color, Matrix3x2, GradientStop, and compatible brushes.

Brush integration

Solid, linear, radial, and sweep brushes support color, opacity, transform, geometry, gradient-stop, and spread-mode transitions.

  • Local brush interpolation clones once and does not mutate either endpoint.
  • Explicit AnimateTo helpers can intentionally mutate a shared observable brush or gradient stop.
  • Incompatible brush types or gradient-stop structures fail before scheduling.
  • Brush notifications request render invalidation only; layout-affecting control values continue to use layout invalidation.
  • Replacing a dynamic resource detaches a control from an old brush that is still animating.

Lifecycle and reduced motion

The backend foundation now exposes a platform-neutral application lifecycle contract. Android's activity tracker reports foreground, background, and no-host states. Backgrounding pauses scheduler time and the tick source; foregrounding resumes remaining work without a time jump.

AnimationPolicy centrally supports animation enable/disable, reduced motion, and duration scaling. Disabled/reduced-motion and zero-duration work posts the final value once without starting the tick source. Design-time execution likewise settles at the final value instead of leaving an active timer.

Windows and Android impact

  • Windows uses the existing UI dispatcher and application shutdown path. The main Windows target and Designer build successfully.
  • Android uses the registered main-Looper dispatcher and activity lifecycle service. The backend and cross-platform sample compile for net10.0-android.
  • Android support remains experimental. Physical-device frame pacing and lifecycle behavior still need manual verification.

Tests

Deterministic coverage includes:

  • idle/start/stop behavior, elapsed-time progress, delays, dropped frames, and high animation counts;
  • start/cancel during tick, idempotence, completion semantics, pause/resume, replacement, and shutdown;
  • UI-thread dispatch and start from a background thread;
  • duration scaling, disabled animations, reduced motion, easing boundaries/failures, and fault isolation;
  • primitive, matrix, gradient-stop, and brush interpolation;
  • dynamic-resource replacement, brush repaint, layout-versus-render invalidation, and no repaint after cancellation;
  • control detach/dispose and Android lifecycle pause/resume.

Validation completed locally:

  • restore: passed;
  • Debug solution build: passed (0 errors);
  • Release solution build: passed (0 errors);
  • all solution tests: 595/595 passed;
  • Windows target: passed with 0 warnings;
  • Android backend target: passed with 0 warnings;
  • Android sample target: passed with two existing XA0141 native page-size warnings;
  • Designer, VSIX Debug, and VSIX Release: passed;
  • ControlGallery and template/reference DemoApp Release builds: passed with 0 warnings;
  • local pack: 8 version-1.8.0 nupkg files and 7 snupkg files, with README/icon/XML docs and Source Link payloads verified;
  • feature diff: clean, no project/manifest/version changes.

ControlGallery

A dedicated opt-in page demonstrates opacity, color, easing, cancel/replace, gradient stops, brush transforms, parallel animations, reduced motion/disabled motion, and diagnostics. It starts no automatic work and cancels tracked handles/restores global policy when unloaded.

No screenshot or recording is attached; interactive Windows visual verification remains a manual review item.

Known limitations

  • No repeat, auto-reverse, queue, blend, or animation groups yet.
  • No general animated-layout abstraction; callers may animate layout-affecting values explicitly.
  • Brush interpolation requires matching brush types and gradient-stop counts.
  • The fallback pacing source is timer-based rather than synchronized to a native compositor frame callback.
  • Native OS reduced-motion discovery is not implemented; applications set the central policy.
  • Android physical-device performance, suspend/resume, configuration-change, and rendering behavior require manual validation.
  • Existing helper calls with a negative integer duration now fail argument validation instead of being silently coerced to a minimal duration.

Next step

ThemeManager with JSON serialization and animated theme transitions, built on this scheduler. ThemeManager and Shape are deliberately not marked complete by this PR.

Document the existing Task.Delay-based animation loop, local UI timers, dispatcher boundaries, lifecycle gaps, and the distinction between property animation and ordinary debounce or media timing.

Choose one process-wide scheduler backed by a monotonic clock, a single idle-aware tick source, and the existing Windows or Android UI dispatcher. Define owner-key replacement, deterministic testing, reduced motion, brush mutation, fault isolation, disposal, and diagnostics before changing implementation.

The decision preserves existing control helpers and defers ThemeManager, Shape, navigation, advanced layout animation, and native frame pacing.
Replace the Task.Delay animation loop with one monotonic, idle-aware scheduler that coalesces timer requests through the existing UI dispatcher and derives progress from elapsed time rather than frame count.

Add public handles, state, options, owner-key replacement, pause and resume, shutdown, reduced-motion policy, diagnostics, easing validation, and platform-neutral value interpolators. Preserve the existing control animation helpers and Switch behavior by delegating them to the shared implementation.

Start, cancel, state reads, and shutdown are thread-safe; user callbacks never run under the scheduler lock. Control disposal and visual-tree detachment cancel owned work, and application exit shuts the scheduler down. Android lifecycle signaling and brush-specific transitions are completed in focused follow-up commits.
Introduce a platform-neutral application lifecycle contract in the backend foundation and have AndroidActivityTracker publish foreground, background, and no-host transitions.

Bind the default animation scheduler lazily to the registered lifecycle service. Backgrounding stops the shared tick source and freezes effective monotonic time; foregrounding resumes remaining work without jumping over the paused interval.

Windows continues to use application shutdown and the existing dispatcher because it has no equivalent backend lifecycle service. The Android target builds with the new contract, while physical-device frame pacing remains a manual validation item.
Add allocation-conscious interpolation for solid, linear, radial, and sweep brushes, including opacity, transforms, gradient geometry, stops, and spread-mode transitions.

Keep local transitions isolated by cloning the source brush once, while providing explicit in-place helpers for shared observable resources. Existing Brush.Changed notifications continue to drive render-only invalidation on the UI thread without adding a parallel rendering path.

Reject incompatible brush types and gradient structures before scheduling so failures are immediate and deterministic on Windows and the experimental Android backend.
Add a manual monotonic clock, tick source, and dispatcher harness so animation tests advance time deterministically without sleeps or real frame timers.

Cover elapsed-time progress, delayed and dropped frames, UI-thread dispatch, replacement, cancellation, pause and resume, reduced motion, duration scaling, fault isolation, shutdown, high animation counts, primitive interpolation, Brush cloning, in-place resource animation, invalidation, and control lifetime cancellation.

The complete ModernFormsNext.Tests project passes with 466 tests after these additions.
Add an opt-in ControlGallery page for opacity, color, easing, cancellation, owner-key replacement, gradient-stop and brush-transform animation, parallel channels, reduced motion, and scheduler diagnostics.

The page starts no background work on its own. It tracks every non-panel-owned handle, cancels all work on unload or disposal, and restores the application motion policy so navigation away cannot leave CPU activity or altered global settings behind.

ControlGallery builds successfully; visual interaction remains a manual Windows validation step.
Inject the platform-neutral application lifecycle through the internal scheduler seam so background pause/resume and event detachment can be verified without a global service registration or Android runtime.

Expand deterministic coverage for cancellation before the first update and during delay, terminal idempotence, easing boundaries and failures, no repaint after cancellation, and the distinction between layout-affecting bounds and render-only transforms.

All 484 ModernFormsNext.Tests cases pass without Thread.Sleep; the production API and platform behavior remain unchanged.
Document control and typed animations, easing, cancellation, replacement, local versus in-place Brush transitions, dynamic resources, UI-thread rules, owner disposal, Android background pause, reduced motion, diagnostics, Designer behavior, and current limitations.

Align the architecture document with the implemented clone-versus-shared Brush decision, link the guide from README, and mark the scheduler foundation complete in the roadmap without claiming ThemeManager or Shape delivery.

The recommended next stage is ThemeManager with JSON serialization and animated theme transitions; all relative documentation links were validated locally.
@ProGraMajster
ProGraMajster marked this pull request as ready for review July 19, 2026 19:54
@ProGraMajster
ProGraMajster merged commit 94101d1 into master Jul 19, 2026
1 check passed
@ProGraMajster
ProGraMajster deleted the feature/ui-animation-scheduler branch July 19, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant