Browse Source

button spinner - add spinner button popup buttons

pull/21771/head
Emmanuel Hansen 3 weeks ago
parent
commit
4f1a0970cd
  1. 69
      src/Avalonia.Controls/ButtonSpinner.cs
  2. 14
      src/Avalonia.Themes.Fluent2/Accents/FluentControlResources.xaml
  3. 211
      src/Avalonia.Themes.Fluent2/Controls/ButtonSpinner.xaml

69
src/Avalonia.Controls/ButtonSpinner.cs

@ -54,12 +54,12 @@ namespace Avalonia.Controls
{
if (_decreaseButton != null)
{
_decreaseButton.Click -= OnButtonClick;
_decreaseButton.Click -= OnDecreaseButtonClick;
}
_decreaseButton = value;
if (_decreaseButton != null)
{
_decreaseButton.Click += OnButtonClick;
_decreaseButton.Click += OnDecreaseButtonClick;
}
}
}
@ -75,12 +75,48 @@ namespace Avalonia.Controls
{
if (_increaseButton != null)
{
_increaseButton.Click -= OnButtonClick;
_increaseButton.Click -= OnIncreaseButtonClick;
}
_increaseButton = value;
if (_increaseButton != null)
{
_increaseButton.Click += OnButtonClick;
_increaseButton.Click += OnIncreaseButtonClick;
}
}
}
private Button? _popupDecreaseButton;
private Button? PopupDecreaseButton
{
get => _popupDecreaseButton;
set
{
if (_popupDecreaseButton != null)
{
_popupDecreaseButton.Click -= OnDecreaseButtonClick;
}
_popupDecreaseButton = value;
if (_popupDecreaseButton != null)
{
_popupDecreaseButton.Click += OnDecreaseButtonClick;
}
}
}
private Button? _popupIncreaseButton;
private Button? PopupIncreaseButton
{
get => _popupIncreaseButton;
set
{
if (_popupIncreaseButton != null)
{
_popupIncreaseButton.Click -= OnIncreaseButtonClick;
}
_popupIncreaseButton = value;
if (_popupIncreaseButton != null)
{
_popupIncreaseButton.Click += OnIncreaseButtonClick;
}
}
}
@ -125,6 +161,8 @@ namespace Avalonia.Controls
{
IncreaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
DecreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
PopupIncreaseButton = e.NameScope.Find<Button>("PART_PopupIncreaseButton");
PopupDecreaseButton = e.NameScope.Find<Button>("PART_PopupDecreaseButton");
SetButtonUsage();
}
@ -265,6 +303,16 @@ namespace Avalonia.Controls
{
DecreaseButton.IsEnabled = AllowSpin && ((ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease);
}
if (PopupIncreaseButton != null)
{
PopupIncreaseButton.IsEnabled = AllowSpin && ((ValidSpinDirection & ValidSpinDirections.Increase) == ValidSpinDirections.Increase);
}
if (PopupDecreaseButton != null)
{
PopupDecreaseButton.IsEnabled = AllowSpin && ((ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease);
}
}
/// <summary>
@ -272,12 +320,19 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void OnButtonClick(object? sender, RoutedEventArgs e)
private void OnIncreaseButtonClick(object? sender, RoutedEventArgs e)
{
if (AllowSpin)
{
OnSpin(new SpinEventArgs(SpinEvent, SpinDirection.Increase));
}
}
private void OnDecreaseButtonClick(object? sender, RoutedEventArgs e)
{
if (AllowSpin)
{
var direction = sender == IncreaseButton ? SpinDirection.Increase : SpinDirection.Decrease;
OnSpin(new SpinEventArgs(SpinEvent, direction));
OnSpin(new SpinEventArgs(SpinEvent, SpinDirection.Decrease));
}
}

14
src/Avalonia.Themes.Fluent2/Accents/FluentControlResources.xaml

@ -83,6 +83,12 @@
<StaticResource x:Key="ToggleButtonBorderBrushIndeterminatePressed" ResourceKey="ControlStrokeColorDefaultBrush" />
<StaticResource x:Key="ToggleButtonBorderBrushIndeterminateDisabled" ResourceKey="ControlStrokeColorDefaultBrush" />
<!--Resources for ButtonSpinner-->
<StaticResource x:Key="SpinnerPopupIndicatorForeground" ResourceKey="TextFillColorSecondaryBrush" />
<StaticResource x:Key="SpinnerPopupBackground" ResourceKey="AcrylicBackgroundFillColorDefaultBrush" />
<StaticResource x:Key="SpinnerPopupBorderBrush" ResourceKey="SurfaceStrokeColorFlyoutBrush" />
<StaticResource x:Key="SpinnerPopupSpinButtonBackground" ResourceKey="SubtleFillColorTransparentBrush" />
<!-- GroupBox -->
<Thickness x:Key="GroupBoxPadding">12</Thickness>
<x:Double x:Key="GroupBoxHeaderFontSize">16</x:Double>
@ -733,7 +739,13 @@
<StaticResource x:Key="RepeatButtonBorderBrush" ResourceKey="ControlElevationBorderBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPointerOver" ResourceKey="ControlElevationBorderBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPressed" ResourceKey="ControlStrokeColorDefaultBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushDisabled" ResourceKey="ControlStrokeColorDefaultBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushDisabled" ResourceKey="ControlStrokeColorDefaultBrush" />
<!--Resources for ButtonSpinner-->
<StaticResource x:Key="SpinnerPopupIndicatorForeground" ResourceKey="TextFillColorSecondaryBrush" />
<StaticResource x:Key="SpinnerPopupBackground" ResourceKey="AcrylicBackgroundFillColorDefaultBrush" />
<StaticResource x:Key="SpinnerPopupBorderBrush" ResourceKey="SurfaceStrokeColorFlyoutBrush" />
<StaticResource x:Key="SpinnerPopupSpinButtonBackground" ResourceKey="SubtleFillColorTransparentBrush" />
<!-- Resources for ToggleButton.xaml -->
<Thickness x:Key="ToggleButtonBorderThemeThickness">1</Thickness>

211
src/Avalonia.Themes.Fluent2/Controls/ButtonSpinner.xaml

@ -37,62 +37,62 @@
</Design.PreviewWith>
<!-- Retained for compatibility: the Fluent 2 spin buttons are borderless, so these
converters are no longer used by the template below. -->
converters are no longer used by the template below. -->
<converters:MarginMultiplierConverter x:Key="ButtonSpinnerLeftThickness" Indent="1" Left="True" />
<converters:MarginMultiplierConverter x:Key="ButtonSpinnerRightThickness" Indent="1" Right="True" />
<StreamGeometry x:Key="ButtonSpinnerIncreaseButtonIcon">M0,9 L10,0 20,9 19,10 10,2 1,10 z</StreamGeometry>
<StreamGeometry x:Key="ButtonSpinnerDecreaseButtonIcon">M0,1 L10,10 20,1 19,0 10,8 1,0 z</StreamGeometry>
<StreamGeometry x:Key="ButtonSpinnerIncreaseButtonIcon">M4.293 15.707a1 1 0 0 0 1.414 0L12 9.414l6.293 6.293a1 1 0 0 0 1.414-1.414l-7-7a1 1 0 0 0-1.414 0l-7 7a1 1 0 0 0 0 1.414Z</StreamGeometry>
<StreamGeometry x:Key="ButtonSpinnerDecreaseButtonIcon">M4.293 8.293a1 1 0 0 1 1.414 0L12 14.586l6.293-6.293a1 1 0 1 1 1.414 1.414l-7 7a1 1 0 0 1-1.414 0l-7-7a1 1 0 0 1 0-1.414Z</StreamGeometry>
<StreamGeometry x:Key="ButtonSpinnerUpDownButtonIcon">M18.79 8.387a1 1 0 0 1-1.497 1.32L12 4.414 6.707 9.707l-.094.083a1 1 0 0 1-1.32-1.497l6-6 .094-.083a1 1 0 0 1 1.32.083l6 6 .083.094ZM5.21 15.613a1 1 0 0 1 1.497-1.32L12 19.586l5.293-5.293.094-.083a1 1 0 0 1 1.32 1.497l-6 6-.094.083a1 1 0 0 1-1.32-.083l-6-6-.083-.094Z</StreamGeometry>
<Thickness x:Key="ButtonSpinnerSpinButtonBorderThickness">0,1,1,1</Thickness>
<Thickness x:Key="ButtonSpinnerIconMargin">10,0,0,0</Thickness>
<x:Double x:Key="ButtonSpinnerPopupShadowDepth">16</x:Double>
<x:Double x:Key="ButtonSpinnerMinWidth">120</x:Double>
<Thickness x:Key="ButtonSpinnerPopupIndicatorMargin">0,0,8,0</Thickness>
<Thickness x:Key="SpinnerPopupBorderThickness">1</Thickness>
<Thickness x:Key="SpinnerPopupSpinButtonBorderThickness">0</Thickness>
<!-- WinUI reference: NumberBox.xaml (NumberBoxSpinButtonStyle), tag winui3/release/1.7.1.
Inline spin buttons are subtle-fill RepeatButtons (transparent at rest, SubtleFillColorSecondary
on hover, SubtleFillColorTertiary when pressed) with ControlCornerRadius, inset inside the field.
Press feedback is a fill change with an 83ms cross-fade; WinUI has no press scale. -->
<ControlTheme x:Key="FluentButtonSpinnerRepeatButton" TargetType="RepeatButton">
<Setter Property="MinWidth" Value="32" />
<ControlTheme x:Key="FluentButtonSpinnerPopupRepeatButton" TargetType="RepeatButton" BasedOn="{StaticResource {x:Type RepeatButton}}">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Width" Value="36" />
<Setter Property="Height" Value="36" />
<Setter Property="Padding" Value="0" />
<Setter Property="Background" Value="{DynamicResource TextControlButtonBackground}" />
<Setter Property="Foreground" Value="{DynamicResource TextControlButtonForeground}" />
<Setter Property="BorderThickness" Value="{DynamicResource SpinnerPopupSpinButtonBorderThickness}"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter x:Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BackgroundSizing="InnerBorderEdge"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
<ContentPresenter.Transitions>
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.083" />
</Transitions>
</ContentPresenter.Transitions>
</ContentPresenter>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TextControlButtonBackgroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource TextControlButtonForegroundPointerOver}" />
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TextControlButtonBackgroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource TextControlButtonForegroundPressed}" />
</Style>
</ControlTheme>
<Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TextControlButtonBackground}" />
<Setter Property="Foreground" Value="{DynamicResource TextFillColorDisabledBrush}" />
</Style>
<ControlTheme x:Key="FluentButtonSpinnerRepeatButton" TargetType="RepeatButton" BasedOn="{StaticResource {x:Type RepeatButton}}">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Focusable" Value="False" />
<Setter Property="MinWidth" Value="32" />
<Setter Property="Padding" Value="0" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="{DynamicResource SpinnerPopupSpinButtonBorderThickness}"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</ControlTheme>
<ControlTheme x:Key="SpinnerPopupIndicatorButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Padding" Value="4" />
<ControlTheme.Resources>
<x:Double x:Key="FlyoutThemeMinWidth">0</x:Double>
<x:Double x:Key="FlyoutThemeMinHeight">0</x:Double>
<SolidColorBrush x:Key="ButtonBackgroundPointerOver">Transparent</SolidColorBrush>
<SolidColorBrush x:Key="ButtonBackgroundPressed">Transparent</SolidColorBrush>
<SolidColorBrush x:Key="ButtonBackgroundDisabled">Transparent</SolidColorBrush>
<SolidColorBrush x:Key="ButtonBorderBrushPointerOver">Transparent</SolidColorBrush>
<SolidColorBrush x:Key="ButtonBorderBrushPressed">Transparent</SolidColorBrush>
<SolidColorBrush x:Key="ButtonBorderBrushDisabled">Transparent</SolidColorBrush>
</ControlTheme.Resources>
</ControlTheme>
<!-- WinUI reference: NumberBox.xaml / TextBox_themeresources.xaml, tag winui3/release/1.7.1.
The control is a TextBox-like field: ControlFillColorDefault fill, hairline elevation border
(TextControlElevationBorderBrush), ControlCornerRadius, with the spin buttons laid out inline. -->
The control is a TextBox-like field: ControlFillColorDefault fill, hairline elevation border
(TextControlElevationBorderBrush), ControlCornerRadius, with the spin buttons laid out inline. -->
<ControlTheme x:Key="{x:Type ButtonSpinner}" TargetType="ButtonSpinner">
<Setter Property="Foreground" Value="{DynamicResource TextControlForeground}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
@ -109,57 +109,115 @@
<Setter Property="Template">
<ControlTemplate>
<DataValidationErrors>
<Panel>
<Grid Height="{TemplateBinding Height}">
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="RepeatButtonBackground" ResourceKey="TextControlButtonBackground" />
<StaticResource x:Key="RepeatButtonBackgroundPointerOver" ResourceKey="TextControlButtonBackgroundPointerOver" />
<StaticResource x:Key="RepeatButtonBackgroundPressed" ResourceKey="TextControlButtonBackgroundPressed" />
<StaticResource x:Key="RepeatButtonForeground" ResourceKey="TextControlButtonForeground" />
<StaticResource x:Key="RepeatButtonForegroundPointerOver" ResourceKey="TextControlButtonForegroundPointerOver" />
<StaticResource x:Key="RepeatButtonForegroundPressed" ResourceKey="TextControlButtonForegroundPressed" />
<StaticResource x:Key="RepeatButtonBorderBrush" ResourceKey="TextControlButtonBorderBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPointerOver" ResourceKey="TextControlButtonBorderBrushPointerOver" />
<StaticResource x:Key="RepeatButtonBorderBrushPressed" ResourceKey="TextControlButtonBorderBrushPressed" />
<StaticResource x:Key="RepeatButtonBorderBrushDisabled" ResourceKey="TextControlButtonBorderBrush" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<StaticResource x:Key="RepeatButtonBackground" ResourceKey="TextControlButtonBackground" />
<StaticResource x:Key="RepeatButtonBackgroundPointerOver" ResourceKey="TextControlButtonBackgroundPointerOver" />
<StaticResource x:Key="RepeatButtonBackgroundPressed" ResourceKey="TextControlButtonBackgroundPressed" />
<StaticResource x:Key="RepeatButtonForeground" ResourceKey="TextControlButtonForeground" />
<StaticResource x:Key="RepeatButtonForegroundPointerOver" ResourceKey="TextControlButtonForegroundPointerOver" />
<StaticResource x:Key="RepeatButtonForegroundPressed" ResourceKey="TextControlButtonForegroundPressed" />
<StaticResource x:Key="RepeatButtonBorderBrush" ResourceKey="TextControlButtonBorderBrush" />
<StaticResource x:Key="RepeatButtonBorderBrushPointerOver" ResourceKey="TextControlButtonBorderBrushPointerOver" />
<StaticResource x:Key="RepeatButtonBorderBrushPressed" ResourceKey="TextControlButtonBorderBrushPressed" />
<StaticResource x:Key="RepeatButtonBorderBrushDisabled" ResourceKey="TextControlButtonBorderBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Grid.Resources>
<Border Name="BorderElement"
Background="{TemplateBinding Background}"
BackgroundSizing="InnerBorderEdge"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
MinHeight="{TemplateBinding MinHeight}" />
<Border Margin="{TemplateBinding BorderThickness}">
<DockPanel>
CornerRadius="{TemplateBinding CornerRadius}">
<DockPanel LastChildFill="True">
<StackPanel Name="PART_SpinnerPanel"
TabIndex="2"
Spacing="4"
Margin="4"
DockPanel.Dock="Right"
Orientation="Horizontal"
IsVisible="{TemplateBinding ShowButtonSpinner}">
<RepeatButton Name="PART_IncreaseButton"
IsTabStop="{TemplateBinding IsTabStop}"
Theme="{StaticResource FluentButtonSpinnerRepeatButton}"
Margin="3"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center">
<PathIcon Width="12"
Height="6"
CornerRadius="{TemplateBinding CornerRadius}"
Theme="{StaticResource FluentButtonSpinnerRepeatButton}">
<PathIcon Width="{TemplateBinding FontSize}"
Height="{TemplateBinding FontSize}"
Data="{StaticResource ButtonSpinnerIncreaseButtonIcon}" />
</RepeatButton>
<RepeatButton Name="PART_DecreaseButton"
IsTabStop="{TemplateBinding IsTabStop}"
Theme="{StaticResource FluentButtonSpinnerRepeatButton}"
Margin="0,3,3,3"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center">
<PathIcon Width="12"
Height="6"
CornerRadius="{TemplateBinding CornerRadius}">
<PathIcon Width="{TemplateBinding FontSize}"
Height="{TemplateBinding FontSize}"
Data="{StaticResource ButtonSpinnerDecreaseButtonIcon}" />
</RepeatButton>
<Button Name="PopupIndicator"
Theme="{StaticResource SpinnerPopupIndicatorButton}"
IsVisible="False">
<Button.Flyout>
<Flyout
Placement="Center">
<Flyout.FlyoutPresenterTheme>
<ControlTheme TargetType="FlyoutPresenter" BasedOn="{StaticResource {x:Type FlyoutPresenter}}">
<Setter Property="Padding" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
</ControlTheme>
</Flyout.FlyoutPresenterTheme>
<Border Name="PopupContentRoot"
Padding="6"
Background="{DynamicResource SpinnerPopupBackground}"
BorderBrush="{DynamicResource SpinnerPopupBorderBrush}"
BorderThickness="{DynamicResource SpinnerPopupBorderThickness}"
CornerRadius="{DynamicResource OverlayCornerRadius}">
<Grid RowDefinitions="*,*">
<RepeatButton Name="PART_PopupIncreaseButton"
Theme="{StaticResource FluentButtonSpinnerPopupRepeatButton}">
<PathIcon Width="16"
Height="16"
Data="{StaticResource ButtonSpinnerIncreaseButtonIcon}" />
</RepeatButton>
<RepeatButton Name="PART_PopupDecreaseButton"
Grid.Row="1"
Theme="{StaticResource FluentButtonSpinnerPopupRepeatButton}">
<PathIcon Width="16"
Height="16"
Data="{StaticResource ButtonSpinnerDecreaseButtonIcon}" />
</RepeatButton>
</Grid>
</Border>
</Flyout>
</Button.Flyout>
<PathIcon Width="12"
Height="12"
Data="{StaticResource ButtonSpinnerUpDownButtonIcon}" />
</Button>
</StackPanel>
<ContentPresenter Name="PART_ContentPresenter"
TabIndex="1"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}" />
</DockPanel>
</Border>
</Panel>
</Grid>
</DataValidationErrors>
</ControlTemplate>
</Setter>
@ -175,7 +233,7 @@
</Style>
<!-- PointerOver State. Only the fill changes; the hairline border is left alone so an
error border stays visible while hovered (WinUI hover reuses the rest border brush). -->
error border stays visible while hovered (WinUI hover reuses the rest border brush). -->
<Style Selector="^:pointerover /template/ Border#BorderElement">
<Setter Property="Background" Value="{DynamicResource TextControlBackgroundPointerOver}" />
</Style>
@ -196,6 +254,13 @@
<Style Selector="^:error /template/ Border#BorderElement">
<Setter Property="BorderBrush" Value="{DynamicResource SystemFillColorCriticalBrush}" />
</Style>
<Style Selector="^.fluent2-compact /template/ Button#PopupIndicator">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="^.fluent2-compact /template/ RepeatButton#PART_IncreaseButton, ^.fluent2-compact /template/ RepeatButton#PART_DecreaseButton">
<Setter Property="IsVisible" Value="False"/>
</Style>
</ControlTheme>
</ResourceDictionary>

Loading…
Cancel
Save