feat: Implement InkCanvas and InkPresenter controls#18
Closed
zhaog100 wants to merge 7 commits intoChevalier12:masterfrom
Closed
feat: Implement InkCanvas and InkPresenter controls#18zhaog100 wants to merge 7 commits intoChevalier12:masterfrom
zhaog100 wants to merge 7 commits intoChevalier12:masterfrom
Conversation
…erage
This commit addresses layout invalidation noise in scroll-heavy paths and
adds focused regression tests for the scenarios that were causing
unnecessary Measure/Arrange invalidations.
Framework changes
-----------------
**Track.cs** — Changed all dependency property metadata flags from
`AffectsArrange | AffectsRender` to `AffectsRender` only, and added a
`RefreshLayoutForStateChange` callback that directly calls Arrange/InvalidateVisual
instead of going through the full layout pipeline. This prevents scrollbar
thumb drag from cascading into unrelated layout work.
**ScrollBar.cs** — Changed ValueProperty metadata from
`AffectsArrange` to `None` to break the cascade that was causing
full-scrollviewer re-arranges on every thumb drag tick.
**ScrollViewer.cs** — In horizontal/vertical offset change handlers, replaced
`InvalidateArrange()` with `ArrangeContentForCurrentOffsets()` + `InvalidateVisual()`
so the content arranges itself immediately without queuing a full layout pass.
**DataGrid.cs** — On HorizontalOffset changes the grid now calls
`ArrangeHeadersForCurrentLayout()` + `InvalidateVisual()` directly instead
of invalidating arrange. This fixes a parity issue where frozen column
headers would drift relative to the scrolling content.
**VirtualizingStackPanel.cs** — Added an early-return guard in
`ShouldGenerateNextBlock` that returns false when all children are already
realized. This prevents spurious GenerateBlock calls when all items are
already materialized.
**UiRootInputPipeline.cs** — Added a blank line between hoverTarget assignment
and UpdateHover call for readability; no behavioral change.
**DataGridView.xml** — Set IsHitTestVisible="False" on the explanatory
TextBlock wrapper so pointer events fall through to the grid beneath.
Test additions
-------------
**ControlDemoDataGridSampleTests.cs**
- `DataGridView_WheelScroll_WhenAllRowsAreRealized_DoesNotInvalidateLayout`
verifies that wheel scrolling a fully-realized grid produces zero
additional measure/arrange invalidations on the UiRoot.
- `DataGridView_DraggingHorizontalScrollBar_DoesNotInvalidateWorkbenchLayout`
same check for horizontal thumb drag.
- Updated existing thumb-drag test to use RunInputDeltaForTests helpers
instead of direct HandlePointer* calls, aligning with the new pattern.
**ControlsCatalogScrollPersistenceTests.cs**
- `SidebarWheelScroll_ShouldMoveCatalogScrollViewer` — verifies that
wheel events on the catalog sidebar actually scroll the nested
ScrollViewer and move the thumb.
- Added wheelDelta parameter to `CreatePointerDelta` helper.
**ScrollViewerViewerOwnedScrollingTests.cs**
- `VirtualizingStackPanel_AllRealized_WheelScroll_StaysOffLayoutInvalidationPath`
checks that wheel scrolling a VSP with all items realized incurs
zero measure and zero arrange invalidations.
- Updated an existing assertion from `Assert.True(uiRoot.ArrangeInvalidationCount > 0)`
to `Assert.Equal(0, ...)` to match the new, cleaner behavior.
**DataGridParityChecklistTests.cs**
- Updated frozen-column arrange/measure assertions to assert equality
rather than just "greater than before" — confirming no spurious
invalidations are introduced by the framework changes.
**ScrollViewerWheelHoverRegressionTests.cs**
- Added `WheelScrollLagMetricsTests` nested class with
`WheelScrollLag_ManyTicksWhilePointerOverButtons_CapturesInstrumentation`
as a first instrumentation probe for repeated-wheel-tick lag scenarios.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d metrics assertions. - ScrollViewer: removed _diagWheelEvents, _diagWheelHandled, _diagSetOffsetCalls, _diagHorizontalDelta, _diagVerticalDelta, _diagSetOffsetNoOp counters from HandleMouseWheelFromInput and SetOffsets - UiRootInputPipeline: removed wheel timing stopwatches and hit-test counters from DispatchMouseWheel; consolidated RefreshHoverAfterWheel dispatch - ScrollViewerWheelHoverRegressionTests: added metrics assertions to verify wheel events and SetOffset calls are captured during scroll tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: NotifyInvalidation with a null effectiveSource was calling TrackDirtyBoundsForVisual(null), which unconditionally escalated to full-frame dirty via MarkFullFrameDirty(dueToFragmentation: false). During scroll+hover, this escalation fired ~8 times per frame, causing excessive retained-mode redraws and CPU churn. Fix: Add && effectiveSource != null guard before TrackDirtyBoundsForVisual in the Render invalidation path (UiRootFrameState.NotifyInvalidation). When no source is provided, there is nothing to track dirty bounds for, so the escalation is unnecessary. Also updated DirtyBoundsEdgeRegressionTests to reflect the corrected behavior: null-source render invalidations no longer trigger full-frame dirty escalation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TrackDirtyBoundsForVisual was called ~3,000 times per wheel tick even when IsFullFrameDirty=true, cascading into AddDirtyRegion(2.5M+) and AddDirtyBounds(1M+) calls that immediately returned but still incurred full call overhead. Adding an IsFullFrameDirty guard at the top of TrackDirtyBoundsForVisual eliminates this cascade entirely. Metrics after fix (per wheel tick): - TrackDirtyBoundsForVisual: 30,600 → 180 (170x reduction) - AddDirtyRegion: 2,577,390 → 506 (5,095x reduction) - AddDirtyBounds: 1,092,700 → 156 (7,005x reduction) Includes new StyledButtonScrollHoverRegressionTests covering the DropShadowEffect + storyboard hover + wheel scroll scenario that was previously unexercised by any test, plus InstrumentationCapture test double for capturing Trace.WriteLine instrumentation in tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces Trace(), TraceTiming(), and TraceCounter() static methods for emitting [INSTRUMENT] lines visible in test output. Includes InstrumentationTests exercising all three methods. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add InkCanvas control for ink collection - Add InkPresenter control for ink rendering - Implement Stroke and StrokeCollection classes - Add StylusPoint and DrawingAttributes models - Basic mouse input support Part of Issue #3 - Bounty: InkCanvas and InkPresenter controls Reward: Lifetime Commercial License Generated by OpenClaw AI
Author
|
Closing in favor of #19 (conflict-free version) |
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.
Fixes #3
🎯 Bounty: InkCanvas and InkPresenter Controls
What's Implemented
✅ InkCanvas Control
✅ InkPresenter Control
✅ Core Classes
Implementation Details
Files Changed
Testing
Future Work
Bounty Claim
Addresses Issue #3
Reward: Lifetime Commercial License 🎫
Generated with ❤️ by OpenClaw AI