committed by
GitHub
30 changed files with 4632 additions and 19 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,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,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,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,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> |
||||
@ -0,0 +1,219 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
|
||||
|
<Styles.Resources> |
||||
|
<x:Double x:Key="SplitViewOpenPaneThemeLength">320</x:Double> |
||||
|
<x:Double x:Key="SplitViewCompactPaneThemeLength">48</x:Double> |
||||
|
|
||||
|
<!-- Not used here (directly) since they're strings, but preserving for reference |
||||
|
<x:String x:Key="SplitViewPaneAnimationOpenDuration">00:00:00.2</x:String> |
||||
|
<x:String x:Key="SplitViewPaneAnimationOpenPreDuration">00:00:00.19999</x:String> |
||||
|
<x:String x:Key="SplitViewPaneAnimationCloseDuration">00:00:00.1</x:String>--> |
||||
|
</Styles.Resources> |
||||
|
|
||||
|
<Style Selector="SplitView"> |
||||
|
<Setter Property="OpenPaneLength" Value="{DynamicResource SplitViewOpenPaneThemeLength}" /> |
||||
|
<Setter Property="CompactPaneLength" Value="{DynamicResource SplitViewCompactPaneThemeLength}" /> |
||||
|
<Setter Property="PaneBackground" Value="{DynamicResource SystemControlPageBackgroundChromeLowBrush}" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Left --> |
||||
|
<Style Selector="SplitView:left"> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Grid Name="Container" Background="{TemplateBinding Background}"> |
||||
|
<Grid.ColumnDefinitions> |
||||
|
<!-- why is this throwing a binding error? --> |
||||
|
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PaneColumnGridLength}"/> |
||||
|
<ColumnDefinition Width="*"/> |
||||
|
</Grid.ColumnDefinitions> |
||||
|
|
||||
|
<Panel Name="PART_PaneRoot" Background="{TemplateBinding PaneBackground}" |
||||
|
ClipToBounds="True" |
||||
|
HorizontalAlignment="Left" |
||||
|
ZIndex="100"> |
||||
|
<Border Child="{TemplateBinding Pane}"/> |
||||
|
<Rectangle Name="HCPaneBorder" Fill="{DynamicResource SystemControlForegroundTransparentBrush}" Width="1" HorizontalAlignment="Right" /> |
||||
|
</Panel> |
||||
|
|
||||
|
<Panel Name="ContentRoot"> |
||||
|
<Border Child="{TemplateBinding Content}" /> |
||||
|
<Rectangle Name="LightDismissLayer"/> |
||||
|
</Panel> |
||||
|
|
||||
|
</Grid> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Overlay --> |
||||
|
<Style Selector="SplitView:overlay:left /template/ Panel#PART_PaneRoot"> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
||||
|
<!-- ColumnSpan should be 2 --> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
<Setter Property="Grid.Column" Value="0"/> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:overlay:left /template/ Panel#ContentRoot"> |
||||
|
<Setter Property="Grid.Column" Value="1"/> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="2"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- CompactInline --> |
||||
|
<Style Selector="SplitView:compactinline:left /template/ Panel#PART_PaneRoot"> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
<Setter Property="Grid.Column" Value="0"/> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:compactinline:left /template/ Panel#ContentRoot"> |
||||
|
<Setter Property="Grid.Column" Value="1"/> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- CompactOverlay --> |
||||
|
<Style Selector="SplitView:compactoverlay:left /template/ Panel#PART_PaneRoot"> |
||||
|
<!-- ColumnSpan should be 2 --> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
<Setter Property="Grid.Column" Value="0"/> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:compactoverlay:left /template/ Panel#ContentRoot"> |
||||
|
<Setter Property="Grid.Column" Value="1"/> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Inline --> |
||||
|
<Style Selector="SplitView:inline:left /template/ Panel#PART_PaneRoot"> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
<Setter Property="Grid.Column" Value="0"/> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:inline:left /template/ Panel#ContentRoot"> |
||||
|
<Setter Property="Grid.Column" Value="1"/> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Right --> |
||||
|
<Style Selector="SplitView:right"> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Grid Name="Container" Background="{TemplateBinding Background}"> |
||||
|
<Grid.ColumnDefinitions> |
||||
|
<ColumnDefinition Width="*"/> |
||||
|
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PaneColumnGridLength}"/> |
||||
|
</Grid.ColumnDefinitions> |
||||
|
|
||||
|
<Panel Name="PART_PaneRoot" Background="{TemplateBinding PaneBackground}" |
||||
|
ClipToBounds="True" |
||||
|
HorizontalAlignment="Right" |
||||
|
ZIndex="100"> |
||||
|
<Border Child="{TemplateBinding Pane}"/> |
||||
|
<Rectangle Name="HCPaneBorder" |
||||
|
Fill="{DynamicResource SystemControlForegroundTransparentBrush}" |
||||
|
Width="1" HorizontalAlignment="Left" /> |
||||
|
</Panel> |
||||
|
|
||||
|
<Panel Name="ContentRoot"> |
||||
|
<Border Child="{TemplateBinding Content}" /> |
||||
|
<Rectangle Name="LightDismissLayer"/> |
||||
|
</Panel> |
||||
|
|
||||
|
</Grid> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Overlay --> |
||||
|
<Style Selector="SplitView:overlay:right /template/ Panel#PART_PaneRoot"> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="2"/> |
||||
|
<Setter Property="Grid.Column" Value="1"/> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:overlay:right /template/ Panel#ContentRoot"> |
||||
|
<Setter Property="Grid.Column" Value="0"/> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="2"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- CompactInline --> |
||||
|
<Style Selector="SplitView:compactinline:right /template/ Panel#PART_PaneRoot"> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
<Setter Property="Grid.Column" Value="1"/> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:compactinline:right /template/ Panel#ContentRoot"> |
||||
|
<Setter Property="Grid.Column" Value="0"/> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- CompactOverlay --> |
||||
|
<Style Selector="SplitView:compactoverlay:right /template/ Panel#PART_PaneRoot"> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="2"/> |
||||
|
<Setter Property="Grid.Column" Value="1"/> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:compactoverlay:right /template/ Panel#ContentRoot"> |
||||
|
<Setter Property="Grid.Column" Value="0"/> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Inline --> |
||||
|
<Style Selector="SplitView:inline:right /template/ Panel#PART_PaneRoot"> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
<Setter Property="Grid.Column" Value="1"/> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:inline:right /template/ Panel#ContentRoot"> |
||||
|
<Setter Property="Grid.Column" Value="0"/> |
||||
|
<Setter Property="Grid.ColumnSpan" Value="1"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Open/Close Pane animation --> |
||||
|
<Style Selector="SplitView:open /template/ Panel#PART_PaneRoot"> |
||||
|
<Setter Property="Transitions"> |
||||
|
<Transitions> |
||||
|
<DoubleTransition Property="Width" Duration="00:00:00.2" Easing="0.1,0.9,0.2,1.0" /> |
||||
|
</Transitions> |
||||
|
</Setter> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=OpenPaneLength}" /> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:open /template/ Rectangle#LightDismissLayer"> |
||||
|
<Setter Property="Transitions"> |
||||
|
<Transitions> |
||||
|
<DoubleTransition Property="Opacity" Duration="00:00:00.2" Easing="0.1,0.9,0.2,1.0" /> |
||||
|
</Transitions> |
||||
|
</Setter> |
||||
|
<Setter Property="Opacity" Value="1.0"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="SplitView:closed /template/ Panel#PART_PaneRoot"> |
||||
|
<Setter Property="Transitions"> |
||||
|
<Transitions> |
||||
|
<DoubleTransition Property="Width" Duration="00:00:00.1" Easing="0.1,0.9,0.2,1.0" /> |
||||
|
</Transitions> |
||||
|
</Setter> |
||||
|
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:closed /template/ Rectangle#LightDismissLayer"> |
||||
|
<Setter Property="Transitions"> |
||||
|
<Transitions> |
||||
|
<DoubleTransition Property="Opacity" Duration="00:00:00.2" Easing="0.1,0.9,0.2,1.0" /> |
||||
|
</Transitions> |
||||
|
</Setter> |
||||
|
<Setter Property="Opacity" Value="0.0"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="SplitView /template/ Rectangle#LightDismissLayer"> |
||||
|
<Setter Property="IsVisible" Value="False"/> |
||||
|
<Setter Property="Fill" Value="Transparent" /> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:lightdismiss /template/ Rectangle#LightDismissLayer"> |
||||
|
<Setter Property="Fill" Value="{DynamicResource SplitViewLightDismissOverlayBackground}" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="SplitView:overlay:open /template/ Rectangle#LightDismissLayer"> |
||||
|
<Setter Property="IsVisible" Value="True"/> |
||||
|
</Style> |
||||
|
<Style Selector="SplitView:compactoverlay:open /template/ Rectangle#LightDismissLayer"> |
||||
|
<Setter Property="IsVisible" Value="True"/> |
||||
|
</Style> |
||||
|
|
||||
|
</Styles> |
||||
@ -0,0 +1,284 @@ |
|||||
|
<!-- |
||||
|
// (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> |
||||
|
<x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double> |
||||
|
<x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double> |
||||
|
<Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness> |
||||
|
<Thickness x:Key="TimePickerTopHeaderMargin">0,0,0,4</Thickness> |
||||
|
<x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double> |
||||
|
<x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> |
||||
|
<x:Double x:Key="TimePickerThemeMinWidth">242</x:Double> |
||||
|
<x:Double x:Key="TimePickerThemeMaxWidth">456</x:Double> |
||||
|
<Thickness x:Key="TimePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness> |
||||
|
<Thickness x:Key="TimePickerHostPadding">0,3,0,6</Thickness> |
||||
|
</Styles.Resources> |
||||
|
|
||||
|
<Style Selector="ListBoxItem.DateTimePickerItem.HourItem"> |
||||
|
<Setter Property="Padding" Value="{DynamicResource TimePickerFlyoutPresenterItemPadding}" /> |
||||
|
</Style> |
||||
|
<Style Selector="ListBoxItem.DateTimePickerItem.MinuteItem"> |
||||
|
<Setter Property="Padding" Value="{DynamicResource TimePickerFlyoutPresenterItemPadding}" /> |
||||
|
</Style> |
||||
|
<Style Selector="ListBoxItem.DateTimePickerItem.TimePeriodItem"> |
||||
|
<Setter Property="Padding" Value="{DynamicResource TimePickerFlyoutPresenterItemPadding}" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TimePicker"> |
||||
|
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
||||
|
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> |
||||
|
<Setter Property="Foreground" Value="{DynamicResource TimePickerButtonForeground}" /> |
||||
|
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackground}"/> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrush}"/> |
||||
|
<Setter Property="BorderThickness" Value="{DynamicResource TimePickerBorderThemeThickness}"/> |
||||
|
<Setter Property="HorizontalAlignment" Value="Left" /> |
||||
|
<Setter Property="VerticalAlignment" Value="Center" /> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*"> |
||||
|
<ContentPresenter x:Name="HeaderContentPresenter" |
||||
|
Grid.Row="0" |
||||
|
Content="{TemplateBinding Header}" |
||||
|
ContentTemplate="{TemplateBinding HeaderTemplate}" |
||||
|
Margin="{DynamicResource TimePickerTopHeaderMargin}" |
||||
|
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}" |
||||
|
TextBlock.Foreground="{DynamicResource TimePickerHeaderForeground}" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Top" /> |
||||
|
|
||||
|
<Button x:Name="FlyoutButton" |
||||
|
Grid.Row="1" |
||||
|
Foreground="{TemplateBinding Foreground}" |
||||
|
Background="{TemplateBinding Background}" |
||||
|
BorderBrush="{TemplateBinding BorderBrush}" |
||||
|
BorderThickness="{TemplateBinding BorderThickness}" |
||||
|
IsEnabled="{TemplateBinding IsEnabled}" |
||||
|
MinWidth="{StaticResource TimePickerThemeMinWidth}" |
||||
|
MaxWidth="{StaticResource TimePickerThemeMaxWidth}" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
HorizontalContentAlignment="Stretch" |
||||
|
VerticalAlignment="Top" |
||||
|
VerticalContentAlignment="Stretch"> |
||||
|
<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="FlyoutButtonContentGrid"> |
||||
|
<!--Ignore col defs here, set in code--> |
||||
|
<Border x:Name="FirstPickerHost" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
||||
|
<TextBlock x:Name="HourTextBlock" |
||||
|
HorizontalAlignment="Center" |
||||
|
Padding="{DynamicResource TimePickerHostPadding}" |
||||
|
FontFamily="{TemplateBinding FontFamily}" |
||||
|
FontWeight="{TemplateBinding FontWeight}" |
||||
|
FontSize="{TemplateBinding FontSize}" /> |
||||
|
</Border> |
||||
|
|
||||
|
<Rectangle Name="FirstColumnDivider" |
||||
|
Fill="{DynamicResource TimePickerSpacerFill}" |
||||
|
HorizontalAlignment="Center" |
||||
|
Width="{DynamicResource TimePickerSpacerThemeWidth}" |
||||
|
Grid.Column="1" /> |
||||
|
|
||||
|
<Border x:Name="SecondPickerHost" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
||||
|
<TextBlock x:Name="MinuteTextBlock" |
||||
|
HorizontalAlignment="Center" |
||||
|
Padding="{DynamicResource TimePickerHostPadding}" |
||||
|
FontFamily="{TemplateBinding FontFamily}" |
||||
|
FontWeight="{TemplateBinding FontWeight}" |
||||
|
FontSize="{TemplateBinding FontSize}"/> |
||||
|
</Border> |
||||
|
|
||||
|
<Rectangle Name="SecondColumnDivider" |
||||
|
Fill="{DynamicResource TimePickerSpacerFill}" |
||||
|
HorizontalAlignment="Center" |
||||
|
Width="{DynamicResource TimePickerSpacerThemeWidth}" |
||||
|
Grid.Column="3" /> |
||||
|
|
||||
|
<Border x:Name="ThirdPickerHost" Grid.Column="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
||||
|
<TextBlock x:Name="PeriodTextBlock" |
||||
|
HorizontalAlignment="Center" |
||||
|
Padding="{DynamicResource TimePickerHostPadding}" |
||||
|
FontFamily="{TemplateBinding FontFamily}" |
||||
|
FontWeight="{TemplateBinding FontWeight}" |
||||
|
FontSize="{TemplateBinding FontSize}" /> |
||||
|
</Border> |
||||
|
</Grid> |
||||
|
</Button> |
||||
|
|
||||
|
<Popup Name="Popup" WindowManagerAddShadowHint="False" |
||||
|
StaysOpen="False" PlacementTarget="{TemplateBinding}" |
||||
|
PlacementMode="Bottom"> |
||||
|
<TimePickerPresenter Name="PickerPresenter" /> |
||||
|
</Popup> |
||||
|
|
||||
|
</Grid> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TimePicker:disabled /template/ ContentPresenter#HeaderContentPresenter"> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerHeaderForegroundDisabled}"/> |
||||
|
</Style> |
||||
|
<Style Selector="TimePicker:disabled /template/ Rectangle"> |
||||
|
<Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TimePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPointerOver}"/> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPointerOver}"/> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundPointerOver}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TimePicker /template/ Button:pressed /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPressed}"/> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPressed}"/> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundPressed}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TimePicker /template/ Button:disabled /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundDisabled}"/> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushDisabled}"/> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundDisabled}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TimePicker:hasnotime /template/ Button#FlyoutButton TextBlock"> |
||||
|
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TimePickerPresenter"> |
||||
|
<Setter Property="Width" Value="242" /> |
||||
|
<Setter Property="MinWidth" Value="242" /> |
||||
|
<Setter Property="MaxHeight" Value="398" /> |
||||
|
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
||||
|
<Setter Property="FontWeight" Value="Normal" /> |
||||
|
<Setter Property="Background" Value="{DynamicResource TimePickerFlyoutPresenterBackground}" /> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerFlyoutPresenterBorderBrush}" /> |
||||
|
<Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" /> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Border Name="Background" |
||||
|
Background="{TemplateBinding Background}" |
||||
|
BorderBrush="{TemplateBinding BorderBrush}" |
||||
|
BorderThickness="{TemplateBinding BorderThickness}" |
||||
|
CornerRadius="{DynamicResource OverlayCornerRadius}" |
||||
|
Padding="{DynamicResource DateTimeFlyoutBorderPadding}" |
||||
|
MaxHeight="398"> |
||||
|
<Grid Name="ContentPanel" RowDefinitions="*,Auto"> |
||||
|
<Grid Name="PickerContainer"> |
||||
|
<!--Ignore col defs here, set in code--> |
||||
|
<Panel Name="HourHost" Grid.Column="0"> |
||||
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
||||
|
VerticalScrollBarVisibility="Hidden"> |
||||
|
<DateTimePickerPanel Name="HourSelector" |
||||
|
PanelType="Hour" |
||||
|
ShouldLoop="True" |
||||
|
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/> |
||||
|
</ScrollViewer> |
||||
|
<RepeatButton Name="HourUpButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
||||
|
<RepeatButton Name="HourDownButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
||||
|
|
||||
|
</Panel> |
||||
|
|
||||
|
<Panel Name="MinuteHost" Grid.Column="2"> |
||||
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
||||
|
VerticalScrollBarVisibility="Hidden"> |
||||
|
<DateTimePickerPanel Name="MinuteSelector" |
||||
|
PanelType="Minute" |
||||
|
ShouldLoop="True" |
||||
|
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/> |
||||
|
</ScrollViewer> |
||||
|
<RepeatButton Name="MinuteUpButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
||||
|
<RepeatButton Name="MinuteDownButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
||||
|
|
||||
|
</Panel> |
||||
|
|
||||
|
<Panel Name="PeriodHost" Grid.Column="4"> |
||||
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
||||
|
VerticalScrollBarVisibility="Hidden"> |
||||
|
<DateTimePickerPanel Name="PeriodSelector" |
||||
|
PanelType="TimePeriod" |
||||
|
ShouldLoop="False" |
||||
|
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/> |
||||
|
</ScrollViewer> |
||||
|
<RepeatButton Name="PeriodUpButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
||||
|
<RepeatButton Name="PeriodDownButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
||||
|
|
||||
|
</Panel> |
||||
|
|
||||
|
<Rectangle x:Name="HighlightRect" ZIndex="-1" |
||||
|
Fill="{DynamicResource TimePickerFlyoutPresenterHighlightFill}" |
||||
|
Grid.Column="0" |
||||
|
Grid.ColumnSpan="5" |
||||
|
VerticalAlignment="Center" |
||||
|
Height="{DynamicResource TimePickerFlyoutPresenterHighlightHeight}" /> |
||||
|
|
||||
|
<Rectangle Name="FirstSpacer" |
||||
|
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}" |
||||
|
HorizontalAlignment="Center" |
||||
|
Width="{DynamicResource TimePickerSpacerThemeWidth}" |
||||
|
Grid.Column="1" /> |
||||
|
|
||||
|
<Rectangle Name="SecondSpacer" |
||||
|
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}" |
||||
|
HorizontalAlignment="Center" |
||||
|
Width="{DynamicResource TimePickerSpacerThemeWidth}" |
||||
|
Grid.Column="3" /> |
||||
|
</Grid> |
||||
|
|
||||
|
<Grid Grid.Row="1" Height="{DynamicResource TimePickerFlyoutPresenterAcceptDismissHostGridHeight}" |
||||
|
Name="AcceptDismissHostGrid" ColumnDefinitions="*,*"> |
||||
|
<Rectangle Height="{DynamicResource TimePickerSpacerThemeWidth}" |
||||
|
VerticalAlignment="Top" |
||||
|
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}" |
||||
|
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="TimePickerPresenter /template/ Panel RepeatButton"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TimePickerPresenter /template/ Panel:pointerover RepeatButton"> |
||||
|
<Setter Property="IsVisible" Value="True" /> |
||||
|
</Style> |
||||
|
|
||||
|
</Styles> |
||||
@ -0,0 +1,253 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Avalonia.VisualTree; |
||||
|
using Moq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests |
||||
|
{ |
||||
|
public class DatePickerTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void SelectedDateChanged_Should_Fire_When_SelectedDate_Set() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(Services)) |
||||
|
{ |
||||
|
bool handled = false; |
||||
|
DatePicker datePicker = new DatePicker(); |
||||
|
datePicker.SelectedDateChanged += (s, e) => |
||||
|
{ |
||||
|
handled = true; |
||||
|
}; |
||||
|
DateTimeOffset value = new DateTimeOffset(2000, 10, 10, 0, 0, 0, TimeSpan.Zero); |
||||
|
datePicker.SelectedDate = value; |
||||
|
Threading.Dispatcher.UIThread.RunJobs(); |
||||
|
Assert.True(handled); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DayVisible_False_Should_Hide_Day() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(Services)) |
||||
|
{ |
||||
|
DatePicker datePicker = new DatePicker |
||||
|
{ |
||||
|
Template = CreateTemplate(), |
||||
|
DayVisible = false |
||||
|
}; |
||||
|
datePicker.ApplyTemplate(); |
||||
|
Threading.Dispatcher.UIThread.RunJobs(); |
||||
|
|
||||
|
var desc = datePicker.GetVisualDescendants(); |
||||
|
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
||||
|
TextBlock dayText = null; |
||||
|
Grid container = null; |
||||
|
|
||||
|
Assert.True(desc.ElementAt(1) is Button); |
||||
|
|
||||
|
container = (desc.ElementAt(1) as Button).Content as Grid; |
||||
|
Assert.True(container != null); |
||||
|
|
||||
|
for(int i = 0; i < container.Children.Count; i++) |
||||
|
{ |
||||
|
if(container.Children[i] is TextBlock tb && tb.Name == "DayText") |
||||
|
{ |
||||
|
dayText = tb; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Assert.True(dayText != null); |
||||
|
Assert.True(!dayText.IsVisible); |
||||
|
Assert.True(container.ColumnDefinitions.Count == 3); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MonthVisible_False_Should_Hide_Month() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(Services)) |
||||
|
{ |
||||
|
DatePicker datePicker = new DatePicker |
||||
|
{ |
||||
|
Template = CreateTemplate(), |
||||
|
MonthVisible = false |
||||
|
}; |
||||
|
datePicker.ApplyTemplate(); |
||||
|
Threading.Dispatcher.UIThread.RunJobs(); |
||||
|
|
||||
|
var desc = datePicker.GetVisualDescendants(); |
||||
|
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
||||
|
TextBlock monthText = null; |
||||
|
Grid container = null; |
||||
|
|
||||
|
Assert.True(desc.ElementAt(1) is Button); |
||||
|
|
||||
|
container = (desc.ElementAt(1) as Button).Content as Grid; |
||||
|
Assert.True(container != null); |
||||
|
|
||||
|
for (int i = 0; i < container.Children.Count; i++) |
||||
|
{ |
||||
|
if (container.Children[i] is TextBlock tb && tb.Name == "MonthText") |
||||
|
{ |
||||
|
monthText = tb; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Assert.True(monthText != null); |
||||
|
Assert.True(!monthText.IsVisible); |
||||
|
Assert.True(container.ColumnDefinitions.Count == 3); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void YearVisible_False_Should_Hide_Year() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(Services)) |
||||
|
{ |
||||
|
DatePicker datePicker = new DatePicker |
||||
|
{ |
||||
|
Template = CreateTemplate(), |
||||
|
YearVisible = false |
||||
|
}; |
||||
|
datePicker.ApplyTemplate(); |
||||
|
Threading.Dispatcher.UIThread.RunJobs(); |
||||
|
|
||||
|
var desc = datePicker.GetVisualDescendants(); |
||||
|
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
||||
|
TextBlock yearText = null; |
||||
|
Grid container = null; |
||||
|
|
||||
|
Assert.True(desc.ElementAt(1) is Button); |
||||
|
|
||||
|
container = (desc.ElementAt(1) as Button).Content as Grid; |
||||
|
Assert.True(container != null); |
||||
|
|
||||
|
for (int i = 0; i < container.Children.Count; i++) |
||||
|
{ |
||||
|
if (container.Children[i] is TextBlock tb && tb.Name == "YearText") |
||||
|
{ |
||||
|
yearText = tb; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Assert.True(yearText != null); |
||||
|
Assert.True(!yearText.IsVisible); |
||||
|
Assert.True(container.ColumnDefinitions.Count == 3); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SelectedDate_null_Should_Use_Placeholders() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(Services)) |
||||
|
{ |
||||
|
DatePicker datePicker = new DatePicker |
||||
|
{ |
||||
|
Template = CreateTemplate(), |
||||
|
YearVisible = false |
||||
|
}; |
||||
|
datePicker.ApplyTemplate(); |
||||
|
Threading.Dispatcher.UIThread.RunJobs(); |
||||
|
|
||||
|
var desc = datePicker.GetVisualDescendants(); |
||||
|
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
||||
|
TextBlock yearText = null; |
||||
|
TextBlock monthText = null; |
||||
|
TextBlock dayText = null; |
||||
|
Grid container = null; |
||||
|
|
||||
|
Assert.True(desc.ElementAt(1) is Button); |
||||
|
|
||||
|
container = (desc.ElementAt(1) as Button).Content as Grid; |
||||
|
Assert.True(container != null); |
||||
|
|
||||
|
for (int i = 0; i < container.Children.Count; i++) |
||||
|
{ |
||||
|
if (container.Children[i] is TextBlock tb && tb.Name == "YearText") |
||||
|
{ |
||||
|
yearText = tb; |
||||
|
} |
||||
|
else if (container.Children[i] is TextBlock tb1 && tb1.Name == "MonthText") |
||||
|
{ |
||||
|
monthText = tb1; |
||||
|
} |
||||
|
else if (container.Children[i] is TextBlock tb2 && tb2.Name == "DayText") |
||||
|
{ |
||||
|
dayText = tb2; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
DateTimeOffset value = new DateTimeOffset(2000, 10, 10, 0, 0, 0, TimeSpan.Zero); |
||||
|
datePicker.SelectedDate = value; |
||||
|
|
||||
|
Assert.False(dayText.Text == "day"); |
||||
|
Assert.False(monthText.Text == "month"); |
||||
|
Assert.False(yearText.Text == "year"); |
||||
|
Assert.False(datePicker.Classes.Contains(":hasnodate")); |
||||
|
|
||||
|
datePicker.SelectedDate = null; |
||||
|
|
||||
|
Assert.True(dayText.Text == "day"); |
||||
|
Assert.True(monthText.Text == "month"); |
||||
|
Assert.True(yearText.Text == "year"); |
||||
|
Assert.True(datePicker.Classes.Contains(":hasnodate")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static TestServices Services => TestServices.MockThreadingInterface.With( |
||||
|
standardCursorFactory: Mock.Of<IStandardCursorFactory>()); |
||||
|
|
||||
|
private IControlTemplate CreateTemplate() |
||||
|
{ |
||||
|
return new FuncControlTemplate((control, scope) => |
||||
|
{ |
||||
|
var layoutRoot = new Grid |
||||
|
{ |
||||
|
Name = "LayoutRoot" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
//Skip contentpresenter
|
||||
|
var flyoutButton = new Button |
||||
|
{ |
||||
|
Name = "FlyoutButton" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
var contentGrid = new Grid |
||||
|
{ |
||||
|
Name = "ButtonContentGrid" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
var dayText = new TextBlock |
||||
|
{ |
||||
|
Name = "DayText" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
var monthText = new TextBlock |
||||
|
{ |
||||
|
Name = "MonthText" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
var yearText = new TextBlock |
||||
|
{ |
||||
|
Name = "YearText" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
var firstSpacer = new Rectangle |
||||
|
{ |
||||
|
Name = "FirstSpacer" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
var secondSpacer = new Rectangle |
||||
|
{ |
||||
|
Name = "SecondSpacer" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
|
||||
|
contentGrid.Children.AddRange(new IControl[] { dayText, monthText, yearText, firstSpacer, secondSpacer }); |
||||
|
flyoutButton.Content = contentGrid; |
||||
|
layoutRoot.Children.Add(flyoutButton); |
||||
|
return layoutRoot; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests |
||||
|
{ |
||||
|
|
||||
|
public class SplitViewTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void SplitView_PaneOpening_Should_Fire_Before_PaneOpened() |
||||
|
{ |
||||
|
var splitView = new SplitView(); |
||||
|
|
||||
|
bool handledOpening = false; |
||||
|
splitView.PaneOpening += (x, e) => |
||||
|
{ |
||||
|
handledOpening = true; |
||||
|
}; |
||||
|
|
||||
|
splitView.PaneOpened += (x, e) => |
||||
|
{ |
||||
|
Assert.True(handledOpening); |
||||
|
}; |
||||
|
|
||||
|
splitView.IsPaneOpen = true; |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SplitView_PaneClosing_Should_Fire_Before_PaneClosed() |
||||
|
{ |
||||
|
var splitView = new SplitView(); |
||||
|
splitView.IsPaneOpen = true; |
||||
|
|
||||
|
bool handledClosing = false; |
||||
|
splitView.PaneClosing += (x, e) => |
||||
|
{ |
||||
|
handledClosing = true; |
||||
|
}; |
||||
|
|
||||
|
splitView.PaneClosed += (x, e) => |
||||
|
{ |
||||
|
Assert.True(handledClosing); |
||||
|
}; |
||||
|
|
||||
|
splitView.IsPaneOpen = false; |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SplitView_Cancel_Close_Should_Prevent_Pane_From_Closing() |
||||
|
{ |
||||
|
var splitView = new SplitView(); |
||||
|
splitView.IsPaneOpen = true; |
||||
|
|
||||
|
splitView.PaneClosing += (x, e) => |
||||
|
{ |
||||
|
e.Cancel = true; |
||||
|
}; |
||||
|
|
||||
|
splitView.IsPaneOpen = false; |
||||
|
|
||||
|
Assert.True(splitView.IsPaneOpen); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,172 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Avalonia.VisualTree; |
||||
|
using Moq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests |
||||
|
{ |
||||
|
public class TimePickerTests |
||||
|
{ |
||||
|
[Fact(Skip = "FIX ME ASAP")] |
||||
|
public void SelectedTimeChanged_Should_Fire_When_SelectedTime_Set() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(Services)) |
||||
|
{ |
||||
|
bool handled = false; |
||||
|
TimePicker timePicker = new TimePicker(); |
||||
|
timePicker.SelectedTimeChanged += (s, e) => |
||||
|
{ |
||||
|
handled = true; |
||||
|
}; |
||||
|
TimeSpan value = TimeSpan.FromHours(10); |
||||
|
timePicker.SelectedTime = value; |
||||
|
Threading.Dispatcher.UIThread.RunJobs(); |
||||
|
Assert.True(handled); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Using_24HourClock_Should_Hide_Period() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(Services)) |
||||
|
{ |
||||
|
TimePicker timePicker = new TimePicker() |
||||
|
{ |
||||
|
ClockIdentifier = "12HourClock", |
||||
|
Template = CreateTemplate() |
||||
|
}; |
||||
|
timePicker.ApplyTemplate(); |
||||
|
|
||||
|
var desc = timePicker.GetVisualDescendants(); |
||||
|
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
||||
|
Grid container = null; |
||||
|
|
||||
|
Assert.True(desc.ElementAt(1) is Button); |
||||
|
|
||||
|
container = (desc.ElementAt(1) as Button).Content as Grid; |
||||
|
Assert.True(container != null); |
||||
|
|
||||
|
var periodTextHost = container.Children[4] as Border; |
||||
|
Assert.True(periodTextHost != null); |
||||
|
Assert.True(periodTextHost.IsVisible); |
||||
|
|
||||
|
timePicker.ClockIdentifier = "24HourClock"; |
||||
|
Assert.False(periodTextHost.IsVisible); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SelectedTime_null_Should_Use_Placeholders() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(Services)) |
||||
|
{ |
||||
|
TimePicker timePicker = new TimePicker() |
||||
|
{ |
||||
|
Template = CreateTemplate() |
||||
|
}; |
||||
|
timePicker.ApplyTemplate(); |
||||
|
|
||||
|
var desc = timePicker.GetVisualDescendants(); |
||||
|
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
||||
|
Grid container = null; |
||||
|
|
||||
|
Assert.True(desc.ElementAt(1) is Button); |
||||
|
|
||||
|
container = (desc.ElementAt(1) as Button).Content as Grid; |
||||
|
Assert.True(container != null); |
||||
|
|
||||
|
var hourTextHost = container.Children[0] as Border; |
||||
|
Assert.True(hourTextHost != null); |
||||
|
var hourText = hourTextHost.Child as TextBlock; |
||||
|
var minuteTextHost = container.Children[2] as Border; |
||||
|
Assert.True(minuteTextHost != null); |
||||
|
var minuteText = minuteTextHost.Child as TextBlock; |
||||
|
|
||||
|
TimeSpan ts = TimeSpan.FromHours(10); |
||||
|
timePicker.SelectedTime = ts; |
||||
|
Assert.False(hourText.Text == "hour"); |
||||
|
Assert.False(minuteText.Text == "minute"); |
||||
|
|
||||
|
timePicker.SelectedTime = null; |
||||
|
Assert.True(hourText.Text == "hour"); |
||||
|
Assert.True(minuteText.Text == "minute"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static TestServices Services => TestServices.MockThreadingInterface.With( |
||||
|
standardCursorFactory: Mock.Of<IStandardCursorFactory>()); |
||||
|
|
||||
|
private IControlTemplate CreateTemplate() |
||||
|
{ |
||||
|
return new FuncControlTemplate((control, scope) => |
||||
|
{ |
||||
|
var layoutRoot = new Grid |
||||
|
{ |
||||
|
Name = "LayoutRoot" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
//Skip contentpresenter
|
||||
|
var flyoutButton = new Button |
||||
|
{ |
||||
|
Name = "FlyoutButton" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
var contentGrid = new Grid |
||||
|
{ |
||||
|
Name = "FlyoutButtonContentGrid" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
|
||||
|
var firstPickerHost = new Border |
||||
|
{ |
||||
|
Name = "FirstPickerHost", |
||||
|
Child = new TextBlock |
||||
|
{ |
||||
|
Name = "HourTextBlock" |
||||
|
}.RegisterInNameScope(scope) |
||||
|
}.RegisterInNameScope(scope); |
||||
|
Grid.SetColumn(firstPickerHost, 0); |
||||
|
|
||||
|
var secondPickerHost = new Border |
||||
|
{ |
||||
|
Name = "SecondPickerHost", |
||||
|
Child = new TextBlock |
||||
|
{ |
||||
|
Name = "MinuteTextBlock" |
||||
|
}.RegisterInNameScope(scope) |
||||
|
}.RegisterInNameScope(scope); |
||||
|
Grid.SetColumn(secondPickerHost, 2); |
||||
|
|
||||
|
var thirdPickerHost = new Border |
||||
|
{ |
||||
|
Name = "ThirdPickerHost", |
||||
|
Child = new TextBlock |
||||
|
{ |
||||
|
Name = "PeriodTextBlock" |
||||
|
}.RegisterInNameScope(scope) |
||||
|
}.RegisterInNameScope(scope); |
||||
|
Grid.SetColumn(thirdPickerHost, 4); |
||||
|
|
||||
|
var firstSpacer = new Rectangle |
||||
|
{ |
||||
|
Name = "FirstColumnDivider" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
Grid.SetColumn(firstSpacer, 1); |
||||
|
|
||||
|
var secondSpacer = new Rectangle |
||||
|
{ |
||||
|
Name = "SecondColumnDivider" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
Grid.SetColumn(secondSpacer, 3); |
||||
|
|
||||
|
contentGrid.Children.AddRange(new IControl[] { firstPickerHost, firstSpacer, secondPickerHost, secondSpacer, thirdPickerHost }); |
||||
|
flyoutButton.Content = contentGrid; |
||||
|
layoutRoot.Children.Add(flyoutButton); |
||||
|
return layoutRoot; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue