[BOUNTY #3] feat: Implement InkCanvas and InkPresenter controls#12
Open
zhaog100 wants to merge 7 commits intoChevalier12:masterfrom
Open
[BOUNTY #3] feat: Implement InkCanvas and InkPresenter controls#12zhaog100 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>
- 添加 InkStroke 数据结构 - 笔触点列表(Vector2) - 颜色/宽度/类型属性 - 边界计算 - 深拷贝支持 - 添加 InkPresenter 渲染器 - 墨迹渲染(硬笔/毛笔/荧光笔) - 抗锯齿圆形纹理 - 笔触管理(添加/删除/清除) - SVG 导出 - Undo 功能 - 添加 InkCanvas 控件 - 鼠标/触摸输入支持 - 光标显示 - 背景颜色 - 事件处理(OnDrawingStarted/Updated/Ended) - Clear/Undo/Export 方法 - XAML 声明式使用支持 - 添加完整文档(README.md) - 快速开始指南 - API 参考 - XAML 使用示例 - 测试用例 - 性能优化建议 - 添加单元测试(InkCanvasTests.cs) - 基础功能测试 - 笔触管理测试 - 导出功能测试 - 边界条件测试 - 添加 XAML 示例(InkCanvasDemo.xml) - 完整演示界面 - 工具栏(颜色/笔触/粗细) - 状态栏实时更新 Acceptance Criteria: ✅ InkCanvas + InkPresenter 实现完成 ✅ 支持指针输入绘制 ✅ 正确的笔触渲染和裁剪 ✅ 确定性行为和回归测试 ✅ 完整的解决方案和测试套件 版权声明:MIT License | Copyright (c) 2026 思捷娅科技 (SJYKJ)
Author
|
Hi @Chevalier12 👋 I implemented the InkCanvas and InkPresenter components as described in #3. The PR includes:
Would love to get your feedback when you have time. Thanks! |
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.
Description
Fully implement InkCanvas + InkPresenter parity for handwritten input support.
Changes
✅ Core Implementation
InkStroke.cs - Stroke data structure
InkPresenter.cs - Stroke rendering engine
InkCanvas.cs - UI control for ink input
✅ Documentation
✅ Testing
✅ Samples
Target Outcome
✅ Users can place an InkCanvas in XML/XAML
✅ Draw strokes with pointer input
✅ See strokes rendered and clipped correctly
✅ Deterministic behavior with focused regression tests
✅ Full solution + test suite green
Acceptance Criteria
Bounty
Closes #3
版权声明: MIT License | Copyright (c) 2026 思捷娅科技 (SJYKJ)