Skip to content

Implement first-class inking support (InkCanvas + InkToolBar)#11016

Open
Nitin-100 wants to merge 2 commits intomicrosoft:mainfrom
Nitin-100:user/nitinc/inking-support
Open

Implement first-class inking support (InkCanvas + InkToolBar)#11016
Nitin-100 wants to merge 2 commits intomicrosoft:mainfrom
Nitin-100:user/nitinc/inking-support

Conversation

@Nitin-100
Copy link
Copy Markdown

@Nitin-100 Nitin-100 commented Mar 1, 2026

Implement First-Class Inking Support (InkCanvas + InkToolBar)


Summary

This PR implements first-class inking support in WinUI 3 by expanding the InkCanvas API surface and replacing the InkToolBar E_NOTIMPL skeleton with a full working implementation. The changes follow the existing WinUI C++/WinRT patterns and integrate with the InkPresenter desktop infrastructure already in place.


Changes

InkCanvas API Expansion

File Change
src/controls/dev/InkCanvas/InkCanvas.idl Added InkCanvasMode enum (Draw/Erase/Select), InkInputType flags enum (Pen/Touch/Mouse), InkCanvasStrokeCollectedEventArgs, InkCanvasStrokesErasedEventArgs runtime classes. Extended InkCanvas with Mode, AllowedInputTypes, DefaultDrawingAttributes properties, StrokeContainer accessor, SaveAsync/LoadAsync persistence methods, ClearStrokes, and StrokeCollected/StrokesErased events.
src/controls/dev/InkCanvas/InkCanvas.h Added property changed callbacks (OnModePropertyChanged, OnAllowedInputTypesPropertyChanged, OnDefaultDrawingAttributesPropertyChanged), public StrokeContainer(), SaveAsync(), LoadAsync(), ClearStrokes() methods, and private helpers (UpdateInkPresenterMode, UpdateInkPresenterInputTypes, SetupStrokeEvents).
src/controls/dev/InkCanvas/InkCanvas.cpp Implemented all new methods: mode switching maps to InkInputProcessingMode, input type filtering maps to CoreInputDeviceTypes, persistence uses InkStrokeContainer.SaveAsync/LoadAsync queued on the ink thread via task_completion_event, stroke events bridge InkPresenter.StrokesCollected/StrokesErased to the WinUI event surface.
src/controls/dev/InkCanvas/InkCanvasStrokeCollectedEventArgs.h New file. Lightweight event args holding a single InkStroke.
src/controls/dev/InkCanvas/InkCanvasStrokesErasedEventArgs.h New file. Lightweight event args holding an IVectorView<InkStroke>.
src/controls/dev/InkCanvas/InkCanvas.vcxitems Added the two new event args headers to the build.

InkToolBar Implementation

File Change
src/controls/dev/InkToolBar/InkToolBar.h Replaced all E_NOTIMPL one-liners with proper method declarations. Added private state: tool button vectors, orientation, flyout placement, initial controls config, default-tools-created flag. Added OnApplyTemplate, CreateDefaultToolButtons, UpdateInkPresenterFromActiveTool, OnActiveToolChanged, OnTargetInkCanvasChanged, GetInkPresenter helpers.
src/controls/dev/InkToolBar/InkToolBar.cpp New file (~280 lines). Full implementation: constructs default tool buttons (BallpointPen, Pencil, Highlighter, Eraser, Stencil) based on InitialControls, manages active tool state, synchronizes inking/erasing mode on the linked InkCanvas's InkPresenter, resolves presenter from either TargetInkCanvas or explicit TargetInkPresenter.
src/controls/dev/InkToolBar/InkToolBar.vcxitems Added InkToolBar.cpp and all existing subclass headers (BallpointPenButton, PencilButton, HighlighterButton, EraserButton, StencilButton, CustomPen, toggles, menus, flyout items, automation peers, trace).

Build & Feature Flags

File Change
src/controls/FeatureAreas.props FeatureInkToolBarEnabled: falsetrue
src/controls/dev/Generated/InkCanvas.properties.h Added AllowedInputTypes, DefaultDrawingAttributes, Mode dependency properties, StrokeCollected/StrokesErased event sources.
src/controls/dev/Generated/InkCanvas.properties.cpp Registered the three new dependency properties with correct defaults and change callbacks. Added event source add/remove implementations.
src/controls/dev/Generated/InkToolBarMenuButton.properties.h/.cpp Removed MenuKind dependency property (provided by base class, was duplicated).
src/controls/dev/dll/XamlMetadataProviderGenerated.h Added XAML metadata entries for new InkCanvas properties/methods and InkToolBar types, enabling XAML tooling and runtime reflection.

New API Surface

enum InkCanvasMode { Draw, Erase, Select };

[flags] enum InkInputType { None, Pen, Touch, Mouse };

runtimeclass InkCanvasStrokeCollectedEventArgs {
    Windows.UI.Input.Inking.InkStroke Stroke { get; };
};

runtimeclass InkCanvasStrokesErasedEventArgs {
    IVectorView<InkStroke> Strokes { get; };
};

unsealed runtimeclass InkCanvas : FrameworkElement {
    // Existing
    Boolean IsEnabled { get; set; };
    IAsyncAction QueueInkPresenterWorkItem(DoInkPresenterWork workItem);

    // New
    InkCanvasMode Mode { get; set; };                          // Default: Draw
    InkInputType AllowedInputTypes { get; set; };              // Default: Pen | Mouse
    InkDrawingAttributes DefaultDrawingAttributes { get; set; };
    InkStrokeContainer StrokeContainer { get; };               // Read-only access

    IAsyncAction SaveAsync(IOutputStream stream);
    IAsyncAction LoadAsync(IInputStream stream);
    void ClearStrokes();

    event TypedEventHandler<InkCanvas, InkCanvasStrokeCollectedEventArgs> StrokeCollected;
    event TypedEventHandler<InkCanvas, InkCanvasStrokesErasedEventArgs> StrokesErased;
};

Design Decisions

  1. Ink thread safety: All InkPresenter interactions are queued via QueueInkPresenterWorkItem / GenericInkCallback, following the existing pattern. SaveAsync and LoadAsync use task_completion_event to bridge async work across threads.

  2. Event bridging: Stroke events subscribe to InkPresenter.StrokesCollected/StrokesErased (which fire on the ink thread) and re-raise through the generated event_source on the UI thread.

  3. InkToolBar integration: InkToolBar resolves the InkPresenter via winrt::get_self<InkCanvas>()->InkPresenter() when TargetInkCanvas is set, or falls back to TargetInkPresenter for advanced scenarios.

  4. Feature flag: FeatureInkToolBarEnabled is flipped to true. InkCanvas was already enabled.


Testing

Full test infrastructure following the repo's standard 3-layer pattern (APITests, TestUI, InteractionTests) for both controls, registered in MUXControlsTestApp.csproj and MUXControls.Test.csproj with feature flag gating.

InkCanvas Tests (23 tests)

APITests (InkCanvas/APITests/InkCanvasTests.cs) — 17 unit tests:

  • Default property values (IsEnabled, Mode, AllowedInputTypes)
  • Mode property cycling (Draw → Erase → Select → Draw)
  • AllowedInputTypes flags (Pen, Touch, Mouse, combined, None)
  • IsEnabled toggle
  • DefaultDrawingAttributes set/get (color, size, pen tip)
  • StrokeContainer accessor safety (pre-load)
  • ClearStrokes safety (pre-load)
  • StrokeCollected / StrokesErased event subscription
  • DependencyProperty access (ModeProperty, AllowedInputTypesProperty, DefaultDrawingAttributesProperty, IsEnabledProperty)
  • XAML visual tree integration (with and without property binding)
  • Multiple instance independence

TestUI (InkCanvas/TestUI/InkCanvasPage.xaml/.cs):

  • Interactive test page with [TopLevelTestPage(Name = "InkCanvas")]
  • Mode selector (Draw/Erase/Select), input type checkboxes (Pen/Mouse/Touch)
  • Save/Load/Clear buttons with status display
  • Live stroke count from StrokeContainer
  • StrokeCollected / StrokesErased event handlers

InteractionTests (InkCanvas/InteractionTests/InkCanvasTests.cs) — 6 integration tests:

  • Visual tree presence
  • Mode switching via ComboBox
  • Input type configuration via CheckBoxes
  • Clear strokes button
  • Save/Load round-trip
  • Stroke count display

InkToolBar Tests (31 tests)

APITests (InkToolBar/APITests/InkToolBarTests.cs) — 24 unit tests:

  • Default constructor
  • InitialControls property (All/None/PensOnly/AllExceptPens)
  • Children collection access
  • Orientation property (Horizontal/Vertical)
  • ButtonFlyoutPlacement property (Auto/Top/Bottom/Left/Right)
  • IsRulerButtonChecked / IsStencilButtonChecked toggle
  • TargetInkCanvas binding and clearing
  • TargetInkPresenter binding
  • ActiveTool / InkDrawingAttributes access
  • GetToolButton / GetToggleButton / GetMenuButton lookup
  • All 10 DependencyProperties existence check
  • ActiveToolChanged / InkDrawingAttributesChanged / EraseAllClicked event subscription
  • XAML visual tree (standalone and with linked InkCanvas)
  • Multiple instance independence
  • Subclass construction: InkToolBarBallpointPenButton, InkToolBarPencilButton, InkToolBarHighlighterButton, InkToolBarCustomToolButton, InkToolBarCustomToggleButton, InkToolBarStencilButton, InkToolBarFlyoutItem, InkToolBarPenConfigurationControl

TestUI (InkToolBar/TestUI/InkToolBarPage.xaml/.cs):

  • Interactive test page with [TopLevelTestPage(Name = "InkToolBar")]
  • InitialControls/Orientation/FlyoutPlacement selectors
  • Ruler/Stencil toggle checkboxes
  • Linked InkCanvas via TargetInkCanvas binding
  • Active tool and event status display

InteractionTests (InkToolBar/InteractionTests/InkToolBarTests.cs) — 7 integration tests:

  • Visual tree presence (toolbar + linked canvas)
  • InitialControls selection cycling
  • Orientation switching
  • Flyout placement switching
  • Ruler toggle
  • Stencil toggle
  • Active tool display

Standalone Validation App

A separate WinUI 3 test application (InkingTestApp) was built outside the repo to validate the Windows.UI.Input.Inking data model APIs:

  • 16 automated tests covering InkStrokeBuilder, InkStrokeContainer, InkDrawingAttributes, serialization round-trip, and attribute fidelity
  • Manual drawing tests: pointer-based canvas, erasing, tool switching, color/width selection
  • Persistence tests: InMemoryRandomAccessStream save/load with visual verification

All 16 tests pass. Not included in this PR.

- Expand InkCanvas API: Mode (Draw/Erase/Select), AllowedInputTypes,
  DefaultDrawingAttributes, StrokeContainer, SaveAsync/LoadAsync,
  ClearStrokes, StrokeCollected/StrokesErased events
- Add InkCanvasMode enum, InkInputType flags enum
- Add InkCanvasStrokeCollectedEventArgs, InkCanvasStrokesErasedEventArgs
- Implement InkToolBar: replace E_NOTIMPL skeleton with full tool
  management, InkPresenter integration, default tool button creation
- Enable FeatureInkToolBarEnabled in FeatureAreas.props
- Update vcxitems, generated properties, and XAML metadata
@microsoft-github-policy-service microsoft-github-policy-service bot added the needs-triage Issue needs to be triaged by the area owners label Mar 1, 2026
- InkCanvas APITests (17 tests): property defaults, Mode/AllowedInputTypes/
  IsEnabled/DefaultDrawingAttributes cycling, DependencyProperty access,
  StrokeContainer/ClearStrokes safety, event subscription, XAML integration,
  multiple instance independence
- InkToolBar APITests (24 tests): constructor, InitialControls/Orientation/
  ButtonFlyoutPlacement/IsRulerButtonChecked/IsStencilButtonChecked properties,
  TargetInkCanvas/TargetInkPresenter binding, ActiveTool/Children access,
  GetToolButton/GetToggleButton/GetMenuButton, all DependencyProperties exist,
  event subscriptions, BallpointPenButton/PencilButton/HighlighterButton/
  CustomToolButton/CustomToggleButton/StencilButton/FlyoutItem/
  PenConfigurationControl construction, XAML visual tree tests
- InkCanvas TestUI: interactive page with mode selector, input type checkboxes,
  save/load/clear buttons, stroke count display, event status
- InkToolBar TestUI: interactive page with InitialControls/Orientation/
  FlyoutPlacement selectors, ruler/stencil toggles, linked InkCanvas,
  active tool and event status display
- InkCanvas InteractionTests (6 tests): visual tree presence, mode switching,
  input type configuration, clear strokes, save/load, stroke count display
- InkToolBar InteractionTests (7 tests): visual tree presence, InitialControls
  selection, orientation switching, flyout placement, ruler/stencil toggle,
  active tool display
- Registered all test projects in MUXControlsTestApp.csproj and
  MUXControls.Test.csproj with feature flag conditions
@riverar
Copy link
Copy Markdown
Contributor

riverar commented Mar 5, 2026

This probably isn't going to land in WinUI directly due to breaking API/etc. A better approach would be to submit this as a control to the Windows Community Toolkit, where it can get community feedback, be iterated on, and potentially/eventually get promoted into the core library if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-triage Issue needs to be triaged by the area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants