Explorar o código

avalonia: Added time selector

Kenric Nugteren hai 2 meses
pai
achega
56de34a997

+ 21 - 0
InABox.Avalonia/Components/TimeSelector/TimeSelectorButton.axaml

@@ -0,0 +1,21 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+			 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+			 xmlns:components="using:InABox.Avalonia.Components"
+			 x:Class="InABox.Avalonia.Components.TimeSelectorButton"
+			 x:DataType="components:TimeSelectorButton">
+	<UserControl.Resources>
+		<components:TimeSelectorTimeSpanFormatter x:Key="timeFormatter"/>
+	</UserControl.Resources>
+	<Button Classes="Standard"
+			Command="{Binding $parent[components:TimeSelectorButton].ClickCommand}"
+			Padding="0"
+			Background="Transparent"
+			BorderThickness="0">
+		<Button.Content>
+			<MultiBinding Converter="{StaticResource timeFormatter}">
+				<Binding Path="$parent[components:TimeSelectorButton].Time"/>
+				<Binding Path="$parent[components:TimeSelectorButton]"/>
+			</MultiBinding>
+		</Button.Content>
+	</Button>
+</UserControl>

+ 104 - 0
InABox.Avalonia/Components/TimeSelector/TimeSelectorButton.axaml.cs

@@ -0,0 +1,104 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data.Converters;
+using CommunityToolkit.Mvvm.Input;
+using ExCSS;
+using InABox.Avalonia.Components.TimeSelector;
+using InABox.Avalonia.Converters;
+using InABox.Core;
+using System.Globalization;
+using System.Windows.Input;
+
+namespace InABox.Avalonia.Components;
+
+public class TimeSelectorTimeChangedEventArgs(TimeSpan? oldTime, TimeSpan? newTime)
+{
+    public TimeSpan? OldTime { get; set; } = oldTime;
+
+    public TimeSpan? NewTime { get; set; } = newTime;
+}
+
+public partial class TimeSelectorButton : UserControl
+{
+    public static readonly StyledProperty<string> PromptProperty =
+        AvaloniaProperty.Register<TimeSelectorButton, string>(nameof(Prompt));
+    public static readonly StyledProperty<string> PrefixProperty =
+        AvaloniaProperty.Register<TimeSelectorButton, string>(nameof(Prefix));
+    public static readonly StyledProperty<string> FormatProperty =
+        AvaloniaProperty.Register<TimeSelectorButton, string>(nameof(Format));
+    public static readonly StyledProperty<TimeSpan?> TimeProperty =
+        AvaloniaProperty.Register<TimeSelectorButton, TimeSpan?>(nameof(Time));
+
+    public string Prompt
+    {
+        get => GetValue(PromptProperty);
+        set => SetValue(PromptProperty, value);
+    }
+
+    public string Prefix 
+    {
+        get => GetValue(PrefixProperty);
+        set => SetValue(PrefixProperty, value);
+    }
+
+    public string Format 
+    {
+        get => GetValue(FormatProperty);
+        set => SetValue(FormatProperty, value);
+    }
+
+    public TimeSpan? Time 
+    {
+        get => GetValue(TimeProperty);
+        set => SetValue(TimeProperty, value);
+    }
+
+    public event EventHandler<TimeSelectorTimeChangedEventArgs>? TimeChanged;
+
+    static TimeSelectorButton()
+    {
+        TimeProperty.Changed.AddClassHandler<TimeSelectorButton>(TimeProperty_Changed);
+    }
+
+    private static void TimeProperty_Changed(TimeSelectorButton button, AvaloniaPropertyChangedEventArgs args)
+    {
+        button.TimeChanged?.Invoke(button, new(
+            args.OldValue is TimeSpan oldTime ? oldTime : null,
+            args.NewValue is TimeSpan newTime ? newTime : null));
+    }
+
+    public TimeSelectorButton()
+    {
+        InitializeComponent();
+    }
+
+    [RelayCommand]
+    private async Task Click()
+    {
+        var time = await Navigation.Popup<TimeSelectorViewModel, TimeSpan?>(x =>
+        {
+            x.Time = Time;
+        });
+        if (time.HasValue)
+        {
+            Time = time.Value == TimeSpan.MinValue ? null : time.Value;
+        }
+    }
+}
+
+public class TimeSelectorTimeSpanFormatter : IMultiValueConverter
+{
+    public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
+    {
+        var time = values[0] as TimeSpan?;
+        if (values[1] is not TimeSelectorButton btn) return null;
+        if (time.HasValue && time.Value != TimeSpan.MinValue)
+        {
+            var sFormat = btn.Format.NotWhiteSpaceOr("hh\\:mm tt");
+            var fmt = "{0} {1:" + sFormat + "}";
+            return string.Format(fmt, btn.Prefix, DateTime.Today.Add(time.Value)).Trim();
+        }
+        return btn.Prompt.NotWhiteSpaceOr("Select Time");
+    }
+}

+ 36 - 0
InABox.Avalonia/Components/TimeSelector/TimeSelectorView.axaml

@@ -0,0 +1,36 @@
+<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"
+			 xmlns:selector="using:InABox.Avalonia.Components.TimeSelector"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="InABox.Avalonia.Components.TimeSelector.TimeSelectorView"
+			 x:DataType="selector:TimeSelectorViewModel">
+	<Grid Margin="10">
+		<Grid.ColumnDefinitions>
+			<ColumnDefinition Width="Auto"/>
+			<ColumnDefinition Width="*"/>
+			<ColumnDefinition Width="*"/>
+			<ColumnDefinition Width="Auto"/>
+		</Grid.ColumnDefinitions>
+		<Grid.RowDefinitions>
+			<RowDefinition Height="*"/>
+			<RowDefinition Height="Auto"/>
+		</Grid.RowDefinitions>
+		<TimePickerPresenter Name="TimePicker"
+							 Time="{Binding Time}"
+							 Grid.Row="0"
+							 Grid.Column="0" Grid.ColumnSpan="4"/>
+		<!--TimePicker SelectedTime="{Binding Time}"
+					Grid.Row="0"
+					Grid.Column="0" Grid.ColumnSpan="4"/-->
+		<Button Grid.Row="1" Grid.Column="0" Command="{Binding CancelCommand}" Classes="Transparent">
+			<Image Classes="Small" Source="{SvgImage /Images/cross.svg}"/>
+		</Button>
+		<Button Grid.Row="1" Grid.Column="1" Content="Clear" Command="{Binding ClearCommand}" Classes="Standard"/>
+		<Button Grid.Row="1" Grid.Column="2" Content="Now" Command="{Binding NowCommand}" Classes="Standard"/>
+		<Button Grid.Row="1" Grid.Column="3" Command="{Binding SelectCommand}" Classes="Transparent">
+			<Image Classes="Small" Source="{SvgImage /Images/tick.svg}"/>
+		</Button>
+	</Grid>
+</UserControl>

+ 37 - 0
InABox.Avalonia/Components/TimeSelector/TimeSelectorView.axaml.cs

@@ -0,0 +1,37 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Markup.Xaml;
+
+namespace InABox.Avalonia.Components.TimeSelector;
+
+public partial class TimeSelectorView : UserControl
+{
+    public TimeSelectorView()
+    {
+        InitializeComponent();
+    }
+
+    protected override void OnInitialized()
+    {
+        base.OnInitialized();
+
+        (DataContext as TimeSelectorViewModel)!.GetTime = GetTime;
+    }
+
+    private TimeSpan? GetTime()
+    {
+        var hourSelector = TimePicker.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(x => x.Name == "PART_HourSelector");
+        var minuteSelector = TimePicker.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(x => x.Name == "PART_MinuteSelector");
+        var periodSelector = TimePicker.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(x => x.Name == "PART_PeriodSelector");
+
+        var hr = hourSelector!.SelectedValue;
+        var min = minuteSelector!.SelectedValue;
+        var per = periodSelector!.SelectedValue;
+
+        hr = per == 1 ? (hr == 12) ? 12 : hr + 12 : per == 0 && hr == 12 ? 0 : hr;
+
+        return new(hr, min, 0);
+    }
+}

+ 43 - 0
InABox.Avalonia/Components/TimeSelector/TimeSelectorViewModel.cs

@@ -0,0 +1,43 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using DialogHostAvalonia;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Avalonia.Components.TimeSelector;
+
+public partial class TimeSelectorViewModel : BasePopupViewModel<TimeSpan?>
+{
+    [ObservableProperty]
+    private TimeSpan? _time;
+
+    [ObservableProperty]
+    private Func<TimeSpan?>? _getTime;
+
+    [RelayCommand]
+    private void Cancel()
+    {
+        Close(null);
+    }
+
+    [RelayCommand]
+    private void Clear()
+    {
+        Close(TimeSpan.MinValue);
+    }
+
+    [RelayCommand]
+    private void Now()
+    {
+        Close(DateTime.Now.TimeOfDay);
+    }
+
+    [RelayCommand]
+    private void Select()
+    {
+        Close(GetTime is null ? Time : GetTime());
+    }
+}

+ 6 - 0
InABox.Avalonia/InABox.Avalonia.csproj

@@ -65,6 +65,12 @@
     </ItemGroup>
 
     <ItemGroup>
+      <Compile Update="Components\TimeSelector\TimeSelectorButton.axaml.cs">
+        <DependentUpon>TimeSelectorButton.axaml</DependentUpon>
+      </Compile>
+      <Compile Update="Components\TimeSelector\TimeSelectorView.axaml.cs">
+        <DependentUpon>TimeSelectorView.axaml</DependentUpon>
+      </Compile>
       <Compile Update="Components\DateSelector\DateSelectorButton.axaml.cs">
         <DependentUpon>DateSelectorButton.axaml</DependentUpon>
       </Compile>

+ 20 - 0
InABox.Avalonia/Theme/Classes/TimeSelectorButton.axaml

@@ -0,0 +1,20 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+		xmlns:components="using:InABox.Avalonia.Components"
+		xmlns:timeSelector="using:InABox.Avalonia.Components.TimeSelector">
+	<Style Selector="components|TimeSelectorButton">
+		<Setter Property="BorderBrush" Value="{DynamicResource PrsButtonBorder}" />
+		<Setter Property="BorderThickness">
+			<Setter.Value>
+				<DynamicResource ResourceKey="PrsBorderThickness" />
+			</Setter.Value>
+		</Setter>
+		<Setter Property="Background" Value="{DynamicResource PrsButtonBackground}"/>
+		<Setter Property="Foreground" Value="{DynamicResource PrsButtonForeground}" />
+		<Setter Property="CornerRadius" Value="{DynamicResource PrsCornerRadius}" />
+		<Setter Property="Padding" Value="10" />
+	</Style>
+	<Style Selector="timeSelector|TimeSelectorView TimePickerPresenter /template/ Grid#AcceptDismissGrid">
+		<Setter Property="IsVisible" Value="False"/>
+	</Style>
+</Styles>

+ 1 - 0
InABox.Avalonia/Theme/Styles.axaml

@@ -14,4 +14,5 @@
 	<StyleInclude Source="/Theme/Classes/TabItem.axaml" />
 	<StyleInclude Source="/Theme/Classes/TabStrip.axaml" />
 	<StyleInclude Source="/Theme/Classes/TextBox.axaml" />
+	<StyleInclude Source="/Theme/Classes/TimeSelectorButton.axaml" />
 </Styles>