committed by
GitHub
105 changed files with 6690 additions and 1117 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,19 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
|
x:Class="ControlCatalog.Pages.WindowCustomizationsPage"> |
||||
|
<StackPanel Spacing="10" Margin="25"> |
||||
|
<CheckBox Content="Extend Client Area to Decorations" IsChecked="{Binding ExtendClientAreaEnabled}" /> |
||||
|
<CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" /> |
||||
|
<CheckBox Content="Prefer System Chrome" IsChecked="{Binding PreferSystemChromeEnabled}" /> |
||||
|
<Slider Minimum="-1" Maximum="200" Value="{Binding TitleBarHeight}" /> |
||||
|
<ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}"> |
||||
|
<ComboBoxItem>None</ComboBoxItem> |
||||
|
<ComboBoxItem>Transparent</ComboBoxItem> |
||||
|
<ComboBoxItem>Blur</ComboBoxItem> |
||||
|
<ComboBoxItem>AcrylicBlur</ComboBoxItem> |
||||
|
</ComboBox> |
||||
|
</StackPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class WindowCustomizationsPage : UserControl |
||||
|
{ |
||||
|
public WindowCustomizationsPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
#pragma warning disable MA0048 // File name must match type name
|
||||
|
#define INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
|
||||
|
|
||||
|
// https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
|
||||
|
|
||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
// See the LICENSE file in the project root for more information.
|
||||
|
|
||||
|
namespace System.Diagnostics.CodeAnalysis |
||||
|
{ |
||||
|
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] |
||||
|
#if INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
internal |
||||
|
#else
|
||||
|
public |
||||
|
#endif
|
||||
|
sealed class AllowNullAttribute : Attribute |
||||
|
{ } |
||||
|
|
||||
|
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
|
||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] |
||||
|
#if INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
internal |
||||
|
#else
|
||||
|
public |
||||
|
#endif
|
||||
|
sealed class DisallowNullAttribute : Attribute |
||||
|
{ } |
||||
|
|
||||
|
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
|
||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] |
||||
|
#if INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
internal |
||||
|
#else
|
||||
|
public |
||||
|
#endif
|
||||
|
sealed class MaybeNullAttribute : Attribute |
||||
|
{ } |
||||
|
|
||||
|
/// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
|
||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] |
||||
|
#if INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
internal |
||||
|
#else
|
||||
|
public |
||||
|
#endif
|
||||
|
sealed class NotNullAttribute : Attribute |
||||
|
{ } |
||||
|
|
||||
|
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
|
||||
|
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
||||
|
#if INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
internal |
||||
|
#else
|
||||
|
public |
||||
|
#endif
|
||||
|
sealed class MaybeNullWhenAttribute : Attribute |
||||
|
{ |
||||
|
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||
|
/// <param name="returnValue">
|
||||
|
/// The return value condition. If the method returns this value, the associated parameter may be null.
|
||||
|
/// </param>
|
||||
|
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; |
||||
|
|
||||
|
/// <summary>Gets the return value condition.</summary>
|
||||
|
public bool ReturnValue { get; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
|
||||
|
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
||||
|
#if INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
internal |
||||
|
#else
|
||||
|
public |
||||
|
#endif
|
||||
|
sealed class NotNullWhenAttribute : Attribute |
||||
|
{ |
||||
|
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||
|
/// <param name="returnValue">
|
||||
|
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||
|
/// </param>
|
||||
|
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; |
||||
|
|
||||
|
/// <summary>Gets the return value condition.</summary>
|
||||
|
public bool ReturnValue { get; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
|
||||
|
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] |
||||
|
#if INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
internal |
||||
|
#else
|
||||
|
public |
||||
|
#endif
|
||||
|
sealed class NotNullIfNotNullAttribute : Attribute |
||||
|
{ |
||||
|
/// <summary>Initializes the attribute with the associated parameter name.</summary>
|
||||
|
/// <param name="parameterName">
|
||||
|
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
|
||||
|
/// </param>
|
||||
|
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; |
||||
|
|
||||
|
/// <summary>Gets the associated parameter name.</summary>
|
||||
|
public string ParameterName { get; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
||||
|
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
||||
|
#if INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
internal |
||||
|
#else
|
||||
|
public |
||||
|
#endif
|
||||
|
sealed class DoesNotReturnAttribute : Attribute |
||||
|
{ } |
||||
|
|
||||
|
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
|
||||
|
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
||||
|
#if INTERNAL_NULLABLE_ATTRIBUTES
|
||||
|
internal |
||||
|
#else
|
||||
|
public |
||||
|
#endif
|
||||
|
sealed class DoesNotReturnIfAttribute : Attribute |
||||
|
{ |
||||
|
/// <summary>Initializes the attribute with the specified parameter value.</summary>
|
||||
|
/// <param name="parameterValue">
|
||||
|
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
|
||||
|
/// the associated parameter matches this value.
|
||||
|
/// </param>
|
||||
|
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; |
||||
|
|
||||
|
/// <summary>Gets the condition parameter value.</summary>
|
||||
|
public bool ParameterValue { get; } |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -0,0 +1,45 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace Avalonia.Utilities |
||||
|
{ |
||||
|
#if !BUILDTASK
|
||||
|
public |
||||
|
#endif
|
||||
|
static class StyleClassParser |
||||
|
{ |
||||
|
public static ReadOnlySpan<char> ParseStyleClass(this ref CharacterReader r) |
||||
|
{ |
||||
|
if (IsValidIdentifierStart(r.Peek)) |
||||
|
{ |
||||
|
return r.TakeWhile(c => IsValidIdentifierChar(c)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return ReadOnlySpan<char>.Empty; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool IsValidIdentifierStart(char c) |
||||
|
{ |
||||
|
return char.IsLetter(c) || c == '_'; |
||||
|
} |
||||
|
|
||||
|
private static bool IsValidIdentifierChar(char c) |
||||
|
{ |
||||
|
if (IsValidIdentifierStart(c) || c == '-') |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var cat = CharUnicodeInfo.GetUnicodeCategory(c); |
||||
|
return cat == UnicodeCategory.NonSpacingMark || |
||||
|
cat == UnicodeCategory.SpacingCombiningMark || |
||||
|
cat == UnicodeCategory.ConnectorPunctuation || |
||||
|
cat == UnicodeCategory.Format || |
||||
|
cat == UnicodeCategory.DecimalDigitNumber; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
using System; |
||||
|
using System.Reactive.Disposables; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
|
namespace Avalonia.Controls.Chrome |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
|
||||
|
/// </summary>
|
||||
|
public class CaptionButtons : TemplatedControl |
||||
|
{ |
||||
|
private CompositeDisposable? _disposables; |
||||
|
private Window? _hostWindow; |
||||
|
|
||||
|
public void Attach(Window hostWindow) |
||||
|
{ |
||||
|
if (_disposables == null) |
||||
|
{ |
||||
|
_hostWindow = hostWindow; |
||||
|
|
||||
|
_disposables = new CompositeDisposable |
||||
|
{ |
||||
|
_hostWindow.GetObservable(Window.WindowStateProperty) |
||||
|
.Subscribe(x => |
||||
|
{ |
||||
|
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
||||
|
PseudoClasses.Set(":normal", x == WindowState.Normal); |
||||
|
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
||||
|
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
||||
|
}) |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Detach() |
||||
|
{ |
||||
|
if (_disposables != null) |
||||
|
{ |
||||
|
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
||||
|
|
||||
|
layer?.Children.Remove(this); |
||||
|
|
||||
|
_disposables.Dispose(); |
||||
|
_disposables = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
base.OnApplyTemplate(e); |
||||
|
|
||||
|
var closeButton = e.NameScope.Get<Panel>("PART_CloseButton"); |
||||
|
var restoreButton = e.NameScope.Get<Panel>("PART_RestoreButton"); |
||||
|
var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton"); |
||||
|
var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton"); |
||||
|
|
||||
|
closeButton.PointerReleased += (sender, e) => _hostWindow?.Close(); |
||||
|
|
||||
|
restoreButton.PointerReleased += (sender, e) => |
||||
|
{ |
||||
|
if (_hostWindow != null) |
||||
|
{ |
||||
|
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
minimiseButton.PointerReleased += (sender, e) => |
||||
|
{ |
||||
|
if (_hostWindow != null) |
||||
|
{ |
||||
|
_hostWindow.WindowState = WindowState.Minimized; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
fullScreenButton.PointerReleased += (sender, e) => |
||||
|
{ |
||||
|
if (_hostWindow != null) |
||||
|
{ |
||||
|
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
using System; |
||||
|
using System.Reactive.Disposables; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
|
namespace Avalonia.Controls.Chrome |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Draws a titlebar when managed client decorations are enabled.
|
||||
|
/// </summary>
|
||||
|
public class TitleBar : TemplatedControl |
||||
|
{ |
||||
|
private CompositeDisposable? _disposables; |
||||
|
private readonly Window? _hostWindow; |
||||
|
private CaptionButtons? _captionButtons; |
||||
|
|
||||
|
public TitleBar(Window hostWindow) |
||||
|
{ |
||||
|
_hostWindow = hostWindow; |
||||
|
} |
||||
|
|
||||
|
public TitleBar() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Attach() |
||||
|
{ |
||||
|
if (_disposables == null) |
||||
|
{ |
||||
|
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
||||
|
|
||||
|
layer?.Children.Add(this); |
||||
|
|
||||
|
if (_hostWindow != null) |
||||
|
{ |
||||
|
_disposables = new CompositeDisposable |
||||
|
{ |
||||
|
_hostWindow.GetObservable(Window.WindowDecorationMarginProperty) |
||||
|
.Subscribe(x => UpdateSize()), |
||||
|
|
||||
|
_hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) |
||||
|
.Subscribe(x => UpdateSize()), |
||||
|
|
||||
|
_hostWindow.GetObservable(Window.OffScreenMarginProperty) |
||||
|
.Subscribe(x => UpdateSize()), |
||||
|
|
||||
|
_hostWindow.GetObservable(Window.WindowStateProperty) |
||||
|
.Subscribe(x => |
||||
|
{ |
||||
|
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
||||
|
PseudoClasses.Set(":normal", x == WindowState.Normal); |
||||
|
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
||||
|
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
||||
|
}) |
||||
|
}; |
||||
|
|
||||
|
_captionButtons?.Attach(_hostWindow); |
||||
|
} |
||||
|
|
||||
|
UpdateSize(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void UpdateSize() |
||||
|
{ |
||||
|
if (_hostWindow != null) |
||||
|
{ |
||||
|
Margin = new Thickness( |
||||
|
_hostWindow.OffScreenMargin.Left, |
||||
|
_hostWindow.OffScreenMargin.Top, |
||||
|
_hostWindow.OffScreenMargin.Right, |
||||
|
_hostWindow.OffScreenMargin.Bottom); |
||||
|
|
||||
|
if (_hostWindow.WindowState != WindowState.FullScreen) |
||||
|
{ |
||||
|
Height = _hostWindow.WindowDecorationMargin.Top; |
||||
|
|
||||
|
if (_captionButtons != null) |
||||
|
{ |
||||
|
_captionButtons.Height = Height; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Detach() |
||||
|
{ |
||||
|
if (_disposables != null) |
||||
|
{ |
||||
|
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
||||
|
|
||||
|
layer?.Children.Remove(this); |
||||
|
|
||||
|
_disposables.Dispose(); |
||||
|
_disposables = null; |
||||
|
|
||||
|
_captionButtons?.Detach(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
base.OnApplyTemplate(e); |
||||
|
|
||||
|
_captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons"); |
||||
|
|
||||
|
if (_hostWindow != null) |
||||
|
{ |
||||
|
_captionButtons.Attach(_hostWindow); |
||||
|
} |
||||
|
|
||||
|
UpdateSize(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,412 @@ |
|||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Interactivity; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A control to allow the user to select a date
|
||||
|
/// </summary>
|
||||
|
public class DatePicker : TemplatedControl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Define the <see cref="DayFormat"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePicker, string> DayFormatProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(DayFormat), |
||||
|
x => x.DayFormat, (x, v) => x.DayFormat = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="DayVisible"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePicker, bool> DayVisibleProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible), |
||||
|
x => x.DayVisible, (x, v) => x.DayVisible = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Header"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<object> HeaderProperty = |
||||
|
AvaloniaProperty.Register<DatePicker, object>(nameof(Header)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="HeaderTemplate"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty = |
||||
|
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MaxYear"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePicker, DateTimeOffset> MaxYearProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MaxYear), |
||||
|
x => x.MaxYear, (x, v) => x.MaxYear = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MinYear"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePicker, DateTimeOffset> MinYearProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MinYear), |
||||
|
x => x.MinYear, (x, v) => x.MinYear = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MonthFormat"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePicker, string> MonthFormatProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(MonthFormat), |
||||
|
x => x.MonthFormat, (x, v) => x.MonthFormat = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MonthVisible"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePicker, bool> MonthVisibleProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(MonthVisible), |
||||
|
x => x.MonthVisible, (x, v) => x.MonthVisible = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defiens the <see cref="YearFormat"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePicker, string> YearFormatProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(YearFormat), |
||||
|
x => x.YearFormat, (x, v) => x.YearFormat = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="YearVisible"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePicker, bool> YearVisibleProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(YearVisible), |
||||
|
x => x.YearVisible, (x, v) => x.YearVisible = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="SelectedDate"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePicker, DateTimeOffset?> SelectedDateProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate), |
||||
|
x => x.SelectedDate, (x, v) => x.SelectedDate = v); |
||||
|
|
||||
|
//Template Items
|
||||
|
private Button _flyoutButton; |
||||
|
private TextBlock _dayText; |
||||
|
private TextBlock _monthText; |
||||
|
private TextBlock _yearText; |
||||
|
private Grid _container; |
||||
|
private Rectangle _spacer1; |
||||
|
private Rectangle _spacer2; |
||||
|
private Popup _popup; |
||||
|
private DatePickerPresenter _presenter; |
||||
|
|
||||
|
private bool _areControlsAvailable; |
||||
|
|
||||
|
private string _dayFormat = "%d"; |
||||
|
private bool _dayVisible = true; |
||||
|
private DateTimeOffset _maxYear; |
||||
|
private DateTimeOffset _minYear; |
||||
|
private string _monthFormat = "MMMM"; |
||||
|
private bool _monthVisible = true; |
||||
|
private string _yearFormat = "yyyy"; |
||||
|
private bool _yearVisible = true; |
||||
|
private DateTimeOffset? _selectedDate; |
||||
|
|
||||
|
public DatePicker() |
||||
|
{ |
||||
|
PseudoClasses.Set(":hasnodate", true); |
||||
|
var now = DateTimeOffset.Now; |
||||
|
_minYear = new DateTimeOffset(now.Date.Year - 100, 1, 1, 0, 0, 0, now.Offset); |
||||
|
_maxYear = new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset); |
||||
|
} |
||||
|
|
||||
|
public string DayFormat |
||||
|
{ |
||||
|
get => _dayFormat; |
||||
|
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets whether the day is visible
|
||||
|
/// </summary>
|
||||
|
public bool DayVisible |
||||
|
{ |
||||
|
get => _dayVisible; |
||||
|
set |
||||
|
{ |
||||
|
SetAndRaise(DayVisibleProperty, ref _dayVisible, value); |
||||
|
SetGrid(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the DatePicker header
|
||||
|
/// </summary>
|
||||
|
public object Header |
||||
|
{ |
||||
|
get => GetValue(HeaderProperty); |
||||
|
set => SetValue(HeaderProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the header template
|
||||
|
/// </summary>
|
||||
|
public IDataTemplate HeaderTemplate |
||||
|
{ |
||||
|
get => GetValue(HeaderTemplateProperty); |
||||
|
set => SetValue(HeaderTemplateProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the maximum year for the picker
|
||||
|
/// </summary>
|
||||
|
public DateTimeOffset MaxYear |
||||
|
{ |
||||
|
get => _maxYear; |
||||
|
set |
||||
|
{ |
||||
|
if (value < MinYear) |
||||
|
throw new InvalidOperationException("MaxDate cannot be less than MinDate"); |
||||
|
SetAndRaise(MaxYearProperty, ref _maxYear, value); |
||||
|
|
||||
|
if (SelectedDate.HasValue && SelectedDate.Value > value) |
||||
|
SelectedDate = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the minimum year for the picker
|
||||
|
/// </summary>
|
||||
|
public DateTimeOffset MinYear |
||||
|
{ |
||||
|
get => _minYear; |
||||
|
set |
||||
|
{ |
||||
|
if (value > MaxYear) |
||||
|
throw new InvalidOperationException("MinDate cannot be greater than MaxDate"); |
||||
|
SetAndRaise(MinYearProperty, ref _minYear, value); |
||||
|
|
||||
|
if (SelectedDate.HasValue && SelectedDate.Value < value) |
||||
|
SelectedDate = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the month format
|
||||
|
/// </summary>
|
||||
|
public string MonthFormat |
||||
|
{ |
||||
|
get => _monthFormat; |
||||
|
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets whether the month is visible
|
||||
|
/// </summary>
|
||||
|
public bool MonthVisible |
||||
|
{ |
||||
|
get => _monthVisible; |
||||
|
set |
||||
|
{ |
||||
|
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value); |
||||
|
SetGrid(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the year format
|
||||
|
/// </summary>
|
||||
|
public string YearFormat |
||||
|
{ |
||||
|
get => _yearFormat; |
||||
|
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets whether the year is visible
|
||||
|
/// </summary>
|
||||
|
public bool YearVisible |
||||
|
{ |
||||
|
get => _yearVisible; |
||||
|
set |
||||
|
{ |
||||
|
SetAndRaise(YearVisibleProperty, ref _yearVisible, value); |
||||
|
SetGrid(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the Selected Date for the picker, can be null
|
||||
|
/// </summary>
|
||||
|
public DateTimeOffset? SelectedDate |
||||
|
{ |
||||
|
get => _selectedDate; |
||||
|
set |
||||
|
{ |
||||
|
var old = _selectedDate; |
||||
|
SetAndRaise(SelectedDateProperty, ref _selectedDate, value); |
||||
|
SetSelectedDateText(); |
||||
|
OnSelectedDateChanged(this, new DatePickerSelectedValueChangedEventArgs(old, value)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Raised when the <see cref="SelectedDate"/> changes
|
||||
|
/// </summary>
|
||||
|
public event EventHandler<DatePickerSelectedValueChangedEventArgs> SelectedDateChanged; |
||||
|
|
||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
_areControlsAvailable = false; |
||||
|
if (_flyoutButton != null) |
||||
|
_flyoutButton.Click -= OnFlyoutButtonClicked; |
||||
|
if (_presenter != null) |
||||
|
{ |
||||
|
_presenter.Confirmed -= OnConfirmed; |
||||
|
_presenter.Dismissed -= OnDismissPicker; |
||||
|
} |
||||
|
|
||||
|
base.OnApplyTemplate(e); |
||||
|
_flyoutButton = e.NameScope.Find<Button>("FlyoutButton"); |
||||
|
_dayText = e.NameScope.Find<TextBlock>("DayText"); |
||||
|
_monthText = e.NameScope.Find<TextBlock>("MonthText"); |
||||
|
_yearText = e.NameScope.Find<TextBlock>("YearText"); |
||||
|
_container = e.NameScope.Find<Grid>("ButtonContentGrid"); |
||||
|
_spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer"); |
||||
|
_spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer"); |
||||
|
_popup = e.NameScope.Find<Popup>("Popup"); |
||||
|
_presenter = e.NameScope.Find<DatePickerPresenter>("PickerPresenter"); |
||||
|
|
||||
|
_areControlsAvailable = true; |
||||
|
|
||||
|
SetGrid(); |
||||
|
SetSelectedDateText(); |
||||
|
|
||||
|
if (_flyoutButton != null) |
||||
|
_flyoutButton.Click += OnFlyoutButtonClicked; |
||||
|
|
||||
|
if (_presenter != null) |
||||
|
{ |
||||
|
_presenter.Confirmed += OnConfirmed; |
||||
|
_presenter.Dismissed += OnDismissPicker; |
||||
|
|
||||
|
_presenter[!DatePickerPresenter.MaxYearProperty] = this[!MaxYearProperty]; |
||||
|
_presenter[!DatePickerPresenter.MinYearProperty] = this[!MinYearProperty]; |
||||
|
|
||||
|
_presenter[!DatePickerPresenter.MonthVisibleProperty] = this[!MonthVisibleProperty]; |
||||
|
_presenter[!DatePickerPresenter.MonthFormatProperty] = this[!MonthFormatProperty]; |
||||
|
|
||||
|
_presenter[!DatePickerPresenter.DayVisibleProperty] = this[!DayVisibleProperty]; |
||||
|
_presenter[!DatePickerPresenter.DayFormatProperty] = this[!DayFormatProperty]; |
||||
|
|
||||
|
_presenter[!DatePickerPresenter.YearVisibleProperty] = this[!YearVisibleProperty]; |
||||
|
_presenter[!DatePickerPresenter.YearFormatProperty] = this[!YearFormatProperty]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnDismissPicker(object sender, EventArgs e) |
||||
|
{ |
||||
|
_popup.Close(); |
||||
|
Focus(); |
||||
|
} |
||||
|
|
||||
|
private void OnConfirmed(object sender, EventArgs e) |
||||
|
{ |
||||
|
_popup.Close(); |
||||
|
SelectedDate = _presenter.Date; |
||||
|
} |
||||
|
|
||||
|
private void SetGrid() |
||||
|
{ |
||||
|
if (_container == null) |
||||
|
return; |
||||
|
|
||||
|
var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; |
||||
|
var columns = new List<(TextBlock, int)> |
||||
|
{ |
||||
|
(_monthText, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1), |
||||
|
(_yearText, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1), |
||||
|
(_dayText, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1), |
||||
|
}; |
||||
|
|
||||
|
columns.Sort((x, y) => x.Item2 - y.Item2); |
||||
|
_container.ColumnDefinitions.Clear(); |
||||
|
|
||||
|
var columnIndex = 0; |
||||
|
|
||||
|
foreach (var column in columns) |
||||
|
{ |
||||
|
if (column.Item1 is null) |
||||
|
continue; |
||||
|
|
||||
|
column.Item1.IsVisible = column.Item2 != -1; |
||||
|
|
||||
|
if (column.Item2 != -1) |
||||
|
{ |
||||
|
if (columnIndex > 0) |
||||
|
{ |
||||
|
_container.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto)); |
||||
|
} |
||||
|
|
||||
|
_container.ColumnDefinitions.Add( |
||||
|
new ColumnDefinition(column.Item1 == _monthText ? 138 : 78, GridUnitType.Star)); |
||||
|
|
||||
|
if (column.Item1.Parent is null) |
||||
|
{ |
||||
|
_container.Children.Add(column.Item1); |
||||
|
} |
||||
|
|
||||
|
Grid.SetColumn(column.Item1, (columnIndex++ * 2)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Grid.SetColumn(_spacer1, 1); |
||||
|
Grid.SetColumn(_spacer2, 3); |
||||
|
_spacer1.IsVisible = columnIndex > 1; |
||||
|
_spacer2.IsVisible = columnIndex > 2; |
||||
|
} |
||||
|
|
||||
|
private void SetSelectedDateText() |
||||
|
{ |
||||
|
if (!_areControlsAvailable) |
||||
|
return; |
||||
|
|
||||
|
if (SelectedDate.HasValue) |
||||
|
{ |
||||
|
PseudoClasses.Set(":hasnodate", false); |
||||
|
var selDate = SelectedDate.Value; |
||||
|
_monthText.Text = selDate.ToString(MonthFormat); |
||||
|
_yearText.Text = selDate.ToString(YearFormat); |
||||
|
_dayText.Text = selDate.ToString(DayFormat); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PseudoClasses.Set(":hasnodate", true); |
||||
|
_monthText.Text = "month"; |
||||
|
_yearText.Text = "year"; |
||||
|
_dayText.Text = "day"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnFlyoutButtonClicked(object sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
if (_presenter == null) |
||||
|
throw new InvalidOperationException("No DatePickerPresenter found"); |
||||
|
|
||||
|
_presenter.Date = SelectedDate ?? DateTimeOffset.Now; |
||||
|
|
||||
|
_popup.IsOpen = true; |
||||
|
|
||||
|
var deltaY = _presenter.GetOffsetForPopup(); |
||||
|
|
||||
|
//The extra 5 px I think is related to default popup placement behavior
|
||||
|
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), |
||||
|
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, |
||||
|
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); |
||||
|
} |
||||
|
|
||||
|
protected virtual void OnSelectedDateChanged(object sender, DatePickerSelectedValueChangedEventArgs e) |
||||
|
{ |
||||
|
SelectedDateChanged?.Invoke(sender, e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,531 @@ |
|||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Interactivity; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the presenter used for selecting a date for a
|
||||
|
/// <see cref="DatePicker"/>
|
||||
|
/// </summary>
|
||||
|
public class DatePickerPresenter : PickerPresenterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Date"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> DateProperty = |
||||
|
AvaloniaProperty.RegisterDirect<DatePickerPresenter, DateTimeOffset>(nameof(Date), |
||||
|
x => x.Date, (x, v) => x.Date = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="DayFormat"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePickerPresenter, string> DayFormatProperty = |
||||
|
DatePicker.DayFormatProperty.AddOwner<DatePickerPresenter>(x => |
||||
|
x.DayFormat, (x, v) => x.DayFormat = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="DayVisible"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePickerPresenter, bool> DayVisibleProperty = |
||||
|
DatePicker.DayVisibleProperty.AddOwner<DatePickerPresenter>(x => |
||||
|
x.DayVisible, (x, v) => x.DayVisible = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MaxYear"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MaxYearProperty = |
||||
|
DatePicker.MaxYearProperty.AddOwner<DatePickerPresenter>(x => |
||||
|
x.MaxYear, (x, v) => x.MaxYear = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MinYear"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MinYearProperty = |
||||
|
DatePicker.MinYearProperty.AddOwner<DatePickerPresenter>(x => |
||||
|
x.MinYear, (x, v) => x.MinYear = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MonthFormat"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePickerPresenter, string> MonthFormatProperty = |
||||
|
DatePicker.MonthFormatProperty.AddOwner<DatePickerPresenter>(x => |
||||
|
x.MonthFormat, (x, v) => x.MonthFormat = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MonthVisible"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePickerPresenter, bool> MonthVisibleProperty = |
||||
|
DatePicker.MonthVisibleProperty.AddOwner<DatePickerPresenter>(x => |
||||
|
x.MonthVisible, (x, v) => x.MonthVisible = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="YearFormat"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePickerPresenter, string> YearFormatProperty = |
||||
|
DatePicker.YearFormatProperty.AddOwner<DatePickerPresenter>(x => |
||||
|
x.YearFormat, (x, v) => x.YearFormat = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="YearVisible"/> Property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<DatePickerPresenter, bool> YearVisibleProperty = |
||||
|
DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x => |
||||
|
x.YearVisible, (x, v) => x.YearVisible = v); |
||||
|
|
||||
|
//Template Items
|
||||
|
private Grid _pickerContainer; |
||||
|
private Button _acceptButton; |
||||
|
private Button _dismissButton; |
||||
|
private Rectangle _spacer1; |
||||
|
private Rectangle _spacer2; |
||||
|
private Panel _monthHost; |
||||
|
private Panel _yearHost; |
||||
|
private Panel _dayHost; |
||||
|
private DateTimePickerPanel _monthSelector; |
||||
|
private DateTimePickerPanel _yearSelector; |
||||
|
private DateTimePickerPanel _daySelector; |
||||
|
private Button _monthUpButton; |
||||
|
private Button _dayUpButton; |
||||
|
private Button _yearUpButton; |
||||
|
private Button _monthDownButton; |
||||
|
private Button _dayDownButton; |
||||
|
private Button _yearDownButton; |
||||
|
|
||||
|
private DateTimeOffset _date; |
||||
|
private string _dayFormat = "%d"; |
||||
|
private bool _dayVisible = true; |
||||
|
private DateTimeOffset _maxYear; |
||||
|
private DateTimeOffset _minYear; |
||||
|
private string _monthFormat = "MMMM"; |
||||
|
private bool _monthVisible = true; |
||||
|
private string _yearFormat = "yyyy"; |
||||
|
private bool _yearVisible = true; |
||||
|
private DateTimeOffset _syncDate; |
||||
|
|
||||
|
private GregorianCalendar _calendar; |
||||
|
private bool _suppressUpdateSelection; |
||||
|
|
||||
|
public DatePickerPresenter() |
||||
|
{ |
||||
|
var now = DateTimeOffset.Now; |
||||
|
_minYear = new DateTimeOffset(now.Year - 100, 1, 1, 0, 0, 0, now.Offset); |
||||
|
_maxYear = new DateTimeOffset(now.Year + 100, 12, 31, 0, 0, 0, now.Offset); |
||||
|
_date = now; |
||||
|
_calendar = new GregorianCalendar(); |
||||
|
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the current Date for the picker
|
||||
|
/// </summary>
|
||||
|
public DateTimeOffset Date |
||||
|
{ |
||||
|
get => _date; |
||||
|
set |
||||
|
{ |
||||
|
SetAndRaise(DateProperty, ref _date, value); |
||||
|
_syncDate = Date; |
||||
|
InitPicker(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the DayFormat
|
||||
|
/// </summary>
|
||||
|
public string DayFormat |
||||
|
{ |
||||
|
get => _dayFormat; |
||||
|
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get or sets whether the Day selector is visible
|
||||
|
/// </summary>
|
||||
|
public bool DayVisible |
||||
|
{ |
||||
|
get => _dayVisible; |
||||
|
set |
||||
|
{ |
||||
|
SetAndRaise(DayVisibleProperty, ref _dayVisible, value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the maximum pickable year
|
||||
|
/// </summary>
|
||||
|
public DateTimeOffset MaxYear |
||||
|
{ |
||||
|
get => _maxYear; |
||||
|
set |
||||
|
{ |
||||
|
if (value < MinYear) |
||||
|
throw new InvalidOperationException("MaxDate cannot be less than MinDate"); |
||||
|
SetAndRaise(MaxYearProperty, ref _maxYear, value); |
||||
|
|
||||
|
if (Date > value) |
||||
|
Date = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the minimum pickable year
|
||||
|
/// </summary>
|
||||
|
public DateTimeOffset MinYear |
||||
|
{ |
||||
|
get => _minYear; |
||||
|
set |
||||
|
{ |
||||
|
if (value > MaxYear) |
||||
|
throw new InvalidOperationException("MinDate cannot be greater than MaxDate"); |
||||
|
SetAndRaise(MinYearProperty, ref _minYear, value); |
||||
|
|
||||
|
if (Date < value) |
||||
|
Date = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the month format
|
||||
|
/// </summary>
|
||||
|
public string MonthFormat |
||||
|
{ |
||||
|
get => _monthFormat; |
||||
|
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets whether the month selector is visible
|
||||
|
/// </summary>
|
||||
|
public bool MonthVisible |
||||
|
{ |
||||
|
get => _monthVisible; |
||||
|
set |
||||
|
{ |
||||
|
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the year format
|
||||
|
/// </summary>
|
||||
|
public string YearFormat |
||||
|
{ |
||||
|
get => _yearFormat; |
||||
|
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets whether the year selector is visible
|
||||
|
/// </summary>
|
||||
|
public bool YearVisible |
||||
|
{ |
||||
|
get => _yearVisible; |
||||
|
set |
||||
|
{ |
||||
|
SetAndRaise(YearVisibleProperty, ref _yearVisible, value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
base.OnApplyTemplate(e); |
||||
|
//These are requirements, so throw if not found
|
||||
|
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer"); |
||||
|
_monthHost = e.NameScope.Get<Panel>("MonthHost"); |
||||
|
_dayHost = e.NameScope.Get<Panel>("DayHost"); |
||||
|
_yearHost = e.NameScope.Get<Panel>("YearHost"); |
||||
|
|
||||
|
_monthSelector = e.NameScope.Get<DateTimePickerPanel>("MonthSelector"); |
||||
|
_monthSelector.SelectionChanged += OnMonthChanged; |
||||
|
|
||||
|
_daySelector = e.NameScope.Get<DateTimePickerPanel>("DaySelector"); |
||||
|
_daySelector.SelectionChanged += OnDayChanged; |
||||
|
|
||||
|
_yearSelector = e.NameScope.Get<DateTimePickerPanel>("YearSelector"); |
||||
|
_yearSelector.SelectionChanged += OnYearChanged; |
||||
|
|
||||
|
_acceptButton = e.NameScope.Get<Button>("AcceptButton"); |
||||
|
|
||||
|
_monthUpButton = e.NameScope.Find<RepeatButton>("MonthUpButton"); |
||||
|
if (_monthUpButton != null) |
||||
|
{ |
||||
|
_monthUpButton.Click += OnSelectorButtonClick; |
||||
|
} |
||||
|
_monthDownButton = e.NameScope.Find<RepeatButton>("MonthDownButton"); |
||||
|
if (_monthDownButton != null) |
||||
|
{ |
||||
|
_monthDownButton.Click += OnSelectorButtonClick; |
||||
|
} |
||||
|
|
||||
|
_dayUpButton = e.NameScope.Find<RepeatButton>("DayUpButton"); |
||||
|
if (_dayUpButton != null) |
||||
|
{ |
||||
|
_dayUpButton.Click += OnSelectorButtonClick; |
||||
|
} |
||||
|
_dayDownButton = e.NameScope.Find<RepeatButton>("DayDownButton"); |
||||
|
if (_dayDownButton != null) |
||||
|
{ |
||||
|
_dayDownButton.Click += OnSelectorButtonClick; |
||||
|
} |
||||
|
|
||||
|
_yearUpButton = e.NameScope.Find<RepeatButton>("YearUpButton"); |
||||
|
if (_yearUpButton != null) |
||||
|
{ |
||||
|
_yearUpButton.Click += OnSelectorButtonClick; |
||||
|
} |
||||
|
_yearDownButton = e.NameScope.Find<RepeatButton>("YearDownButton"); |
||||
|
if (_yearDownButton != null) |
||||
|
{ |
||||
|
_yearDownButton.Click += OnSelectorButtonClick; |
||||
|
} |
||||
|
|
||||
|
_dismissButton = e.NameScope.Find<Button>("DismissButton"); |
||||
|
_spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer"); |
||||
|
_spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer"); |
||||
|
|
||||
|
if (_acceptButton != null) |
||||
|
{ |
||||
|
_acceptButton.Click += OnAcceptButtonClicked; |
||||
|
} |
||||
|
if (_dismissButton != null) |
||||
|
{ |
||||
|
_dismissButton.Click += OnDismissButtonClicked; |
||||
|
} |
||||
|
InitPicker(); |
||||
|
} |
||||
|
|
||||
|
protected override void OnKeyDown(KeyEventArgs e) |
||||
|
{ |
||||
|
switch (e.Key) |
||||
|
{ |
||||
|
case Key.Escape: |
||||
|
OnDismiss(); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
case Key.Tab: |
||||
|
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next); |
||||
|
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
case Key.Enter: |
||||
|
Date = _syncDate; |
||||
|
OnConfirmed(); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
} |
||||
|
base.OnKeyDown(e); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes the picker selectors.
|
||||
|
/// </summary>
|
||||
|
private void InitPicker() |
||||
|
{ |
||||
|
//OnApplyTemplate must've been called before we can init here...
|
||||
|
if (_pickerContainer == null) |
||||
|
return; |
||||
|
|
||||
|
_suppressUpdateSelection = true; |
||||
|
|
||||
|
_monthSelector.MaximumValue = 12; |
||||
|
_monthSelector.MinimumValue = 1; |
||||
|
_monthSelector.ItemFormat = MonthFormat; |
||||
|
|
||||
|
_daySelector.ItemFormat = DayFormat; |
||||
|
|
||||
|
_yearSelector.MaximumValue = MaxYear.Year; |
||||
|
_yearSelector.MinimumValue = MinYear.Year; |
||||
|
_yearSelector.ItemFormat = YearFormat; |
||||
|
|
||||
|
SetGrid(); |
||||
|
|
||||
|
//Date should've been set when we reach this point
|
||||
|
var dt = Date; |
||||
|
if (DayVisible) |
||||
|
{ |
||||
|
GregorianCalendar gc = new GregorianCalendar(); |
||||
|
var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month); |
||||
|
_daySelector.MaximumValue = maxDays; |
||||
|
_daySelector.MinimumValue = 1; |
||||
|
_daySelector.SelectedValue = dt.Day; |
||||
|
_daySelector.FormatDate = dt.Date; |
||||
|
} |
||||
|
|
||||
|
if (MonthVisible) |
||||
|
_monthSelector.SelectedValue = dt.Month; |
||||
|
|
||||
|
if (YearVisible) |
||||
|
_yearSelector.SelectedValue = dt.Year; |
||||
|
_suppressUpdateSelection = false; |
||||
|
|
||||
|
SetInitialFocus(); |
||||
|
} |
||||
|
|
||||
|
private void SetGrid() |
||||
|
{ |
||||
|
var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; |
||||
|
var columns = new List<(Panel, int)> |
||||
|
{ |
||||
|
(_monthHost, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1), |
||||
|
(_yearHost, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1), |
||||
|
(_dayHost, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1), |
||||
|
}; |
||||
|
|
||||
|
columns.Sort((x, y) => x.Item2 - y.Item2); |
||||
|
_pickerContainer.ColumnDefinitions.Clear(); |
||||
|
|
||||
|
var columnIndex = 0; |
||||
|
|
||||
|
foreach (var column in columns) |
||||
|
{ |
||||
|
if (column.Item1 is null) |
||||
|
continue; |
||||
|
|
||||
|
column.Item1.IsVisible = column.Item2 != -1; |
||||
|
|
||||
|
if (column.Item2 != -1) |
||||
|
{ |
||||
|
if (columnIndex > 0) |
||||
|
{ |
||||
|
_pickerContainer.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto)); |
||||
|
} |
||||
|
|
||||
|
_pickerContainer.ColumnDefinitions.Add( |
||||
|
new ColumnDefinition(column.Item1 == _monthHost ? 138 : 78, GridUnitType.Star)); |
||||
|
|
||||
|
if (column.Item1.Parent is null) |
||||
|
{ |
||||
|
_pickerContainer.Children.Add(column.Item1); |
||||
|
} |
||||
|
|
||||
|
Grid.SetColumn(column.Item1, (columnIndex++ * 2)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Grid.SetColumn(_spacer1, 1); |
||||
|
Grid.SetColumn(_spacer2, 3); |
||||
|
_spacer1.IsVisible = columnIndex > 1; |
||||
|
_spacer2.IsVisible = columnIndex > 2; |
||||
|
} |
||||
|
|
||||
|
private void SetInitialFocus() |
||||
|
{ |
||||
|
int monthCol = MonthVisible ? Grid.GetColumn(_monthHost) : int.MaxValue; |
||||
|
int dayCol = DayVisible ? Grid.GetColumn(_dayHost) : int.MaxValue; |
||||
|
int yearCol = YearVisible ? Grid.GetColumn(_yearHost) : int.MaxValue; |
||||
|
|
||||
|
if (monthCol < dayCol && monthCol < yearCol) |
||||
|
{ |
||||
|
KeyboardDevice.Instance?.SetFocusedElement(_monthSelector, NavigationMethod.Pointer, KeyModifiers.None); |
||||
|
} |
||||
|
else if (dayCol < monthCol && dayCol < yearCol) |
||||
|
{ |
||||
|
KeyboardDevice.Instance?.SetFocusedElement(_daySelector, NavigationMethod.Pointer, KeyModifiers.None); |
||||
|
} |
||||
|
else if (yearCol < monthCol && yearCol < dayCol) |
||||
|
{ |
||||
|
KeyboardDevice.Instance?.SetFocusedElement(_yearSelector, NavigationMethod.Pointer, KeyModifiers.None); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
||||
|
{ |
||||
|
OnDismiss(); |
||||
|
} |
||||
|
|
||||
|
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
||||
|
{ |
||||
|
Date = _syncDate; |
||||
|
OnConfirmed(); |
||||
|
} |
||||
|
|
||||
|
private void OnSelectorButtonClick(object sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
if (sender == _monthUpButton) |
||||
|
_monthSelector.ScrollUp(); |
||||
|
else if (sender == _monthDownButton) |
||||
|
_monthSelector.ScrollDown(); |
||||
|
else if (sender == _yearUpButton) |
||||
|
_yearSelector.ScrollUp(); |
||||
|
else if (sender == _yearDownButton) |
||||
|
_yearSelector.ScrollDown(); |
||||
|
else if (sender == _dayUpButton) |
||||
|
_daySelector.ScrollUp(); |
||||
|
else if (sender == _dayDownButton) |
||||
|
_daySelector.ScrollDown(); |
||||
|
} |
||||
|
|
||||
|
private void OnYearChanged(object sender, EventArgs e) |
||||
|
{ |
||||
|
if (_suppressUpdateSelection) |
||||
|
return; |
||||
|
|
||||
|
int maxDays = _calendar.GetDaysInMonth(_yearSelector.SelectedValue, _syncDate.Month); |
||||
|
var newDate = new DateTimeOffset(_yearSelector.SelectedValue, _syncDate.Month, |
||||
|
_syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset); |
||||
|
|
||||
|
_syncDate = newDate; |
||||
|
|
||||
|
//We don't need to update the days if not displaying day, not february
|
||||
|
if (!DayVisible || _syncDate.Month != 2) |
||||
|
return; |
||||
|
|
||||
|
_suppressUpdateSelection = true; |
||||
|
|
||||
|
_daySelector.FormatDate = newDate.Date; |
||||
|
|
||||
|
if (_daySelector.MaximumValue != maxDays) |
||||
|
_daySelector.MaximumValue = maxDays; |
||||
|
else |
||||
|
_daySelector.RefreshItems(); |
||||
|
|
||||
|
_suppressUpdateSelection = false; |
||||
|
} |
||||
|
|
||||
|
private void OnDayChanged(object sender, EventArgs e) |
||||
|
{ |
||||
|
if (_suppressUpdateSelection) |
||||
|
return; |
||||
|
_syncDate = new DateTimeOffset(_syncDate.Year, _syncDate.Month, _daySelector.SelectedValue, 0, 0, 0, _syncDate.Offset); |
||||
|
} |
||||
|
|
||||
|
private void OnMonthChanged(object sender, EventArgs e) |
||||
|
{ |
||||
|
if (_suppressUpdateSelection) |
||||
|
return; |
||||
|
|
||||
|
int maxDays = _calendar.GetDaysInMonth(_syncDate.Year, _monthSelector.SelectedValue); |
||||
|
var newDate = new DateTimeOffset(_syncDate.Year, _monthSelector.SelectedValue, |
||||
|
_syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset); |
||||
|
|
||||
|
if (!DayVisible) |
||||
|
{ |
||||
|
_syncDate = newDate; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_suppressUpdateSelection = true; |
||||
|
|
||||
|
_daySelector.FormatDate = newDate.Date; |
||||
|
_syncDate = newDate; |
||||
|
|
||||
|
if (_daySelector.MaximumValue != maxDays) |
||||
|
_daySelector.MaximumValue = maxDays; |
||||
|
else |
||||
|
_daySelector.RefreshItems(); |
||||
|
|
||||
|
_suppressUpdateSelection = false; |
||||
|
} |
||||
|
|
||||
|
internal double GetOffsetForPopup() |
||||
|
{ |
||||
|
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; |
||||
|
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the argument passed when the <see cref="DatePicker"/> SelectedDate changes
|
||||
|
/// </summary>
|
||||
|
public class DatePickerSelectedValueChangedEventArgs |
||||
|
{ |
||||
|
public DateTimeOffset? NewDate { get; } |
||||
|
public DateTimeOffset? OldDate { get; } |
||||
|
|
||||
|
public DatePickerSelectedValueChangedEventArgs(DateTimeOffset? oldDate, DateTimeOffset? newDate) |
||||
|
{ |
||||
|
NewDate = newDate; |
||||
|
OldDate = oldDate; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,566 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
public enum DateTimePickerPanelType |
||||
|
{ |
||||
|
Year, |
||||
|
Month, |
||||
|
Day, |
||||
|
Hour, |
||||
|
Minute, |
||||
|
TimePeriod //AM or PM
|
||||
|
} |
||||
|
|
||||
|
public class DateTimePickerPanel : Panel, ILogicalScrollable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="ItemHeight"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<double> ItemHeightProperty = |
||||
|
AvaloniaProperty.Register<DateTimePickerPanel, double>(nameof(ItemHeight), 40.0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="PanelType"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<DateTimePickerPanelType> PanelTypeProperty = |
||||
|
AvaloniaProperty.Register<DateTimePickerPanel, DateTimePickerPanelType>(nameof(PanelType)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="ItemFormat"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<string> ItemFormatProperty = |
||||
|
AvaloniaProperty.Register<DateTimePickerPanel, string>(nameof(ItemFormat), "yyyy"); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="ShouldLoop"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<bool> ShouldLoopProperty = |
||||
|
AvaloniaProperty.Register<DateTimePickerPanel, bool>(nameof(ShouldLoop)); |
||||
|
|
||||
|
//Backing fields for properties
|
||||
|
private int _minimumValue = 1; |
||||
|
private int _maximumValue = 2; |
||||
|
private int _selectedValue = 1; |
||||
|
private int _increment = 1; |
||||
|
|
||||
|
//Helper fields
|
||||
|
private int _selectedIndex = 0; |
||||
|
private int _totalItems; |
||||
|
private int _numItemsAboveBelowSelected; |
||||
|
private int _range; |
||||
|
private double _extentOne; |
||||
|
private Size _extent; |
||||
|
private Vector _offset; |
||||
|
private bool _hasInit; |
||||
|
private bool _suppressUpdateOffset; |
||||
|
private ListBoxItem _pressedItem; |
||||
|
|
||||
|
public DateTimePickerPanel() |
||||
|
{ |
||||
|
FormatDate = DateTime.Now; |
||||
|
AddHandler(ListBoxItem.PointerPressedEvent, OnItemPointerDown, Avalonia.Interactivity.RoutingStrategies.Bubble); |
||||
|
AddHandler(ListBoxItem.PointerReleasedEvent, OnItemPointerUp, Avalonia.Interactivity.RoutingStrategies.Bubble); |
||||
|
} |
||||
|
|
||||
|
static DateTimePickerPanel() |
||||
|
{ |
||||
|
FocusableProperty.OverrideDefaultValue<DateTimePickerPanel>(true); |
||||
|
AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets what this panel displays in date or time units
|
||||
|
/// </summary>
|
||||
|
public DateTimePickerPanelType PanelType |
||||
|
{ |
||||
|
get => GetValue(PanelTypeProperty); |
||||
|
set => SetValue(PanelTypeProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the height of each item
|
||||
|
/// </summary>
|
||||
|
public double ItemHeight |
||||
|
{ |
||||
|
get => GetValue(ItemHeightProperty); |
||||
|
set => SetValue(ItemHeightProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the string format for the items, using standard
|
||||
|
/// .net DateTime or TimeSpan formatting. Format must match panel type
|
||||
|
/// </summary>
|
||||
|
public string ItemFormat |
||||
|
{ |
||||
|
get => GetValue(ItemFormatProperty); |
||||
|
set => SetValue(ItemFormatProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets whether the panel should loop
|
||||
|
/// </summary>
|
||||
|
public bool ShouldLoop |
||||
|
{ |
||||
|
get => GetValue(ShouldLoopProperty); |
||||
|
set => SetValue(ShouldLoopProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the minimum value
|
||||
|
/// </summary>
|
||||
|
public int MinimumValue |
||||
|
{ |
||||
|
get => _minimumValue; |
||||
|
set |
||||
|
{ |
||||
|
if (value > MaximumValue) |
||||
|
throw new InvalidOperationException("Minimum cannot be greater than Maximum"); |
||||
|
_minimumValue = value; |
||||
|
UpdateHelperInfo(); |
||||
|
var sel = CoerceSelected(SelectedValue); |
||||
|
if (sel != SelectedValue) |
||||
|
SelectedValue = sel; |
||||
|
UpdateItems(); |
||||
|
InvalidateArrange(); |
||||
|
RaiseScrollInvalidated(EventArgs.Empty); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the maximum value
|
||||
|
/// </summary>
|
||||
|
public int MaximumValue |
||||
|
{ |
||||
|
get => _maximumValue; |
||||
|
set |
||||
|
{ |
||||
|
if (value < MinimumValue) |
||||
|
throw new InvalidOperationException("Maximum cannot be less than Minimum"); |
||||
|
_maximumValue = value; |
||||
|
UpdateHelperInfo(); |
||||
|
var sel = CoerceSelected(SelectedValue); |
||||
|
if (sel != SelectedValue) |
||||
|
SelectedValue = sel; |
||||
|
UpdateItems(); |
||||
|
InvalidateArrange(); |
||||
|
RaiseScrollInvalidated(EventArgs.Empty); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the selected value
|
||||
|
/// </summary>
|
||||
|
public int SelectedValue |
||||
|
{ |
||||
|
get => _selectedValue; |
||||
|
set |
||||
|
{ |
||||
|
if (value > MaximumValue || value < MinimumValue) |
||||
|
throw new ArgumentOutOfRangeException("SelectedValue"); |
||||
|
|
||||
|
var sel = CoerceSelected(value); |
||||
|
_selectedValue = sel; |
||||
|
_selectedIndex = (value - MinimumValue) / Increment; |
||||
|
|
||||
|
if (!ShouldLoop) |
||||
|
CreateOrDestroyItems(Children); |
||||
|
|
||||
|
if (!_suppressUpdateOffset) |
||||
|
_offset = new Vector(0, ShouldLoop ? _selectedIndex * ItemHeight + (_extentOne * 50) : |
||||
|
_selectedIndex * ItemHeight); |
||||
|
|
||||
|
UpdateItems(); |
||||
|
InvalidateArrange(); |
||||
|
RaiseScrollInvalidated(EventArgs.Empty); |
||||
|
|
||||
|
SelectionChanged?.Invoke(this, EventArgs.Empty); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the increment
|
||||
|
/// </summary>
|
||||
|
public int Increment |
||||
|
{ |
||||
|
get => _increment; |
||||
|
set |
||||
|
{ |
||||
|
if (value <= 0 || value > _range) |
||||
|
throw new ArgumentOutOfRangeException("Increment"); |
||||
|
_increment = value; |
||||
|
UpdateHelperInfo(); |
||||
|
var sel = CoerceSelected(SelectedValue); |
||||
|
if (sel != SelectedValue) |
||||
|
SelectedValue = sel; |
||||
|
UpdateItems(); |
||||
|
InvalidateArrange(); |
||||
|
RaiseScrollInvalidated(EventArgs.Empty); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//Used to help format the date (if applicable), for ex.,
|
||||
|
//if we're want to display the day of week, we need context
|
||||
|
//for the month/year, this is our context
|
||||
|
internal DateTime FormatDate { get; set; } |
||||
|
|
||||
|
public Vector Offset |
||||
|
{ |
||||
|
get => _offset; |
||||
|
set |
||||
|
{ |
||||
|
var old = _offset; |
||||
|
_offset = value; |
||||
|
var dy = _offset.Y - old.Y; |
||||
|
var children = Children; |
||||
|
|
||||
|
if (dy > 0) // Scroll Down
|
||||
|
{ |
||||
|
int numContsToMove = 0; |
||||
|
for (int i = 0; i < children.Count; i++) |
||||
|
{ |
||||
|
if (children[i].Bounds.Bottom - dy < 0) |
||||
|
numContsToMove++; |
||||
|
else |
||||
|
break; |
||||
|
} |
||||
|
children.MoveRange(0, numContsToMove, children.Count); |
||||
|
|
||||
|
var scrollHeight = _extent.Height - Viewport.Height; |
||||
|
if (ShouldLoop && value.Y >= scrollHeight - _extentOne) |
||||
|
_offset = new Vector(0, value.Y - (_extentOne * 50)); |
||||
|
} |
||||
|
else if (dy < 0) // Scroll Up
|
||||
|
{ |
||||
|
int numContsToMove = 0; |
||||
|
for (int i = children.Count - 1; i >= 0; i--) |
||||
|
{ |
||||
|
if (children[i].Bounds.Top - dy > Bounds.Height) |
||||
|
numContsToMove++; |
||||
|
else |
||||
|
break; |
||||
|
} |
||||
|
children.MoveRange(children.Count - numContsToMove, numContsToMove, 0); |
||||
|
if (ShouldLoop && value.Y < _extentOne) |
||||
|
_offset = new Vector(0, value.Y + (_extentOne * 50)); |
||||
|
} |
||||
|
|
||||
|
//Setting selection will handle all invalidation
|
||||
|
var newSel = (Offset.Y / ItemHeight) % _totalItems; |
||||
|
_suppressUpdateOffset = true; |
||||
|
SelectedValue = (int)newSel * Increment + MinimumValue; |
||||
|
_suppressUpdateOffset = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool CanHorizontallyScroll { get => false; set { } } |
||||
|
|
||||
|
public bool CanVerticallyScroll { get => true; set { } } |
||||
|
|
||||
|
public bool IsLogicalScrollEnabled => true; |
||||
|
|
||||
|
public Size ScrollSize => new Size(0, ItemHeight); |
||||
|
|
||||
|
public Size PageScrollSize => new Size(0, ItemHeight * 4); |
||||
|
|
||||
|
public Size Extent => _extent; |
||||
|
|
||||
|
public Size Viewport => new Size(0, ItemHeight); |
||||
|
|
||||
|
public event EventHandler ScrollInvalidated; |
||||
|
|
||||
|
public event EventHandler SelectionChanged; |
||||
|
|
||||
|
protected override Size MeasureOverride(Size availableSize) |
||||
|
{ |
||||
|
if (double.IsInfinity(availableSize.Width) || |
||||
|
double.IsInfinity(availableSize.Height)) |
||||
|
throw new InvalidOperationException("Panel must have finite height"); |
||||
|
|
||||
|
if (!_hasInit) |
||||
|
UpdateHelperInfo(); |
||||
|
|
||||
|
double initY = (availableSize.Height / 2.0) - (ItemHeight / 2.0); |
||||
|
_numItemsAboveBelowSelected = (int)Math.Ceiling(initY / ItemHeight) + 1; |
||||
|
|
||||
|
var children = Children; |
||||
|
|
||||
|
CreateOrDestroyItems(children); |
||||
|
|
||||
|
for (int i = 0; i < children.Count; i++) |
||||
|
children[i].Measure(availableSize); |
||||
|
|
||||
|
if (!_hasInit) |
||||
|
{ |
||||
|
UpdateItems(); |
||||
|
RaiseScrollInvalidated(EventArgs.Empty); |
||||
|
_hasInit = true; |
||||
|
} |
||||
|
|
||||
|
return availableSize; |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
if (Children.Count == 0) |
||||
|
return base.ArrangeOverride(finalSize); |
||||
|
|
||||
|
var itemHgt = ItemHeight; |
||||
|
var children = Children; |
||||
|
Rect rc; |
||||
|
double initY = (finalSize.Height / 2.0) - (itemHgt / 2.0); |
||||
|
|
||||
|
if (ShouldLoop) |
||||
|
{ |
||||
|
var currentSet = Math.Truncate(Offset.Y / _extentOne); |
||||
|
initY += (_extentOne * currentSet) + (_selectedIndex - _numItemsAboveBelowSelected) * ItemHeight; |
||||
|
|
||||
|
for (int i = 0; i < children.Count; i++) |
||||
|
{ |
||||
|
rc = new Rect(0, initY - Offset.Y, finalSize.Width, itemHgt); |
||||
|
children[i].Arrange(rc); |
||||
|
initY += itemHgt; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var first = Math.Max(0, (_selectedIndex - _numItemsAboveBelowSelected)); |
||||
|
for (int i = 0; i < children.Count; i++) |
||||
|
{ |
||||
|
rc = new Rect(0, (initY + first * itemHgt) - Offset.Y, finalSize.Width, itemHgt); |
||||
|
children[i].Arrange(rc); |
||||
|
initY += itemHgt; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return finalSize; |
||||
|
} |
||||
|
|
||||
|
protected override void OnKeyDown(KeyEventArgs e) |
||||
|
{ |
||||
|
switch (e.Key) |
||||
|
{ |
||||
|
case Key.Up: |
||||
|
ScrollUp(); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
case Key.Down: |
||||
|
ScrollDown(); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
case Key.PageUp: |
||||
|
ScrollUp(4); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
case Key.PageDown: |
||||
|
ScrollDown(4); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
} |
||||
|
base.OnKeyDown(e); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Refreshes the content of the visible items
|
||||
|
/// </summary>
|
||||
|
public void RefreshItems() |
||||
|
{ |
||||
|
UpdateItems(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Scrolls up the specified number of items
|
||||
|
/// </summary>
|
||||
|
public void ScrollUp(int numItems = 1) |
||||
|
{ |
||||
|
var newY = Math.Max(Offset.Y - (numItems * ItemHeight), 0); |
||||
|
Offset = new Vector(0, newY); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Scrolls down the specified number of items
|
||||
|
/// </summary>
|
||||
|
public void ScrollDown(int numItems = 1) |
||||
|
{ |
||||
|
var scrollHeight = _extent.Height - Viewport.Height; |
||||
|
var newY = Math.Min(Offset.Y + (numItems * ItemHeight), scrollHeight); |
||||
|
Offset = new Vector(0, newY); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Updates helper fields used in various calculations
|
||||
|
/// </summary>
|
||||
|
private void UpdateHelperInfo() |
||||
|
{ |
||||
|
_range = _maximumValue - _minimumValue + 1; |
||||
|
_totalItems = (int)Math.Ceiling((double)_range / _increment); |
||||
|
|
||||
|
var itemHgt = ItemHeight; |
||||
|
//If looping, measure 100x as many items as we actually have
|
||||
|
_extent = new Size(0, ShouldLoop ? _totalItems * itemHgt * 100 : _totalItems * itemHgt); |
||||
|
|
||||
|
//Height of 1 "set" of items
|
||||
|
_extentOne = _totalItems * itemHgt; |
||||
|
_offset = new Vector(0, ShouldLoop ? _extentOne * 50 + _selectedIndex * itemHgt : _selectedIndex * itemHgt); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Ensures enough containers are visible in the viewport
|
||||
|
/// </summary>
|
||||
|
/// <param name="children"></param>
|
||||
|
private void CreateOrDestroyItems(Controls children) |
||||
|
{ |
||||
|
int totalItemsInViewport = _numItemsAboveBelowSelected * 2 + 1; |
||||
|
|
||||
|
if (!ShouldLoop) |
||||
|
{ |
||||
|
int numItemAboveSelect = _numItemsAboveBelowSelected; |
||||
|
if (_selectedIndex - _numItemsAboveBelowSelected < 0) |
||||
|
numItemAboveSelect = _selectedIndex; |
||||
|
int numItemBelowSelect = _numItemsAboveBelowSelected; |
||||
|
if (_selectedIndex + _numItemsAboveBelowSelected >= _totalItems) |
||||
|
numItemBelowSelect = _totalItems - _selectedIndex - 1; |
||||
|
|
||||
|
totalItemsInViewport = numItemBelowSelect + numItemAboveSelect + 1; |
||||
|
} |
||||
|
|
||||
|
while (children.Count < totalItemsInViewport) |
||||
|
{ |
||||
|
children.Add(new ListBoxItem |
||||
|
{ |
||||
|
Height = ItemHeight, |
||||
|
Classes = new Classes("DateTimePickerItem", $"{PanelType}Item"), |
||||
|
VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center, |
||||
|
Focusable = false |
||||
|
}); |
||||
|
} |
||||
|
if (children.Count > totalItemsInViewport) |
||||
|
{ |
||||
|
var numToRemove = children.Count - totalItemsInViewport; |
||||
|
children.RemoveRange(children.Count - numToRemove, numToRemove); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Updates item content based on the current selection
|
||||
|
/// and the panel type
|
||||
|
/// </summary>
|
||||
|
private void UpdateItems() |
||||
|
{ |
||||
|
var children = Children; |
||||
|
var min = MinimumValue; |
||||
|
var panelType = PanelType; |
||||
|
var selected = SelectedValue; |
||||
|
var max = MaximumValue; |
||||
|
|
||||
|
int first; |
||||
|
if (ShouldLoop) |
||||
|
{ |
||||
|
first = (_selectedIndex - _numItemsAboveBelowSelected) % _totalItems; |
||||
|
first = first < 0 ? min + (first + _totalItems) * Increment : min + first * Increment; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
first = min + Math.Max(0, _selectedIndex - _numItemsAboveBelowSelected) * Increment; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < children.Count; i++) |
||||
|
{ |
||||
|
ListBoxItem item = (ListBoxItem)children[i]; |
||||
|
item.Content = FormatContent(first, panelType); |
||||
|
item.Tag = first; |
||||
|
item.IsSelected = first == selected; |
||||
|
first += Increment; |
||||
|
if (first > max) |
||||
|
first = min; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private string FormatContent(int value, DateTimePickerPanelType panelType) |
||||
|
{ |
||||
|
switch (panelType) |
||||
|
{ |
||||
|
case DateTimePickerPanelType.Year: |
||||
|
return new DateTime(value, FormatDate.Month, FormatDate.Day).ToString(ItemFormat); |
||||
|
case DateTimePickerPanelType.Month: |
||||
|
return new DateTime(FormatDate.Year, value, FormatDate.Day).ToString(ItemFormat); |
||||
|
case DateTimePickerPanelType.Day: |
||||
|
return new DateTime(FormatDate.Year, FormatDate.Month, value).ToString(ItemFormat); |
||||
|
case DateTimePickerPanelType.Hour: |
||||
|
return new TimeSpan(value, 0, 0).ToString(ItemFormat); |
||||
|
case DateTimePickerPanelType.Minute: |
||||
|
return new TimeSpan(0, value, 0).ToString(ItemFormat); |
||||
|
case DateTimePickerPanelType.TimePeriod: |
||||
|
return value == MinimumValue ? CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator : |
||||
|
CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator; |
||||
|
default: |
||||
|
return ""; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Ensures the <see cref="SelectedValue"/> is within the bounds and
|
||||
|
/// follows the current Increment
|
||||
|
/// </summary>
|
||||
|
private int CoerceSelected(int newValue) |
||||
|
{ |
||||
|
if (newValue < MinimumValue) |
||||
|
return MinimumValue; |
||||
|
if (newValue > MaximumValue) |
||||
|
return MaximumValue; |
||||
|
|
||||
|
if (newValue % Increment != 0) |
||||
|
{ |
||||
|
var items = Enumerable.Range(MinimumValue, MaximumValue + 1).Where(i => i % Increment == 0).ToList(); |
||||
|
var nearest = items.Aggregate((x, y) => Math.Abs(x - newValue) > Math.Abs(y - newValue) ? y : x); |
||||
|
return items.IndexOf(nearest) * Increment; |
||||
|
} |
||||
|
return newValue; |
||||
|
} |
||||
|
|
||||
|
private void OnItemPointerDown(object sender, PointerPressedEventArgs e) |
||||
|
{ |
||||
|
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) |
||||
|
{ |
||||
|
_pressedItem = GetItemFromSource((IVisual)e.Source); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnItemPointerUp(object sender, PointerReleasedEventArgs e) |
||||
|
{ |
||||
|
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased && |
||||
|
_pressedItem != null) |
||||
|
{ |
||||
|
SelectedValue = (int)GetItemFromSource((IVisual)e.Source).Tag; |
||||
|
_pressedItem = null; |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//Helper to get ListBoxItem from pointerevent source
|
||||
|
private ListBoxItem GetItemFromSource(IVisual src) |
||||
|
{ |
||||
|
var item = src; |
||||
|
while (item != null && !(item is ListBoxItem)) |
||||
|
{ |
||||
|
item = item.VisualParent; |
||||
|
} |
||||
|
return (ListBoxItem)item; |
||||
|
} |
||||
|
|
||||
|
public bool BringIntoView(IControl target, Rect targetRect) { return false; } |
||||
|
|
||||
|
public IControl GetControlInDirection(NavigationDirection direction, IControl from) { return null; } |
||||
|
|
||||
|
public void RaiseScrollInvalidated(EventArgs e) |
||||
|
{ |
||||
|
ScrollInvalidated?.Invoke(this, e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the base class for Date and Time PickerPresenters
|
||||
|
/// </summary>
|
||||
|
public abstract class PickerPresenterBase : TemplatedControl |
||||
|
{ |
||||
|
protected virtual void OnConfirmed() |
||||
|
{ |
||||
|
Confirmed?.Invoke(this, EventArgs.Empty); |
||||
|
} |
||||
|
|
||||
|
protected virtual void OnDismiss() |
||||
|
{ |
||||
|
Dismissed.Invoke(this, EventArgs.Empty); |
||||
|
} |
||||
|
|
||||
|
public event EventHandler Confirmed; |
||||
|
public event EventHandler Dismissed; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,292 @@ |
|||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A control to allow the user to select a time
|
||||
|
/// </summary>
|
||||
|
public class TimePicker : TemplatedControl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MinuteIncrement"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<TimePicker, int> MinuteIncrementProperty = |
||||
|
AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement), |
||||
|
x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Header"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<object> HeaderProperty = |
||||
|
AvaloniaProperty.Register<DatePicker, object>(nameof(Header)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="HeaderTemplate"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty = |
||||
|
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="ClockIdentifier"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<TimePicker, string> ClockIdentifierProperty = |
||||
|
AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier), |
||||
|
x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="SelectedTime"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<TimePicker, TimeSpan?> SelectedTimeProperty = |
||||
|
AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime), |
||||
|
x => x.SelectedTime, (x, v) => x.SelectedTime = v); |
||||
|
|
||||
|
//Template Items
|
||||
|
private TimePickerPresenter _presenter; |
||||
|
private Button _flyoutButton; |
||||
|
private Border _firstPickerHost; |
||||
|
private Border _secondPickerHost; |
||||
|
private Border _thirdPickerHost; |
||||
|
private TextBlock _hourText; |
||||
|
private TextBlock _minuteText; |
||||
|
public TextBlock _periodText; |
||||
|
private Rectangle _firstSplitter; |
||||
|
private Rectangle _secondSplitter; |
||||
|
private Grid _contentGrid; |
||||
|
private Popup _popup; |
||||
|
|
||||
|
private TimeSpan? _selectedTime; |
||||
|
private int _minuteIncrement = 1; |
||||
|
private string _clockIdentifier = "12HourClock"; |
||||
|
|
||||
|
public TimePicker() |
||||
|
{ |
||||
|
PseudoClasses.Set(":hasnotime", true); |
||||
|
|
||||
|
var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern; |
||||
|
if (timePattern.IndexOf("H") != -1) |
||||
|
_clockIdentifier = "24HourClock"; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the minute increment in the picker
|
||||
|
/// </summary>
|
||||
|
public int MinuteIncrement |
||||
|
{ |
||||
|
get => _minuteIncrement; |
||||
|
set |
||||
|
{ |
||||
|
if (value < 1 || value > 59) |
||||
|
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59"); |
||||
|
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value); |
||||
|
SetSelectedTimeText(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the header
|
||||
|
/// </summary>
|
||||
|
public object Header |
||||
|
{ |
||||
|
get => GetValue(HeaderProperty); |
||||
|
set => SetValue(HeaderProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the header template
|
||||
|
/// </summary>
|
||||
|
public IDataTemplate HeaderTemplate |
||||
|
{ |
||||
|
get => GetValue(HeaderTemplateProperty); |
||||
|
set => SetValue(HeaderTemplateProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the clock identifier, either 12HourClock or 24HourClock
|
||||
|
/// </summary>
|
||||
|
public string ClockIdentifier |
||||
|
{ |
||||
|
get => _clockIdentifier; |
||||
|
set |
||||
|
{ |
||||
|
if (!(string.IsNullOrEmpty(value) || value == "" || value == "12HourClock" || value == "24HourClock")) |
||||
|
throw new ArgumentException("Invalid ClockIdentifier"); |
||||
|
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value); |
||||
|
SetGrid(); |
||||
|
SetSelectedTimeText(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the selected time. Can be null.
|
||||
|
/// </summary>
|
||||
|
public TimeSpan? SelectedTime |
||||
|
{ |
||||
|
get => _selectedTime; |
||||
|
set |
||||
|
{ |
||||
|
var old = _selectedTime; |
||||
|
SetAndRaise(SelectedTimeProperty, ref _selectedTime, value); |
||||
|
OnSelectedTimeChanged(old, value); |
||||
|
SetSelectedTimeText(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Raised when the <see cref="SelectedTime"/> property changes
|
||||
|
/// </summary>
|
||||
|
public event EventHandler<TimePickerSelectedValueChangedEventArgs> SelectedTimeChanged; |
||||
|
|
||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
if (_flyoutButton != null) |
||||
|
_flyoutButton.Click -= OnFlyoutButtonClicked; |
||||
|
|
||||
|
if(_presenter != null) |
||||
|
{ |
||||
|
_presenter.Confirmed -= OnConfirmed; |
||||
|
_presenter.Dismissed -= OnDismissPicker; |
||||
|
} |
||||
|
base.OnApplyTemplate(e); |
||||
|
|
||||
|
_flyoutButton = e.NameScope.Find<Button>("FlyoutButton"); |
||||
|
|
||||
|
_firstPickerHost = e.NameScope.Find<Border>("FirstPickerHost"); |
||||
|
_secondPickerHost = e.NameScope.Find<Border>("SecondPickerHost"); |
||||
|
_thirdPickerHost = e.NameScope.Find<Border>("ThirdPickerHost"); |
||||
|
|
||||
|
_hourText = e.NameScope.Find<TextBlock>("HourTextBlock"); |
||||
|
_minuteText = e.NameScope.Find<TextBlock>("MinuteTextBlock"); |
||||
|
_periodText = e.NameScope.Find<TextBlock>("PeriodTextBlock"); |
||||
|
|
||||
|
_firstSplitter = e.NameScope.Find<Rectangle>("FirstColumnDivider"); |
||||
|
_secondSplitter = e.NameScope.Find<Rectangle>("SecondColumnDivider"); |
||||
|
|
||||
|
_contentGrid = e.NameScope.Find<Grid>("FlyoutButtonContentGrid"); |
||||
|
|
||||
|
_popup = e.NameScope.Find<Popup>("Popup"); |
||||
|
_presenter = e.NameScope.Find<TimePickerPresenter>("PickerPresenter"); |
||||
|
|
||||
|
|
||||
|
if (_flyoutButton != null) |
||||
|
_flyoutButton.Click += OnFlyoutButtonClicked; |
||||
|
|
||||
|
SetGrid(); |
||||
|
SetSelectedTimeText(); |
||||
|
|
||||
|
if (_presenter != null) |
||||
|
{ |
||||
|
_presenter.Confirmed += OnConfirmed; |
||||
|
_presenter.Dismissed += OnDismissPicker; |
||||
|
|
||||
|
_presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty]; |
||||
|
_presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty]; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void SetGrid() |
||||
|
{ |
||||
|
if (_contentGrid == null) |
||||
|
return; |
||||
|
|
||||
|
bool use24HourClock = ClockIdentifier == "24HourClock"; |
||||
|
|
||||
|
if (!use24HourClock) |
||||
|
{ |
||||
|
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*"); |
||||
|
_thirdPickerHost.IsVisible = true; |
||||
|
_secondSplitter.IsVisible = true; |
||||
|
|
||||
|
Grid.SetColumn(_firstPickerHost, 0); |
||||
|
Grid.SetColumn(_secondPickerHost, 2); |
||||
|
Grid.SetColumn(_thirdPickerHost, 4); |
||||
|
|
||||
|
Grid.SetColumn(_firstSplitter, 1); |
||||
|
Grid.SetColumn(_secondSplitter, 3); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*"); |
||||
|
_thirdPickerHost.IsVisible = false; |
||||
|
_secondSplitter.IsVisible = false; |
||||
|
|
||||
|
Grid.SetColumn(_firstPickerHost, 0); |
||||
|
Grid.SetColumn(_secondPickerHost, 2); |
||||
|
|
||||
|
Grid.SetColumn(_firstSplitter, 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SetSelectedTimeText() |
||||
|
{ |
||||
|
if (_hourText == null || _minuteText == null || _periodText == null) |
||||
|
return; |
||||
|
|
||||
|
var time = SelectedTime; |
||||
|
if (time.HasValue) |
||||
|
{ |
||||
|
var newTime = SelectedTime.Value; |
||||
|
|
||||
|
if (ClockIdentifier == "12HourClock") |
||||
|
{ |
||||
|
var hr = newTime.Hours; |
||||
|
hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr; |
||||
|
newTime = new TimeSpan(hr, newTime.Minutes, 0); |
||||
|
} |
||||
|
_hourText.Text = newTime.ToString("%h"); |
||||
|
|
||||
|
_minuteText.Text = newTime.ToString("mm"); |
||||
|
PseudoClasses.Set(":hasnotime", false); |
||||
|
|
||||
|
_periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : |
||||
|
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator; |
||||
|
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_hourText.Text = "hour"; |
||||
|
_minuteText.Text = "minute"; |
||||
|
PseudoClasses.Set(":hasnotime", true); |
||||
|
|
||||
|
_periodText.Text = DateTime.Now.Hour >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : |
||||
|
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual void OnSelectedTimeChanged(TimeSpan? oldTime, TimeSpan? newTime) |
||||
|
{ |
||||
|
SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime)); |
||||
|
} |
||||
|
|
||||
|
private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
||||
|
{ |
||||
|
_presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay; |
||||
|
|
||||
|
_popup.IsOpen = true; |
||||
|
|
||||
|
var deltaY = _presenter.GetOffsetForPopup(); |
||||
|
|
||||
|
//The extra 5 px I think is related to default popup placement behavior
|
||||
|
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), |
||||
|
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, |
||||
|
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); |
||||
|
} |
||||
|
|
||||
|
private void OnDismissPicker(object sender, EventArgs e) |
||||
|
{ |
||||
|
_popup.Close(); |
||||
|
Focus(); |
||||
|
} |
||||
|
|
||||
|
private void OnConfirmed(object sender, EventArgs e) |
||||
|
{ |
||||
|
_popup.Close(); |
||||
|
SelectedTime = _presenter.Time; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,262 @@ |
|||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Interactivity; |
||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the presenter used for selecting a time. Intended for use with
|
||||
|
/// <see cref="TimePicker"/> but can be used independently
|
||||
|
/// </summary>
|
||||
|
public class TimePickerPresenter : PickerPresenterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MinuteIncrement"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<TimePickerPresenter, int> MinuteIncrementProperty = |
||||
|
TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>(x => x.MinuteIncrement, |
||||
|
(x, v) => x.MinuteIncrement = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="ClockIdentifier"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<TimePickerPresenter, string> ClockIdentifierProperty = |
||||
|
TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>(x => x.ClockIdentifier, |
||||
|
(x, v) => x.ClockIdentifier = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Time"/> property
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<TimePickerPresenter, TimeSpan> TimeProperty = |
||||
|
AvaloniaProperty.RegisterDirect<TimePickerPresenter, TimeSpan>(nameof(Time), |
||||
|
x => x.Time, (x, v) => x.Time = v); |
||||
|
|
||||
|
public TimePickerPresenter() |
||||
|
{ |
||||
|
Time = DateTime.Now.TimeOfDay; |
||||
|
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); |
||||
|
} |
||||
|
|
||||
|
//TemplateItems
|
||||
|
private Grid _pickerContainer; |
||||
|
private Button _acceptButton; |
||||
|
private Button _dismissButton; |
||||
|
private Rectangle _spacer2; |
||||
|
private Panel _periodHost; |
||||
|
private DateTimePickerPanel _hourSelector; |
||||
|
private DateTimePickerPanel _minuteSelector; |
||||
|
private DateTimePickerPanel _periodSelector; |
||||
|
private Button _hourUpButton; |
||||
|
private Button _minuteUpButton; |
||||
|
private Button _periodUpButton; |
||||
|
private Button _hourDownButton; |
||||
|
private Button _minuteDownButton; |
||||
|
private Button _periodDownButton; |
||||
|
|
||||
|
//Backing Fields
|
||||
|
private TimeSpan _Time; |
||||
|
private int _minuteIncrement = 1; |
||||
|
private string _clockIdentifier = "12HourClock"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the minute increment in the selector
|
||||
|
/// </summary>
|
||||
|
public int MinuteIncrement |
||||
|
{ |
||||
|
get => _minuteIncrement; |
||||
|
set |
||||
|
{ |
||||
|
if (value < 1 || value > 59) |
||||
|
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59"); |
||||
|
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value); |
||||
|
InitPicker(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the current clock identifier, either 12HourClock or 24HourClock
|
||||
|
/// </summary>
|
||||
|
public string ClockIdentifier |
||||
|
{ |
||||
|
get => _clockIdentifier; |
||||
|
set |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(value) || value == "" || !(value == "12HourClock" || value == "24HourClock")) |
||||
|
throw new ArgumentException("Invalid ClockIdentifier"); |
||||
|
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value); |
||||
|
InitPicker(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the current time
|
||||
|
/// </summary>
|
||||
|
public TimeSpan Time |
||||
|
{ |
||||
|
get => _Time; |
||||
|
set |
||||
|
{ |
||||
|
SetAndRaise(TimeProperty, ref _Time, value); |
||||
|
InitPicker(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
base.OnApplyTemplate(e); |
||||
|
|
||||
|
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer"); |
||||
|
_periodHost = e.NameScope.Get<Panel>("PeriodHost"); |
||||
|
|
||||
|
_hourSelector = e.NameScope.Get<DateTimePickerPanel>("HourSelector"); |
||||
|
_minuteSelector = e.NameScope.Get<DateTimePickerPanel>("MinuteSelector"); |
||||
|
_periodSelector = e.NameScope.Get<DateTimePickerPanel>("PeriodSelector"); |
||||
|
|
||||
|
_spacer2 = e.NameScope.Get<Rectangle>("SecondSpacer"); |
||||
|
|
||||
|
_acceptButton = e.NameScope.Get<Button>("AcceptButton"); |
||||
|
_acceptButton.Click += OnAcceptButtonClicked; |
||||
|
|
||||
|
_hourUpButton = e.NameScope.Find<RepeatButton>("HourUpButton"); |
||||
|
if (_hourUpButton != null) |
||||
|
_hourUpButton.Click += OnSelectorButtonClick; |
||||
|
_hourDownButton = e.NameScope.Find<RepeatButton>("HourDownButton"); |
||||
|
if (_hourDownButton != null) |
||||
|
_hourDownButton.Click += OnSelectorButtonClick; |
||||
|
|
||||
|
_minuteUpButton = e.NameScope.Find<RepeatButton>("MinuteUpButton"); |
||||
|
if (_minuteUpButton != null) |
||||
|
_minuteUpButton.Click += OnSelectorButtonClick; |
||||
|
_minuteDownButton = e.NameScope.Find<RepeatButton>("MinuteDownButton"); |
||||
|
if (_minuteDownButton != null) |
||||
|
_minuteDownButton.Click += OnSelectorButtonClick; |
||||
|
|
||||
|
_periodUpButton = e.NameScope.Find<RepeatButton>("PeriodUpButton"); |
||||
|
if (_periodUpButton != null) |
||||
|
_periodUpButton.Click += OnSelectorButtonClick; |
||||
|
_periodDownButton = e.NameScope.Find<RepeatButton>("PeriodDownButton"); |
||||
|
if (_periodDownButton != null) |
||||
|
_periodDownButton.Click += OnSelectorButtonClick; |
||||
|
|
||||
|
_dismissButton = e.NameScope.Find<Button>("DismissButton"); |
||||
|
if (_dismissButton != null) |
||||
|
_dismissButton.Click += OnDismissButtonClicked; |
||||
|
|
||||
|
InitPicker(); |
||||
|
} |
||||
|
|
||||
|
protected override void OnKeyDown(KeyEventArgs e) |
||||
|
{ |
||||
|
switch (e.Key) |
||||
|
{ |
||||
|
case Key.Escape: |
||||
|
OnDismiss(); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
case Key.Tab: |
||||
|
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next); |
||||
|
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
case Key.Enter: |
||||
|
OnConfirmed(); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
} |
||||
|
base.OnKeyDown(e); |
||||
|
} |
||||
|
|
||||
|
protected override void OnConfirmed() |
||||
|
{ |
||||
|
var hr = _hourSelector.SelectedValue; |
||||
|
var min = _minuteSelector.SelectedValue; |
||||
|
var per = _periodSelector.SelectedValue; |
||||
|
|
||||
|
if (ClockIdentifier == "12HourClock") |
||||
|
{ |
||||
|
hr = per == 1 ? hr + 12 : per == 0 && hr == 12 ? 0 : hr; |
||||
|
} |
||||
|
|
||||
|
Time = new TimeSpan(hr, min, 0); |
||||
|
|
||||
|
base.OnConfirmed(); |
||||
|
} |
||||
|
|
||||
|
private void InitPicker() |
||||
|
{ |
||||
|
if (_pickerContainer == null) |
||||
|
return; |
||||
|
|
||||
|
bool clock12 = ClockIdentifier == "12HourClock"; |
||||
|
_hourSelector.MaximumValue = clock12 ? 12 : 23; |
||||
|
_hourSelector.MinimumValue = clock12 ? 1 : 0; |
||||
|
_hourSelector.ItemFormat = "%h"; |
||||
|
var hr = Time.Hours; |
||||
|
_hourSelector.SelectedValue = !clock12 ? hr : |
||||
|
hr > 12 ? hr - 12 : hr == 0 ? 12 : hr; |
||||
|
|
||||
|
_minuteSelector.MaximumValue = 59; |
||||
|
_minuteSelector.MinimumValue = 0; |
||||
|
_minuteSelector.Increment = MinuteIncrement; |
||||
|
_minuteSelector.SelectedValue = Time.Minutes; |
||||
|
_minuteSelector.ItemFormat = "mm"; |
||||
|
|
||||
|
_periodSelector.MaximumValue = 1; |
||||
|
_periodSelector.MinimumValue = 0; |
||||
|
_periodSelector.SelectedValue = hr >= 12 ? 1 : 0; |
||||
|
|
||||
|
SetGrid(); |
||||
|
KeyboardDevice.Instance?.SetFocusedElement(_hourSelector, NavigationMethod.Pointer, KeyModifiers.None); |
||||
|
} |
||||
|
|
||||
|
private void SetGrid() |
||||
|
{ |
||||
|
if (ClockIdentifier == "12HourClock") |
||||
|
{ |
||||
|
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*"); |
||||
|
_spacer2.IsVisible = true; |
||||
|
_periodHost.IsVisible = true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*"); |
||||
|
_spacer2.IsVisible = false; |
||||
|
_periodHost.IsVisible = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
||||
|
{ |
||||
|
OnDismiss(); |
||||
|
} |
||||
|
|
||||
|
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
||||
|
{ |
||||
|
OnConfirmed(); |
||||
|
} |
||||
|
|
||||
|
private void OnSelectorButtonClick(object sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
if (sender == _hourUpButton) |
||||
|
_hourSelector.ScrollUp(); |
||||
|
else if (sender == _hourDownButton) |
||||
|
_hourSelector.ScrollDown(); |
||||
|
else if (sender == _minuteUpButton) |
||||
|
_minuteSelector.ScrollUp(); |
||||
|
else if (sender == _minuteDownButton) |
||||
|
_minuteSelector.ScrollDown(); |
||||
|
else if (sender == _periodUpButton) |
||||
|
_periodSelector.ScrollUp(); |
||||
|
else if (sender == _periodDownButton) |
||||
|
_periodSelector.ScrollDown(); |
||||
|
} |
||||
|
|
||||
|
internal double GetOffsetForPopup() |
||||
|
{ |
||||
|
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; |
||||
|
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector.ItemHeight / 2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
public class TimePickerSelectedValueChangedEventArgs |
||||
|
{ |
||||
|
public TimeSpan? OldTime { get; } |
||||
|
public TimeSpan? NewTime { get; } |
||||
|
public TimePickerSelectedValueChangedEventArgs(TimeSpan? old, TimeSpan? newT) |
||||
|
{ |
||||
|
OldTime = old; |
||||
|
NewTime = newT; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Platform |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Hint for Window Chrome when ClientArea is Extended.
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
public enum ExtendClientAreaChromeHints |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The will be no chrome at all.
|
||||
|
/// </summary>
|
||||
|
NoChrome, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The default for the platform.
|
||||
|
/// </summary>
|
||||
|
Default = SystemChrome, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Use SystemChrome
|
||||
|
/// </summary>
|
||||
|
SystemChrome = 0x01, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
|
||||
|
/// This is because Windows Chrome can not be shown ontop of user content.
|
||||
|
/// </summary>
|
||||
|
PreferSystemChrome = 0x02, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// On OSX the titlebar is the thicker toolbar kind. Causes traffic lights to be positioned
|
||||
|
/// slightly lower than normal.
|
||||
|
/// </summary>
|
||||
|
OSXThickTitleBar = 0x08, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System.Linq; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest |
||||
|
{ |
||||
|
public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual) |
||||
|
{ |
||||
|
foreach (var v in visual.GetVisualAncestors()) |
||||
|
if (v is VisualLayerManager vlm) |
||||
|
if (vlm.OverlayLayer != null) |
||||
|
return vlm.ChromeOverlayLayer; |
||||
|
|
||||
|
if (visual is TopLevel tl) |
||||
|
{ |
||||
|
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault(); |
||||
|
return layers?.ChromeOverlayLayer; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public bool HitTest(Point point) => Children.HitTestCustom(point); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
<Style Selector="CaptionButtons"> |
||||
|
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/> |
||||
|
<Setter Property="MaxHeight" Value="30" /> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal"> |
||||
|
<StackPanel.Styles> |
||||
|
<Style Selector="Panel"> |
||||
|
<Setter Property="Width" Value="45" /> |
||||
|
<Setter Property="Background" Value="Transparent" /> |
||||
|
</Style> |
||||
|
<Style Selector="Panel:pointerover"> |
||||
|
<Setter Property="Background" Value="#7F7f7f7f" /> |
||||
|
</Style> |
||||
|
<Style Selector="Panel#PART_CloseButton:pointerover"> |
||||
|
<Setter Property="Background" Value="#7FFF0000" /> |
||||
|
</Style> |
||||
|
<Style Selector="Viewbox"> |
||||
|
<Setter Property="Width" Value="11" /> |
||||
|
<Setter Property="Margin" Value="2" /> |
||||
|
</Style> |
||||
|
</StackPanel.Styles> |
||||
|
<Panel x:Name="PART_FullScreenButton"> |
||||
|
<Viewbox> |
||||
|
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" /> |
||||
|
</Viewbox> |
||||
|
</Panel> |
||||
|
|
||||
|
<Panel x:Name="PART_MinimiseButton"> |
||||
|
<Viewbox> |
||||
|
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" /> |
||||
|
</Viewbox> |
||||
|
</Panel> |
||||
|
|
||||
|
<Panel x:Name="PART_RestoreButton"> |
||||
|
<Viewbox> |
||||
|
<Viewbox.RenderTransform> |
||||
|
<RotateTransform Angle="-90" /> |
||||
|
</Viewbox.RenderTransform> |
||||
|
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/> |
||||
|
</Viewbox> |
||||
|
</Panel> |
||||
|
|
||||
|
<Panel x:Name="PART_CloseButton"> |
||||
|
<Viewbox> |
||||
|
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
||||
|
</Viewbox> |
||||
|
</Panel> |
||||
|
</StackPanel> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons Panel#PART_RestoreButton Path"> |
||||
|
<Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" /> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path"> |
||||
|
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path"> |
||||
|
<Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path"> |
||||
|
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
</Styles> |
||||
@ -0,0 +1,53 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
<Design.PreviewWith> |
||||
|
<Border> |
||||
|
<TitleBar Background="SkyBlue" Height="30" Width="300" Foreground="Black" /> |
||||
|
</Border> |
||||
|
</Design.PreviewWith> |
||||
|
<Style Selector="TitleBar"> |
||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> |
||||
|
<Setter Property="VerticalAlignment" Value="Top" /> |
||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
||||
|
<Setter Property="Background" Value="Transparent" /> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="Stretch"> |
||||
|
<Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" /> |
||||
|
<Panel x:Name="PART_Container"> |
||||
|
<Border x:Name="PART_Background" Background="{TemplateBinding Background}" /> |
||||
|
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" /> |
||||
|
</Panel> |
||||
|
</Panel> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen"> |
||||
|
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar /template/ Border#PART_Background"> |
||||
|
<Setter Property="IsHitTestVisible" Value="False" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen /template/ Border#PART_Background"> |
||||
|
<Setter Property="IsHitTestVisible" Value="True" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_MouseTracker"> |
||||
|
<Setter Property="Background" Value="Transparent" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_Container"> |
||||
|
<Setter Property="RenderTransform" Value="translateY(-30px)" /> |
||||
|
<Setter Property="Transitions"> |
||||
|
<Transitions> |
||||
|
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.25" /> |
||||
|
</Transitions> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen:pointerover /template/ Panel#PART_Container"> |
||||
|
<Setter Property="RenderTransform" Value="none" /> |
||||
|
</Style> |
||||
|
</Styles> |
||||
@ -0,0 +1,68 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
<Style Selector="CaptionButtons"> |
||||
|
<Setter Property="MaxHeight" Value="30" /> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal"> |
||||
|
<StackPanel.Styles> |
||||
|
<Style Selector="Panel"> |
||||
|
<Setter Property="Width" Value="45" /> |
||||
|
<Setter Property="Background" Value="Transparent" /> |
||||
|
</Style> |
||||
|
<Style Selector="Panel:pointerover"> |
||||
|
<Setter Property="Background" Value="#7F7f7f7f" /> |
||||
|
</Style> |
||||
|
<Style Selector="Panel#PART_CloseButton:pointerover"> |
||||
|
<Setter Property="Background" Value="#7FFF0000" /> |
||||
|
</Style> |
||||
|
<Style Selector="Viewbox"> |
||||
|
<Setter Property="Width" Value="11" /> |
||||
|
<Setter Property="Margin" Value="2" /> |
||||
|
</Style> |
||||
|
</StackPanel.Styles> |
||||
|
<Panel x:Name="PART_FullScreenButton"> |
||||
|
<Viewbox> |
||||
|
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" /> |
||||
|
</Viewbox> |
||||
|
</Panel> |
||||
|
|
||||
|
<Panel x:Name="PART_MinimiseButton"> |
||||
|
<Viewbox> |
||||
|
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" /> |
||||
|
</Viewbox> |
||||
|
</Panel> |
||||
|
|
||||
|
<Panel x:Name="PART_RestoreButton"> |
||||
|
<Viewbox> |
||||
|
<Viewbox.RenderTransform> |
||||
|
<RotateTransform Angle="-90" /> |
||||
|
</Viewbox.RenderTransform> |
||||
|
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/> |
||||
|
</Viewbox> |
||||
|
</Panel> |
||||
|
|
||||
|
<Panel x:Name="PART_CloseButton"> |
||||
|
<Viewbox> |
||||
|
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
||||
|
</Viewbox> |
||||
|
</Panel> |
||||
|
</StackPanel> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons Panel#PART_RestoreButton Path"> |
||||
|
<Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" /> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path"> |
||||
|
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path"> |
||||
|
<Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path"> |
||||
|
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
||||
|
</Style> |
||||
|
<Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
</Styles> |
||||
@ -0,0 +1,338 @@ |
|||||
|
<!-- |
||||
|
// (c) Copyright Microsoft Corporation. |
||||
|
// This source is subject to the Microsoft Public License (Ms-PL). |
||||
|
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. |
||||
|
// All other rights reserved. |
||||
|
--> |
||||
|
|
||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:sys="clr-namespace:System;assembly=netstandard"> |
||||
|
<Styles.Resources> |
||||
|
<Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness> |
||||
|
<x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double> |
||||
|
<x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double> |
||||
|
<x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> |
||||
|
<x:Double x:Key="DatePickerThemeMinWidth">296</x:Double> |
||||
|
<x:Double x:Key="DatePickerThemeMaxWidth">456</x:Double> |
||||
|
<Thickness x:Key="DatePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness> |
||||
|
<Thickness x:Key="DatePickerFlyoutPresenterMonthPadding">9,3,0,6</Thickness> |
||||
|
<Thickness x:Key="DatePickerHostPadding">0,3,0,6</Thickness> |
||||
|
<Thickness x:Key="DatePickerHostMonthPadding">9,3,0,6</Thickness> |
||||
|
<x:Double x:Key="DatePickerSpacerThemeWidth">1</x:Double> |
||||
|
</Styles.Resources> |
||||
|
|
||||
|
<!-- Styles for the items displayed in the selectors --> |
||||
|
<Style Selector="ListBoxItem.DateTimePickerItem"> |
||||
|
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterItemPadding}"/> |
||||
|
<Setter Property="VerticalContentAlignment" Value="Center" /> |
||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" /> |
||||
|
</Style> |
||||
|
<Style Selector="ListBoxItem.DateTimePickerItem:selected"> |
||||
|
<Setter Property="IsHitTestVisible" Value="False"/> |
||||
|
</Style> |
||||
|
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ Rectangle#PressedBackground"> |
||||
|
<Setter Property="Fill" Value="Transparent"/> |
||||
|
</Style> |
||||
|
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="Transparent" /> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> |
||||
|
</Style> |
||||
|
<Style Selector="ListBoxItem.DateTimePickerItem.MonthItem"> |
||||
|
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterMonthPadding}"/> |
||||
|
<Setter Property="VerticalContentAlignment" Value="Center" /> |
||||
|
<Setter Property="HorizontalContentAlignment" Value="Left" /> |
||||
|
</Style> |
||||
|
|
||||
|
|
||||
|
<!-- This is used for both the accept/dismiss & repeatbuttons in the presenter--> |
||||
|
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle"> |
||||
|
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackground}" /> |
||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/> |
||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<!-- |
||||
|
The background is doubled here for the loopingselector up/down repeat buttons |
||||
|
that appear opaque. Not sure how MS does it though I suspect this is it |
||||
|
but source isn't MIT yet, so this is my solution --> |
||||
|
<Border Background="{TemplateBinding Background}"> |
||||
|
<ContentPresenter x:Name="ContentPresenter" |
||||
|
Background="{TemplateBinding Background}" |
||||
|
BorderBrush="{DynamicResource DateTimePickerFlyoutButtonBorderBrush}" |
||||
|
BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}" |
||||
|
Content="{TemplateBinding Content}" |
||||
|
TextBlock.Foreground="{DynamicResource SystemControlHighlightAltBaseHighBrush}" |
||||
|
ContentTemplate="{TemplateBinding ContentTemplate}" |
||||
|
Padding="{TemplateBinding Padding}" |
||||
|
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" |
||||
|
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" |
||||
|
CornerRadius="{DynamicResource ControlCornerRadius}"/> |
||||
|
</Border> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pointerover /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPointerOver}"/> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPointerOver}"/> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPointerOver}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pressed /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPressed}"/> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPressed}"/> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPressed}"/> |
||||
|
</Style> |
||||
|
|
||||
|
|
||||
|
<Style Selector="RepeatButton.UpButton"> |
||||
|
<Setter Property="VerticalAlignment" Value="Top"/> |
||||
|
<Setter Property="Height" Value="22" /> |
||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
||||
|
<Setter Property="Focusable" Value="False" /> |
||||
|
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" /> |
||||
|
<Setter Property="Content"> |
||||
|
<Template> |
||||
|
<Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center"> |
||||
|
<Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,9 L 9,0 L 18,9"/> |
||||
|
</Viewbox> |
||||
|
</Template> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
<Style Selector="RepeatButton.DownButton"> |
||||
|
<Setter Property="VerticalAlignment" Value="Bottom"/> |
||||
|
<Setter Property="Height" Value="22" /> |
||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
||||
|
<Setter Property="Focusable" Value="False" /> |
||||
|
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" /> |
||||
|
<Setter Property="Content"> |
||||
|
<Template> |
||||
|
<Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center"> |
||||
|
<Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,0 L 9,9 L 18,0"/> |
||||
|
</Viewbox> |
||||
|
</Template> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="DatePicker"> |
||||
|
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
||||
|
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> |
||||
|
<Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForeground}" /> |
||||
|
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackground}"/> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrush}"/> |
||||
|
<Setter Property="BorderThickness" Value="1"/> |
||||
|
<Setter Property="HorizontalAlignment" Value="Left" /> |
||||
|
<Setter Property="VerticalAlignment" Value="Center" /> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*"> |
||||
|
<ContentPresenter Name="HeaderContentPresenter" Grid.Row="0" |
||||
|
Content="{TemplateBinding Header}" |
||||
|
ContentTemplate="{TemplateBinding HeaderTemplate}" |
||||
|
Margin="{DynamicResource DatePickerTopHeaderMargin}" |
||||
|
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Top"/> |
||||
|
|
||||
|
<Button Name="FlyoutButton" Grid.Row="1" |
||||
|
Foreground="{TemplateBinding Foreground}" |
||||
|
Background="{TemplateBinding Background}" |
||||
|
BorderBrush="{TemplateBinding BorderBrush}" |
||||
|
BorderThickness="{TemplateBinding BorderThickness}" |
||||
|
IsEnabled="{TemplateBinding IsEnabled}" |
||||
|
MinWidth="{StaticResource DatePickerThemeMinWidth}" |
||||
|
MaxWidth="{StaticResource DatePickerThemeMaxWidth}" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
HorizontalContentAlignment="Stretch" |
||||
|
VerticalContentAlignment="Stretch" |
||||
|
TemplatedControl.IsTemplateFocusTarget="True"> |
||||
|
<Button.Template> |
||||
|
<ControlTemplate> |
||||
|
<ContentPresenter Name="ContentPresenter" |
||||
|
BorderBrush="{TemplateBinding BorderBrush}" |
||||
|
Background="{TemplateBinding Background}" |
||||
|
BorderThickness="{TemplateBinding BorderThickness}" |
||||
|
Content="{TemplateBinding Content}" |
||||
|
TextBlock.Foreground="{TemplateBinding Foreground}" |
||||
|
HorizontalContentAlignment="Stretch" |
||||
|
VerticalContentAlignment="Stretch" |
||||
|
CornerRadius="{DynamicResource ControlCornerRadius}"/> |
||||
|
</ControlTemplate> |
||||
|
</Button.Template> |
||||
|
<Grid Name="ButtonContentGrid" ColumnDefinitions="78*,Auto,132*,Auto,78*"> |
||||
|
<TextBlock Name="DayText" Text="day" HorizontalAlignment="Center" |
||||
|
Padding="{DynamicResource DatePickerHostPadding}" |
||||
|
FontFamily="{TemplateBinding FontFamily}" |
||||
|
FontWeight="{TemplateBinding FontWeight}" |
||||
|
FontSize="{TemplateBinding FontSize}"/> |
||||
|
<TextBlock Name="MonthText" Text="month" TextAlignment="Left" |
||||
|
Padding="{DynamicResource DatePickerHostMonthPadding}" |
||||
|
FontFamily="{TemplateBinding FontFamily}" |
||||
|
FontWeight="{TemplateBinding FontWeight}" |
||||
|
FontSize="{TemplateBinding FontSize}"/> |
||||
|
<TextBlock Name="YearText" Text="year" HorizontalAlignment="Center" |
||||
|
Padding="{DynamicResource DatePickerHostPadding}" |
||||
|
FontFamily="{TemplateBinding FontFamily}" |
||||
|
FontWeight="{TemplateBinding FontWeight}" |
||||
|
FontSize="{TemplateBinding FontSize}"/> |
||||
|
<Rectangle x:Name="FirstSpacer" |
||||
|
Fill="{DynamicResource DatePickerSpacerFill}" |
||||
|
HorizontalAlignment="Center" |
||||
|
Width="1" |
||||
|
Grid.Column="1" /> |
||||
|
<Rectangle x:Name="SecondSpacer" |
||||
|
Fill="{DynamicResource DatePickerSpacerFill}" |
||||
|
HorizontalAlignment="Center" |
||||
|
Width="1" |
||||
|
Grid.Column="3" /> |
||||
|
</Grid> |
||||
|
</Button> |
||||
|
|
||||
|
<Popup Name="Popup" WindowManagerAddShadowHint="False" |
||||
|
StaysOpen="False" PlacementTarget="{TemplateBinding}" |
||||
|
PlacementMode="Bottom"> |
||||
|
<DatePickerPresenter Name="PickerPresenter" /> |
||||
|
</Popup> |
||||
|
|
||||
|
</Grid> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
<Style Selector="DatePicker /template/ ContentPresenter#HeaderContentPresenter"> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerHeaderForeground}"/> |
||||
|
</Style> |
||||
|
<Style Selector="DatePicker:disabled /template/ Rectangle"> |
||||
|
<Setter Property="Fill" Value="{DynamicResource DatePickerSpacerFillDisabled}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="DatePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter"> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPointerOver}"/> |
||||
|
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPointerOver}"/> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPointerOver}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="DatePicker /template/ Button#FlyoutButton:pressed /template/ ContentPresenter"> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPressed}"/> |
||||
|
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPressed}"/> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPressed}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="DatePicker /template/ Button#FlyoutButton:disabled /template/ ContentPresenter"> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushDisabled}"/> |
||||
|
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundDisabled}"/> |
||||
|
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Changes foreground for watermark text when SelectedDate is null--> |
||||
|
<Style Selector="DatePicker:hasnodate /template/ Button#FlyoutButton TextBlock"> |
||||
|
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!--WinUI: DatePickerFlyoutPresenter--> |
||||
|
<Style Selector="DatePickerPresenter"> |
||||
|
<Setter Property="Width" Value="296" /> |
||||
|
<Setter Property="MinWidth" Value="296" /> |
||||
|
<Setter Property="MaxHeight" Value="398" /> |
||||
|
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
||||
|
<Setter Property="FontWeight" Value="Normal" /> |
||||
|
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> |
||||
|
<Setter Property="Background" Value="{DynamicResource DatePickerFlyoutPresenterBackground}" /> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerFlyoutPresenterBorderBrush}" /> |
||||
|
<Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" /> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Border Name="Background" Background="{TemplateBinding Background}" |
||||
|
BorderBrush="{TemplateBinding BorderBrush}" |
||||
|
BorderThickness="{TemplateBinding BorderThickness}" |
||||
|
Padding="{DynamicResource DateTimeFlyoutBorderPadding}" |
||||
|
MaxHeight="398" CornerRadius="{DynamicResource OverlayCornerRadius}"> |
||||
|
<Grid Name="ContentRoot" RowDefinitions="*,Auto"> |
||||
|
<Grid Name="PickerContainer"> |
||||
|
<!--Column Definitions set in code, ignore here--> |
||||
|
<Panel Name="MonthHost"> |
||||
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
||||
|
VerticalScrollBarVisibility="Hidden"> |
||||
|
<DateTimePickerPanel Name="MonthSelector" |
||||
|
PanelType="Month" |
||||
|
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" |
||||
|
ShouldLoop="True" /> |
||||
|
</ScrollViewer> |
||||
|
<RepeatButton Name="MonthUpButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
||||
|
<RepeatButton Name="MonthDownButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
||||
|
</Panel> |
||||
|
<Panel Name="DayHost"> |
||||
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
||||
|
VerticalScrollBarVisibility="Hidden"> |
||||
|
<DateTimePickerPanel Name="DaySelector" |
||||
|
PanelType="Day" |
||||
|
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" |
||||
|
ShouldLoop="True" /> |
||||
|
</ScrollViewer> |
||||
|
<RepeatButton Name="DayUpButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
||||
|
<RepeatButton Name="DayDownButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
||||
|
</Panel> |
||||
|
<Panel Name="YearHost"> |
||||
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
||||
|
VerticalScrollBarVisibility="Hidden"> |
||||
|
<DateTimePickerPanel Name="YearSelector" |
||||
|
PanelType="Year" |
||||
|
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" |
||||
|
ShouldLoop="False" /> |
||||
|
</ScrollViewer> |
||||
|
<RepeatButton Name="YearUpButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
||||
|
<RepeatButton Name="YearDownButton" |
||||
|
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
||||
|
</Panel> |
||||
|
<Rectangle Name="HighlightRect" IsHitTestVisible="False" ZIndex="-1" |
||||
|
Fill="{DynamicResource DatePickerFlyoutPresenterHighlightFill}" |
||||
|
Grid.Column="0" Grid.ColumnSpan="5" VerticalAlignment="Center" |
||||
|
Height="{DynamicResource DatePickerFlyoutPresenterHighlightHeight}" /> |
||||
|
<Rectangle Name="FirstSpacer" |
||||
|
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}" |
||||
|
HorizontalAlignment="Center" |
||||
|
Width="{DynamicResource DatePickerSpacerThemeWidth}" |
||||
|
Grid.Column="1" /> |
||||
|
<Rectangle Name="SecondSpacer" |
||||
|
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}" |
||||
|
HorizontalAlignment="Center" |
||||
|
Width="{DynamicResource DatePickerSpacerThemeWidth}" |
||||
|
Grid.Column="3" /> |
||||
|
</Grid> |
||||
|
<Grid Grid.Row="1" Height="{DynamicResource DatePickerFlyoutPresenterAcceptDismissHostGridHeight}" |
||||
|
Name="AcceptDismissGrid" ColumnDefinitions="*,*"> |
||||
|
<Rectangle Height="{DynamicResource DatePickerSpacerThemeWidth}" VerticalAlignment="Top" |
||||
|
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}" |
||||
|
Grid.ColumnSpan="2"/> |
||||
|
<Button Name="AcceptButton" Grid.Column="0" Classes="DateTimeFlyoutButtonStyle" |
||||
|
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
||||
|
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round" |
||||
|
StrokeThickness="0.75" Data="M0.5,8.5 5,13.5 15.5,3" /> |
||||
|
</Button> |
||||
|
<Button Name="DismissButton" Grid.Column="1" Classes="DateTimeFlyoutButtonStyle" |
||||
|
FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
||||
|
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round" |
||||
|
StrokeThickness="0.75" Data="M2,2 14,14 M2,14 14 2" /> |
||||
|
</Button> |
||||
|
</Grid> |
||||
|
</Grid> |
||||
|
</Border> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="DatePickerPresenter /template/ Panel RepeatButton"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="DatePickerPresenter /template/ Panel:pointerover RepeatButton"> |
||||
|
<Setter Property="IsVisible" Value="True" /> |
||||
|
</Style> |
||||
|
|
||||
|
</Styles> |
||||
@ -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,53 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
<Design.PreviewWith> |
||||
|
<Border> |
||||
|
<TitleBar Background="SkyBlue" Height="30" Width="300" Foreground="Black" /> |
||||
|
</Border> |
||||
|
</Design.PreviewWith> |
||||
|
<Style Selector="TitleBar"> |
||||
|
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> |
||||
|
<Setter Property="VerticalAlignment" Value="Top" /> |
||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
||||
|
<Setter Property="Background" Value="Transparent" /> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="Stretch"> |
||||
|
<Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" /> |
||||
|
<Panel x:Name="PART_Container"> |
||||
|
<Border x:Name="PART_Background" Background="{TemplateBinding Background}" /> |
||||
|
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" /> |
||||
|
</Panel> |
||||
|
</Panel> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen"> |
||||
|
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar /template/ Border#PART_Background"> |
||||
|
<Setter Property="IsHitTestVisible" Value="False" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen /template/ Border#PART_Background"> |
||||
|
<Setter Property="IsHitTestVisible" Value="True" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_MouseTracker"> |
||||
|
<Setter Property="Background" Value="Transparent" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_Container"> |
||||
|
<Setter Property="RenderTransform" Value="translateY(-30px)" /> |
||||
|
<Setter Property="Transitions"> |
||||
|
<Transitions> |
||||
|
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.25" /> |
||||
|
</Transitions> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="TitleBar:fullscreen:pointerover /template/ Panel#PART_Container"> |
||||
|
<Setter Property="RenderTransform" Value="none" /> |
||||
|
</Style> |
||||
|
</Styles> |
||||
@ -1 +1 @@ |
|||||
Subproject commit 068162245473ec39ee36da12150e928072b96403 |
Subproject commit 0028377ce7c7dc21f9fe71b45f62a95991b1ab58 |
||||
@ -0,0 +1,539 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Platform; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Win32.Input; |
||||
|
using static Avalonia.Win32.Interop.UnmanagedMethods; |
||||
|
|
||||
|
namespace Avalonia.Win32 |
||||
|
{ |
||||
|
public partial class WindowImpl |
||||
|
{ |
||||
|
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", |
||||
|
Justification = "Using Win32 naming for consistency.")] |
||||
|
protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) |
||||
|
{ |
||||
|
const double wheelDelta = 120.0; |
||||
|
uint timestamp = unchecked((uint)GetMessageTime()); |
||||
|
|
||||
|
RawInputEventArgs e = null; |
||||
|
var shouldTakeFocus = false; |
||||
|
|
||||
|
switch ((WindowsMessage)msg) |
||||
|
{ |
||||
|
case WindowsMessage.WM_ACTIVATE: |
||||
|
{ |
||||
|
var wa = (WindowActivate)(ToInt32(wParam) & 0xffff); |
||||
|
|
||||
|
switch (wa) |
||||
|
{ |
||||
|
case WindowActivate.WA_ACTIVE: |
||||
|
case WindowActivate.WA_CLICKACTIVE: |
||||
|
{ |
||||
|
Activated?.Invoke(); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowActivate.WA_INACTIVE: |
||||
|
{ |
||||
|
Deactivated?.Invoke(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_NCCALCSIZE: |
||||
|
{ |
||||
|
if (ToInt32(wParam) == 1 && !HasFullDecorations || _isClientAreaExtended) |
||||
|
{ |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_CLOSE: |
||||
|
{ |
||||
|
bool? preventClosing = Closing?.Invoke(); |
||||
|
if (preventClosing == true) |
||||
|
{ |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_DESTROY: |
||||
|
{ |
||||
|
//Window doesn't exist anymore
|
||||
|
_hwnd = IntPtr.Zero; |
||||
|
//Remove root reference to this class, so unmanaged delegate can be collected
|
||||
|
s_instances.Remove(this); |
||||
|
Closed?.Invoke(); |
||||
|
|
||||
|
_mouseDevice.Dispose(); |
||||
|
_touchDevice?.Dispose(); |
||||
|
//Free other resources
|
||||
|
Dispose(); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_DPICHANGED: |
||||
|
{ |
||||
|
var dpi = ToInt32(wParam) & 0xffff; |
||||
|
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam); |
||||
|
_scaling = dpi / 96.0; |
||||
|
ScalingChanged?.Invoke(_scaling); |
||||
|
SetWindowPos(hWnd, |
||||
|
IntPtr.Zero, |
||||
|
newDisplayRect.left, |
||||
|
newDisplayRect.top, |
||||
|
newDisplayRect.right - newDisplayRect.left, |
||||
|
newDisplayRect.bottom - newDisplayRect.top, |
||||
|
SetWindowPosFlags.SWP_NOZORDER | |
||||
|
SetWindowPosFlags.SWP_NOACTIVATE); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_KEYDOWN: |
||||
|
case WindowsMessage.WM_SYSKEYDOWN: |
||||
|
{ |
||||
|
e = new RawKeyEventArgs( |
||||
|
WindowsKeyboardDevice.Instance, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
RawKeyEventType.KeyDown, |
||||
|
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), |
||||
|
WindowsKeyboardDevice.Instance.Modifiers); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MENUCHAR: |
||||
|
{ |
||||
|
// mute the system beep
|
||||
|
return (IntPtr)((int)MenuCharParam.MNC_CLOSE << 16); |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_KEYUP: |
||||
|
case WindowsMessage.WM_SYSKEYUP: |
||||
|
{ |
||||
|
e = new RawKeyEventArgs( |
||||
|
WindowsKeyboardDevice.Instance, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
RawKeyEventType.KeyUp, |
||||
|
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), |
||||
|
WindowsKeyboardDevice.Instance.Modifiers); |
||||
|
break; |
||||
|
} |
||||
|
case WindowsMessage.WM_CHAR: |
||||
|
{ |
||||
|
// Ignore control chars
|
||||
|
if (ToInt32(wParam) >= 32) |
||||
|
{ |
||||
|
e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner, |
||||
|
new string((char)ToInt32(wParam), 1)); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_LBUTTONDOWN: |
||||
|
case WindowsMessage.WM_RBUTTONDOWN: |
||||
|
case WindowsMessage.WM_MBUTTONDOWN: |
||||
|
case WindowsMessage.WM_XBUTTONDOWN: |
||||
|
{ |
||||
|
shouldTakeFocus = ShouldTakeFocusOnClick; |
||||
|
if (ShouldIgnoreTouchEmulatedMessage()) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
(WindowsMessage)msg switch |
||||
|
{ |
||||
|
WindowsMessage.WM_LBUTTONDOWN => RawPointerEventType.LeftButtonDown, |
||||
|
WindowsMessage.WM_RBUTTONDOWN => RawPointerEventType.RightButtonDown, |
||||
|
WindowsMessage.WM_MBUTTONDOWN => RawPointerEventType.MiddleButtonDown, |
||||
|
WindowsMessage.WM_XBUTTONDOWN => |
||||
|
HighWord(ToInt32(wParam)) == 1 ? |
||||
|
RawPointerEventType.XButton1Down : |
||||
|
RawPointerEventType.XButton2Down |
||||
|
}, |
||||
|
DipFromLParam(lParam), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_LBUTTONUP: |
||||
|
case WindowsMessage.WM_RBUTTONUP: |
||||
|
case WindowsMessage.WM_MBUTTONUP: |
||||
|
case WindowsMessage.WM_XBUTTONUP: |
||||
|
{ |
||||
|
shouldTakeFocus = ShouldTakeFocusOnClick; |
||||
|
if (ShouldIgnoreTouchEmulatedMessage()) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
(WindowsMessage)msg switch |
||||
|
{ |
||||
|
WindowsMessage.WM_LBUTTONUP => RawPointerEventType.LeftButtonUp, |
||||
|
WindowsMessage.WM_RBUTTONUP => RawPointerEventType.RightButtonUp, |
||||
|
WindowsMessage.WM_MBUTTONUP => RawPointerEventType.MiddleButtonUp, |
||||
|
WindowsMessage.WM_XBUTTONUP => |
||||
|
HighWord(ToInt32(wParam)) == 1 ? |
||||
|
RawPointerEventType.XButton1Up : |
||||
|
RawPointerEventType.XButton2Up, |
||||
|
}, |
||||
|
DipFromLParam(lParam), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOUSEMOVE: |
||||
|
{ |
||||
|
if (ShouldIgnoreTouchEmulatedMessage()) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (!_trackingMouse) |
||||
|
{ |
||||
|
var tm = new TRACKMOUSEEVENT |
||||
|
{ |
||||
|
cbSize = Marshal.SizeOf<TRACKMOUSEEVENT>(), |
||||
|
dwFlags = 2, |
||||
|
hwndTrack = _hwnd, |
||||
|
dwHoverTime = 0, |
||||
|
}; |
||||
|
|
||||
|
TrackMouseEvent(ref tm); |
||||
|
} |
||||
|
|
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
RawPointerEventType.Move, |
||||
|
DipFromLParam(lParam), GetMouseModifiers(wParam)); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOUSEWHEEL: |
||||
|
{ |
||||
|
e = new RawMouseWheelEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
PointToClient(PointFromLParam(lParam)), |
||||
|
new Vector(0, (ToInt32(wParam) >> 16) / wheelDelta), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOUSEHWHEEL: |
||||
|
{ |
||||
|
e = new RawMouseWheelEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
PointToClient(PointFromLParam(lParam)), |
||||
|
new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOUSELEAVE: |
||||
|
{ |
||||
|
_trackingMouse = false; |
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
RawPointerEventType.LeaveWindow, |
||||
|
new Point(-1, -1), WindowsKeyboardDevice.Instance.Modifiers); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_NCLBUTTONDOWN: |
||||
|
case WindowsMessage.WM_NCRBUTTONDOWN: |
||||
|
case WindowsMessage.WM_NCMBUTTONDOWN: |
||||
|
case WindowsMessage.WM_NCXBUTTONDOWN: |
||||
|
{ |
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
(WindowsMessage)msg switch |
||||
|
{ |
||||
|
WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType |
||||
|
.NonClientLeftButtonDown, |
||||
|
WindowsMessage.WM_NCRBUTTONDOWN => RawPointerEventType.RightButtonDown, |
||||
|
WindowsMessage.WM_NCMBUTTONDOWN => RawPointerEventType.MiddleButtonDown, |
||||
|
WindowsMessage.WM_NCXBUTTONDOWN => |
||||
|
HighWord(ToInt32(wParam)) == 1 ? |
||||
|
RawPointerEventType.XButton1Down : |
||||
|
RawPointerEventType.XButton2Down, |
||||
|
}, |
||||
|
PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
case WindowsMessage.WM_TOUCH: |
||||
|
{ |
||||
|
var touchInputCount = wParam.ToInt32(); |
||||
|
|
||||
|
var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount]; |
||||
|
var touchInputs = new Span<TOUCHINPUT>(pTouchInputs, touchInputCount); |
||||
|
|
||||
|
if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf<TOUCHINPUT>())) |
||||
|
{ |
||||
|
foreach (var touchInput in touchInputs) |
||||
|
{ |
||||
|
Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, |
||||
|
_owner, |
||||
|
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ? |
||||
|
RawPointerEventType.TouchEnd : |
||||
|
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ? |
||||
|
RawPointerEventType.TouchBegin : |
||||
|
RawPointerEventType.TouchUpdate, |
||||
|
PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)), |
||||
|
WindowsKeyboardDevice.Instance.Modifiers, |
||||
|
touchInput.Id)); |
||||
|
} |
||||
|
|
||||
|
CloseTouchInputHandle(lParam); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
case WindowsMessage.WM_NCPAINT: |
||||
|
{ |
||||
|
if (!HasFullDecorations) |
||||
|
{ |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_NCACTIVATE: |
||||
|
{ |
||||
|
if (!HasFullDecorations) |
||||
|
{ |
||||
|
return new IntPtr(1); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_PAINT: |
||||
|
{ |
||||
|
using (_rendererLock.Lock()) |
||||
|
{ |
||||
|
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) |
||||
|
{ |
||||
|
var f = Scaling; |
||||
|
var r = ps.rcPaint; |
||||
|
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, |
||||
|
(r.bottom - r.top) / f)); |
||||
|
EndPaint(_hwnd, ref ps); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_SIZE: |
||||
|
{ |
||||
|
using (_rendererLock.Lock()) |
||||
|
{ |
||||
|
// Do nothing here, just block until the pending frame render is completed on the render thread
|
||||
|
} |
||||
|
|
||||
|
var size = (SizeCommand)wParam; |
||||
|
|
||||
|
if (Resized != null && |
||||
|
(size == SizeCommand.Restored || |
||||
|
size == SizeCommand.Maximized)) |
||||
|
{ |
||||
|
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); |
||||
|
Resized(clientSize / Scaling); |
||||
|
} |
||||
|
|
||||
|
var windowState = size == SizeCommand.Maximized ? |
||||
|
WindowState.Maximized : |
||||
|
(size == SizeCommand.Minimized ? WindowState.Minimized : WindowState.Normal); |
||||
|
|
||||
|
if (windowState != _lastWindowState) |
||||
|
{ |
||||
|
_lastWindowState = windowState; |
||||
|
|
||||
|
WindowStateChanged?.Invoke(windowState); |
||||
|
|
||||
|
if (_isClientAreaExtended) |
||||
|
{ |
||||
|
UpdateExtendMargins(); |
||||
|
|
||||
|
ExtendClientAreaToDecorationsChanged?.Invoke(true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOVE: |
||||
|
{ |
||||
|
PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), |
||||
|
(short)(ToInt32(lParam) >> 16))); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_GETMINMAXINFO: |
||||
|
{ |
||||
|
MINMAXINFO mmi = Marshal.PtrToStructure<MINMAXINFO>(lParam); |
||||
|
|
||||
|
if (_minSize.Width > 0) |
||||
|
{ |
||||
|
mmi.ptMinTrackSize.X = |
||||
|
(int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); |
||||
|
} |
||||
|
|
||||
|
if (_minSize.Height > 0) |
||||
|
{ |
||||
|
mmi.ptMinTrackSize.Y = |
||||
|
(int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); |
||||
|
} |
||||
|
|
||||
|
if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) |
||||
|
{ |
||||
|
mmi.ptMaxTrackSize.X = |
||||
|
(int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); |
||||
|
} |
||||
|
|
||||
|
if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0) |
||||
|
{ |
||||
|
mmi.ptMaxTrackSize.Y = |
||||
|
(int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); |
||||
|
} |
||||
|
|
||||
|
Marshal.StructureToPtr(mmi, lParam, true); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_DISPLAYCHANGE: |
||||
|
{ |
||||
|
(Screen as ScreenImpl)?.InvalidateScreensCache(); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_KILLFOCUS: |
||||
|
LostFocus?.Invoke(); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
#if USE_MANAGED_DRAG
|
||||
|
if (_managedDrag.PreprocessInputEvent(ref e)) |
||||
|
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); |
||||
|
#endif
|
||||
|
|
||||
|
if(shouldTakeFocus) |
||||
|
{ |
||||
|
SetFocus(_hwnd); |
||||
|
} |
||||
|
|
||||
|
if (e != null && Input != null) |
||||
|
{ |
||||
|
Input(e); |
||||
|
|
||||
|
if (e.Handled) |
||||
|
{ |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
using (_rendererLock.Lock()) |
||||
|
{ |
||||
|
return DefWindowProc(hWnd, msg, wParam, lParam); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static int ToInt32(IntPtr ptr) |
||||
|
{ |
||||
|
if (IntPtr.Size == 4) |
||||
|
return ptr.ToInt32(); |
||||
|
|
||||
|
return (int)(ptr.ToInt64() & 0xffffffff); |
||||
|
} |
||||
|
|
||||
|
private static int HighWord(int param) => param >> 16; |
||||
|
|
||||
|
private Point DipFromLParam(IntPtr lParam) |
||||
|
{ |
||||
|
return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; |
||||
|
} |
||||
|
|
||||
|
private PixelPoint PointFromLParam(IntPtr lParam) |
||||
|
{ |
||||
|
return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)); |
||||
|
} |
||||
|
|
||||
|
private bool ShouldIgnoreTouchEmulatedMessage() |
||||
|
{ |
||||
|
if (!_multitouch) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// MI_WP_SIGNATURE
|
||||
|
// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages
|
||||
|
const long marker = 0xFF515700L; |
||||
|
|
||||
|
var info = GetMessageExtraInfo().ToInt64(); |
||||
|
return (info & marker) == marker; |
||||
|
} |
||||
|
|
||||
|
private static RawInputModifiers GetMouseModifiers(IntPtr wParam) |
||||
|
{ |
||||
|
var keys = (ModifierKeys)ToInt32(wParam); |
||||
|
var modifiers = WindowsKeyboardDevice.Instance.Modifiers; |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_LBUTTON)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.LeftMouseButton; |
||||
|
} |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_RBUTTON)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.RightMouseButton; |
||||
|
} |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_MBUTTON)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.MiddleMouseButton; |
||||
|
} |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON1)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.XButton1MouseButton; |
||||
|
} |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON2)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.XButton2MouseButton; |
||||
|
} |
||||
|
|
||||
|
return modifiers; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Input; |
||||
|
using static Avalonia.Win32.Interop.UnmanagedMethods; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
|
namespace Avalonia.Win32 |
||||
|
{ |
||||
|
public partial class WindowImpl |
||||
|
{ |
||||
|
// Hit test the frame for resizing and moving.
|
||||
|
HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) |
||||
|
{ |
||||
|
// Get the point coordinates for the hit test.
|
||||
|
var ptMouse = PointFromLParam(lParam); |
||||
|
|
||||
|
// Get the window rectangle.
|
||||
|
GetWindowRect(hWnd, out var rcWindow); |
||||
|
|
||||
|
// Get the frame rectangle, adjusted for the style without a caption.
|
||||
|
RECT rcFrame = new RECT(); |
||||
|
AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0); |
||||
|
|
||||
|
RECT border_thickness = new RECT(); |
||||
|
if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) |
||||
|
{ |
||||
|
AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); |
||||
|
border_thickness.left *= -1; |
||||
|
border_thickness.top *= -1; |
||||
|
} |
||||
|
else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) |
||||
|
{ |
||||
|
border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; |
||||
|
} |
||||
|
|
||||
|
if (_extendTitleBarHint >= 0) |
||||
|
{ |
||||
|
border_thickness.top = (int)(_extendedMargins.Top * Scaling); |
||||
|
} |
||||
|
|
||||
|
// Determine if the hit test is for resizing. Default middle (1,1).
|
||||
|
ushort uRow = 1; |
||||
|
ushort uCol = 1; |
||||
|
bool fOnResizeBorder = false; |
||||
|
|
||||
|
// Determine if the point is at the top or bottom of the window.
|
||||
|
if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + border_thickness.top) |
||||
|
{ |
||||
|
fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); |
||||
|
uRow = 0; |
||||
|
} |
||||
|
else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - border_thickness.bottom) |
||||
|
{ |
||||
|
uRow = 2; |
||||
|
} |
||||
|
|
||||
|
// Determine if the point is at the left or right of the window.
|
||||
|
if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + border_thickness.left) |
||||
|
{ |
||||
|
uCol = 0; // left side
|
||||
|
} |
||||
|
else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - border_thickness.right) |
||||
|
{ |
||||
|
uCol = 2; // right side
|
||||
|
} |
||||
|
|
||||
|
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
|
||||
|
HitTestValues[][] hitTests = new[] |
||||
|
{ |
||||
|
new []{ HitTestValues.HTTOPLEFT, fOnResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT }, |
||||
|
new []{ HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT }, |
||||
|
new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT }, |
||||
|
}; |
||||
|
|
||||
|
return hitTests[uRow][uCol]; |
||||
|
} |
||||
|
|
||||
|
protected virtual IntPtr CustomCaptionProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool callDwp) |
||||
|
{ |
||||
|
IntPtr lRet = IntPtr.Zero; |
||||
|
|
||||
|
callDwp = !DwmDefWindowProc(hWnd, msg, wParam, lParam, ref lRet); |
||||
|
|
||||
|
switch ((WindowsMessage)msg) |
||||
|
{ |
||||
|
case WindowsMessage.WM_DWMCOMPOSITIONCHANGED: |
||||
|
// TODO handle composition changed.
|
||||
|
break; |
||||
|
|
||||
|
case WindowsMessage.WM_NCHITTEST: |
||||
|
if (lRet == IntPtr.Zero) |
||||
|
{ |
||||
|
if(WindowState == WindowState.FullScreen) |
||||
|
{ |
||||
|
return (IntPtr)HitTestValues.HTCLIENT; |
||||
|
} |
||||
|
var hittestResult = HitTestNCA(hWnd, wParam, lParam); |
||||
|
|
||||
|
lRet = (IntPtr)hittestResult; |
||||
|
|
||||
|
uint timestamp = unchecked((uint)GetMessageTime()); |
||||
|
|
||||
|
if (hittestResult == HitTestValues.HTCAPTION) |
||||
|
{ |
||||
|
var position = PointToClient(PointFromLParam(lParam)); |
||||
|
|
||||
|
if (_owner is Window window) |
||||
|
{ |
||||
|
var visual = window.Renderer.HitTestFirst(position, _owner as Window, x => |
||||
|
{ |
||||
|
if (x is IInputElement ie && !ie.IsHitTestVisible) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
}); |
||||
|
|
||||
|
if (visual != null) |
||||
|
{ |
||||
|
hittestResult = HitTestValues.HTCLIENT; |
||||
|
lRet = (IntPtr)hittestResult; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (hittestResult != HitTestValues.HTNOWHERE) |
||||
|
{ |
||||
|
callDwp = false; |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return lRet; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue