Skip to content

Comments

Configurable Mouse-Wheel Scroll Direction for Tool and Document Tab Strips#1027

Merged
wieslawsoltes merged 4 commits intomasterfrom
feature/tabstrip-wheel-scroll-orientation
Feb 7, 2026
Merged

Configurable Mouse-Wheel Scroll Direction for Tool and Document Tab Strips#1027
wieslawsoltes merged 4 commits intomasterfrom
feature/tabstrip-wheel-scroll-orientation

Conversation

@wieslawsoltes
Copy link
Owner

@wieslawsoltes wieslawsoltes commented Feb 7, 2026

PR Summary: Configurable Mouse-Wheel Scroll Direction for Tool and Document Tab Strips

Branch

  • feature/tabstrip-wheel-scroll-orientation

Problem Statement

When tab overflow occurs in ToolTabStrip and DocumentTabStrip, mouse-wheel behavior was not configurable for tab overflow navigation. The requested behavior is:

  • Make horizontal wheel scrolling the default for both tab strips.
  • Keep wheel direction configurable at runtime.
  • Avoid a re-templating-hostile attached-property requirement.

Goals

  • Keep public API simple and additive.
  • Default to horizontal wheel scrolling.
  • Handle runtime changes to MouseWheelScrollOrientation.
  • Keep behavior robust across detach/reattach visual tree lifecycle.
  • Preserve existing drag/drop and window-drag behavior.

Final Design

The final implementation uses template-time PART_ScrollViewer event wiring in control code-behind:

  • Each tab strip captures PART_ScrollViewer in OnApplyTemplate.
  • Wheel handling is attached directly to that instance via a helper.
  • The helper calls ScrollViewer line-scroll API based on the configured orientation.
  • On MouseWheelScrollOrientation property changes, controls reattach the handler so behavior updates immediately.
  • On detach/reattach, handlers are disposed/re-attached to avoid leaks and stale subscriptions.

This replaces the intermediate approach that handled wheel at the tab strip level or via attached behavior bindings.

Detailed Changes

1. Shared scrolling logic helper

File: /Users/wieslawsoltes/GitHub/Dock/src/Dock.Avalonia/Internal/TabStripMouseWheelScrollHelper.cs

Provides:

  • TryHandle(ScrollViewer? scrollViewer, Orientation orientation, Vector delta)

Behavior:

  • Returns false for null viewer, no wheel delta, or no overflow in target axis.
  • Uses delta.Y and converts magnitude to line-step count.
  • Scrolls with LineLeft/LineRight or LineUp/LineDown.
  • Returns true only if offset changed.

2. New hook helper for PART_ScrollViewer

File: /Users/wieslawsoltes/GitHub/Dock/src/Dock.Avalonia/Internal/ScrollViewerMouseWheelHookHelper.cs

Provides:

  • Attach(ScrollViewer? scrollViewer, Orientation orientation) : IDisposable?

Behavior:

  • Subscribes to scrollViewer.PointerWheelChanged.
  • Invokes TabStripMouseWheelScrollHelper.TryHandle(...).
  • Sets e.Handled only on actual scroll movement.
  • Returns disposable to cleanly unhook.

3. DocumentTabStrip updates

File: /Users/wieslawsoltes/GitHub/Dock/src/Dock.Avalonia/Controls/DocumentTabStrip.axaml.cs

Key updates:

  • Keeps public property:
    • MouseWheelScrollOrientation (default Horizontal)
  • Uses PART_ScrollViewer in OnApplyTemplate and hooks wheel handling through helper.
  • Handles property updates:
    • Reattaches wheel hook when MouseWheelScrollOrientation changes.
  • Handles lifecycle:
    • Reattaches on OnAttachedToVisualTree.
    • Disposes hook on OnDetachedFromVisualTree.

4. ToolTabStrip updates

File: /Users/wieslawsoltes/GitHub/Dock/src/Dock.Avalonia/Controls/ToolTabStrip.axaml.cs

Key updates:

  • Keeps public property:
    • MouseWheelScrollOrientation (default Horizontal)
  • Uses PART_ScrollViewer in OnApplyTemplate and hooks wheel handling through helper.
  • Handles property updates:
    • Reattaches wheel hook when MouseWheelScrollOrientation changes.
  • Handles lifecycle:
    • Disposes hook on OnDetachedFromVisualTree.
    • Reattaches on OnAttachedToVisualTree.

5. Tests

File: /Users/wieslawsoltes/GitHub/Dock/tests/Dock.Avalonia.HeadlessTests/TabStripMouseWheelScrollTests.cs

Coverage now includes:

  • Default horizontal behavior:
    • DocumentTabStrip_MouseWheelScrollsHorizontally_ByDefault
    • ToolTabStrip_MouseWheelScrollsHorizontally_ByDefault
  • Configurable vertical behavior:
    • DocumentTabStrip_CanScrollVertically_WhenConfigured
  • Runtime property-change behavior:
    • DocumentTabStrip_MouseWheelScrollOrientation_ChangesAtRuntime
    • ToolTabStrip_MouseWheelScrollOrientation_ChangesAtRuntime
  • Detach/reattach lifecycle behavior:
    • DocumentTabStrip_MouseWheelScrolls_AfterDetachAndReattach
    • ToolTabStrip_MouseWheelScrolls_AfterDetachAndReattach

Notes:

  • Tests raise wheel from tab item source path, not only from the parent tab strip, to reflect routed-event behavior with ScrollViewer-level hooks.

6. Existing API docs and defaults

Previously added docs/default assertions remain valid:

  • Docs: /Users/wieslawsoltes/GitHub/Dock/docfx/articles/dock-controls-reference.md
  • Default property tests: /Users/wieslawsoltes/GitHub/Dock/tests/Dock.Avalonia.Diagnostics.UnitTests/DockControlInstantiationTests.cs

Public API Impact

New Properties (already introduced in this branch)

  • DocumentTabStrip.MouseWheelScrollOrientation : Orientation (default Horizontal)
  • ToolTabStrip.MouseWheelScrollOrientation : Orientation (default Horizontal)

Breaking Changes

  • None.
  • Additive API, implementation refactor only.

Behavior Matrix

Control Config value Wheel action
DocumentTabStrip Horizontal (default) Scroll left/right
DocumentTabStrip Vertical Scroll up/down
ToolTabStrip Horizontal (default) Scroll left/right
ToolTabStrip Vertical Scroll up/down

Validation Performed

  1. dotnet test tests/Dock.Avalonia.HeadlessTests/Dock.Avalonia.HeadlessTests.csproj --filter "FullyQualifiedName~TabStripMouseWheelScrollTests"
    • Result: Passed (7/7)
  2. dotnet test tests/Dock.Avalonia.Diagnostics.UnitTests/Dock.Avalonia.Diagnostics.UnitTests.csproj --filter "FullyQualifiedName~DockControlInstantiationTests"
    • Result: Passed
  3. dotnet build src/Dock.Avalonia/Dock.Avalonia.csproj -f net10.0
    • Result: Succeeded

Usage Example

<DocumentTabStrip MouseWheelScrollOrientation="Horizontal" />
<ToolTabStrip MouseWheelScrollOrientation="Horizontal" />

<!-- Optional override -->
<DocumentTabStrip MouseWheelScrollOrientation="Vertical" />
<ToolTabStrip MouseWheelScrollOrientation="Vertical" />

Risk Assessment

Low risk:

  • Scoped to tab strip wheel behavior.
  • Hook lifecycle is explicit (attach/dispose/reattach).
  • Event is consumed only when effective scrolling occurred.

Rollback Plan

If issues arise:

  1. Revert the latest wheel-hook refactor commit.
  2. If needed, revert earlier MouseWheelScrollOrientation commits in this branch.

Reviewer Checklist

  • Confirm wheel handling is wired via PART_ScrollViewer and not tab strip override.
  • Confirm runtime orientation changes are applied immediately.
  • Confirm detach/reattach keeps wheel behavior working.
  • Confirm docs/defaults still match behavior.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant