Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
df185e4
PoC for hijacking RequestBringIntoView event
nicolaihenriksen Dec 9, 2025
8f02def
Improves tab header scrolling behavior
nicolaihenriksen Dec 9, 2025
0511117
Adding smooth/animated scrolling to the behavior
nicolaihenriksen Dec 9, 2025
721a25c
Ensure padding on tab headers is only applied when they overflow
nicolaihenriksen Dec 9, 2025
f631a2d
Adding UI tests without assertions to easily test behavior
nicolaihenriksen Dec 10, 2025
9cace11
Minor hack! Prevent double-click on tab (control) while animating
nicolaihenriksen Dec 10, 2025
7c0c9ee
Add TabAssist.AnimateTabScrolling to toggle tab switch animation feature
nicolaihenriksen Dec 10, 2025
c27cc43
Add TODO comment regarding destructive-read on TabScrollDirection AP
nicolaihenriksen Dec 10, 2025
d18d4d4
Add TabAssist.TabScrollOffset to give control of the offset
nicolaihenriksen Dec 10, 2025
4a960c0
Replace TabAssist.AnimateTabScrolling with TabAssist.TabScrollDuration
nicolaihenriksen Dec 10, 2025
9631e7a
Rename hijacking StackPanel
nicolaihenriksen Dec 14, 2025
f001a07
Rename TabScrollDirection
nicolaihenriksen Dec 14, 2025
737223f
Rename TabScrollOffset
nicolaihenriksen Dec 14, 2025
eb5d42d
Rename TabScrollDuration
nicolaihenriksen Dec 14, 2025
966c1e8
Remove debug output
nicolaihenriksen Dec 14, 2025
a604ccf
Add TabAssist.UseHeaderPadding to enable/disable new behavior
nicolaihenriksen Dec 14, 2025
0200ded
Change bring into view event listener to class handler
nicolaihenriksen Dec 21, 2025
a4512b4
Implement "destructive read" of TabScrollDirection
nicolaihenriksen Dec 21, 2025
eca4a48
Mitigate quick keypresses causing tab animation to stop
nicolaihenriksen Dec 21, 2025
fe5b943
Use tab animation even when tab has focusable content
nicolaihenriksen Dec 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add TabAssist.TabScrollOffset to give control of the offset
Default value (40, although spec says 52) is set in style to allow easy override at the call site.
  • Loading branch information
nicolaihenriksen committed Dec 10, 2025
commit d18d4d4ef789b7a39e3b5e12745b59c40f5f32ae
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,6 @@ private static void OnScrollableContentChanged(DependencyObject d, DependencyPro
behavior.AddPaddingToScrollableContentIfWiderThanViewPort();
}

internal const double ScrollOffset = 40; // MD spec says 52 DP, but that seems a little excessive in practice
internal static readonly Thickness ScrollableContentPadding = new(ScrollOffset, 0, ScrollOffset, 0);
internal static readonly Thickness NoScrollableContentPadding = new(0);

private double? _desiredScrollStart;
private bool _isAnimatingScroll;

Expand Down Expand Up @@ -103,11 +99,12 @@ private void AddPaddingToScrollableContentIfWiderThanViewPort()

if (ScrollableContent.ActualWidth > TabControl.ActualWidth)
{
ScrollableContent.Margin = ScrollableContentPadding;
double offset = TabAssist.GetTabScrollOffset(TabControl);
ScrollableContent.Margin = new(offset, 0, offset, 0);
}
else
{
ScrollableContent.Margin = NoScrollableContentPadding;
ScrollableContent.Margin = new();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ public TabScrollDirection TabScrollDirection
DependencyProperty.Register(nameof(TabScrollDirection), typeof(TabScrollDirection),
typeof(BringIntoViewHijackingStackPanel), new PropertyMetadata(TabScrollDirection.Unknown));

public double TabScrollOffset
{
get => (double)GetValue(TabScrollOffsetProperty);
set => SetValue(TabScrollOffsetProperty, value);
}

public static readonly DependencyProperty TabScrollOffsetProperty =
DependencyProperty.Register(nameof(TabScrollOffset),
typeof(double), typeof(BringIntoViewHijackingStackPanel), new PropertyMetadata(0d));

public BringIntoViewHijackingStackPanel()
=> AddHandler(FrameworkElement.RequestBringIntoViewEvent, new RoutedEventHandler(OnRequestBringIntoView), false);

Expand All @@ -26,8 +36,8 @@ private void OnRequestBringIntoView(object sender, RoutedEventArgs e)

// TODO: Consider making the "TabScrollDirection" a destructive read (i.e. reset the value once it is read) to avoid leaving a Backward/Forward value that may be misinterpreted at a later stage.
double offset = TabScrollDirection switch {
TabScrollDirection.Backward => -TabControlHeaderScrollBehavior.ScrollOffset,
TabScrollDirection.Forward => TabControlHeaderScrollBehavior.ScrollOffset,
TabScrollDirection.Backward => -TabScrollOffset,
TabScrollDirection.Forward => TabScrollOffset,
_ => 0
};
var point = child.TranslatePoint(new Point(), this);
Expand Down
10 changes: 10 additions & 0 deletions src/MaterialDesignThemes.Wpf/TabAssist.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,14 @@ public static void SetAnimateTabScrolling(DependencyObject obj, bool value)

public static readonly DependencyProperty AnimateTabScrollingProperty =
DependencyProperty.RegisterAttached("AnimateTabScrolling", typeof(bool), typeof(TabAssist), new PropertyMetadata(false));

public static double GetTabScrollOffset(DependencyObject obj)
=> (double)obj.GetValue(TabScrollOffsetProperty);

public static void SetTabScrollOffset(DependencyObject obj, double value)
=> obj.SetValue(TabScrollOffsetProperty, value);

public static readonly DependencyProperty TabScrollOffsetProperty =
DependencyProperty.RegisterAttached("TabScrollOffset", typeof(double),
typeof(TabAssist), new PropertyMetadata(0d));
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
<b:Interaction.Behaviors>
<behaviorsInternal:TabControlHeaderScrollBehavior TabControl="{Binding RelativeSource={RelativeSource TemplatedParent}}" ScrollableContent="{Binding ElementName=ScrollableContent}" />
</b:Interaction.Behaviors>
<internal:BringIntoViewHijackingStackPanel x:Name="ScrollableContent" TabScrollDirection="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(behaviorsInternal:TabControlHeaderScrollBehavior.TabScrollDirection)}">
<internal:BringIntoViewHijackingStackPanel x:Name="ScrollableContent"
TabScrollDirection="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(behaviorsInternal:TabControlHeaderScrollBehavior.TabScrollDirection)}"
TabScrollOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(wpf:TabAssist.TabScrollOffset)}">
<UniformGrid x:Name="CenteredHeaderPanel"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{Binding Path=(wpf:TabAssist.HeaderPanelMargin), RelativeSource={RelativeSource TemplatedParent}}"
Expand Down Expand Up @@ -234,6 +236,8 @@
<Setter Property="wpf:RippleAssist.Feedback" Value="{DynamicResource MaterialDesign.Brush.Button.Ripple}" />
<Setter Property="wpf:TabAssist.HasUniformTabWidth" Value="False" />
<Setter Property="wpf:TabAssist.AnimateTabScrolling" Value="True" />
<!-- MD spec says 52 DP, but that seems a little excessive in practice -->
<Setter Property="wpf:TabAssist.TabScrollOffset" Value="40" />

<Style.Triggers>
<Trigger Property="wpf:TabAssist.HeaderBehavior" Value="Wrapping">
Expand Down
Loading