Add extensible Paint and gradient system - #3
Merged
Conversation
Document why the established Brush hierarchy remains the canonical paint model instead of introducing a parallel Paint abstraction. Record the current Skia coupling, mutation and allocation problems, the platform-neutral preferred API, dynamic-resource invalidation model, shader ownership rules, compatibility plan, performance risks, and completion criteria. The ADR preserves existing Color/SKColor and SKPoint compatibility adapters while keeping Windows and experimental Android on one shared rendering path. Theme JSON, absolute coordinates, Shape controls, and the animation scheduler remain explicitly deferred.
Make the existing Brush hierarchy the mutable, observable visual-value foundation required by dynamic themes and future shapes, instead of adding a competing Paint hierarchy. Add opacity, a platform-neutral Matrix3x2 transform, explicit no-fill semantics, strict finite-value validation, pad/repeat/reflect spread modes, and an observable GradientStopCollection with stable cached ordering. Add System.Drawing.Color and PointF properties while preserving the published SKColor/SKPoint members as adapters to the same backing data. All behavior remains in the shared framework project for identical Windows and experimental Android semantics. GradientStops changes from List<T> to the typed collection, and invalid offsets now fail explicitly; absolute coordinates, JSON loading, advanced color spaces, and animation batching remain future work.
Move linear, radial, focal-radial, and sweep shader construction into one shared Skia adapter so control renderers no longer duplicate coordinate, tile-mode, alpha, transform, and ownership logic. Render pad/repeat/reflect spread modes, brush opacity, explicit no-fill, one-stop gradients, zero-radius radial brushes, and platform-neutral transforms. Bounds are consumed exactly once, preventing a second DPI scale, while every bounds-specific SKShader remains scoped and disposed after drawing; only managed stop ordering is cached. Windows, experimental Android, the Designer, and off-screen surfaces use the same code. Native shader caching and advanced color-space interpolation are intentionally deferred until measurements justify a bounded policy.
Wire Control, GroupBox, and Switch brush properties to a shared reference-counted invalidation helper so changing a brush or an individual gradient stop repaints every current consumer without requiring reassignment. Use weak control references in brush event handlers, detach on property replacement and disposal, and share one subscription when multiple properties use the same brush. Dynamic-resource replacement, hierarchy fallback, and ordinary CLR setter behavior remain unchanged, preserving BackColor/ForeColor fallback precedence and targeted invalidation. The logic is platform-neutral and runs synchronously with the existing UI-thread resource contract on Windows and experimental Android. It does not introduce layout invalidation, a global resource broadcast, or an animation scheduler.
Add focused tests for neutral and SKColor compatibility views, opacity and transform validation, observable stops, stable equal-offset ordering, collection membership changes, relative coordinates, radial origins, zero radius, spread mapping, bounds-specific shaders, and scoped disposal. Cover application, window, and control resource precedence; Solid-to-gradient replacement; nested stop mutation; fallback restoration; shared-property reference counting; disposal; and garbage collection of a forgotten control bound to a long-lived brush. The rendering tests execute the same shared Skia path used by Windows and experimental Android and verify that bounds are resolved without a second DPI conversion. Physical-device GPU rendering and animation stress remain manual or future validation.
Round-trip brush opacity, transforms, gradient spread modes, and radial focal origins through designer documents and generated C# initializers. Keep legacy documents compatible by applying the original defaults when the new fields are absent. Preserve common and type-specific values when Property Grid or BrushEditDialog edits recreate a brush. Add focused serialization and code-generation coverage for the new data and backward-compatible defaults.
Assert that BackgroundBrush takes precedence over the compatibility BackColor facade and that assigning null restores the style color fallback.
Add a dedicated ControlGallery surface for solid, linear, radial, focal, sweep, spread, opacity, and transformed brushes. Include interactive dynamic-resource examples that exercise both in-place brush mutation and resource replacement without reassigning the target property.
Document the canonical Brush API, relative coordinates, gradient stops, spread, transforms, color fallback, no-fill semantics, runtime mutation, and dynamic-resource lifetime behavior. Record a renderer-neutral JSON schema direction without introducing a partial ThemeManager serializer. Refresh public XML examples to use the preferred System.Drawing and Matrix3x2 surface while retaining published Skia adapters for compatibility. Mark only the paint/gradient foundation as implemented in the roadmap and recommend UI animation scheduler hardening next. Full ThemeManager, Shape, absolute mapping, and advanced color interpolation remain deferred; Android remains experimental.
ProGraMajster
marked this pull request as ready for review
July 19, 2026 15:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ModernFormsNext already exposed several brush and gradient types, but mutation, invalidation, resource sharing, coordinate semantics, and Skia shader creation were inconsistent. That made the existing surface difficult to reuse safely for dynamic themes and the planned Shape controls.
Current state
The framework used the
Brushhierarchy directly and already supported solid, linear, radial, sweep, and glass rendering. Several public properties exposed SkiaSharp types, gradient stops were plain mutable data, shader construction was distributed through rendering extensions, and controls did not observe internal brush mutations.Solution
Brushhierarchy as the single canonical model instead of introducing a parallelPainthierarchy.NoBrush.SkiaBrushFactory..Designer.csround trips.Public API
The preferred platform-neutral API uses
System.Drawing.Color,PointF, andSystem.Numerics.Matrix3x2. The extended surface includesBrush.Opacity,Brush.Transform,GradientSpreadMode,GradientStopCollection, relative geometry on linear/radial/sweep brushes,RadialGradientBrush.GradientOrigin, andNoBrush.Existing
SKColorandSKPointmembers remain as compatibility views over the same backing values. They are documented as legacy integration surfaces; new application code does not need SkiaSharp types.Compatibility
Existing
BackColor,ForeColor, and legacy brush constructors continue to work.BackgroundBrushtakes precedence when assigned; clearing it restores the color fallback, so there are not two independent rendering sources of truth.The main compatibility risk is that
GradientStopsnow returnsGradientStopCollectionrather thanList<GradientStop>, and invalid offsets are rejected instead of flowing into rendering. Existing source that only uses normal collection operations remains straightforward to migrate, but already compiled consumers must rebuild.Dynamic resources
Brush values participate in the existing application/window/control resource hierarchy. Tests cover hierarchy overrides, object replacement, in-place gradient mutation, removal and fallback, duplicate-subscription prevention, and release of controls without a durable brush subscription.
SkiaSharp integration
SkiaBrushFactoryis the single conversion boundary. It maps spread modes to Skia tile modes, applies relative bounds and transforms once, and creates short-lived shaders owned by the drawing operation. Bounds-dependent native shaders are deliberately not cached, avoiding stale-size entries and unclear native lifetime.Validation
dotnet restore ModernFormsNext.slnx: passed..nupkgfiles, seven expected.snupkgfiles, README and XML documentation present.Known warnings are pre-existing dependency/security advisories in the VSIX graph, .NET Framework compatibility warnings for older Visual Studio SDK packages, nullable/generated interop warnings, obsolete Skia text/filter APIs, Windows platform annotations, and Android 16 KB page-size warnings for the bundled HarfBuzz native library. No warning was suppressed.
Windows impact
The primary Windows rendering and Designer paths use the shared brush factory. Designer code generation now preserves opacity, transform, spread mode, and radial focal origin while legacy brush expressions continue to deserialize.
Android impact
The Android backend consumes the same shared brush and Skia conversion layers. Its target builds and backend tests pass. Android remains experimental; GPU behavior, performance, lifecycle interactions, and rendering on physical devices still require manual validation.
ControlGallery
A dedicated Paint and Gradients panel demonstrates solid, linear, radial/focal, sweep, spread, transform, opacity, resource replacement, and in-place resource mutation. No screenshot is attached because an interactive visual session was not performed in this environment.
Limitations
Next steps