committed by
GitHub
141 changed files with 8706 additions and 1271 deletions
@ -0,0 +1,123 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.DateTimePickerPage"> |
|||
<StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Stretch"> |
|||
<TextBlock Classes="h1">DatePicker and TimePicker</TextBlock> |
|||
<TextBlock Name="DatePickerDesc" Classes="h2" TextWrapping="Wrap"/> |
|||
<TextBlock Name="TimePickerDesc" Classes="h2" TextWrapping="Wrap"/> |
|||
|
|||
<StackPanel Orientation="Vertical" |
|||
Margin="16" |
|||
HorizontalAlignment="Stretch" |
|||
Spacing="16"> |
|||
<TextBlock FontSize="18">A simple DatePicker with a header</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<DatePicker Header="Pick a date" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<DatePicker Header="Pick a date" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<TextBlock FontSize="18">A DatePicker with day formatted and year hidden.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<DatePicker x:Name="Control2" DayFormat="d (ddd)" |
|||
YearVisible="False" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<DatePicker DayFormat="d (ddd)" YearVisible="False" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<Border Background="{DynamicResource SystemControlHighlightBaseLowBrush}" BorderThickness="1" Margin="15" /> |
|||
|
|||
<TextBlock FontSize="18">A simple TimePicker.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<TimePicker /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<TimePicker /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<TextBlock FontSize="18">A TimePicker with a header and minute increments specified.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<TimePicker Header="Arrival time" MinuteIncrement="15" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<TimePicker Header="Arrival time" MinuteIncrement="15" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<TextBlock FontSize="18">A TimePicker using a 12-hour clock.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<TextBlock FontSize="18">A TimePicker using a 24-hour clock.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
</StackPanel> |
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,30 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class DateTimePickerPage : UserControl |
|||
{ |
|||
public DateTimePickerPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
this.FindControl<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " + |
|||
"for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " + |
|||
"These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + |
|||
"Order of month, day, and year is dynamically set based on user date settings"; |
|||
|
|||
this.FindControl<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " + |
|||
"to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " + |
|||
"are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + |
|||
"12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden."; |
|||
|
|||
|
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.SplitViewPage"> |
|||
|
|||
<Border> |
|||
|
|||
<Grid ColumnDefinitions="*,225"> |
|||
|
|||
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="4" Margin="5"> |
|||
<ToggleButton Name="PaneOpenButton" |
|||
Content="IsPaneOpen" |
|||
IsChecked="{Binding IsPaneOpen, ElementName=SplitView}" /> |
|||
|
|||
<ToggleButton Name="UseLightDismissOverlayModeButton" |
|||
Content="UseLightDismissOverlayMode" |
|||
IsChecked="{Binding UseLightDismissOverlayMode, ElementName=SplitView}" /> |
|||
|
|||
<ToggleSwitch OffContent="Left" OnContent="Right" Content="Placement" IsChecked="{Binding !IsLeft}" /> |
|||
|
|||
<TextBlock Text="DisplayMode" /> |
|||
<ComboBox Name="DisplayModeSelector" Width="170" Margin="10" SelectedIndex="{Binding DisplayMode}"> |
|||
<ComboBoxItem>Inline</ComboBoxItem> |
|||
<ComboBoxItem>CompactInline</ComboBoxItem> |
|||
<ComboBoxItem>Overlay</ComboBoxItem> |
|||
<ComboBoxItem>CompactOverlay</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<TextBlock Text="PaneBackground" /> |
|||
<ComboBox Name="PaneBackgroundSelector" SelectedIndex="0" Width="170" Margin="10"> |
|||
<ComboBoxItem Tag="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}">SystemControlBackgroundChromeMediumLowBrush</ComboBoxItem> |
|||
<ComboBoxItem Tag="Red">Red</ComboBoxItem> |
|||
<ComboBoxItem Tag="Blue">Blue</ComboBoxItem> |
|||
<ComboBoxItem Tag="Green">Green</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<TextBlock Text="{Binding Value, ElementName=OpenPaneLengthSlider, StringFormat='{}OpenPaneLength: {0}'}" /> |
|||
<Slider Name="OpenPaneLengthSlider" Value="256" Minimum="128" Maximum="500" |
|||
Width="150" /> |
|||
|
|||
<TextBlock Text="{Binding Value, ElementName=CompactPaneLengthSlider, StringFormat='{}CompactPaneLength: {0}'}" /> |
|||
<Slider Name="CompactPaneLengthSlider" Value="48" Minimum="24" Maximum="128" |
|||
Width="150" /> |
|||
|
|||
</StackPanel> |
|||
|
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1"> |
|||
<!--{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}--> |
|||
<SplitView Name="SplitView" |
|||
PanePlacement="{Binding PanePlacement}" |
|||
PaneBackground="{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}" |
|||
OpenPaneLength="{Binding Value, ElementName=OpenPaneLengthSlider}" |
|||
CompactPaneLength="{Binding Value, ElementName=CompactPaneLengthSlider}" |
|||
DisplayMode="{Binding CurrentDisplayMode}"> |
|||
<SplitView.Pane> |
|||
<Grid> |
|||
<Grid.RowDefinitions> |
|||
<RowDefinition Height="Auto" /> |
|||
<RowDefinition Height="*" /> |
|||
<RowDefinition Height="Auto" /> |
|||
</Grid.RowDefinitions> |
|||
<TextBlock Text="PANE CONTENT" FontWeight="Bold" Name="PaneHeader" Margin="5,12,0,0" /> |
|||
<ListBoxItem Grid.Row="1" VerticalAlignment="Top" Margin="0 10"> |
|||
<StackPanel Orientation="Horizontal"> |
|||
<!--Path glyph from materialdesignicons.com--> |
|||
<Border Width="48"> |
|||
<Viewbox Width="24" Height="24" HorizontalAlignment="Left"> |
|||
<Canvas Width="24" Height="24"> |
|||
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}" Data="M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z" /> |
|||
</Canvas> |
|||
</Viewbox> |
|||
</Border> |
|||
<TextBlock Text="People" VerticalAlignment="Center" /> |
|||
</StackPanel> |
|||
</ListBoxItem> |
|||
<TextBlock Grid.Row="2" Text="Item at bottom" Margin="60,12" /> |
|||
</Grid> |
|||
</SplitView.Pane> |
|||
|
|||
<Grid> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
</Grid> |
|||
|
|||
</SplitView> |
|||
</Border> |
|||
|
|||
</Grid> |
|||
</Border> |
|||
|
|||
</UserControl> |
|||
@ -0,0 +1,21 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
using ControlCatalog.ViewModels; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class SplitViewPage : UserControl |
|||
{ |
|||
public SplitViewPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
DataContext = new SplitViewPageViewModel(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.WindowCustomizationsPage"> |
|||
<StackPanel Spacing="10" Margin="25"> |
|||
<CheckBox Content="Extend Client Area to Decorations" IsChecked="{Binding ExtendClientAreaEnabled}" /> |
|||
<CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" /> |
|||
<CheckBox Content="Prefer System Chrome" IsChecked="{Binding PreferSystemChromeEnabled}" /> |
|||
<Slider Minimum="-1" Maximum="200" Value="{Binding TitleBarHeight}" /> |
|||
<ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}"> |
|||
<ComboBoxItem>None</ComboBoxItem> |
|||
<ComboBoxItem>Transparent</ComboBoxItem> |
|||
<ComboBoxItem>Blur</ComboBoxItem> |
|||
<ComboBoxItem>AcrylicBlur</ComboBoxItem> |
|||
</ComboBox> |
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,19 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class WindowCustomizationsPage : UserControl |
|||
{ |
|||
public WindowCustomizationsPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using ReactiveUI; |
|||
|
|||
namespace ControlCatalog.ViewModels |
|||
{ |
|||
public class SplitViewPageViewModel : ReactiveObject |
|||
{ |
|||
private bool _isLeft = true; |
|||
private int _displayMode = 3; //CompactOverlay
|
|||
|
|||
public bool IsLeft |
|||
{ |
|||
get => _isLeft; |
|||
set |
|||
{ |
|||
this.RaiseAndSetIfChanged(ref _isLeft, value); |
|||
this.RaisePropertyChanged(nameof(PanePlacement)); |
|||
} |
|||
} |
|||
|
|||
public int DisplayMode |
|||
{ |
|||
get => _displayMode; |
|||
set |
|||
{ |
|||
this.RaiseAndSetIfChanged(ref _displayMode, value); |
|||
this.RaisePropertyChanged(nameof(CurrentDisplayMode)); |
|||
} |
|||
} |
|||
|
|||
public SplitViewPanePlacement PanePlacement => _isLeft ? SplitViewPanePlacement.Left : SplitViewPanePlacement.Right; |
|||
|
|||
public SplitViewDisplayMode CurrentDisplayMode |
|||
{ |
|||
get |
|||
{ |
|||
if (Enum.IsDefined(typeof(SplitViewDisplayMode), _displayMode)) |
|||
{ |
|||
return (SplitViewDisplayMode)_displayMode; |
|||
} |
|||
return SplitViewDisplayMode.CompactOverlay; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a user-defined cubic bezier curve.
|
|||
/// Good for custom easing functions that doesn't quite
|
|||
/// fit with the built-in ones.
|
|||
/// </summary>
|
|||
public class SplineEasing : Easing |
|||
{ |
|||
/// <summary>
|
|||
/// X coordinate of the first control point
|
|||
/// </summary>
|
|||
public double X1 |
|||
{ |
|||
get => _internalKeySpline.ControlPointX1; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointX1 = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Y coordinate of the first control point
|
|||
/// </summary>
|
|||
public double Y1 |
|||
{ |
|||
get => _internalKeySpline.ControlPointY1; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointY1 = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// X coordinate of the second control point
|
|||
/// </summary>
|
|||
public double X2 |
|||
{ |
|||
get => _internalKeySpline.ControlPointX2; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointX2 = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Y coordinate of the second control point
|
|||
/// </summary>
|
|||
public double Y2 |
|||
{ |
|||
get => _internalKeySpline.ControlPointY2; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointY2 = value; |
|||
} |
|||
} |
|||
|
|||
private readonly KeySpline _internalKeySpline; |
|||
|
|||
public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d) |
|||
{ |
|||
_internalKeySpline = new KeySpline(); |
|||
|
|||
this.X1 = x1; |
|||
this.Y1 = y1; |
|||
this.X2 = x2; |
|||
this.Y1 = y2; |
|||
} |
|||
|
|||
public SplineEasing(KeySpline keySpline) |
|||
{ |
|||
_internalKeySpline = keySpline; |
|||
} |
|||
|
|||
public SplineEasing() |
|||
{ |
|||
_internalKeySpline = new KeySpline(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) => |
|||
_internalKeySpline.GetSplineProgress(progress); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
|
|||
// Ported from WPF open-source code.
|
|||
// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs
|
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Converts string values to <see cref="KeySpline"/> values
|
|||
/// </summary>
|
|||
public class KeySplineTypeConverter : TypeConverter |
|||
{ |
|||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
|||
{ |
|||
return sourceType == typeof(string); |
|||
} |
|||
|
|||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) |
|||
{ |
|||
return KeySpline.Parse((string)value, culture); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
#pragma warning disable MA0048 // File name must match type name
|
|||
#define INTERNAL_NULLABLE_ATTRIBUTES
|
|||
#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
|
|||
|
|||
// https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
|
|||
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
namespace System.Diagnostics.CodeAnalysis |
|||
{ |
|||
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class AllowNullAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class DisallowNullAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class MaybeNullAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class NotNullAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class MaybeNullWhenAttribute : Attribute |
|||
{ |
|||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
|||
/// <param name="returnValue">
|
|||
/// The return value condition. If the method returns this value, the associated parameter may be null.
|
|||
/// </param>
|
|||
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; |
|||
|
|||
/// <summary>Gets the return value condition.</summary>
|
|||
public bool ReturnValue { get; } |
|||
} |
|||
|
|||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class NotNullWhenAttribute : Attribute |
|||
{ |
|||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
|||
/// <param name="returnValue">
|
|||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
|||
/// </param>
|
|||
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; |
|||
|
|||
/// <summary>Gets the return value condition.</summary>
|
|||
public bool ReturnValue { get; } |
|||
} |
|||
|
|||
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class NotNullIfNotNullAttribute : Attribute |
|||
{ |
|||
/// <summary>Initializes the attribute with the associated parameter name.</summary>
|
|||
/// <param name="parameterName">
|
|||
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
|
|||
/// </param>
|
|||
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; |
|||
|
|||
/// <summary>Gets the associated parameter name.</summary>
|
|||
public string ParameterName { get; } |
|||
} |
|||
|
|||
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class DoesNotReturnAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class DoesNotReturnIfAttribute : Attribute |
|||
{ |
|||
/// <summary>Initializes the attribute with the specified parameter value.</summary>
|
|||
/// <param name="parameterValue">
|
|||
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
|
|||
/// the associated parameter matches this value.
|
|||
/// </param>
|
|||
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; |
|||
|
|||
/// <summary>Gets the condition parameter value.</summary>
|
|||
public bool ParameterValue { get; } |
|||
} |
|||
} |
|||
#endif
|
|||
@ -0,0 +1,45 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
#if !BUILDTASK
|
|||
public |
|||
#endif
|
|||
static class StyleClassParser |
|||
{ |
|||
public static ReadOnlySpan<char> ParseStyleClass(this ref CharacterReader r) |
|||
{ |
|||
if (IsValidIdentifierStart(r.Peek)) |
|||
{ |
|||
return r.TakeWhile(c => IsValidIdentifierChar(c)); |
|||
} |
|||
else |
|||
{ |
|||
return ReadOnlySpan<char>.Empty; |
|||
} |
|||
} |
|||
|
|||
private static bool IsValidIdentifierStart(char c) |
|||
{ |
|||
return char.IsLetter(c) || c == '_'; |
|||
} |
|||
|
|||
private static bool IsValidIdentifierChar(char c) |
|||
{ |
|||
if (IsValidIdentifierStart(c) || c == '-') |
|||
{ |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
var cat = CharUnicodeInfo.GetUnicodeCategory(c); |
|||
return cat == UnicodeCategory.NonSpacingMark || |
|||
cat == UnicodeCategory.SpacingCombiningMark || |
|||
cat == UnicodeCategory.ConnectorPunctuation || |
|||
cat == UnicodeCategory.Format || |
|||
cat == UnicodeCategory.DecimalDigitNumber; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Controls.Primitives; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.Chrome |
|||
{ |
|||
/// <summary>
|
|||
/// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
|
|||
/// </summary>
|
|||
public class CaptionButtons : TemplatedControl |
|||
{ |
|||
private CompositeDisposable? _disposables; |
|||
private Window? _hostWindow; |
|||
|
|||
public void Attach(Window hostWindow) |
|||
{ |
|||
if (_disposables == null) |
|||
{ |
|||
_hostWindow = hostWindow; |
|||
|
|||
_disposables = new CompositeDisposable |
|||
{ |
|||
_hostWindow.GetObservable(Window.WindowStateProperty) |
|||
.Subscribe(x => |
|||
{ |
|||
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
|||
PseudoClasses.Set(":normal", x == WindowState.Normal); |
|||
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
|||
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
|||
}) |
|||
}; |
|||
} |
|||
} |
|||
|
|||
public void Detach() |
|||
{ |
|||
if (_disposables != null) |
|||
{ |
|||
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
|||
|
|||
layer?.Children.Remove(this); |
|||
|
|||
_disposables.Dispose(); |
|||
_disposables = null; |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
|
|||
var closeButton = e.NameScope.Get<Panel>("PART_CloseButton"); |
|||
var restoreButton = e.NameScope.Get<Panel>("PART_RestoreButton"); |
|||
var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton"); |
|||
var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton"); |
|||
|
|||
closeButton.PointerReleased += (sender, e) => _hostWindow?.Close(); |
|||
|
|||
restoreButton.PointerReleased += (sender, e) => |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; |
|||
} |
|||
}; |
|||
|
|||
minimiseButton.PointerReleased += (sender, e) => |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
_hostWindow.WindowState = WindowState.Minimized; |
|||
} |
|||
}; |
|||
|
|||
fullScreenButton.PointerReleased += (sender, e) => |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Controls.Primitives; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.Chrome |
|||
{ |
|||
/// <summary>
|
|||
/// Draws a titlebar when managed client decorations are enabled.
|
|||
/// </summary>
|
|||
public class TitleBar : TemplatedControl |
|||
{ |
|||
private CompositeDisposable? _disposables; |
|||
private readonly Window? _hostWindow; |
|||
private CaptionButtons? _captionButtons; |
|||
|
|||
public TitleBar(Window hostWindow) |
|||
{ |
|||
_hostWindow = hostWindow; |
|||
} |
|||
|
|||
public TitleBar() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void Attach() |
|||
{ |
|||
if (_disposables == null) |
|||
{ |
|||
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
|||
|
|||
layer?.Children.Add(this); |
|||
|
|||
if (_hostWindow != null) |
|||
{ |
|||
_disposables = new CompositeDisposable |
|||
{ |
|||
_hostWindow.GetObservable(Window.WindowDecorationMarginProperty) |
|||
.Subscribe(x => UpdateSize()), |
|||
|
|||
_hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) |
|||
.Subscribe(x => UpdateSize()), |
|||
|
|||
_hostWindow.GetObservable(Window.OffScreenMarginProperty) |
|||
.Subscribe(x => UpdateSize()), |
|||
|
|||
_hostWindow.GetObservable(Window.WindowStateProperty) |
|||
.Subscribe(x => |
|||
{ |
|||
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
|||
PseudoClasses.Set(":normal", x == WindowState.Normal); |
|||
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
|||
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
|||
}) |
|||
}; |
|||
|
|||
_captionButtons?.Attach(_hostWindow); |
|||
} |
|||
|
|||
UpdateSize(); |
|||
} |
|||
} |
|||
|
|||
private void UpdateSize() |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
Margin = new Thickness( |
|||
_hostWindow.OffScreenMargin.Left, |
|||
_hostWindow.OffScreenMargin.Top, |
|||
_hostWindow.OffScreenMargin.Right, |
|||
_hostWindow.OffScreenMargin.Bottom); |
|||
|
|||
if (_hostWindow.WindowState != WindowState.FullScreen) |
|||
{ |
|||
Height = _hostWindow.WindowDecorationMargin.Top; |
|||
|
|||
if (_captionButtons != null) |
|||
{ |
|||
_captionButtons.Height = Height; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Detach() |
|||
{ |
|||
if (_disposables != null) |
|||
{ |
|||
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
|||
|
|||
layer?.Children.Remove(this); |
|||
|
|||
_disposables.Dispose(); |
|||
_disposables = null; |
|||
|
|||
_captionButtons?.Detach(); |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
|
|||
_captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons"); |
|||
|
|||
if (_hostWindow != null) |
|||
{ |
|||
_captionButtons.Attach(_hostWindow); |
|||
} |
|||
|
|||
UpdateSize(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,412 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Interactivity; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// A control to allow the user to select a date
|
|||
/// </summary>
|
|||
public class DatePicker : TemplatedControl |
|||
{ |
|||
/// <summary>
|
|||
/// Define the <see cref="DayFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, string> DayFormatProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(DayFormat), |
|||
x => x.DayFormat, (x, v) => x.DayFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DayVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, bool> DayVisibleProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible), |
|||
x => x.DayVisible, (x, v) => x.DayVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Header"/> Property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<object> HeaderProperty = |
|||
AvaloniaProperty.Register<DatePicker, object>(nameof(Header)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="HeaderTemplate"/> Property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty = |
|||
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MaxYear"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, DateTimeOffset> MaxYearProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MaxYear), |
|||
x => x.MaxYear, (x, v) => x.MaxYear = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MinYear"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, DateTimeOffset> MinYearProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MinYear), |
|||
x => x.MinYear, (x, v) => x.MinYear = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MonthFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, string> MonthFormatProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(MonthFormat), |
|||
x => x.MonthFormat, (x, v) => x.MonthFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MonthVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, bool> MonthVisibleProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(MonthVisible), |
|||
x => x.MonthVisible, (x, v) => x.MonthVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defiens the <see cref="YearFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, string> YearFormatProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(YearFormat), |
|||
x => x.YearFormat, (x, v) => x.YearFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="YearVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, bool> YearVisibleProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(YearVisible), |
|||
x => x.YearVisible, (x, v) => x.YearVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="SelectedDate"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, DateTimeOffset?> SelectedDateProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate), |
|||
x => x.SelectedDate, (x, v) => x.SelectedDate = v); |
|||
|
|||
//Template Items
|
|||
private Button _flyoutButton; |
|||
private TextBlock _dayText; |
|||
private TextBlock _monthText; |
|||
private TextBlock _yearText; |
|||
private Grid _container; |
|||
private Rectangle _spacer1; |
|||
private Rectangle _spacer2; |
|||
private Popup _popup; |
|||
private DatePickerPresenter _presenter; |
|||
|
|||
private bool _areControlsAvailable; |
|||
|
|||
private string _dayFormat = "%d"; |
|||
private bool _dayVisible = true; |
|||
private DateTimeOffset _maxYear; |
|||
private DateTimeOffset _minYear; |
|||
private string _monthFormat = "MMMM"; |
|||
private bool _monthVisible = true; |
|||
private string _yearFormat = "yyyy"; |
|||
private bool _yearVisible = true; |
|||
private DateTimeOffset? _selectedDate; |
|||
|
|||
public DatePicker() |
|||
{ |
|||
PseudoClasses.Set(":hasnodate", true); |
|||
var now = DateTimeOffset.Now; |
|||
_minYear = new DateTimeOffset(now.Date.Year - 100, 1, 1, 0, 0, 0, now.Offset); |
|||
_maxYear = new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset); |
|||
} |
|||
|
|||
public string DayFormat |
|||
{ |
|||
get => _dayFormat; |
|||
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the day is visible
|
|||
/// </summary>
|
|||
public bool DayVisible |
|||
{ |
|||
get => _dayVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(DayVisibleProperty, ref _dayVisible, value); |
|||
SetGrid(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the DatePicker header
|
|||
/// </summary>
|
|||
public object Header |
|||
{ |
|||
get => GetValue(HeaderProperty); |
|||
set => SetValue(HeaderProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the header template
|
|||
/// </summary>
|
|||
public IDataTemplate HeaderTemplate |
|||
{ |
|||
get => GetValue(HeaderTemplateProperty); |
|||
set => SetValue(HeaderTemplateProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum year for the picker
|
|||
/// </summary>
|
|||
public DateTimeOffset MaxYear |
|||
{ |
|||
get => _maxYear; |
|||
set |
|||
{ |
|||
if (value < MinYear) |
|||
throw new InvalidOperationException("MaxDate cannot be less than MinDate"); |
|||
SetAndRaise(MaxYearProperty, ref _maxYear, value); |
|||
|
|||
if (SelectedDate.HasValue && SelectedDate.Value > value) |
|||
SelectedDate = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minimum year for the picker
|
|||
/// </summary>
|
|||
public DateTimeOffset MinYear |
|||
{ |
|||
get => _minYear; |
|||
set |
|||
{ |
|||
if (value > MaxYear) |
|||
throw new InvalidOperationException("MinDate cannot be greater than MaxDate"); |
|||
SetAndRaise(MinYearProperty, ref _minYear, value); |
|||
|
|||
if (SelectedDate.HasValue && SelectedDate.Value < value) |
|||
SelectedDate = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the month format
|
|||
/// </summary>
|
|||
public string MonthFormat |
|||
{ |
|||
get => _monthFormat; |
|||
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the month is visible
|
|||
/// </summary>
|
|||
public bool MonthVisible |
|||
{ |
|||
get => _monthVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value); |
|||
SetGrid(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the year format
|
|||
/// </summary>
|
|||
public string YearFormat |
|||
{ |
|||
get => _yearFormat; |
|||
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the year is visible
|
|||
/// </summary>
|
|||
public bool YearVisible |
|||
{ |
|||
get => _yearVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(YearVisibleProperty, ref _yearVisible, value); |
|||
SetGrid(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Selected Date for the picker, can be null
|
|||
/// </summary>
|
|||
public DateTimeOffset? SelectedDate |
|||
{ |
|||
get => _selectedDate; |
|||
set |
|||
{ |
|||
var old = _selectedDate; |
|||
SetAndRaise(SelectedDateProperty, ref _selectedDate, value); |
|||
SetSelectedDateText(); |
|||
OnSelectedDateChanged(this, new DatePickerSelectedValueChangedEventArgs(old, value)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Raised when the <see cref="SelectedDate"/> changes
|
|||
/// </summary>
|
|||
public event EventHandler<DatePickerSelectedValueChangedEventArgs> SelectedDateChanged; |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
_areControlsAvailable = false; |
|||
if (_flyoutButton != null) |
|||
_flyoutButton.Click -= OnFlyoutButtonClicked; |
|||
if (_presenter != null) |
|||
{ |
|||
_presenter.Confirmed -= OnConfirmed; |
|||
_presenter.Dismissed -= OnDismissPicker; |
|||
} |
|||
|
|||
base.OnApplyTemplate(e); |
|||
_flyoutButton = e.NameScope.Find<Button>("FlyoutButton"); |
|||
_dayText = e.NameScope.Find<TextBlock>("DayText"); |
|||
_monthText = e.NameScope.Find<TextBlock>("MonthText"); |
|||
_yearText = e.NameScope.Find<TextBlock>("YearText"); |
|||
_container = e.NameScope.Find<Grid>("ButtonContentGrid"); |
|||
_spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer"); |
|||
_spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer"); |
|||
_popup = e.NameScope.Find<Popup>("Popup"); |
|||
_presenter = e.NameScope.Find<DatePickerPresenter>("PickerPresenter"); |
|||
|
|||
_areControlsAvailable = true; |
|||
|
|||
SetGrid(); |
|||
SetSelectedDateText(); |
|||
|
|||
if (_flyoutButton != null) |
|||
_flyoutButton.Click += OnFlyoutButtonClicked; |
|||
|
|||
if (_presenter != null) |
|||
{ |
|||
_presenter.Confirmed += OnConfirmed; |
|||
_presenter.Dismissed += OnDismissPicker; |
|||
|
|||
_presenter[!DatePickerPresenter.MaxYearProperty] = this[!MaxYearProperty]; |
|||
_presenter[!DatePickerPresenter.MinYearProperty] = this[!MinYearProperty]; |
|||
|
|||
_presenter[!DatePickerPresenter.MonthVisibleProperty] = this[!MonthVisibleProperty]; |
|||
_presenter[!DatePickerPresenter.MonthFormatProperty] = this[!MonthFormatProperty]; |
|||
|
|||
_presenter[!DatePickerPresenter.DayVisibleProperty] = this[!DayVisibleProperty]; |
|||
_presenter[!DatePickerPresenter.DayFormatProperty] = this[!DayFormatProperty]; |
|||
|
|||
_presenter[!DatePickerPresenter.YearVisibleProperty] = this[!YearVisibleProperty]; |
|||
_presenter[!DatePickerPresenter.YearFormatProperty] = this[!YearFormatProperty]; |
|||
} |
|||
} |
|||
|
|||
private void OnDismissPicker(object sender, EventArgs e) |
|||
{ |
|||
_popup.Close(); |
|||
Focus(); |
|||
} |
|||
|
|||
private void OnConfirmed(object sender, EventArgs e) |
|||
{ |
|||
_popup.Close(); |
|||
SelectedDate = _presenter.Date; |
|||
} |
|||
|
|||
private void SetGrid() |
|||
{ |
|||
if (_container == null) |
|||
return; |
|||
|
|||
var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; |
|||
var columns = new List<(TextBlock, int)> |
|||
{ |
|||
(_monthText, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1), |
|||
(_yearText, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1), |
|||
(_dayText, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1), |
|||
}; |
|||
|
|||
columns.Sort((x, y) => x.Item2 - y.Item2); |
|||
_container.ColumnDefinitions.Clear(); |
|||
|
|||
var columnIndex = 0; |
|||
|
|||
foreach (var column in columns) |
|||
{ |
|||
if (column.Item1 is null) |
|||
continue; |
|||
|
|||
column.Item1.IsVisible = column.Item2 != -1; |
|||
|
|||
if (column.Item2 != -1) |
|||
{ |
|||
if (columnIndex > 0) |
|||
{ |
|||
_container.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto)); |
|||
} |
|||
|
|||
_container.ColumnDefinitions.Add( |
|||
new ColumnDefinition(column.Item1 == _monthText ? 138 : 78, GridUnitType.Star)); |
|||
|
|||
if (column.Item1.Parent is null) |
|||
{ |
|||
_container.Children.Add(column.Item1); |
|||
} |
|||
|
|||
Grid.SetColumn(column.Item1, (columnIndex++ * 2)); |
|||
} |
|||
} |
|||
|
|||
Grid.SetColumn(_spacer1, 1); |
|||
Grid.SetColumn(_spacer2, 3); |
|||
_spacer1.IsVisible = columnIndex > 1; |
|||
_spacer2.IsVisible = columnIndex > 2; |
|||
} |
|||
|
|||
private void SetSelectedDateText() |
|||
{ |
|||
if (!_areControlsAvailable) |
|||
return; |
|||
|
|||
if (SelectedDate.HasValue) |
|||
{ |
|||
PseudoClasses.Set(":hasnodate", false); |
|||
var selDate = SelectedDate.Value; |
|||
_monthText.Text = selDate.ToString(MonthFormat); |
|||
_yearText.Text = selDate.ToString(YearFormat); |
|||
_dayText.Text = selDate.ToString(DayFormat); |
|||
} |
|||
else |
|||
{ |
|||
PseudoClasses.Set(":hasnodate", true); |
|||
_monthText.Text = "month"; |
|||
_yearText.Text = "year"; |
|||
_dayText.Text = "day"; |
|||
} |
|||
} |
|||
|
|||
private void OnFlyoutButtonClicked(object sender, RoutedEventArgs e) |
|||
{ |
|||
if (_presenter == null) |
|||
throw new InvalidOperationException("No DatePickerPresenter found"); |
|||
|
|||
_presenter.Date = SelectedDate ?? DateTimeOffset.Now; |
|||
|
|||
_popup.IsOpen = true; |
|||
|
|||
var deltaY = _presenter.GetOffsetForPopup(); |
|||
|
|||
//The extra 5 px I think is related to default popup placement behavior
|
|||
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), |
|||
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, |
|||
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); |
|||
} |
|||
|
|||
protected virtual void OnSelectedDateChanged(object sender, DatePickerSelectedValueChangedEventArgs e) |
|||
{ |
|||
SelectedDateChanged?.Invoke(sender, e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,531 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the presenter used for selecting a date for a
|
|||
/// <see cref="DatePicker"/>
|
|||
/// </summary>
|
|||
public class DatePickerPresenter : PickerPresenterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Date"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> DateProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePickerPresenter, DateTimeOffset>(nameof(Date), |
|||
x => x.Date, (x, v) => x.Date = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DayFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, string> DayFormatProperty = |
|||
DatePicker.DayFormatProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.DayFormat, (x, v) => x.DayFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DayVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, bool> DayVisibleProperty = |
|||
DatePicker.DayVisibleProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.DayVisible, (x, v) => x.DayVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MaxYear"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MaxYearProperty = |
|||
DatePicker.MaxYearProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.MaxYear, (x, v) => x.MaxYear = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MinYear"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MinYearProperty = |
|||
DatePicker.MinYearProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.MinYear, (x, v) => x.MinYear = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MonthFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, string> MonthFormatProperty = |
|||
DatePicker.MonthFormatProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.MonthFormat, (x, v) => x.MonthFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MonthVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, bool> MonthVisibleProperty = |
|||
DatePicker.MonthVisibleProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.MonthVisible, (x, v) => x.MonthVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="YearFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, string> YearFormatProperty = |
|||
DatePicker.YearFormatProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.YearFormat, (x, v) => x.YearFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="YearVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, bool> YearVisibleProperty = |
|||
DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.YearVisible, (x, v) => x.YearVisible = v); |
|||
|
|||
//Template Items
|
|||
private Grid _pickerContainer; |
|||
private Button _acceptButton; |
|||
private Button _dismissButton; |
|||
private Rectangle _spacer1; |
|||
private Rectangle _spacer2; |
|||
private Panel _monthHost; |
|||
private Panel _yearHost; |
|||
private Panel _dayHost; |
|||
private DateTimePickerPanel _monthSelector; |
|||
private DateTimePickerPanel _yearSelector; |
|||
private DateTimePickerPanel _daySelector; |
|||
private Button _monthUpButton; |
|||
private Button _dayUpButton; |
|||
private Button _yearUpButton; |
|||
private Button _monthDownButton; |
|||
private Button _dayDownButton; |
|||
private Button _yearDownButton; |
|||
|
|||
private DateTimeOffset _date; |
|||
private string _dayFormat = "%d"; |
|||
private bool _dayVisible = true; |
|||
private DateTimeOffset _maxYear; |
|||
private DateTimeOffset _minYear; |
|||
private string _monthFormat = "MMMM"; |
|||
private bool _monthVisible = true; |
|||
private string _yearFormat = "yyyy"; |
|||
private bool _yearVisible = true; |
|||
private DateTimeOffset _syncDate; |
|||
|
|||
private GregorianCalendar _calendar; |
|||
private bool _suppressUpdateSelection; |
|||
|
|||
public DatePickerPresenter() |
|||
{ |
|||
var now = DateTimeOffset.Now; |
|||
_minYear = new DateTimeOffset(now.Year - 100, 1, 1, 0, 0, 0, now.Offset); |
|||
_maxYear = new DateTimeOffset(now.Year + 100, 12, 31, 0, 0, 0, now.Offset); |
|||
_date = now; |
|||
_calendar = new GregorianCalendar(); |
|||
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current Date for the picker
|
|||
/// </summary>
|
|||
public DateTimeOffset Date |
|||
{ |
|||
get => _date; |
|||
set |
|||
{ |
|||
SetAndRaise(DateProperty, ref _date, value); |
|||
_syncDate = Date; |
|||
InitPicker(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the DayFormat
|
|||
/// </summary>
|
|||
public string DayFormat |
|||
{ |
|||
get => _dayFormat; |
|||
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get or sets whether the Day selector is visible
|
|||
/// </summary>
|
|||
public bool DayVisible |
|||
{ |
|||
get => _dayVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(DayVisibleProperty, ref _dayVisible, value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum pickable year
|
|||
/// </summary>
|
|||
public DateTimeOffset MaxYear |
|||
{ |
|||
get => _maxYear; |
|||
set |
|||
{ |
|||
if (value < MinYear) |
|||
throw new InvalidOperationException("MaxDate cannot be less than MinDate"); |
|||
SetAndRaise(MaxYearProperty, ref _maxYear, value); |
|||
|
|||
if (Date > value) |
|||
Date = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minimum pickable year
|
|||
/// </summary>
|
|||
public DateTimeOffset MinYear |
|||
{ |
|||
get => _minYear; |
|||
set |
|||
{ |
|||
if (value > MaxYear) |
|||
throw new InvalidOperationException("MinDate cannot be greater than MaxDate"); |
|||
SetAndRaise(MinYearProperty, ref _minYear, value); |
|||
|
|||
if (Date < value) |
|||
Date = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the month format
|
|||
/// </summary>
|
|||
public string MonthFormat |
|||
{ |
|||
get => _monthFormat; |
|||
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the month selector is visible
|
|||
/// </summary>
|
|||
public bool MonthVisible |
|||
{ |
|||
get => _monthVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the year format
|
|||
/// </summary>
|
|||
public string YearFormat |
|||
{ |
|||
get => _yearFormat; |
|||
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the year selector is visible
|
|||
/// </summary>
|
|||
public bool YearVisible |
|||
{ |
|||
get => _yearVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(YearVisibleProperty, ref _yearVisible, value); |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
//These are requirements, so throw if not found
|
|||
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer"); |
|||
_monthHost = e.NameScope.Get<Panel>("MonthHost"); |
|||
_dayHost = e.NameScope.Get<Panel>("DayHost"); |
|||
_yearHost = e.NameScope.Get<Panel>("YearHost"); |
|||
|
|||
_monthSelector = e.NameScope.Get<DateTimePickerPanel>("MonthSelector"); |
|||
_monthSelector.SelectionChanged += OnMonthChanged; |
|||
|
|||
_daySelector = e.NameScope.Get<DateTimePickerPanel>("DaySelector"); |
|||
_daySelector.SelectionChanged += OnDayChanged; |
|||
|
|||
_yearSelector = e.NameScope.Get<DateTimePickerPanel>("YearSelector"); |
|||
_yearSelector.SelectionChanged += OnYearChanged; |
|||
|
|||
_acceptButton = e.NameScope.Get<Button>("AcceptButton"); |
|||
|
|||
_monthUpButton = e.NameScope.Find<RepeatButton>("MonthUpButton"); |
|||
if (_monthUpButton != null) |
|||
{ |
|||
_monthUpButton.Click += OnSelectorButtonClick; |
|||
} |
|||
_monthDownButton = e.NameScope.Find<RepeatButton>("MonthDownButton"); |
|||
if (_monthDownButton != null) |
|||
{ |
|||
_monthDownButton.Click += OnSelectorButtonClick; |
|||
} |
|||
|
|||
_dayUpButton = e.NameScope.Find<RepeatButton>("DayUpButton"); |
|||
if (_dayUpButton != null) |
|||
{ |
|||
_dayUpButton.Click += OnSelectorButtonClick; |
|||
} |
|||
_dayDownButton = e.NameScope.Find<RepeatButton>("DayDownButton"); |
|||
if (_dayDownButton != null) |
|||
{ |
|||
_dayDownButton.Click += OnSelectorButtonClick; |
|||
} |
|||
|
|||
_yearUpButton = e.NameScope.Find<RepeatButton>("YearUpButton"); |
|||
if (_yearUpButton != null) |
|||
{ |
|||
_yearUpButton.Click += OnSelectorButtonClick; |
|||
} |
|||
_yearDownButton = e.NameScope.Find<RepeatButton>("YearDownButton"); |
|||
if (_yearDownButton != null) |
|||
{ |
|||
_yearDownButton.Click += OnSelectorButtonClick; |
|||
} |
|||
|
|||
_dismissButton = e.NameScope.Find<Button>("DismissButton"); |
|||
_spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer"); |
|||
_spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer"); |
|||
|
|||
if (_acceptButton != null) |
|||
{ |
|||
_acceptButton.Click += OnAcceptButtonClicked; |
|||
} |
|||
if (_dismissButton != null) |
|||
{ |
|||
_dismissButton.Click += OnDismissButtonClicked; |
|||
} |
|||
InitPicker(); |
|||
} |
|||
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
switch (e.Key) |
|||
{ |
|||
case Key.Escape: |
|||
OnDismiss(); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Tab: |
|||
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next); |
|||
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Enter: |
|||
Date = _syncDate; |
|||
OnConfirmed(); |
|||
e.Handled = true; |
|||
break; |
|||
} |
|||
base.OnKeyDown(e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes the picker selectors.
|
|||
/// </summary>
|
|||
private void InitPicker() |
|||
{ |
|||
//OnApplyTemplate must've been called before we can init here...
|
|||
if (_pickerContainer == null) |
|||
return; |
|||
|
|||
_suppressUpdateSelection = true; |
|||
|
|||
_monthSelector.MaximumValue = 12; |
|||
_monthSelector.MinimumValue = 1; |
|||
_monthSelector.ItemFormat = MonthFormat; |
|||
|
|||
_daySelector.ItemFormat = DayFormat; |
|||
|
|||
_yearSelector.MaximumValue = MaxYear.Year; |
|||
_yearSelector.MinimumValue = MinYear.Year; |
|||
_yearSelector.ItemFormat = YearFormat; |
|||
|
|||
SetGrid(); |
|||
|
|||
//Date should've been set when we reach this point
|
|||
var dt = Date; |
|||
if (DayVisible) |
|||
{ |
|||
GregorianCalendar gc = new GregorianCalendar(); |
|||
var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month); |
|||
_daySelector.MaximumValue = maxDays; |
|||
_daySelector.MinimumValue = 1; |
|||
_daySelector.SelectedValue = dt.Day; |
|||
_daySelector.FormatDate = dt.Date; |
|||
} |
|||
|
|||
if (MonthVisible) |
|||
_monthSelector.SelectedValue = dt.Month; |
|||
|
|||
if (YearVisible) |
|||
_yearSelector.SelectedValue = dt.Year; |
|||
_suppressUpdateSelection = false; |
|||
|
|||
SetInitialFocus(); |
|||
} |
|||
|
|||
private void SetGrid() |
|||
{ |
|||
var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; |
|||
var columns = new List<(Panel, int)> |
|||
{ |
|||
(_monthHost, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1), |
|||
(_yearHost, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1), |
|||
(_dayHost, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1), |
|||
}; |
|||
|
|||
columns.Sort((x, y) => x.Item2 - y.Item2); |
|||
_pickerContainer.ColumnDefinitions.Clear(); |
|||
|
|||
var columnIndex = 0; |
|||
|
|||
foreach (var column in columns) |
|||
{ |
|||
if (column.Item1 is null) |
|||
continue; |
|||
|
|||
column.Item1.IsVisible = column.Item2 != -1; |
|||
|
|||
if (column.Item2 != -1) |
|||
{ |
|||
if (columnIndex > 0) |
|||
{ |
|||
_pickerContainer.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto)); |
|||
} |
|||
|
|||
_pickerContainer.ColumnDefinitions.Add( |
|||
new ColumnDefinition(column.Item1 == _monthHost ? 138 : 78, GridUnitType.Star)); |
|||
|
|||
if (column.Item1.Parent is null) |
|||
{ |
|||
_pickerContainer.Children.Add(column.Item1); |
|||
} |
|||
|
|||
Grid.SetColumn(column.Item1, (columnIndex++ * 2)); |
|||
} |
|||
} |
|||
|
|||
Grid.SetColumn(_spacer1, 1); |
|||
Grid.SetColumn(_spacer2, 3); |
|||
_spacer1.IsVisible = columnIndex > 1; |
|||
_spacer2.IsVisible = columnIndex > 2; |
|||
} |
|||
|
|||
private void SetInitialFocus() |
|||
{ |
|||
int monthCol = MonthVisible ? Grid.GetColumn(_monthHost) : int.MaxValue; |
|||
int dayCol = DayVisible ? Grid.GetColumn(_dayHost) : int.MaxValue; |
|||
int yearCol = YearVisible ? Grid.GetColumn(_yearHost) : int.MaxValue; |
|||
|
|||
if (monthCol < dayCol && monthCol < yearCol) |
|||
{ |
|||
KeyboardDevice.Instance?.SetFocusedElement(_monthSelector, NavigationMethod.Pointer, KeyModifiers.None); |
|||
} |
|||
else if (dayCol < monthCol && dayCol < yearCol) |
|||
{ |
|||
KeyboardDevice.Instance?.SetFocusedElement(_daySelector, NavigationMethod.Pointer, KeyModifiers.None); |
|||
} |
|||
else if (yearCol < monthCol && yearCol < dayCol) |
|||
{ |
|||
KeyboardDevice.Instance?.SetFocusedElement(_yearSelector, NavigationMethod.Pointer, KeyModifiers.None); |
|||
} |
|||
} |
|||
|
|||
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
OnDismiss(); |
|||
} |
|||
|
|||
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
Date = _syncDate; |
|||
OnConfirmed(); |
|||
} |
|||
|
|||
private void OnSelectorButtonClick(object sender, RoutedEventArgs e) |
|||
{ |
|||
if (sender == _monthUpButton) |
|||
_monthSelector.ScrollUp(); |
|||
else if (sender == _monthDownButton) |
|||
_monthSelector.ScrollDown(); |
|||
else if (sender == _yearUpButton) |
|||
_yearSelector.ScrollUp(); |
|||
else if (sender == _yearDownButton) |
|||
_yearSelector.ScrollDown(); |
|||
else if (sender == _dayUpButton) |
|||
_daySelector.ScrollUp(); |
|||
else if (sender == _dayDownButton) |
|||
_daySelector.ScrollDown(); |
|||
} |
|||
|
|||
private void OnYearChanged(object sender, EventArgs e) |
|||
{ |
|||
if (_suppressUpdateSelection) |
|||
return; |
|||
|
|||
int maxDays = _calendar.GetDaysInMonth(_yearSelector.SelectedValue, _syncDate.Month); |
|||
var newDate = new DateTimeOffset(_yearSelector.SelectedValue, _syncDate.Month, |
|||
_syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset); |
|||
|
|||
_syncDate = newDate; |
|||
|
|||
//We don't need to update the days if not displaying day, not february
|
|||
if (!DayVisible || _syncDate.Month != 2) |
|||
return; |
|||
|
|||
_suppressUpdateSelection = true; |
|||
|
|||
_daySelector.FormatDate = newDate.Date; |
|||
|
|||
if (_daySelector.MaximumValue != maxDays) |
|||
_daySelector.MaximumValue = maxDays; |
|||
else |
|||
_daySelector.RefreshItems(); |
|||
|
|||
_suppressUpdateSelection = false; |
|||
} |
|||
|
|||
private void OnDayChanged(object sender, EventArgs e) |
|||
{ |
|||
if (_suppressUpdateSelection) |
|||
return; |
|||
_syncDate = new DateTimeOffset(_syncDate.Year, _syncDate.Month, _daySelector.SelectedValue, 0, 0, 0, _syncDate.Offset); |
|||
} |
|||
|
|||
private void OnMonthChanged(object sender, EventArgs e) |
|||
{ |
|||
if (_suppressUpdateSelection) |
|||
return; |
|||
|
|||
int maxDays = _calendar.GetDaysInMonth(_syncDate.Year, _monthSelector.SelectedValue); |
|||
var newDate = new DateTimeOffset(_syncDate.Year, _monthSelector.SelectedValue, |
|||
_syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset); |
|||
|
|||
if (!DayVisible) |
|||
{ |
|||
_syncDate = newDate; |
|||
return; |
|||
} |
|||
|
|||
_suppressUpdateSelection = true; |
|||
|
|||
_daySelector.FormatDate = newDate.Date; |
|||
_syncDate = newDate; |
|||
|
|||
if (_daySelector.MaximumValue != maxDays) |
|||
_daySelector.MaximumValue = maxDays; |
|||
else |
|||
_daySelector.RefreshItems(); |
|||
|
|||
_suppressUpdateSelection = false; |
|||
} |
|||
|
|||
internal double GetOffsetForPopup() |
|||
{ |
|||
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; |
|||
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the argument passed when the <see cref="DatePicker"/> SelectedDate changes
|
|||
/// </summary>
|
|||
public class DatePickerSelectedValueChangedEventArgs |
|||
{ |
|||
public DateTimeOffset? NewDate { get; } |
|||
public DateTimeOffset? OldDate { get; } |
|||
|
|||
public DatePickerSelectedValueChangedEventArgs(DateTimeOffset? oldDate, DateTimeOffset? newDate) |
|||
{ |
|||
NewDate = newDate; |
|||
OldDate = oldDate; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,566 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using Avalonia.Input; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
public enum DateTimePickerPanelType |
|||
{ |
|||
Year, |
|||
Month, |
|||
Day, |
|||
Hour, |
|||
Minute, |
|||
TimePeriod //AM or PM
|
|||
} |
|||
|
|||
public class DateTimePickerPanel : Panel, ILogicalScrollable |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="ItemHeight"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> ItemHeightProperty = |
|||
AvaloniaProperty.Register<DateTimePickerPanel, double>(nameof(ItemHeight), 40.0); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PanelType"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<DateTimePickerPanelType> PanelTypeProperty = |
|||
AvaloniaProperty.Register<DateTimePickerPanel, DateTimePickerPanelType>(nameof(PanelType)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ItemFormat"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<string> ItemFormatProperty = |
|||
AvaloniaProperty.Register<DateTimePickerPanel, string>(nameof(ItemFormat), "yyyy"); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ShouldLoop"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> ShouldLoopProperty = |
|||
AvaloniaProperty.Register<DateTimePickerPanel, bool>(nameof(ShouldLoop)); |
|||
|
|||
//Backing fields for properties
|
|||
private int _minimumValue = 1; |
|||
private int _maximumValue = 2; |
|||
private int _selectedValue = 1; |
|||
private int _increment = 1; |
|||
|
|||
//Helper fields
|
|||
private int _selectedIndex = 0; |
|||
private int _totalItems; |
|||
private int _numItemsAboveBelowSelected; |
|||
private int _range; |
|||
private double _extentOne; |
|||
private Size _extent; |
|||
private Vector _offset; |
|||
private bool _hasInit; |
|||
private bool _suppressUpdateOffset; |
|||
private ListBoxItem _pressedItem; |
|||
|
|||
public DateTimePickerPanel() |
|||
{ |
|||
FormatDate = DateTime.Now; |
|||
AddHandler(ListBoxItem.PointerPressedEvent, OnItemPointerDown, Avalonia.Interactivity.RoutingStrategies.Bubble); |
|||
AddHandler(ListBoxItem.PointerReleasedEvent, OnItemPointerUp, Avalonia.Interactivity.RoutingStrategies.Bubble); |
|||
} |
|||
|
|||
static DateTimePickerPanel() |
|||
{ |
|||
FocusableProperty.OverrideDefaultValue<DateTimePickerPanel>(true); |
|||
AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets what this panel displays in date or time units
|
|||
/// </summary>
|
|||
public DateTimePickerPanelType PanelType |
|||
{ |
|||
get => GetValue(PanelTypeProperty); |
|||
set => SetValue(PanelTypeProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the height of each item
|
|||
/// </summary>
|
|||
public double ItemHeight |
|||
{ |
|||
get => GetValue(ItemHeightProperty); |
|||
set => SetValue(ItemHeightProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the string format for the items, using standard
|
|||
/// .net DateTime or TimeSpan formatting. Format must match panel type
|
|||
/// </summary>
|
|||
public string ItemFormat |
|||
{ |
|||
get => GetValue(ItemFormatProperty); |
|||
set => SetValue(ItemFormatProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the panel should loop
|
|||
/// </summary>
|
|||
public bool ShouldLoop |
|||
{ |
|||
get => GetValue(ShouldLoopProperty); |
|||
set => SetValue(ShouldLoopProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minimum value
|
|||
/// </summary>
|
|||
public int MinimumValue |
|||
{ |
|||
get => _minimumValue; |
|||
set |
|||
{ |
|||
if (value > MaximumValue) |
|||
throw new InvalidOperationException("Minimum cannot be greater than Maximum"); |
|||
_minimumValue = value; |
|||
UpdateHelperInfo(); |
|||
var sel = CoerceSelected(SelectedValue); |
|||
if (sel != SelectedValue) |
|||
SelectedValue = sel; |
|||
UpdateItems(); |
|||
InvalidateArrange(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum value
|
|||
/// </summary>
|
|||
public int MaximumValue |
|||
{ |
|||
get => _maximumValue; |
|||
set |
|||
{ |
|||
if (value < MinimumValue) |
|||
throw new InvalidOperationException("Maximum cannot be less than Minimum"); |
|||
_maximumValue = value; |
|||
UpdateHelperInfo(); |
|||
var sel = CoerceSelected(SelectedValue); |
|||
if (sel != SelectedValue) |
|||
SelectedValue = sel; |
|||
UpdateItems(); |
|||
InvalidateArrange(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the selected value
|
|||
/// </summary>
|
|||
public int SelectedValue |
|||
{ |
|||
get => _selectedValue; |
|||
set |
|||
{ |
|||
if (value > MaximumValue || value < MinimumValue) |
|||
throw new ArgumentOutOfRangeException("SelectedValue"); |
|||
|
|||
var sel = CoerceSelected(value); |
|||
_selectedValue = sel; |
|||
_selectedIndex = (value - MinimumValue) / Increment; |
|||
|
|||
if (!ShouldLoop) |
|||
CreateOrDestroyItems(Children); |
|||
|
|||
if (!_suppressUpdateOffset) |
|||
_offset = new Vector(0, ShouldLoop ? _selectedIndex * ItemHeight + (_extentOne * 50) : |
|||
_selectedIndex * ItemHeight); |
|||
|
|||
UpdateItems(); |
|||
InvalidateArrange(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
|
|||
SelectionChanged?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the increment
|
|||
/// </summary>
|
|||
public int Increment |
|||
{ |
|||
get => _increment; |
|||
set |
|||
{ |
|||
if (value <= 0 || value > _range) |
|||
throw new ArgumentOutOfRangeException("Increment"); |
|||
_increment = value; |
|||
UpdateHelperInfo(); |
|||
var sel = CoerceSelected(SelectedValue); |
|||
if (sel != SelectedValue) |
|||
SelectedValue = sel; |
|||
UpdateItems(); |
|||
InvalidateArrange(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
} |
|||
} |
|||
|
|||
//Used to help format the date (if applicable), for ex.,
|
|||
//if we're want to display the day of week, we need context
|
|||
//for the month/year, this is our context
|
|||
internal DateTime FormatDate { get; set; } |
|||
|
|||
public Vector Offset |
|||
{ |
|||
get => _offset; |
|||
set |
|||
{ |
|||
var old = _offset; |
|||
_offset = value; |
|||
var dy = _offset.Y - old.Y; |
|||
var children = Children; |
|||
|
|||
if (dy > 0) // Scroll Down
|
|||
{ |
|||
int numContsToMove = 0; |
|||
for (int i = 0; i < children.Count; i++) |
|||
{ |
|||
if (children[i].Bounds.Bottom - dy < 0) |
|||
numContsToMove++; |
|||
else |
|||
break; |
|||
} |
|||
children.MoveRange(0, numContsToMove, children.Count); |
|||
|
|||
var scrollHeight = _extent.Height - Viewport.Height; |
|||
if (ShouldLoop && value.Y >= scrollHeight - _extentOne) |
|||
_offset = new Vector(0, value.Y - (_extentOne * 50)); |
|||
} |
|||
else if (dy < 0) // Scroll Up
|
|||
{ |
|||
int numContsToMove = 0; |
|||
for (int i = children.Count - 1; i >= 0; i--) |
|||
{ |
|||
if (children[i].Bounds.Top - dy > Bounds.Height) |
|||
numContsToMove++; |
|||
else |
|||
break; |
|||
} |
|||
children.MoveRange(children.Count - numContsToMove, numContsToMove, 0); |
|||
if (ShouldLoop && value.Y < _extentOne) |
|||
_offset = new Vector(0, value.Y + (_extentOne * 50)); |
|||
} |
|||
|
|||
//Setting selection will handle all invalidation
|
|||
var newSel = (Offset.Y / ItemHeight) % _totalItems; |
|||
_suppressUpdateOffset = true; |
|||
SelectedValue = (int)newSel * Increment + MinimumValue; |
|||
_suppressUpdateOffset = false; |
|||
} |
|||
} |
|||
|
|||
public bool CanHorizontallyScroll { get => false; set { } } |
|||
|
|||
public bool CanVerticallyScroll { get => true; set { } } |
|||
|
|||
public bool IsLogicalScrollEnabled => true; |
|||
|
|||
public Size ScrollSize => new Size(0, ItemHeight); |
|||
|
|||
public Size PageScrollSize => new Size(0, ItemHeight * 4); |
|||
|
|||
public Size Extent => _extent; |
|||
|
|||
public Size Viewport => new Size(0, ItemHeight); |
|||
|
|||
public event EventHandler ScrollInvalidated; |
|||
|
|||
public event EventHandler SelectionChanged; |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
if (double.IsInfinity(availableSize.Width) || |
|||
double.IsInfinity(availableSize.Height)) |
|||
throw new InvalidOperationException("Panel must have finite height"); |
|||
|
|||
if (!_hasInit) |
|||
UpdateHelperInfo(); |
|||
|
|||
double initY = (availableSize.Height / 2.0) - (ItemHeight / 2.0); |
|||
_numItemsAboveBelowSelected = (int)Math.Ceiling(initY / ItemHeight) + 1; |
|||
|
|||
var children = Children; |
|||
|
|||
CreateOrDestroyItems(children); |
|||
|
|||
for (int i = 0; i < children.Count; i++) |
|||
children[i].Measure(availableSize); |
|||
|
|||
if (!_hasInit) |
|||
{ |
|||
UpdateItems(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
_hasInit = true; |
|||
} |
|||
|
|||
return availableSize; |
|||
} |
|||
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
if (Children.Count == 0) |
|||
return base.ArrangeOverride(finalSize); |
|||
|
|||
var itemHgt = ItemHeight; |
|||
var children = Children; |
|||
Rect rc; |
|||
double initY = (finalSize.Height / 2.0) - (itemHgt / 2.0); |
|||
|
|||
if (ShouldLoop) |
|||
{ |
|||
var currentSet = Math.Truncate(Offset.Y / _extentOne); |
|||
initY += (_extentOne * currentSet) + (_selectedIndex - _numItemsAboveBelowSelected) * ItemHeight; |
|||
|
|||
for (int i = 0; i < children.Count; i++) |
|||
{ |
|||
rc = new Rect(0, initY - Offset.Y, finalSize.Width, itemHgt); |
|||
children[i].Arrange(rc); |
|||
initY += itemHgt; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
var first = Math.Max(0, (_selectedIndex - _numItemsAboveBelowSelected)); |
|||
for (int i = 0; i < children.Count; i++) |
|||
{ |
|||
rc = new Rect(0, (initY + first * itemHgt) - Offset.Y, finalSize.Width, itemHgt); |
|||
children[i].Arrange(rc); |
|||
initY += itemHgt; |
|||
} |
|||
} |
|||
|
|||
return finalSize; |
|||
} |
|||
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
switch (e.Key) |
|||
{ |
|||
case Key.Up: |
|||
ScrollUp(); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Down: |
|||
ScrollDown(); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.PageUp: |
|||
ScrollUp(4); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.PageDown: |
|||
ScrollDown(4); |
|||
e.Handled = true; |
|||
break; |
|||
} |
|||
base.OnKeyDown(e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Refreshes the content of the visible items
|
|||
/// </summary>
|
|||
public void RefreshItems() |
|||
{ |
|||
UpdateItems(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scrolls up the specified number of items
|
|||
/// </summary>
|
|||
public void ScrollUp(int numItems = 1) |
|||
{ |
|||
var newY = Math.Max(Offset.Y - (numItems * ItemHeight), 0); |
|||
Offset = new Vector(0, newY); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scrolls down the specified number of items
|
|||
/// </summary>
|
|||
public void ScrollDown(int numItems = 1) |
|||
{ |
|||
var scrollHeight = _extent.Height - Viewport.Height; |
|||
var newY = Math.Min(Offset.Y + (numItems * ItemHeight), scrollHeight); |
|||
Offset = new Vector(0, newY); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates helper fields used in various calculations
|
|||
/// </summary>
|
|||
private void UpdateHelperInfo() |
|||
{ |
|||
_range = _maximumValue - _minimumValue + 1; |
|||
_totalItems = (int)Math.Ceiling((double)_range / _increment); |
|||
|
|||
var itemHgt = ItemHeight; |
|||
//If looping, measure 100x as many items as we actually have
|
|||
_extent = new Size(0, ShouldLoop ? _totalItems * itemHgt * 100 : _totalItems * itemHgt); |
|||
|
|||
//Height of 1 "set" of items
|
|||
_extentOne = _totalItems * itemHgt; |
|||
_offset = new Vector(0, ShouldLoop ? _extentOne * 50 + _selectedIndex * itemHgt : _selectedIndex * itemHgt); |
|||
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ensures enough containers are visible in the viewport
|
|||
/// </summary>
|
|||
/// <param name="children"></param>
|
|||
private void CreateOrDestroyItems(Controls children) |
|||
{ |
|||
int totalItemsInViewport = _numItemsAboveBelowSelected * 2 + 1; |
|||
|
|||
if (!ShouldLoop) |
|||
{ |
|||
int numItemAboveSelect = _numItemsAboveBelowSelected; |
|||
if (_selectedIndex - _numItemsAboveBelowSelected < 0) |
|||
numItemAboveSelect = _selectedIndex; |
|||
int numItemBelowSelect = _numItemsAboveBelowSelected; |
|||
if (_selectedIndex + _numItemsAboveBelowSelected >= _totalItems) |
|||
numItemBelowSelect = _totalItems - _selectedIndex - 1; |
|||
|
|||
totalItemsInViewport = numItemBelowSelect + numItemAboveSelect + 1; |
|||
} |
|||
|
|||
while (children.Count < totalItemsInViewport) |
|||
{ |
|||
children.Add(new ListBoxItem |
|||
{ |
|||
Height = ItemHeight, |
|||
Classes = new Classes("DateTimePickerItem", $"{PanelType}Item"), |
|||
VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center, |
|||
Focusable = false |
|||
}); |
|||
} |
|||
if (children.Count > totalItemsInViewport) |
|||
{ |
|||
var numToRemove = children.Count - totalItemsInViewport; |
|||
children.RemoveRange(children.Count - numToRemove, numToRemove); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates item content based on the current selection
|
|||
/// and the panel type
|
|||
/// </summary>
|
|||
private void UpdateItems() |
|||
{ |
|||
var children = Children; |
|||
var min = MinimumValue; |
|||
var panelType = PanelType; |
|||
var selected = SelectedValue; |
|||
var max = MaximumValue; |
|||
|
|||
int first; |
|||
if (ShouldLoop) |
|||
{ |
|||
first = (_selectedIndex - _numItemsAboveBelowSelected) % _totalItems; |
|||
first = first < 0 ? min + (first + _totalItems) * Increment : min + first * Increment; |
|||
} |
|||
else |
|||
{ |
|||
first = min + Math.Max(0, _selectedIndex - _numItemsAboveBelowSelected) * Increment; |
|||
} |
|||
|
|||
for (int i = 0; i < children.Count; i++) |
|||
{ |
|||
ListBoxItem item = (ListBoxItem)children[i]; |
|||
item.Content = FormatContent(first, panelType); |
|||
item.Tag = first; |
|||
item.IsSelected = first == selected; |
|||
first += Increment; |
|||
if (first > max) |
|||
first = min; |
|||
} |
|||
} |
|||
|
|||
private string FormatContent(int value, DateTimePickerPanelType panelType) |
|||
{ |
|||
switch (panelType) |
|||
{ |
|||
case DateTimePickerPanelType.Year: |
|||
return new DateTime(value, FormatDate.Month, FormatDate.Day).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.Month: |
|||
return new DateTime(FormatDate.Year, value, FormatDate.Day).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.Day: |
|||
return new DateTime(FormatDate.Year, FormatDate.Month, value).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.Hour: |
|||
return new TimeSpan(value, 0, 0).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.Minute: |
|||
return new TimeSpan(0, value, 0).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.TimePeriod: |
|||
return value == MinimumValue ? CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator : |
|||
CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator; |
|||
default: |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ensures the <see cref="SelectedValue"/> is within the bounds and
|
|||
/// follows the current Increment
|
|||
/// </summary>
|
|||
private int CoerceSelected(int newValue) |
|||
{ |
|||
if (newValue < MinimumValue) |
|||
return MinimumValue; |
|||
if (newValue > MaximumValue) |
|||
return MaximumValue; |
|||
|
|||
if (newValue % Increment != 0) |
|||
{ |
|||
var items = Enumerable.Range(MinimumValue, MaximumValue + 1).Where(i => i % Increment == 0).ToList(); |
|||
var nearest = items.Aggregate((x, y) => Math.Abs(x - newValue) > Math.Abs(y - newValue) ? y : x); |
|||
return items.IndexOf(nearest) * Increment; |
|||
} |
|||
return newValue; |
|||
} |
|||
|
|||
private void OnItemPointerDown(object sender, PointerPressedEventArgs e) |
|||
{ |
|||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) |
|||
{ |
|||
_pressedItem = GetItemFromSource((IVisual)e.Source); |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
|
|||
private void OnItemPointerUp(object sender, PointerReleasedEventArgs e) |
|||
{ |
|||
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased && |
|||
_pressedItem != null) |
|||
{ |
|||
SelectedValue = (int)GetItemFromSource((IVisual)e.Source).Tag; |
|||
_pressedItem = null; |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
|
|||
//Helper to get ListBoxItem from pointerevent source
|
|||
private ListBoxItem GetItemFromSource(IVisual src) |
|||
{ |
|||
var item = src; |
|||
while (item != null && !(item is ListBoxItem)) |
|||
{ |
|||
item = item.VisualParent; |
|||
} |
|||
return (ListBoxItem)item; |
|||
} |
|||
|
|||
public bool BringIntoView(IControl target, Rect targetRect) { return false; } |
|||
|
|||
public IControl GetControlInDirection(NavigationDirection direction, IControl from) { return null; } |
|||
|
|||
public void RaiseScrollInvalidated(EventArgs e) |
|||
{ |
|||
ScrollInvalidated?.Invoke(this, e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the base class for Date and Time PickerPresenters
|
|||
/// </summary>
|
|||
public abstract class PickerPresenterBase : TemplatedControl |
|||
{ |
|||
protected virtual void OnConfirmed() |
|||
{ |
|||
Confirmed?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
|
|||
protected virtual void OnDismiss() |
|||
{ |
|||
Dismissed.Invoke(this, EventArgs.Empty); |
|||
} |
|||
|
|||
public event EventHandler Confirmed; |
|||
public event EventHandler Dismissed; |
|||
} |
|||
} |
|||
@ -0,0 +1,292 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Controls.Templates; |
|||
using System; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// A control to allow the user to select a time
|
|||
/// </summary>
|
|||
public class TimePicker : TemplatedControl |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="MinuteIncrement"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePicker, int> MinuteIncrementProperty = |
|||
AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement), |
|||
x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Header"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<object> HeaderProperty = |
|||
AvaloniaProperty.Register<DatePicker, object>(nameof(Header)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="HeaderTemplate"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty = |
|||
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ClockIdentifier"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePicker, string> ClockIdentifierProperty = |
|||
AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier), |
|||
x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="SelectedTime"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePicker, TimeSpan?> SelectedTimeProperty = |
|||
AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime), |
|||
x => x.SelectedTime, (x, v) => x.SelectedTime = v); |
|||
|
|||
//Template Items
|
|||
private TimePickerPresenter _presenter; |
|||
private Button _flyoutButton; |
|||
private Border _firstPickerHost; |
|||
private Border _secondPickerHost; |
|||
private Border _thirdPickerHost; |
|||
private TextBlock _hourText; |
|||
private TextBlock _minuteText; |
|||
public TextBlock _periodText; |
|||
private Rectangle _firstSplitter; |
|||
private Rectangle _secondSplitter; |
|||
private Grid _contentGrid; |
|||
private Popup _popup; |
|||
|
|||
private TimeSpan? _selectedTime; |
|||
private int _minuteIncrement = 1; |
|||
private string _clockIdentifier = "12HourClock"; |
|||
|
|||
public TimePicker() |
|||
{ |
|||
PseudoClasses.Set(":hasnotime", true); |
|||
|
|||
var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern; |
|||
if (timePattern.IndexOf("H") != -1) |
|||
_clockIdentifier = "24HourClock"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minute increment in the picker
|
|||
/// </summary>
|
|||
public int MinuteIncrement |
|||
{ |
|||
get => _minuteIncrement; |
|||
set |
|||
{ |
|||
if (value < 1 || value > 59) |
|||
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59"); |
|||
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value); |
|||
SetSelectedTimeText(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the header
|
|||
/// </summary>
|
|||
public object Header |
|||
{ |
|||
get => GetValue(HeaderProperty); |
|||
set => SetValue(HeaderProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the header template
|
|||
/// </summary>
|
|||
public IDataTemplate HeaderTemplate |
|||
{ |
|||
get => GetValue(HeaderTemplateProperty); |
|||
set => SetValue(HeaderTemplateProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the clock identifier, either 12HourClock or 24HourClock
|
|||
/// </summary>
|
|||
public string ClockIdentifier |
|||
{ |
|||
get => _clockIdentifier; |
|||
set |
|||
{ |
|||
if (!(string.IsNullOrEmpty(value) || value == "" || value == "12HourClock" || value == "24HourClock")) |
|||
throw new ArgumentException("Invalid ClockIdentifier"); |
|||
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value); |
|||
SetGrid(); |
|||
SetSelectedTimeText(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the selected time. Can be null.
|
|||
/// </summary>
|
|||
public TimeSpan? SelectedTime |
|||
{ |
|||
get => _selectedTime; |
|||
set |
|||
{ |
|||
var old = _selectedTime; |
|||
SetAndRaise(SelectedTimeProperty, ref _selectedTime, value); |
|||
OnSelectedTimeChanged(old, value); |
|||
SetSelectedTimeText(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Raised when the <see cref="SelectedTime"/> property changes
|
|||
/// </summary>
|
|||
public event EventHandler<TimePickerSelectedValueChangedEventArgs> SelectedTimeChanged; |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
if (_flyoutButton != null) |
|||
_flyoutButton.Click -= OnFlyoutButtonClicked; |
|||
|
|||
if(_presenter != null) |
|||
{ |
|||
_presenter.Confirmed -= OnConfirmed; |
|||
_presenter.Dismissed -= OnDismissPicker; |
|||
} |
|||
base.OnApplyTemplate(e); |
|||
|
|||
_flyoutButton = e.NameScope.Find<Button>("FlyoutButton"); |
|||
|
|||
_firstPickerHost = e.NameScope.Find<Border>("FirstPickerHost"); |
|||
_secondPickerHost = e.NameScope.Find<Border>("SecondPickerHost"); |
|||
_thirdPickerHost = e.NameScope.Find<Border>("ThirdPickerHost"); |
|||
|
|||
_hourText = e.NameScope.Find<TextBlock>("HourTextBlock"); |
|||
_minuteText = e.NameScope.Find<TextBlock>("MinuteTextBlock"); |
|||
_periodText = e.NameScope.Find<TextBlock>("PeriodTextBlock"); |
|||
|
|||
_firstSplitter = e.NameScope.Find<Rectangle>("FirstColumnDivider"); |
|||
_secondSplitter = e.NameScope.Find<Rectangle>("SecondColumnDivider"); |
|||
|
|||
_contentGrid = e.NameScope.Find<Grid>("FlyoutButtonContentGrid"); |
|||
|
|||
_popup = e.NameScope.Find<Popup>("Popup"); |
|||
_presenter = e.NameScope.Find<TimePickerPresenter>("PickerPresenter"); |
|||
|
|||
|
|||
if (_flyoutButton != null) |
|||
_flyoutButton.Click += OnFlyoutButtonClicked; |
|||
|
|||
SetGrid(); |
|||
SetSelectedTimeText(); |
|||
|
|||
if (_presenter != null) |
|||
{ |
|||
_presenter.Confirmed += OnConfirmed; |
|||
_presenter.Dismissed += OnDismissPicker; |
|||
|
|||
_presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty]; |
|||
_presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty]; |
|||
} |
|||
|
|||
} |
|||
|
|||
private void SetGrid() |
|||
{ |
|||
if (_contentGrid == null) |
|||
return; |
|||
|
|||
bool use24HourClock = ClockIdentifier == "24HourClock"; |
|||
|
|||
if (!use24HourClock) |
|||
{ |
|||
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*"); |
|||
_thirdPickerHost.IsVisible = true; |
|||
_secondSplitter.IsVisible = true; |
|||
|
|||
Grid.SetColumn(_firstPickerHost, 0); |
|||
Grid.SetColumn(_secondPickerHost, 2); |
|||
Grid.SetColumn(_thirdPickerHost, 4); |
|||
|
|||
Grid.SetColumn(_firstSplitter, 1); |
|||
Grid.SetColumn(_secondSplitter, 3); |
|||
} |
|||
else |
|||
{ |
|||
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*"); |
|||
_thirdPickerHost.IsVisible = false; |
|||
_secondSplitter.IsVisible = false; |
|||
|
|||
Grid.SetColumn(_firstPickerHost, 0); |
|||
Grid.SetColumn(_secondPickerHost, 2); |
|||
|
|||
Grid.SetColumn(_firstSplitter, 1); |
|||
} |
|||
} |
|||
|
|||
private void SetSelectedTimeText() |
|||
{ |
|||
if (_hourText == null || _minuteText == null || _periodText == null) |
|||
return; |
|||
|
|||
var time = SelectedTime; |
|||
if (time.HasValue) |
|||
{ |
|||
var newTime = SelectedTime.Value; |
|||
|
|||
if (ClockIdentifier == "12HourClock") |
|||
{ |
|||
var hr = newTime.Hours; |
|||
hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr; |
|||
newTime = new TimeSpan(hr, newTime.Minutes, 0); |
|||
} |
|||
_hourText.Text = newTime.ToString("%h"); |
|||
|
|||
_minuteText.Text = newTime.ToString("mm"); |
|||
PseudoClasses.Set(":hasnotime", false); |
|||
|
|||
_periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : |
|||
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator; |
|||
|
|||
} |
|||
else |
|||
{ |
|||
_hourText.Text = "hour"; |
|||
_minuteText.Text = "minute"; |
|||
PseudoClasses.Set(":hasnotime", true); |
|||
|
|||
_periodText.Text = DateTime.Now.Hour >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : |
|||
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator; |
|||
} |
|||
} |
|||
|
|||
protected virtual void OnSelectedTimeChanged(TimeSpan? oldTime, TimeSpan? newTime) |
|||
{ |
|||
SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime)); |
|||
} |
|||
|
|||
private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
_presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay; |
|||
|
|||
_popup.IsOpen = true; |
|||
|
|||
var deltaY = _presenter.GetOffsetForPopup(); |
|||
|
|||
//The extra 5 px I think is related to default popup placement behavior
|
|||
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), |
|||
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, |
|||
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); |
|||
} |
|||
|
|||
private void OnDismissPicker(object sender, EventArgs e) |
|||
{ |
|||
_popup.Close(); |
|||
Focus(); |
|||
} |
|||
|
|||
private void OnConfirmed(object sender, EventArgs e) |
|||
{ |
|||
_popup.Close(); |
|||
SelectedTime = _presenter.Time; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,262 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the presenter used for selecting a time. Intended for use with
|
|||
/// <see cref="TimePicker"/> but can be used independently
|
|||
/// </summary>
|
|||
public class TimePickerPresenter : PickerPresenterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="MinuteIncrement"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePickerPresenter, int> MinuteIncrementProperty = |
|||
TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>(x => x.MinuteIncrement, |
|||
(x, v) => x.MinuteIncrement = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ClockIdentifier"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePickerPresenter, string> ClockIdentifierProperty = |
|||
TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>(x => x.ClockIdentifier, |
|||
(x, v) => x.ClockIdentifier = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Time"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePickerPresenter, TimeSpan> TimeProperty = |
|||
AvaloniaProperty.RegisterDirect<TimePickerPresenter, TimeSpan>(nameof(Time), |
|||
x => x.Time, (x, v) => x.Time = v); |
|||
|
|||
public TimePickerPresenter() |
|||
{ |
|||
Time = DateTime.Now.TimeOfDay; |
|||
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); |
|||
} |
|||
|
|||
//TemplateItems
|
|||
private Grid _pickerContainer; |
|||
private Button _acceptButton; |
|||
private Button _dismissButton; |
|||
private Rectangle _spacer2; |
|||
private Panel _periodHost; |
|||
private DateTimePickerPanel _hourSelector; |
|||
private DateTimePickerPanel _minuteSelector; |
|||
private DateTimePickerPanel _periodSelector; |
|||
private Button _hourUpButton; |
|||
private Button _minuteUpButton; |
|||
private Button _periodUpButton; |
|||
private Button _hourDownButton; |
|||
private Button _minuteDownButton; |
|||
private Button _periodDownButton; |
|||
|
|||
//Backing Fields
|
|||
private TimeSpan _Time; |
|||
private int _minuteIncrement = 1; |
|||
private string _clockIdentifier = "12HourClock"; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minute increment in the selector
|
|||
/// </summary>
|
|||
public int MinuteIncrement |
|||
{ |
|||
get => _minuteIncrement; |
|||
set |
|||
{ |
|||
if (value < 1 || value > 59) |
|||
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59"); |
|||
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value); |
|||
InitPicker(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current clock identifier, either 12HourClock or 24HourClock
|
|||
/// </summary>
|
|||
public string ClockIdentifier |
|||
{ |
|||
get => _clockIdentifier; |
|||
set |
|||
{ |
|||
if (string.IsNullOrEmpty(value) || value == "" || !(value == "12HourClock" || value == "24HourClock")) |
|||
throw new ArgumentException("Invalid ClockIdentifier"); |
|||
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value); |
|||
InitPicker(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current time
|
|||
/// </summary>
|
|||
public TimeSpan Time |
|||
{ |
|||
get => _Time; |
|||
set |
|||
{ |
|||
SetAndRaise(TimeProperty, ref _Time, value); |
|||
InitPicker(); |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
|
|||
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer"); |
|||
_periodHost = e.NameScope.Get<Panel>("PeriodHost"); |
|||
|
|||
_hourSelector = e.NameScope.Get<DateTimePickerPanel>("HourSelector"); |
|||
_minuteSelector = e.NameScope.Get<DateTimePickerPanel>("MinuteSelector"); |
|||
_periodSelector = e.NameScope.Get<DateTimePickerPanel>("PeriodSelector"); |
|||
|
|||
_spacer2 = e.NameScope.Get<Rectangle>("SecondSpacer"); |
|||
|
|||
_acceptButton = e.NameScope.Get<Button>("AcceptButton"); |
|||
_acceptButton.Click += OnAcceptButtonClicked; |
|||
|
|||
_hourUpButton = e.NameScope.Find<RepeatButton>("HourUpButton"); |
|||
if (_hourUpButton != null) |
|||
_hourUpButton.Click += OnSelectorButtonClick; |
|||
_hourDownButton = e.NameScope.Find<RepeatButton>("HourDownButton"); |
|||
if (_hourDownButton != null) |
|||
_hourDownButton.Click += OnSelectorButtonClick; |
|||
|
|||
_minuteUpButton = e.NameScope.Find<RepeatButton>("MinuteUpButton"); |
|||
if (_minuteUpButton != null) |
|||
_minuteUpButton.Click += OnSelectorButtonClick; |
|||
_minuteDownButton = e.NameScope.Find<RepeatButton>("MinuteDownButton"); |
|||
if (_minuteDownButton != null) |
|||
_minuteDownButton.Click += OnSelectorButtonClick; |
|||
|
|||
_periodUpButton = e.NameScope.Find<RepeatButton>("PeriodUpButton"); |
|||
if (_periodUpButton != null) |
|||
_periodUpButton.Click += OnSelectorButtonClick; |
|||
_periodDownButton = e.NameScope.Find<RepeatButton>("PeriodDownButton"); |
|||
if (_periodDownButton != null) |
|||
_periodDownButton.Click += OnSelectorButtonClick; |
|||
|
|||
_dismissButton = e.NameScope.Find<Button>("DismissButton"); |
|||
if (_dismissButton != null) |
|||
_dismissButton.Click += OnDismissButtonClicked; |
|||
|
|||
InitPicker(); |
|||
} |
|||
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
switch (e.Key) |
|||
{ |
|||
case Key.Escape: |
|||
OnDismiss(); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Tab: |
|||
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next); |
|||
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Enter: |
|||
OnConfirmed(); |
|||
e.Handled = true; |
|||
break; |
|||
} |
|||
base.OnKeyDown(e); |
|||
} |
|||
|
|||
protected override void OnConfirmed() |
|||
{ |
|||
var hr = _hourSelector.SelectedValue; |
|||
var min = _minuteSelector.SelectedValue; |
|||
var per = _periodSelector.SelectedValue; |
|||
|
|||
if (ClockIdentifier == "12HourClock") |
|||
{ |
|||
hr = per == 1 ? hr + 12 : per == 0 && hr == 12 ? 0 : hr; |
|||
} |
|||
|
|||
Time = new TimeSpan(hr, min, 0); |
|||
|
|||
base.OnConfirmed(); |
|||
} |
|||
|
|||
private void InitPicker() |
|||
{ |
|||
if (_pickerContainer == null) |
|||
return; |
|||
|
|||
bool clock12 = ClockIdentifier == "12HourClock"; |
|||
_hourSelector.MaximumValue = clock12 ? 12 : 23; |
|||
_hourSelector.MinimumValue = clock12 ? 1 : 0; |
|||
_hourSelector.ItemFormat = "%h"; |
|||
var hr = Time.Hours; |
|||
_hourSelector.SelectedValue = !clock12 ? hr : |
|||
hr > 12 ? hr - 12 : hr == 0 ? 12 : hr; |
|||
|
|||
_minuteSelector.MaximumValue = 59; |
|||
_minuteSelector.MinimumValue = 0; |
|||
_minuteSelector.Increment = MinuteIncrement; |
|||
_minuteSelector.SelectedValue = Time.Minutes; |
|||
_minuteSelector.ItemFormat = "mm"; |
|||
|
|||
_periodSelector.MaximumValue = 1; |
|||
_periodSelector.MinimumValue = 0; |
|||
_periodSelector.SelectedValue = hr >= 12 ? 1 : 0; |
|||
|
|||
SetGrid(); |
|||
KeyboardDevice.Instance?.SetFocusedElement(_hourSelector, NavigationMethod.Pointer, KeyModifiers.None); |
|||
} |
|||
|
|||
private void SetGrid() |
|||
{ |
|||
if (ClockIdentifier == "12HourClock") |
|||
{ |
|||
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*"); |
|||
_spacer2.IsVisible = true; |
|||
_periodHost.IsVisible = true; |
|||
} |
|||
else |
|||
{ |
|||
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*"); |
|||
_spacer2.IsVisible = false; |
|||
_periodHost.IsVisible = false; |
|||
} |
|||
} |
|||
|
|||
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
OnDismiss(); |
|||
} |
|||
|
|||
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
OnConfirmed(); |
|||
} |
|||
|
|||
private void OnSelectorButtonClick(object sender, RoutedEventArgs e) |
|||
{ |
|||
if (sender == _hourUpButton) |
|||
_hourSelector.ScrollUp(); |
|||
else if (sender == _hourDownButton) |
|||
_hourSelector.ScrollDown(); |
|||
else if (sender == _minuteUpButton) |
|||
_minuteSelector.ScrollUp(); |
|||
else if (sender == _minuteDownButton) |
|||
_minuteSelector.ScrollDown(); |
|||
else if (sender == _periodUpButton) |
|||
_periodSelector.ScrollUp(); |
|||
else if (sender == _periodDownButton) |
|||
_periodSelector.ScrollDown(); |
|||
} |
|||
|
|||
internal double GetOffsetForPopup() |
|||
{ |
|||
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; |
|||
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector.ItemHeight / 2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class TimePickerSelectedValueChangedEventArgs |
|||
{ |
|||
public TimeSpan? OldTime { get; } |
|||
public TimeSpan? NewTime { get; } |
|||
public TimePickerSelectedValueChangedEventArgs(TimeSpan? old, TimeSpan? newT) |
|||
{ |
|||
OldTime = old; |
|||
NewTime = newT; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Hint for Window Chrome when ClientArea is Extended.
|
|||
/// </summary>
|
|||
[Flags] |
|||
public enum ExtendClientAreaChromeHints |
|||
{ |
|||
/// <summary>
|
|||
/// The will be no chrome at all.
|
|||
/// </summary>
|
|||
NoChrome, |
|||
|
|||
/// <summary>
|
|||
/// The default for the platform.
|
|||
/// </summary>
|
|||
Default = SystemChrome, |
|||
|
|||
/// <summary>
|
|||
/// Use SystemChrome
|
|||
/// </summary>
|
|||
SystemChrome = 0x01, |
|||
|
|||
/// <summary>
|
|||
/// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
|
|||
/// This is because Windows Chrome can not be shown ontop of user content.
|
|||
/// </summary>
|
|||
PreferSystemChrome = 0x02, |
|||
|
|||
/// <summary>
|
|||
/// On OSX the titlebar is the thicker toolbar kind. Causes traffic lights to be positioned
|
|||
/// slightly lower than normal.
|
|||
/// </summary>
|
|||
OSXThickTitleBar = 0x08, |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using System.Linq; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.VisualTree; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest |
|||
{ |
|||
public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual) |
|||
{ |
|||
foreach (var v in visual.GetVisualAncestors()) |
|||
if (v is VisualLayerManager vlm) |
|||
if (vlm.OverlayLayer != null) |
|||
return vlm.ChromeOverlayLayer; |
|||
|
|||
if (visual is TopLevel tl) |
|||
{ |
|||
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault(); |
|||
return layers?.ChromeOverlayLayer; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public bool HitTest(Point point) => Children.HitTestCustom(point); |
|||
} |
|||
} |
|||
@ -0,0 +1,487 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Media; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines constants for how the SplitView Pane should display
|
|||
/// </summary>
|
|||
public enum SplitViewDisplayMode |
|||
{ |
|||
/// <summary>
|
|||
/// Pane is displayed next to content, and does not auto collapse
|
|||
/// when tapped outside
|
|||
/// </summary>
|
|||
Inline, |
|||
/// <summary>
|
|||
/// Pane is displayed next to content. When collapsed, pane is still
|
|||
/// visible according to CompactPaneLength. Pane does not auto collapse
|
|||
/// when tapped outside
|
|||
/// </summary>
|
|||
CompactInline, |
|||
/// <summary>
|
|||
/// Pane is displayed above content. Pane collapses when tapped outside
|
|||
/// </summary>
|
|||
Overlay, |
|||
/// <summary>
|
|||
/// Pane is displayed above content. When collapsed, pane is still
|
|||
/// visible according to CompactPaneLength. Pane collapses when tapped outside
|
|||
/// </summary>
|
|||
CompactOverlay |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines constants for where the Pane should appear
|
|||
/// </summary>
|
|||
public enum SplitViewPanePlacement |
|||
{ |
|||
Left, |
|||
Right |
|||
} |
|||
|
|||
public class SplitViewTemplateSettings : AvaloniaObject |
|||
{ |
|||
internal SplitViewTemplateSettings() { } |
|||
|
|||
public static readonly StyledProperty<double> ClosedPaneWidthProperty = |
|||
AvaloniaProperty.Register<SplitViewTemplateSettings, double>(nameof(ClosedPaneWidth), 0d); |
|||
|
|||
public static readonly StyledProperty<GridLength> PaneColumnGridLengthProperty = |
|||
AvaloniaProperty.Register<SplitViewTemplateSettings, GridLength>(nameof(PaneColumnGridLength)); |
|||
|
|||
public double ClosedPaneWidth |
|||
{ |
|||
get => GetValue(ClosedPaneWidthProperty); |
|||
internal set => SetValue(ClosedPaneWidthProperty, value); |
|||
} |
|||
|
|||
public GridLength PaneColumnGridLength |
|||
{ |
|||
get => GetValue(PaneColumnGridLengthProperty); |
|||
internal set => SetValue(PaneColumnGridLengthProperty, value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A control with two views: A collapsible pane and an area for content
|
|||
/// </summary>
|
|||
public class SplitView : TemplatedControl |
|||
{ |
|||
/* |
|||
Pseudo classes & combos |
|||
:open / :closed |
|||
:compactoverlay :compactinline :overlay :inline |
|||
:left :right |
|||
*/ |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Content"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IControl> ContentProperty = |
|||
AvaloniaProperty.Register<SplitView, IControl>(nameof(Content)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="CompactPaneLength"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> CompactPaneLengthProperty = |
|||
AvaloniaProperty.Register<SplitView, double>(nameof(CompactPaneLength), defaultValue: 48); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DisplayMode"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<SplitViewDisplayMode> DisplayModeProperty = |
|||
AvaloniaProperty.Register<SplitView, SplitViewDisplayMode>(nameof(DisplayMode), defaultValue: SplitViewDisplayMode.Overlay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsPaneOpen"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<SplitView, bool> IsPaneOpenProperty = |
|||
AvaloniaProperty.RegisterDirect<SplitView, bool>(nameof(IsPaneOpen), |
|||
x => x.IsPaneOpen, (x, v) => x.IsPaneOpen = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="OpenPaneLength"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> OpenPaneLengthProperty = |
|||
AvaloniaProperty.Register<SplitView, double>(nameof(OpenPaneLength), defaultValue: 320); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PaneBackground"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IBrush> PaneBackgroundProperty = |
|||
AvaloniaProperty.Register<SplitView, IBrush>(nameof(PaneBackground)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PanePlacement"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<SplitViewPanePlacement> PanePlacementProperty = |
|||
AvaloniaProperty.Register<SplitView, SplitViewPanePlacement>(nameof(PanePlacement)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Pane"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IControl> PaneProperty = |
|||
AvaloniaProperty.Register<SplitView, IControl>(nameof(Pane)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="UseLightDismissOverlayMode"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> UseLightDismissOverlayModeProperty = |
|||
AvaloniaProperty.Register<SplitView, bool>(nameof(UseLightDismissOverlayMode)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="TemplateSettings"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<SplitViewTemplateSettings> TemplateSettingsProperty = |
|||
AvaloniaProperty.Register<SplitView, SplitViewTemplateSettings>(nameof(TemplateSettings)); |
|||
|
|||
private bool _isPaneOpen; |
|||
private Panel _pane; |
|||
private CompositeDisposable _pointerDisposables; |
|||
|
|||
public SplitView() |
|||
{ |
|||
PseudoClasses.Add(":overlay"); |
|||
PseudoClasses.Add(":left"); |
|||
|
|||
TemplateSettings = new SplitViewTemplateSettings(); |
|||
} |
|||
|
|||
static SplitView() |
|||
{ |
|||
UseLightDismissOverlayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnUseLightDismissChanged(v)); |
|||
CompactPaneLengthProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnCompactPaneLengthChanged(v)); |
|||
PanePlacementProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnPanePlacementChanged(v)); |
|||
DisplayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnDisplayModeChanged(v)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the content of the SplitView
|
|||
/// </summary>
|
|||
[Content] |
|||
public IControl Content |
|||
{ |
|||
get => GetValue(ContentProperty); |
|||
set => SetValue(ContentProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the length of the pane when in <see cref="SplitViewDisplayMode.CompactOverlay"/>
|
|||
/// or <see cref="SplitViewDisplayMode.CompactInline"/> mode
|
|||
/// </summary>
|
|||
public double CompactPaneLength |
|||
{ |
|||
get => GetValue(CompactPaneLengthProperty); |
|||
set => SetValue(CompactPaneLengthProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="SplitViewDisplayMode"/> for the SplitView
|
|||
/// </summary>
|
|||
public SplitViewDisplayMode DisplayMode |
|||
{ |
|||
get => GetValue(DisplayModeProperty); |
|||
set => SetValue(DisplayModeProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the pane is open or closed
|
|||
/// </summary>
|
|||
public bool IsPaneOpen |
|||
{ |
|||
get => _isPaneOpen; |
|||
set |
|||
{ |
|||
if (value == _isPaneOpen) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (value) |
|||
{ |
|||
OnPaneOpening(this, null); |
|||
SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value); |
|||
|
|||
PseudoClasses.Add(":open"); |
|||
PseudoClasses.Remove(":closed"); |
|||
OnPaneOpened(this, null); |
|||
} |
|||
else |
|||
{ |
|||
SplitViewPaneClosingEventArgs args = new SplitViewPaneClosingEventArgs(false); |
|||
OnPaneClosing(this, args); |
|||
if (!args.Cancel) |
|||
{ |
|||
SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value); |
|||
|
|||
PseudoClasses.Add(":closed"); |
|||
PseudoClasses.Remove(":open"); |
|||
OnPaneClosed(this, null); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the length of the pane when open
|
|||
/// </summary>
|
|||
public double OpenPaneLength |
|||
{ |
|||
get => GetValue(OpenPaneLengthProperty); |
|||
set => SetValue(OpenPaneLengthProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the background of the pane
|
|||
/// </summary>
|
|||
public IBrush PaneBackground |
|||
{ |
|||
get => GetValue(PaneBackgroundProperty); |
|||
set => SetValue(PaneBackgroundProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="SplitViewPanePlacement"/> for the SplitView
|
|||
/// </summary>
|
|||
public SplitViewPanePlacement PanePlacement |
|||
{ |
|||
get => GetValue(PanePlacementProperty); |
|||
set => SetValue(PanePlacementProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Pane for the SplitView
|
|||
/// </summary>
|
|||
public IControl Pane |
|||
{ |
|||
get => GetValue(PaneProperty); |
|||
set => SetValue(PaneProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether WinUI equivalent LightDismissOverlayMode is enabled
|
|||
/// <para>When enabled, and the pane is open in Overlay or CompactOverlay mode,
|
|||
/// the contents of the splitview are darkened to visually separate the open pane
|
|||
/// and the rest of the SplitView</para>
|
|||
/// </summary>
|
|||
public bool UseLightDismissOverlayMode |
|||
{ |
|||
get => GetValue(UseLightDismissOverlayModeProperty); |
|||
set => SetValue(UseLightDismissOverlayModeProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the TemplateSettings for the SplitView
|
|||
/// </summary>
|
|||
public SplitViewTemplateSettings TemplateSettings |
|||
{ |
|||
get => GetValue(TemplateSettingsProperty); |
|||
set => SetValue(TemplateSettingsProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is closed
|
|||
/// </summary>
|
|||
public event EventHandler<EventArgs> PaneClosed; |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is closing
|
|||
/// </summary>
|
|||
public event EventHandler<SplitViewPaneClosingEventArgs> PaneClosing; |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is opened
|
|||
/// </summary>
|
|||
public event EventHandler<EventArgs> PaneOpened; |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is opening
|
|||
/// </summary>
|
|||
public event EventHandler<EventArgs> PaneOpening; |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
_pane = e.NameScope.Find<Panel>("PART_PaneRoot"); |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
|
|||
var topLevel = this.VisualRoot; |
|||
if (topLevel is Window window) |
|||
{ |
|||
//Logic adapted from Popup
|
|||
//Basically if we're using an overlay DisplayMode, close the pane if we don't click on the pane
|
|||
IDisposable subscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler, |
|||
Action<T, TEventHandler> subscribe, Action<T, TEventHandler> unsubscribe) |
|||
{ |
|||
subscribe(target, handler); |
|||
return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler)); |
|||
} |
|||
|
|||
_pointerDisposables = new CompositeDisposable( |
|||
window.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel), |
|||
InputManager.Instance?.Process.Subscribe(OnNonClientClick), |
|||
subscribeToEventHandler<Window, EventHandler>(window, Window_Deactivated, |
|||
(x, handler) => x.Deactivated += handler, (x, handler) => x.Deactivated -= handler), |
|||
subscribeToEventHandler<IWindowImpl, Action>(window.PlatformImpl, OnWindowLostFocus, |
|||
(x, handler) => x.LostFocus += handler, (x, handler) => x.LostFocus -= handler)); |
|||
} |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnDetachedFromVisualTree(e); |
|||
_pointerDisposables?.Dispose(); |
|||
} |
|||
|
|||
private void OnWindowLostFocus() |
|||
{ |
|||
if (IsPaneOpen && ShouldClosePane()) |
|||
{ |
|||
IsPaneOpen = false; |
|||
} |
|||
} |
|||
|
|||
private void PointerPressedOutside(object sender, PointerPressedEventArgs e) |
|||
{ |
|||
if (!IsPaneOpen) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
//If we click within the Pane, don't do anything
|
|||
//Otherwise, ClosePane if open & using an overlay display mode
|
|||
bool closePane = ShouldClosePane(); |
|||
if (!closePane) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var src = e.Source as IVisual; |
|||
while (src != null) |
|||
{ |
|||
if (src == _pane) |
|||
{ |
|||
closePane = false; |
|||
break; |
|||
} |
|||
|
|||
src = src.VisualParent; |
|||
} |
|||
if (closePane) |
|||
{ |
|||
IsPaneOpen = false; |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
|
|||
private void OnNonClientClick(RawInputEventArgs obj) |
|||
{ |
|||
if (!IsPaneOpen) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var mouse = obj as RawPointerEventArgs; |
|||
if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown) |
|||
|
|||
{ |
|||
if (ShouldClosePane()) |
|||
IsPaneOpen = false; |
|||
} |
|||
} |
|||
|
|||
private void Window_Deactivated(object sender, EventArgs e) |
|||
{ |
|||
if (IsPaneOpen && ShouldClosePane()) |
|||
{ |
|||
IsPaneOpen = false; |
|||
} |
|||
} |
|||
|
|||
private bool ShouldClosePane() |
|||
{ |
|||
return (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay); |
|||
} |
|||
|
|||
protected virtual void OnPaneOpening(SplitView sender, EventArgs args) |
|||
{ |
|||
PaneOpening?.Invoke(sender, args); |
|||
} |
|||
|
|||
protected virtual void OnPaneOpened(SplitView sender, EventArgs args) |
|||
{ |
|||
PaneOpened?.Invoke(sender, args); |
|||
} |
|||
|
|||
protected virtual void OnPaneClosing(SplitView sender, SplitViewPaneClosingEventArgs args) |
|||
{ |
|||
PaneClosing?.Invoke(sender, args); |
|||
} |
|||
|
|||
protected virtual void OnPaneClosed(SplitView sender, EventArgs args) |
|||
{ |
|||
PaneClosed?.Invoke(sender, args); |
|||
} |
|||
|
|||
private void OnCompactPaneLengthChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var newLen = (double)e.NewValue; |
|||
var displayMode = DisplayMode; |
|||
if (displayMode == SplitViewDisplayMode.CompactInline) |
|||
{ |
|||
TemplateSettings.ClosedPaneWidth = newLen; |
|||
} |
|||
else if (displayMode == SplitViewDisplayMode.CompactOverlay) |
|||
{ |
|||
TemplateSettings.ClosedPaneWidth = newLen; |
|||
TemplateSettings.PaneColumnGridLength = new GridLength(newLen, GridUnitType.Pixel); |
|||
} |
|||
} |
|||
|
|||
private void OnPanePlacementChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var oldState = e.OldValue.ToString().ToLower(); |
|||
var newState = e.NewValue.ToString().ToLower(); |
|||
PseudoClasses.Remove($":{oldState}"); |
|||
PseudoClasses.Add($":{newState}"); |
|||
} |
|||
|
|||
private void OnDisplayModeChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var oldState = e.OldValue.ToString().ToLower(); |
|||
var newState = e.NewValue.ToString().ToLower(); |
|||
|
|||
PseudoClasses.Remove($":{oldState}"); |
|||
PseudoClasses.Add($":{newState}"); |
|||
|
|||
var (closedPaneWidth, paneColumnGridLength) = (SplitViewDisplayMode)e.NewValue switch |
|||
{ |
|||
SplitViewDisplayMode.Overlay => (0, new GridLength(0, GridUnitType.Pixel)), |
|||
SplitViewDisplayMode.CompactOverlay => (CompactPaneLength, new GridLength(CompactPaneLength, GridUnitType.Pixel)), |
|||
SplitViewDisplayMode.Inline => (0, new GridLength(0, GridUnitType.Auto)), |
|||
SplitViewDisplayMode.CompactInline => (CompactPaneLength, new GridLength(0, GridUnitType.Auto)), |
|||
_ => throw new NotImplementedException(), |
|||
}; |
|||
TemplateSettings.ClosedPaneWidth = closedPaneWidth; |
|||
TemplateSettings.PaneColumnGridLength = paneColumnGridLength; |
|||
} |
|||
|
|||
private void OnUseLightDismissChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var mode = (bool)e.NewValue; |
|||
PseudoClasses.Set(":lightdismiss", mode); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class SplitViewPaneClosingEventArgs : EventArgs |
|||
{ |
|||
public bool Cancel { get; set; } |
|||
|
|||
public SplitViewPaneClosingEventArgs(bool cancel) |
|||
{ |
|||
Cancel = cancel; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using System.ComponentModel; |
|||
using Avalonia.Collections; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable |
|||
{ |
|||
private AvaloniaList<TreeNode> _inner; |
|||
|
|||
public TreeNodeCollection(TreeNode owner) => Owner = owner; |
|||
|
|||
public TreeNode this[int index] |
|||
{ |
|||
get |
|||
{ |
|||
EnsureInitialized(); |
|||
return _inner[index]; |
|||
} |
|||
} |
|||
|
|||
public int Count |
|||
{ |
|||
get |
|||
{ |
|||
EnsureInitialized(); |
|||
return _inner.Count; |
|||
} |
|||
} |
|||
|
|||
protected TreeNode Owner { get; } |
|||
|
|||
public event NotifyCollectionChangedEventHandler CollectionChanged |
|||
{ |
|||
add => _inner.CollectionChanged += value; |
|||
remove => _inner.CollectionChanged -= value; |
|||
} |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged |
|||
{ |
|||
add => _inner.PropertyChanged += value; |
|||
remove => _inner.PropertyChanged -= value; |
|||
} |
|||
|
|||
public virtual void Dispose() |
|||
{ |
|||
if (_inner is object) |
|||
{ |
|||
foreach (var node in _inner) |
|||
{ |
|||
node.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IEnumerator<TreeNode> GetEnumerator() |
|||
{ |
|||
EnsureInitialized(); |
|||
return _inner.GetEnumerator(); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|||
|
|||
protected abstract void Initialize(AvaloniaList<TreeNode> nodes); |
|||
|
|||
private void EnsureInitialized() |
|||
{ |
|||
if (_inner is null) |
|||
{ |
|||
_inner = new AvaloniaList<TreeNode>(); |
|||
Initialize(_inner); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Style Selector="CaptionButtons"> |
|||
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/> |
|||
<Setter Property="MaxHeight" Value="30" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal"> |
|||
<StackPanel.Styles> |
|||
<Style Selector="Panel"> |
|||
<Setter Property="Width" Value="45" /> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
<Style Selector="Panel:pointerover"> |
|||
<Setter Property="Background" Value="#7F7f7f7f" /> |
|||
</Style> |
|||
<Style Selector="Panel#PART_CloseButton:pointerover"> |
|||
<Setter Property="Background" Value="#7FFF0000" /> |
|||
</Style> |
|||
<Style Selector="Viewbox"> |
|||
<Setter Property="Width" Value="11" /> |
|||
<Setter Property="Margin" Value="2" /> |
|||
</Style> |
|||
</StackPanel.Styles> |
|||
<Panel x:Name="PART_FullScreenButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_MinimiseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_RestoreButton"> |
|||
<Viewbox> |
|||
<Viewbox.RenderTransform> |
|||
<RotateTransform Angle="-90" /> |
|||
</Viewbox.RenderTransform> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_CloseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
</StackPanel> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton"> |
|||
<Setter Property="IsVisible" Value="False" /> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,53 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Design.PreviewWith> |
|||
<Border> |
|||
<TitleBar Background="SkyBlue" Height="30" Width="300" Foreground="Black" /> |
|||
</Border> |
|||
</Design.PreviewWith> |
|||
<Style Selector="TitleBar"> |
|||
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> |
|||
<Setter Property="VerticalAlignment" Value="Top" /> |
|||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="Stretch"> |
|||
<Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" /> |
|||
<Panel x:Name="PART_Container"> |
|||
<Border x:Name="PART_Background" Background="{TemplateBinding Background}" /> |
|||
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" /> |
|||
</Panel> |
|||
</Panel> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen"> |
|||
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar /template/ Border#PART_Background"> |
|||
<Setter Property="IsHitTestVisible" Value="False" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Border#PART_Background"> |
|||
<Setter Property="IsHitTestVisible" Value="True" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_MouseTracker"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_Container"> |
|||
<Setter Property="RenderTransform" Value="translateY(-30px)" /> |
|||
<Setter Property="Transitions"> |
|||
<Transitions> |
|||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.25" /> |
|||
</Transitions> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen:pointerover /template/ Panel#PART_Container"> |
|||
<Setter Property="RenderTransform" Value="none" /> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,68 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Style Selector="CaptionButtons"> |
|||
<Setter Property="MaxHeight" Value="30" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal"> |
|||
<StackPanel.Styles> |
|||
<Style Selector="Panel"> |
|||
<Setter Property="Width" Value="45" /> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
<Style Selector="Panel:pointerover"> |
|||
<Setter Property="Background" Value="#7F7f7f7f" /> |
|||
</Style> |
|||
<Style Selector="Panel#PART_CloseButton:pointerover"> |
|||
<Setter Property="Background" Value="#7FFF0000" /> |
|||
</Style> |
|||
<Style Selector="Viewbox"> |
|||
<Setter Property="Width" Value="11" /> |
|||
<Setter Property="Margin" Value="2" /> |
|||
</Style> |
|||
</StackPanel.Styles> |
|||
<Panel x:Name="PART_FullScreenButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_MinimiseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_RestoreButton"> |
|||
<Viewbox> |
|||
<Viewbox.RenderTransform> |
|||
<RotateTransform Angle="-90" /> |
|||
</Viewbox.RenderTransform> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_CloseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
</StackPanel> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton"> |
|||
<Setter Property="IsVisible" Value="False" /> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,338 @@ |
|||
<!-- |
|||
// (c) Copyright Microsoft Corporation. |
|||
// This source is subject to the Microsoft Public License (Ms-PL). |
|||
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. |
|||
// All other rights reserved. |
|||
--> |
|||
|
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:sys="clr-namespace:System;assembly=netstandard"> |
|||
<Styles.Resources> |
|||
<Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness> |
|||
<x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double> |
|||
<x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double> |
|||
<x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> |
|||
<x:Double x:Key="DatePickerThemeMinWidth">296</x:Double> |
|||
<x:Double x:Key="DatePickerThemeMaxWidth">456</x:Double> |
|||
<Thickness x:Key="DatePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness> |
|||
<Thickness x:Key="DatePickerFlyoutPresenterMonthPadding">9,3,0,6</Thickness> |
|||
<Thickness x:Key="DatePickerHostPadding">0,3,0,6</Thickness> |
|||
<Thickness x:Key="DatePickerHostMonthPadding">9,3,0,6</Thickness> |
|||
<x:Double x:Key="DatePickerSpacerThemeWidth">1</x:Double> |
|||
</Styles.Resources> |
|||
|
|||
<!-- Styles for the items displayed in the selectors --> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem"> |
|||
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterItemPadding}"/> |
|||
<Setter Property="VerticalContentAlignment" Value="Center" /> |
|||
<Setter Property="HorizontalContentAlignment" Value="Center" /> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem:selected"> |
|||
<Setter Property="IsHitTestVisible" Value="False"/> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ Rectangle#PressedBackground"> |
|||
<Setter Property="Fill" Value="Transparent"/> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ ContentPresenter"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem.MonthItem"> |
|||
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterMonthPadding}"/> |
|||
<Setter Property="VerticalContentAlignment" Value="Center" /> |
|||
<Setter Property="HorizontalContentAlignment" Value="Left" /> |
|||
</Style> |
|||
|
|||
|
|||
<!-- This is used for both the accept/dismiss & repeatbuttons in the presenter--> |
|||
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle"> |
|||
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackground}" /> |
|||
<Setter Property="HorizontalContentAlignment" Value="Center"/> |
|||
<Setter Property="VerticalContentAlignment" Value="Center"/> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<!-- |
|||
The background is doubled here for the loopingselector up/down repeat buttons |
|||
that appear opaque. Not sure how MS does it though I suspect this is it |
|||
but source isn't MIT yet, so this is my solution --> |
|||
<Border Background="{TemplateBinding Background}"> |
|||
<ContentPresenter x:Name="ContentPresenter" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{DynamicResource DateTimePickerFlyoutButtonBorderBrush}" |
|||
BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}" |
|||
Content="{TemplateBinding Content}" |
|||
TextBlock.Foreground="{DynamicResource SystemControlHighlightAltBaseHighBrush}" |
|||
ContentTemplate="{TemplateBinding ContentTemplate}" |
|||
Padding="{TemplateBinding Padding}" |
|||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" |
|||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" |
|||
CornerRadius="{DynamicResource ControlCornerRadius}"/> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pointerover /template/ ContentPresenter"> |
|||
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPointerOver}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPointerOver}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPointerOver}"/> |
|||
</Style> |
|||
|
|||
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pressed /template/ ContentPresenter"> |
|||
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPressed}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPressed}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPressed}"/> |
|||
</Style> |
|||
|
|||
|
|||
<Style Selector="RepeatButton.UpButton"> |
|||
<Setter Property="VerticalAlignment" Value="Top"/> |
|||
<Setter Property="Height" Value="22" /> |
|||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|||
<Setter Property="Focusable" Value="False" /> |
|||
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" /> |
|||
<Setter Property="Content"> |
|||
<Template> |
|||
<Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center"> |
|||
<Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,9 L 9,0 L 18,9"/> |
|||
</Viewbox> |
|||
</Template> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="RepeatButton.DownButton"> |
|||
<Setter Property="VerticalAlignment" Value="Bottom"/> |
|||
<Setter Property="Height" Value="22" /> |
|||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|||
<Setter Property="Focusable" Value="False" /> |
|||
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" /> |
|||
<Setter Property="Content"> |
|||
<Template> |
|||
<Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center"> |
|||
<Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,0 L 9,9 L 18,0"/> |
|||
</Viewbox> |
|||
</Template> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePicker"> |
|||
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
|||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> |
|||
<Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForeground}" /> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackground}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrush}"/> |
|||
<Setter Property="BorderThickness" Value="1"/> |
|||
<Setter Property="HorizontalAlignment" Value="Left" /> |
|||
<Setter Property="VerticalAlignment" Value="Center" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*"> |
|||
<ContentPresenter Name="HeaderContentPresenter" Grid.Row="0" |
|||
Content="{TemplateBinding Header}" |
|||
ContentTemplate="{TemplateBinding HeaderTemplate}" |
|||
Margin="{DynamicResource DatePickerTopHeaderMargin}" |
|||
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Top"/> |
|||
|
|||
<Button Name="FlyoutButton" Grid.Row="1" |
|||
Foreground="{TemplateBinding Foreground}" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
IsEnabled="{TemplateBinding IsEnabled}" |
|||
MinWidth="{StaticResource DatePickerThemeMinWidth}" |
|||
MaxWidth="{StaticResource DatePickerThemeMaxWidth}" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
HorizontalContentAlignment="Stretch" |
|||
VerticalContentAlignment="Stretch" |
|||
TemplatedControl.IsTemplateFocusTarget="True"> |
|||
<Button.Template> |
|||
<ControlTemplate> |
|||
<ContentPresenter Name="ContentPresenter" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
Background="{TemplateBinding Background}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
Content="{TemplateBinding Content}" |
|||
TextBlock.Foreground="{TemplateBinding Foreground}" |
|||
HorizontalContentAlignment="Stretch" |
|||
VerticalContentAlignment="Stretch" |
|||
CornerRadius="{DynamicResource ControlCornerRadius}"/> |
|||
</ControlTemplate> |
|||
</Button.Template> |
|||
<Grid Name="ButtonContentGrid" ColumnDefinitions="78*,Auto,132*,Auto,78*"> |
|||
<TextBlock Name="DayText" Text="day" HorizontalAlignment="Center" |
|||
Padding="{DynamicResource DatePickerHostPadding}" |
|||
FontFamily="{TemplateBinding FontFamily}" |
|||
FontWeight="{TemplateBinding FontWeight}" |
|||
FontSize="{TemplateBinding FontSize}"/> |
|||
<TextBlock Name="MonthText" Text="month" TextAlignment="Left" |
|||
Padding="{DynamicResource DatePickerHostMonthPadding}" |
|||
FontFamily="{TemplateBinding FontFamily}" |
|||
FontWeight="{TemplateBinding FontWeight}" |
|||
FontSize="{TemplateBinding FontSize}"/> |
|||
<TextBlock Name="YearText" Text="year" HorizontalAlignment="Center" |
|||
Padding="{DynamicResource DatePickerHostPadding}" |
|||
FontFamily="{TemplateBinding FontFamily}" |
|||
FontWeight="{TemplateBinding FontWeight}" |
|||
FontSize="{TemplateBinding FontSize}"/> |
|||
<Rectangle x:Name="FirstSpacer" |
|||
Fill="{DynamicResource DatePickerSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="1" |
|||
Grid.Column="1" /> |
|||
<Rectangle x:Name="SecondSpacer" |
|||
Fill="{DynamicResource DatePickerSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="1" |
|||
Grid.Column="3" /> |
|||
</Grid> |
|||
</Button> |
|||
|
|||
<Popup Name="Popup" WindowManagerAddShadowHint="False" |
|||
StaysOpen="False" PlacementTarget="{TemplateBinding}" |
|||
PlacementMode="Bottom"> |
|||
<DatePickerPresenter Name="PickerPresenter" /> |
|||
</Popup> |
|||
|
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="DatePicker /template/ ContentPresenter#HeaderContentPresenter"> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerHeaderForeground}"/> |
|||
</Style> |
|||
<Style Selector="DatePicker:disabled /template/ Rectangle"> |
|||
<Setter Property="Fill" Value="{DynamicResource DatePickerSpacerFillDisabled}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter"> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPointerOver}"/> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPointerOver}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPointerOver}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePicker /template/ Button#FlyoutButton:pressed /template/ ContentPresenter"> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPressed}"/> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPressed}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPressed}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePicker /template/ Button#FlyoutButton:disabled /template/ ContentPresenter"> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushDisabled}"/> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundDisabled}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/> |
|||
</Style> |
|||
|
|||
<!-- Changes foreground for watermark text when SelectedDate is null--> |
|||
<Style Selector="DatePicker:hasnodate /template/ Button#FlyoutButton TextBlock"> |
|||
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/> |
|||
</Style> |
|||
|
|||
<!--WinUI: DatePickerFlyoutPresenter--> |
|||
<Style Selector="DatePickerPresenter"> |
|||
<Setter Property="Width" Value="296" /> |
|||
<Setter Property="MinWidth" Value="296" /> |
|||
<Setter Property="MaxHeight" Value="398" /> |
|||
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
|||
<Setter Property="FontWeight" Value="Normal" /> |
|||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerFlyoutPresenterBackground}" /> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerFlyoutPresenterBorderBrush}" /> |
|||
<Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border Name="Background" Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
Padding="{DynamicResource DateTimeFlyoutBorderPadding}" |
|||
MaxHeight="398" CornerRadius="{DynamicResource OverlayCornerRadius}"> |
|||
<Grid Name="ContentRoot" RowDefinitions="*,Auto"> |
|||
<Grid Name="PickerContainer"> |
|||
<!--Column Definitions set in code, ignore here--> |
|||
<Panel Name="MonthHost"> |
|||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
|||
VerticalScrollBarVisibility="Hidden"> |
|||
<DateTimePickerPanel Name="MonthSelector" |
|||
PanelType="Month" |
|||
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" |
|||
ShouldLoop="True" /> |
|||
</ScrollViewer> |
|||
<RepeatButton Name="MonthUpButton" |
|||
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
|||
<RepeatButton Name="MonthDownButton" |
|||
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
|||
</Panel> |
|||
<Panel Name="DayHost"> |
|||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
|||
VerticalScrollBarVisibility="Hidden"> |
|||
<DateTimePickerPanel Name="DaySelector" |
|||
PanelType="Day" |
|||
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" |
|||
ShouldLoop="True" /> |
|||
</ScrollViewer> |
|||
<RepeatButton Name="DayUpButton" |
|||
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
|||
<RepeatButton Name="DayDownButton" |
|||
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
|||
</Panel> |
|||
<Panel Name="YearHost"> |
|||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
|||
VerticalScrollBarVisibility="Hidden"> |
|||
<DateTimePickerPanel Name="YearSelector" |
|||
PanelType="Year" |
|||
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" |
|||
ShouldLoop="False" /> |
|||
</ScrollViewer> |
|||
<RepeatButton Name="YearUpButton" |
|||
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
|||
<RepeatButton Name="YearDownButton" |
|||
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
|||
</Panel> |
|||
<Rectangle Name="HighlightRect" IsHitTestVisible="False" ZIndex="-1" |
|||
Fill="{DynamicResource DatePickerFlyoutPresenterHighlightFill}" |
|||
Grid.Column="0" Grid.ColumnSpan="5" VerticalAlignment="Center" |
|||
Height="{DynamicResource DatePickerFlyoutPresenterHighlightHeight}" /> |
|||
<Rectangle Name="FirstSpacer" |
|||
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="{DynamicResource DatePickerSpacerThemeWidth}" |
|||
Grid.Column="1" /> |
|||
<Rectangle Name="SecondSpacer" |
|||
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="{DynamicResource DatePickerSpacerThemeWidth}" |
|||
Grid.Column="3" /> |
|||
</Grid> |
|||
<Grid Grid.Row="1" Height="{DynamicResource DatePickerFlyoutPresenterAcceptDismissHostGridHeight}" |
|||
Name="AcceptDismissGrid" ColumnDefinitions="*,*"> |
|||
<Rectangle Height="{DynamicResource DatePickerSpacerThemeWidth}" VerticalAlignment="Top" |
|||
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}" |
|||
Grid.ColumnSpan="2"/> |
|||
<Button Name="AcceptButton" Grid.Column="0" Classes="DateTimeFlyoutButtonStyle" |
|||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
|||
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round" |
|||
StrokeThickness="0.75" Data="M0.5,8.5 5,13.5 15.5,3" /> |
|||
</Button> |
|||
<Button Name="DismissButton" Grid.Column="1" Classes="DateTimeFlyoutButtonStyle" |
|||
FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
|||
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round" |
|||
StrokeThickness="0.75" Data="M2,2 14,14 M2,14 14 2" /> |
|||
</Button> |
|||
</Grid> |
|||
</Grid> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePickerPresenter /template/ Panel RepeatButton"> |
|||
<Setter Property="IsVisible" Value="False" /> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePickerPresenter /template/ Panel:pointerover RepeatButton"> |
|||
<Setter Property="IsVisible" Value="True" /> |
|||
</Style> |
|||
|
|||
</Styles> |
|||
@ -1,10 +1,34 @@ |
|||
<Style xmlns="https://github.com/avaloniaui" Selector=":is(Control)"> |
|||
<Setter Property="FocusAdorner"> |
|||
<FocusAdornerTemplate> |
|||
<Rectangle Stroke="Black" |
|||
StrokeThickness="1" |
|||
StrokeDashArray="1,2" |
|||
Margin="1"/> |
|||
</FocusAdornerTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Styles.Resources> |
|||
<Thickness x:Key="SystemControlFocusVisualMargin">0</Thickness> |
|||
<Thickness x:Key="SystemControlFocusVisualPrimaryThickness">2</Thickness> |
|||
<Thickness x:Key="SystemControlFocusVisualSecondaryThickness">1</Thickness> |
|||
</Styles.Resources> |
|||
|
|||
<!-- HighVisibility FocusAdorner --> |
|||
<Style Selector=":is(Control)"> |
|||
<Setter Property="FocusAdorner"> |
|||
<FocusAdornerTemplate> |
|||
<Border BorderThickness="{StaticResource SystemControlFocusVisualPrimaryThickness}" |
|||
BorderBrush="{DynamicResource SystemControlFocusVisualPrimaryBrush}" |
|||
Margin="{StaticResource SystemControlFocusVisualMargin}"> |
|||
<Border BorderThickness="{StaticResource SystemControlFocusVisualSecondaryThickness}" |
|||
BorderBrush="{DynamicResource SystemControlFocusVisualSecondaryBrush}" /> |
|||
</Border> |
|||
</FocusAdornerTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<!-- DottedLine FocusAdorner --> |
|||
<Style Selector="Window.DottedLineFocusAdorner :is(Control)"> |
|||
<Setter Property="FocusAdorner"> |
|||
<FocusAdornerTemplate> |
|||
<Rectangle Stroke="{StaticResource SystemControlFocusVisualPrimaryBrush}" |
|||
StrokeThickness="1" |
|||
StrokeDashArray="1,2" |
|||
Margin="1" /> |
|||
</FocusAdornerTemplate> |
|||
</Setter> |
|||
</Style> |
|||
</Styles> |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue