feat: wip: implement year view notification.

This commit is contained in:
rabbitism
2024-06-05 00:04:03 +08:00
parent bbac227aae
commit 66a1d4ab2a
9 changed files with 245 additions and 61 deletions

View File

@@ -7,6 +7,7 @@
x:Class="Ursa.Demo.Pages.DatePickerDemo">
<StackPanel Margin="20" HorizontalAlignment="Left">
<u:CalendarMonthView />
<u:CalendarYearView />
<u:Calendar/>
</StackPanel>
</UserControl>

View File

@@ -4,7 +4,7 @@
xmlns:u="https://irihi.tech/ursa">
<Design.PreviewWith>
<StackPanel Margin="20" Spacing="5">
<u:CalendarMonthView/>
<u:CalendarMonthView />
<u:Calendar />
</StackPanel>
</Design.PreviewWith>
@@ -42,13 +42,13 @@
<Setter Property="CornerRadius" Value="0" />
<Setter Property="BorderThickness" Value="0" />
</Style>
<Style Selector="^:today">
<Setter Property="Background" Value="{DynamicResource SemiGrey1}" />
<Setter Property="Foreground" Value="{DynamicResource SemiBlue5}" />
<Setter Property="Cursor" Value="Hand" />
</Style>
<Style Selector="^:not-current-month">
<Setter Property="Foreground" Value="{DynamicResource SemiGrey3}" />
</Style>
@@ -121,6 +121,7 @@
</ControlTheme>
<ControlTheme x:Key="{x:Type u:Calendar}" TargetType="u:Calendar">
<Setter Property="MinHeight" Value="300"></Setter>
<Setter Property="Template">
<ControlTemplate TargetType="u:Calendar">
<Panel>
@@ -152,13 +153,20 @@
Foreground="{DynamicResource CalendarItemIconForeground}" />
</Button>
<Button
Name="PART_HeaderButton"
Grid.Column="2"
HorizontalContentAlignment="Center"
Content="Apr 2024"
Foreground="{TemplateBinding Foreground}"
Theme="{DynamicResource BorderlessButton}" />
<Grid ColumnDefinitions="*, *" Grid.Column="2">
<Button Grid.Column="0"
Name="{x:Static u:Calendar.PART_YearButton}"
HorizontalContentAlignment="Center"
Content="2024"
Foreground="{TemplateBinding Foreground}"
Theme="{DynamicResource BorderlessButton}" />
<Button Grid.Column="1"
Name="{x:Static u:Calendar.PART_MonthButton}"
HorizontalContentAlignment="Center"
Content="Apr"
Foreground="{TemplateBinding Foreground}"
Theme="{DynamicResource BorderlessButton}" />
</Grid>
<Button
Name="{x:Static u:Calendar.PART_NextButton}"
@@ -185,28 +193,95 @@
Foreground="{DynamicResource CalendarItemIconForeground}" />
</Button>
</Grid>
<u:CalendarMonthView Grid.Row="1" Name="{x:Static u:Calendar.PART_MonthView}"></u:CalendarMonthView>
<u:CalendarMonthView
Name="{x:Static u:Calendar.PART_MonthView}"
Grid.Row="1"
VerticalAlignment="Top"
IsVisible="{TemplateBinding IsMonthMode}" />
<u:CalendarYearView
Name="{x:Static u:Calendar.PART_YearView}"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{TemplateBinding IsMonthMode,
Converter={x:Static BoolConverters.Not}}" />
</Grid>
</Panel>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme TargetType="u:CalendarMonthView" x:Key="{x:Type u:CalendarMonthView}">
<ControlTheme x:Key="{x:Type u:CalendarMonthView}" TargetType="u:CalendarMonthView">
<Setter Property="Template">
<ControlTemplate>
<Grid Name="{x:Static u:CalendarMonthView.PART_Grid}" ColumnDefinitions="*, *, *, *, *, *, *" RowDefinitions="*, Auto, *, *, *, *, *, *">
<Grid
Name="{x:Static u:CalendarMonthView.PART_Grid}"
ColumnDefinitions="*, *, *, *, *, *, *"
RowDefinitions="*, Auto, *, *, *, *, *, *">
<Rectangle
Grid.Column="0"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="7"
Height="1"
Margin="8 8 8 0"
Margin="8,8,8,0"
HorizontalAlignment="Stretch"
Fill="{DynamicResource SemiGrey2}" />
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type u:CalendarYearView}" TargetType="u:CalendarYearView">
<Setter Property="Template">
<ControlTemplate>
<Grid
Name="{x:Static u:CalendarYearView.PART_Grid}"
ColumnDefinitions="*, *, *"
RowDefinitions="*, *, *, *" />
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type u:CalendarYearButton}" TargetType="u:CalendarYearButton">
<Setter Property="MinWidth" Value="32" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Margin" Value="0 2" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Template">
<ControlTemplate TargetType="u:CalendarDayButton">
<Panel>
<Border
Name="PART_Background"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter
Name="PART_ContentPresenter"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
Foreground="{TemplateBinding Foreground}" />
</Border>
</Panel>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover">
<Setter Property="Background" Value="{DynamicResource SemiGrey1}" />
<Setter Property="Cursor" Value="Hand" />
</Style>
<Style Selector="^:selected">
<Setter Property="Background" Value="{DynamicResource SemiBlue5}" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Foreground" Value="White" />
<Style Selector="^:pointerover">
<Setter Property="Background" Value="{DynamicResource SemiBlue6}" />
</Style>
</Style>
</ControlTheme>
</ResourceDictionary>

View File

@@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Irihi.Avalonia.Shared.Helpers;
namespace Ursa.Controls;
@@ -11,8 +12,9 @@ namespace Ursa.Controls;
[TemplatePart(PART_PreviousYearButton, typeof(Button))]
[TemplatePart(PART_NextButton, typeof(Button))]
[TemplatePart(PART_PreviousButton, typeof(Button))]
[TemplatePart(PART_YearButton, typeof(Button))]
[TemplatePart(PART_MonthButton, typeof(Button))]
[TemplatePart(PART_HeaderButton, typeof(Button))]
[TemplatePart(PART_BackButton, typeof(Button))]
[TemplatePart(PART_MonthView, typeof(CalendarMonthView))]
[TemplatePart(PART_YearView, typeof(CalendarYearView))]
public class Calendar: TemplatedControl
@@ -21,14 +23,18 @@ public class Calendar: TemplatedControl
public const string PART_PreviousYearButton = "PART_PreviousYearButton";
public const string PART_NextButton = "PART_NextButton";
public const string PART_PreviousButton = "PART_PreviousButton";
public const string PART_HeaderButton = "PART_HeaderButton";
public const string PART_BackButton = "PART_BackButton";
public const string PART_YearButton = "PART_YearButton";
public const string PART_MonthButton = "PART_MonthButton";
public const string PART_MonthView = "PART_MonthView";
public const string PART_YearView = "PART_YearView";
public const string PART_HeaderButton = "PART_HeaderButton";
private CalendarMonthView? _monthView;
private CalendarYearView? _yearView;
private DatePickerState _state = DatePickerState.None;
private Button? _yearButton;
private Button? _monthButton;
private Button? _headerButton;
public static readonly StyledProperty<DateTime> SelectedDateProperty = AvaloniaProperty.Register<Calendar, DateTime>(nameof(SelectedDate), DateTime.Now);
@@ -73,6 +79,17 @@ public class Calendar: TemplatedControl
set => SetValue(BlackoutDateRuleProperty, value);
}
private bool _isMonthMode = true;
public static readonly DirectProperty<Calendar, bool> IsMonthModeProperty = AvaloniaProperty.RegisterDirect<Calendar, bool>(
nameof(IsMonthMode), o => o.IsMonthMode, (o, v) => o.IsMonthMode = v);
public bool IsMonthMode
{
get => _isMonthMode;
set => SetAndRaise(IsMonthModeProperty, ref _isMonthMode, value);
}
internal DateTime? StartDate;
internal DateTime? EndDate;
@@ -84,13 +101,50 @@ public class Calendar: TemplatedControl
_monthView.OnDateSelected -= OnDateSelected;
_monthView.OnDatePreviewed -= OnDatePreviewed;
}
if (_yearView is not null)
{
_yearView.OnMonthSelected -= OnMonthSelected;
}
Button.ClickEvent.RemoveHandler(OnYearButtonClick, _yearButton);
Button.ClickEvent.RemoveHandler(OnMonthButtonClick, _monthButton);
_monthView = e.NameScope.Find<CalendarMonthView>(PART_MonthView);
_yearView = e.NameScope.Find<CalendarYearView>(PART_YearView);
_yearButton = e.NameScope.Find<Button>(PART_YearButton);
_monthButton = e.NameScope.Find<Button>(PART_MonthButton);
if(_monthView is not null)
{
_monthView.OnDateSelected += OnDateSelected;
_monthView.OnDatePreviewed += OnDatePreviewed;
}
if (_yearView is not null)
{
_yearView.OnMonthSelected += OnMonthSelected;
}
Button.ClickEvent.AddHandler(OnYearButtonClick, _yearButton);
Button.ClickEvent.AddHandler(OnMonthButtonClick, _monthButton);
}
private void OnMonthSelected(object sender, CalendarYearButtonEventArgs e)
{
SetCurrentValue(IsMonthModeProperty, true);
}
private void OnMonthButtonClick(object sender, RoutedEventArgs e)
{
SetCurrentValue(IsMonthModeProperty, false);
if (_yearView is null) return;
_yearView.Mode = CalendarYearViewMode.Month;
}
private void OnYearButtonClick(object sender, RoutedEventArgs e)
{
if (_yearView is null) return;
_yearView.Mode = CalendarYearViewMode.Year;
SetCurrentValue(IsMonthModeProperty, false);
}
private void OnDatePreviewed(object sender, CalendarDayButtonEventArgs e)

View File

@@ -107,7 +107,10 @@ public class CalendarMonthView : TemplatedControl
}
/// <summary>
/// Set day buttons according to context date.
/// </summary>
/// <param name="date"></param>
private void SetDayButtons(DateTime date)
{
if (_grid is null) return;
@@ -149,8 +152,7 @@ public class CalendarMonthView : TemplatedControl
if (children[i] is CalendarDayButton { DataContext: DateTime d } button && d.Month != _contextDate.Month)
button.IsNotCurrentMonth = true;
}
public event EventHandler<CalendarDayButtonEventArgs>? OnDateSelected;
public event EventHandler<CalendarDayButtonEventArgs>? OnDatePreviewed;

View File

@@ -1,14 +0,0 @@
namespace Ursa.Controls;
public class CalendarStateMachine
{
}
public enum CalendarState
{
PreviewingStartDate,
PreviewingEndDate,
SelectingStartDate,
SelectingEndDate,
}

View File

@@ -1,17 +1,25 @@
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Input;
using Avalonia.Interactivity;
using Irihi.Avalonia.Shared.Common;
namespace Ursa.Controls;
internal enum CalendarYearViewMode
{
Month,
Year,
// The button represents ten year, with one year before and one year after, 12 in total.
// The button represents 10 years.
YearRange,
}
[PseudoClasses(PC_Range, PseudoClassName.PC_Selected)]
public class CalendarYearButton: ContentControl
{
public const string PC_Range = ":range";
static CalendarYearButton()
{
PressedMixin.Attach<CalendarYearButton>();
@@ -26,6 +34,15 @@ public class CalendarYearButton: ContentControl
internal int EndYear { get; private set; }
internal CalendarYearViewMode Mode { get; private set; }
public static readonly RoutedEvent<CalendarYearButtonEventArgs> ItemSelectedEvent = RoutedEvent.Register<CalendarYearButton, CalendarYearButtonEventArgs>(
nameof(ItemSelected), RoutingStrategies.Bubble);
public event EventHandler<CalendarDayButtonEventArgs> ItemSelected
{
add => AddHandler(ItemSelectedEvent, value);
remove => RemoveHandler(ItemSelectedEvent, value);
}
internal void SetValues(CalendarYearViewMode mode, DateTime contextDate, int? month = null, int? year = null, int? startYear = null, int? endYear = null)
{
@@ -43,4 +60,12 @@ public class CalendarYearButton: ContentControl
_ => Content
};
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
RaiseEvent(new CalendarYearButtonEventArgs(Mode, Year, Month, StartYear, EndYear)
{ RoutedEvent = ItemSelectedEvent, Source = this });
}
}

View File

@@ -4,5 +4,17 @@ namespace Ursa.Controls;
public class CalendarYearButtonEventArgs: RoutedEventArgs
{
internal int? Year { get; }
internal int? Month { get; }
internal int? StartYear { get; }
internal int? EndYear { get; }
internal CalendarYearViewMode Mode { get; }
internal CalendarYearButtonEventArgs( CalendarYearViewMode mode, int? year, int? month, int? startYear, int? endYear )
{
Year = year;
Month = month;
StartYear = startYear;
EndYear = endYear;
Mode = mode;
}
}

View File

@@ -2,6 +2,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Irihi.Avalonia.Shared.Helpers;
namespace Ursa.Controls;
@@ -15,36 +16,21 @@ namespace Ursa.Controls;
public class CalendarYearView: TemplatedControl
{
public const string PART_Grid = "PART_Grid";
private CalendarYearViewMode _mode = CalendarYearViewMode.Month;
private readonly CalendarYearButton[] _buttons = new CalendarYearButton[12];
internal CalendarYearViewMode Mode
{
get => _mode;
set
{
_mode = value;
RefreshButtons();
}
}
public event EventHandler<CalendarYearButtonEventArgs>? OnMonthSelected;
private DateTime _contextDate = DateTime.Today;
internal DateTime ContextDate
{
get => _contextDate;
set
{
_contextDate = value;
RefreshButtons();
}
}
internal CalendarYearViewMode Mode { get; set; } = CalendarYearViewMode.Month;
internal DateTime ContextDate { get; set; } = DateTime.Today;
private Grid? _grid;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
ContextDate = DateTime.Today;
base.OnApplyTemplate(e);
_grid = e.NameScope.Find<Grid>(PART_Grid);
GenerateGridElements();
RefreshButtons();
}
public void GenerateGridElements()
@@ -59,7 +45,43 @@ public class CalendarYearView: TemplatedControl
var button = new CalendarYearButton();
Grid.SetRow(button, i / 3);
Grid.SetColumn(button, i % 3);
button.AddHandler(CalendarYearButton.ItemSelectedEvent, OnItemSelected);
_grid.Children.Add(button);
_buttons[i] = button;
}
}
private void OnItemSelected(object sender, CalendarYearButtonEventArgs e)
{
if (_grid is null) return;
var buttons = _grid.Children.OfType<CalendarYearButton>().ToList();
if (e.Mode == CalendarYearViewMode.Month)
{
if (e.Month is null) return;
var day = MathHelpers.SafeClamp(e.Month.Value, 0, DateTime.DaysInMonth(ContextDate.Year, e.Month.Value+1));
ContextDate = new DateTime(ContextDate.Year, e.Month.Value+1, day+1);
OnMonthSelected?.Invoke(this, e);
}
else if (e.Mode == CalendarYearViewMode.Year)
{
// Set CalendarYearView to Month mode
for (var i = 0; i < 12; i++)
{
buttons[i].SetValues(CalendarYearViewMode.Month, ContextDate, month: i);
}
ContextDate = new DateTime(e.Year!.Value, ContextDate.Month, 1);
Mode = CalendarYearViewMode.Month;
}
else if (e.Mode == CalendarYearViewMode.YearRange)
{
// Set CalendarYearView to Year mode
for (var i = 0; i < 12; i++)
{
if (e.StartYear is null || e.EndYear is null) continue;
var year = e.StartYear.Value - 1 + i;
buttons[i].SetValues(CalendarYearViewMode.Year, ContextDate, year: year);
}
Mode = CalendarYearViewMode.Year;
}
}
@@ -89,4 +111,11 @@ public class CalendarYearView: TemplatedControl
}
}
public void ShiftMode()
{
if (Mode == CalendarYearViewMode.Month)
{
}
}
}

View File

@@ -17,7 +17,7 @@
<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)"/>
<PackageReference Include="Irihi.Avalonia.Shared" Version="0.1.7" />
<PackageReference Include="Irihi.Avalonia.Shared" Version="0.1.8" />
</ItemGroup>
<ItemGroup>