Calendar rendering.
This commit is contained in:
@@ -121,6 +121,9 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial bool IsCalendarEnabled { get; set; } = true;
|
public partial bool IsCalendarEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial IReadOnlyList<CalendarItemViewModel> CalendarItems { get; set; } = [];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Event Details
|
#region Event Details
|
||||||
@@ -321,6 +324,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
VisibleDateRangeText = string.Empty;
|
VisibleDateRangeText = string.Empty;
|
||||||
LoadedDateWindow = null;
|
LoadedDateWindow = null;
|
||||||
_loadedCalendarItems = [];
|
_loadedCalendarItems = [];
|
||||||
|
CalendarItems = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -586,6 +590,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
await ExecuteUIThreadIfActiveAsync(lifetimeVersion, () =>
|
await ExecuteUIThreadIfActiveAsync(lifetimeVersion, () =>
|
||||||
{
|
{
|
||||||
_loadedCalendarItems = loadedItems;
|
_loadedCalendarItems = loadedItems;
|
||||||
|
CalendarItems = loadedItems;
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using Wino.Calendar.ViewModels.Data;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
internal static class CalendarItemAccessor
|
||||||
|
{
|
||||||
|
public static bool TryGetTimeRange(CalendarItemViewModel item, out DateTimeOffset start, out DateTimeOffset end)
|
||||||
|
{
|
||||||
|
start = new DateTimeOffset(item.StartDate);
|
||||||
|
end = new DateTimeOffset(item.EndDate);
|
||||||
|
return end > start;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<UserControl
|
||||||
|
x:Class="Wino.Calendar.Controls.CalendarPeriodControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="using:Wino.Calendar.Controls"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:skia="using:SkiaSharp.Views.Windows"
|
||||||
|
xmlns:viewModels="using:Wino.Calendar.ViewModels.Data"
|
||||||
|
x:Name="Root"
|
||||||
|
Loaded="ControlLoaded"
|
||||||
|
SizeChanged="ControlSizeChanged"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<DataTemplate x:Key="CalendarEventTemplate" x:DataType="viewModels:CalendarItemViewModel">
|
||||||
|
<local:CalendarItemControl CalendarItem="{x:Bind}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="MonthEventTemplate" x:DataType="viewModels:CalendarItemViewModel">
|
||||||
|
<local:CalendarItemControl
|
||||||
|
Margin="0,2,0,0"
|
||||||
|
CalendarItem="{x:Bind}"
|
||||||
|
IsCustomEventArea="True" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="TimedHeaderTemplate" x:DataType="local:HeaderTextLayout">
|
||||||
|
<TextBlock
|
||||||
|
Width="{x:Bind Width}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{x:Bind Text}"
|
||||||
|
TextAlignment="Center" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="MonthHeaderTemplate" x:DataType="local:HeaderTextLayout">
|
||||||
|
<TextBlock
|
||||||
|
Width="{x:Bind Width}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{x:Bind Text}"
|
||||||
|
TextAlignment="Center" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<ItemsPanelTemplate x:Key="HorizontalItemsPanel">
|
||||||
|
<StackPanel Orientation="Horizontal" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid x:Name="TimedRoot" Visibility="Collapsed">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="44" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid x:Name="TimedHeaderHost">
|
||||||
|
<skia:SKXamlCanvas x:Name="TimedHeaderCanvas" PaintSurface="TimedHeaderCanvasPaintSurface" />
|
||||||
|
<ItemsControl
|
||||||
|
x:Name="TimedHeadersItemsControl"
|
||||||
|
ItemTemplate="{StaticResource TimedHeaderTemplate}"
|
||||||
|
ItemsSource="{x:Bind TimedHeaderTexts, Mode=OneWay}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<StaticResource ResourceKey="HorizontalItemsPanel" />
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ScrollViewer
|
||||||
|
x:Name="TimedScrollViewer"
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollMode="Enabled">
|
||||||
|
<Grid x:Name="TimedViewport" Height="{x:Bind TimelineHeight, Mode=OneWay}">
|
||||||
|
<skia:SKXamlCanvas x:Name="TimedStructureCanvas" PaintSurface="TimedStructureCanvasPaintSurface" />
|
||||||
|
<Canvas x:Name="TimedItemsCanvas" />
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid x:Name="MonthRoot" Visibility="Collapsed">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Height="36">
|
||||||
|
<ItemsControl
|
||||||
|
x:Name="MonthHeadersItemsControl"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ItemTemplate="{StaticResource MonthHeaderTemplate}"
|
||||||
|
ItemsSource="{x:Bind MonthHeaderTexts, Mode=OneWay}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<StaticResource ResourceKey="HorizontalItemsPanel" />
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
x:Name="MonthViewport"
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<skia:SKXamlCanvas x:Name="MonthStructureCanvas" PaintSurface="MonthStructureCanvasPaintSurface" />
|
||||||
|
<Canvas x:Name="MonthCellLabelsCanvas" />
|
||||||
|
<Canvas x:Name="MonthItemsCanvas" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,835 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using CommunityToolkit.WinUI;
|
||||||
|
using Itenso.TimePeriod;
|
||||||
|
using Microsoft.UI.Composition;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Hosting;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using SkiaSharp;
|
||||||
|
using SkiaSharp.Views.Windows;
|
||||||
|
using Windows.UI;
|
||||||
|
using Wino.Calendar.ViewModels.Data;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public sealed partial class CalendarPeriodControl : UserControl, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private VisibleDateRange _currentRange = new(
|
||||||
|
CalendarDisplayType.Month,
|
||||||
|
DateOnly.FromDateTime(DateTime.Today),
|
||||||
|
DateOnly.FromDateTime(DateTime.Today),
|
||||||
|
DateOnly.FromDateTime(DateTime.Today),
|
||||||
|
DateOnly.FromDateTime(DateTime.Today),
|
||||||
|
1,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
[DateOnly.FromDateTime(DateTime.Today)]);
|
||||||
|
|
||||||
|
private TimedCalendarLayoutResult _timedLayout = new([], 0, []);
|
||||||
|
private MonthCalendarLayoutResult _monthLayout = new(0, 0, [], []);
|
||||||
|
private INotifyCollectionChanged? _observableItemsSource;
|
||||||
|
private double _timedDayWidth;
|
||||||
|
private double _monthCellWidth;
|
||||||
|
private double _monthCellHeight;
|
||||||
|
private bool _hasPresentedState;
|
||||||
|
private CalendarDisplayType _lastDisplayMode = CalendarDisplayType.Month;
|
||||||
|
private DateOnly _lastDisplayDate = DateOnly.FromDateTime(DateTime.Today);
|
||||||
|
private DayOfWeek _lastFirstDayOfWeek = DayOfWeek.Monday;
|
||||||
|
|
||||||
|
[GeneratedDependencyProperty]
|
||||||
|
public partial VisibleDateRange? VisibleRange { get; set; }
|
||||||
|
|
||||||
|
[GeneratedDependencyProperty]
|
||||||
|
public partial CalendarSettings? CalendarSettings { get; set; }
|
||||||
|
|
||||||
|
[GeneratedDependencyProperty]
|
||||||
|
public partial IReadOnlyList<CalendarItemViewModel>? CalendarItems { get; set; }
|
||||||
|
|
||||||
|
[GeneratedDependencyProperty]
|
||||||
|
public partial string? TimedHeaderDateFormat { get; set; }
|
||||||
|
|
||||||
|
public CalendarPeriodControl() => InitializeComponent();
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
private ObservableCollection<HeaderTextLayout> TimedHeaderTextsCollection { get; } = [];
|
||||||
|
private ObservableCollection<HeaderTextLayout> MonthHeaderTextsCollection { get; } = [];
|
||||||
|
private ObservableCollection<TimedItemLayout> TimedItemsCollection { get; } = [];
|
||||||
|
private ObservableCollection<MonthCellLabelLayout> MonthCellLabelsCollection { get; } = [];
|
||||||
|
private ObservableCollection<MonthItemLayout> MonthItemsCollection { get; } = [];
|
||||||
|
|
||||||
|
public IEnumerable<HeaderTextLayout> TimedHeaderTexts => TimedHeaderTextsCollection;
|
||||||
|
public IEnumerable<HeaderTextLayout> MonthHeaderTexts => MonthHeaderTextsCollection;
|
||||||
|
public IEnumerable<TimedItemLayout> TimedItems => TimedItemsCollection;
|
||||||
|
public IEnumerable<MonthCellLabelLayout> MonthCellLabels => MonthCellLabelsCollection;
|
||||||
|
public IEnumerable<MonthItemLayout> MonthItems => MonthItemsCollection;
|
||||||
|
|
||||||
|
public double TimedDayWidth
|
||||||
|
{
|
||||||
|
get => _timedDayWidth;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (_timedDayWidth == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_timedDayWidth = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MonthCellWidth
|
||||||
|
{
|
||||||
|
get => _monthCellWidth;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (_monthCellWidth == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_monthCellWidth = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MonthCellHeight
|
||||||
|
{
|
||||||
|
get => _monthCellHeight;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (_monthCellHeight == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_monthCellHeight = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double TimelineHeight => TimedCalendarLayoutCalculator.GetTimelineHeight(GetHourHeight());
|
||||||
|
|
||||||
|
partial void OnVisibleRangeChanged(VisibleDateRange? newValue) => Refresh();
|
||||||
|
partial void OnCalendarSettingsChanged(CalendarSettings? newValue) => Refresh();
|
||||||
|
partial void OnTimedHeaderDateFormatChanged(string? newValue) => Refresh();
|
||||||
|
|
||||||
|
partial void OnCalendarItemsChanged(IReadOnlyList<CalendarItemViewModel>? newValue)
|
||||||
|
{
|
||||||
|
DetachCurrentItemsSource();
|
||||||
|
AttachItemsSource(newValue);
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ControlLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
AttachItemsSource(CalendarItems);
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ControlSizeChanged(object sender, SizeChangedEventArgs e) => Refresh();
|
||||||
|
|
||||||
|
private IEnumerable<CalendarItemViewModel> CurrentItems => CalendarItems ?? [];
|
||||||
|
|
||||||
|
private void AttachItemsSource(IReadOnlyList<CalendarItemViewModel>? itemsSource)
|
||||||
|
{
|
||||||
|
_observableItemsSource = itemsSource as INotifyCollectionChanged;
|
||||||
|
|
||||||
|
if (_observableItemsSource is not null)
|
||||||
|
{
|
||||||
|
_observableItemsSource.CollectionChanged += ItemsSourceCollectionChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetachItemsSource(IReadOnlyList<CalendarItemViewModel>? itemsSource)
|
||||||
|
{
|
||||||
|
var observableItemsSource = itemsSource as INotifyCollectionChanged;
|
||||||
|
|
||||||
|
if (observableItemsSource is not null)
|
||||||
|
{
|
||||||
|
observableItemsSource.CollectionChanged -= ItemsSourceCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(_observableItemsSource, observableItemsSource))
|
||||||
|
{
|
||||||
|
_observableItemsSource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetachCurrentItemsSource()
|
||||||
|
{
|
||||||
|
if (_observableItemsSource is not null)
|
||||||
|
{
|
||||||
|
_observableItemsSource.CollectionChanged -= ItemsSourceCollectionChanged;
|
||||||
|
_observableItemsSource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ItemsSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => Refresh();
|
||||||
|
|
||||||
|
private void Refresh()
|
||||||
|
{
|
||||||
|
if (!IsLoaded || ActualWidth <= 0 || VisibleRange is null || CalendarSettings is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var transition = GetTransitionInfo();
|
||||||
|
_currentRange = CreateLayoutRange(VisibleRange, CalendarSettings);
|
||||||
|
|
||||||
|
if (VisibleRange.DisplayType == CalendarDisplayType.Month)
|
||||||
|
{
|
||||||
|
RefreshMonthView();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RefreshTimedView();
|
||||||
|
}
|
||||||
|
|
||||||
|
RunTransition(transition);
|
||||||
|
_hasPresentedState = true;
|
||||||
|
_lastDisplayMode = VisibleRange.DisplayType;
|
||||||
|
_lastDisplayDate = VisibleRange.AnchorDate;
|
||||||
|
_lastFirstDayOfWeek = CalendarSettings.FirstDayOfWeek;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshTimedView()
|
||||||
|
{
|
||||||
|
TimedRoot.Visibility = Visibility.Visible;
|
||||||
|
MonthRoot.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
TimedDayWidth = _currentRange.Dates.Count == 0 ? 0d : ActualWidth / _currentRange.Dates.Count;
|
||||||
|
TimedViewport.Width = ActualWidth;
|
||||||
|
TimedViewport.Height = TimelineHeight;
|
||||||
|
|
||||||
|
_timedLayout = TimedCalendarLayoutCalculator.Calculate(_currentRange, CurrentItems, ActualWidth, GetHourHeight());
|
||||||
|
|
||||||
|
ReplaceCollection(
|
||||||
|
TimedHeaderTextsCollection,
|
||||||
|
_timedLayout.VisibleDates.Select(date =>
|
||||||
|
new HeaderTextLayout(
|
||||||
|
date.ToDateTime(TimeOnly.MinValue).ToString(
|
||||||
|
string.IsNullOrWhiteSpace(TimedHeaderDateFormat) ? "ddd dd" : TimedHeaderDateFormat,
|
||||||
|
CalendarSettings!.CultureInfo),
|
||||||
|
TimedDayWidth)));
|
||||||
|
|
||||||
|
var eventTemplate = (DataTemplate)Resources["CalendarEventTemplate"];
|
||||||
|
ReplaceCollection(TimedItemsCollection, _timedLayout.Items.Select(item =>
|
||||||
|
{
|
||||||
|
PrepareDisplayMetadata(item.Item, item.Date);
|
||||||
|
item.Template = eventTemplate;
|
||||||
|
return item;
|
||||||
|
}));
|
||||||
|
RenderTimedItems();
|
||||||
|
|
||||||
|
TimedHeaderCanvas.Invalidate();
|
||||||
|
TimedStructureCanvas.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshMonthView()
|
||||||
|
{
|
||||||
|
TimedRoot.Visibility = Visibility.Collapsed;
|
||||||
|
MonthRoot.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
var availableMonthHeight = Math.Max(0d, ActualHeight - MonthHeadersItemsControl.ActualHeight);
|
||||||
|
_monthLayout = MonthCalendarLayoutCalculator.Calculate(_currentRange, CurrentItems, ActualWidth, availableMonthHeight);
|
||||||
|
|
||||||
|
MonthCellWidth = _monthLayout.CellWidth;
|
||||||
|
MonthCellHeight = _monthLayout.CellHeight;
|
||||||
|
|
||||||
|
MonthViewport.Width = ActualWidth;
|
||||||
|
MonthViewport.Height = availableMonthHeight;
|
||||||
|
|
||||||
|
ReplaceCollection(
|
||||||
|
MonthHeaderTextsCollection,
|
||||||
|
Enumerable.Range(0, MonthCalendarLayoutCalculator.ColumnCount)
|
||||||
|
.Select(index =>
|
||||||
|
{
|
||||||
|
var day = (DayOfWeek)(((int)CalendarSettings!.FirstDayOfWeek + index) % 7);
|
||||||
|
return new HeaderTextLayout(
|
||||||
|
CalendarSettings.CultureInfo.DateTimeFormat.AbbreviatedDayNames[(int)day],
|
||||||
|
MonthCellWidth);
|
||||||
|
}));
|
||||||
|
|
||||||
|
ReplaceCollection(
|
||||||
|
MonthCellLabelsCollection,
|
||||||
|
_monthLayout.Cells.Select(cell =>
|
||||||
|
new MonthCellLabelLayout(
|
||||||
|
cell.Date.Day.ToString(CalendarSettings!.CultureInfo),
|
||||||
|
cell.Date.Month == VisibleRange!.AnchorDate.Month && cell.Date.Year == VisibleRange.AnchorDate.Year ? 1d : 0.55d,
|
||||||
|
new LayoutRect(cell.Bounds.X + 4, cell.Bounds.Y + 2, cell.Bounds.Width, cell.Bounds.Height))));
|
||||||
|
|
||||||
|
var monthEventTemplate = (DataTemplate)Resources["MonthEventTemplate"];
|
||||||
|
ReplaceCollection(MonthItemsCollection, _monthLayout.Items.Select(item =>
|
||||||
|
{
|
||||||
|
PrepareDisplayMetadata(item.Item, item.Date);
|
||||||
|
item.Template = monthEventTemplate;
|
||||||
|
return item;
|
||||||
|
}));
|
||||||
|
RenderMonthCellLabels();
|
||||||
|
RenderMonthItems();
|
||||||
|
|
||||||
|
MonthStructureCanvas.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrepareDisplayMetadata(CalendarItemViewModel item, DateOnly date)
|
||||||
|
{
|
||||||
|
if (CalendarSettings is null || item is not ICalendarItemViewModel calendarItemViewModel)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
calendarItemViewModel.DisplayingPeriod = new TimeRange(
|
||||||
|
date.ToDateTime(TimeOnly.MinValue),
|
||||||
|
date.AddDays(1).ToDateTime(TimeOnly.MinValue));
|
||||||
|
calendarItemViewModel.CalendarSettings = CalendarSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VisibleDateRange CreateLayoutRange(VisibleDateRange visibleRange, CalendarSettings calendarSettings)
|
||||||
|
{
|
||||||
|
if (visibleRange.DisplayType != CalendarDisplayType.Month)
|
||||||
|
{
|
||||||
|
return visibleRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
var start = AlignToWeekStart(visibleRange.StartDate, calendarSettings.FirstDayOfWeek);
|
||||||
|
var end = AlignToWeekEnd(visibleRange.EndDate, calendarSettings.FirstDayOfWeek);
|
||||||
|
var totalDays = end.DayNumber - start.DayNumber + 1;
|
||||||
|
|
||||||
|
if (totalDays <= 35)
|
||||||
|
{
|
||||||
|
end = start.AddDays(34);
|
||||||
|
}
|
||||||
|
else if (totalDays < 42)
|
||||||
|
{
|
||||||
|
end = start.AddDays(41);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dates = Enumerable.Range(0, end.DayNumber - start.DayNumber + 1)
|
||||||
|
.Select(offset => start.AddDays(offset))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return new VisibleDateRange(
|
||||||
|
visibleRange.DisplayType,
|
||||||
|
visibleRange.AnchorDate,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
visibleRange.PrimaryDate,
|
||||||
|
dates.Length,
|
||||||
|
dates.Contains(DateOnly.FromDateTime(DateTime.Today)),
|
||||||
|
start.Year == end.Year && start.Month == end.Month,
|
||||||
|
dates);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateOnly AlignToWeekStart(DateOnly date, DayOfWeek firstDayOfWeek)
|
||||||
|
{
|
||||||
|
var offset = ((int)date.DayOfWeek - (int)firstDayOfWeek + 7) % 7;
|
||||||
|
return date.AddDays(-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateOnly AlignToWeekEnd(DateOnly date, DayOfWeek firstDayOfWeek)
|
||||||
|
{
|
||||||
|
var lastDayOfWeek = (DayOfWeek)(((int)firstDayOfWeek + 6) % 7);
|
||||||
|
var offset = ((int)lastDayOfWeek - (int)date.DayOfWeek + 7) % 7;
|
||||||
|
return date.AddDays(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimedHeaderCanvasPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
|
||||||
|
{
|
||||||
|
using var borderPaint = CreateLinePaint();
|
||||||
|
var canvas = e.Surface.Canvas;
|
||||||
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
|
||||||
|
if (_timedLayout.VisibleDates.Count == 0 || ActualWidth <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scaleX = (float)(e.Info.Width / ActualWidth);
|
||||||
|
var height = e.Info.Height;
|
||||||
|
var dayWidth = (float)(_timedLayout.DayWidth * scaleX);
|
||||||
|
|
||||||
|
for (var index = 1; index < _timedLayout.VisibleDates.Count; index++)
|
||||||
|
{
|
||||||
|
var x = dayWidth * index;
|
||||||
|
canvas.DrawLine(x, 0, x, height, borderPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.DrawLine(0, height - 1, e.Info.Width, height - 1, borderPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimedStructureCanvasPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
|
||||||
|
{
|
||||||
|
using var linePaint = CreateLinePaint();
|
||||||
|
using var defaultFillPaint = CreateFillPaint(GetDefaultHourBackground());
|
||||||
|
using var workFillPaint = CreateFillPaint(GetWorkHourBackground());
|
||||||
|
var canvas = e.Surface.Canvas;
|
||||||
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
|
||||||
|
if (_timedLayout.VisibleDates.Count == 0 || ActualWidth <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hourHeight = GetHourHeight();
|
||||||
|
var timelineHeight = TimedCalendarLayoutCalculator.GetTimelineHeight(hourHeight);
|
||||||
|
var scaleX = (float)(e.Info.Width / ActualWidth);
|
||||||
|
var scaleY = (float)(e.Info.Height / timelineHeight);
|
||||||
|
var dayWidth = (float)(_timedLayout.DayWidth * scaleX);
|
||||||
|
var workDayStartHour = CalendarSettings?.WorkingHourStart.TotalHours ?? 9d;
|
||||||
|
var workDayEndHour = CalendarSettings?.WorkingHourEnd.TotalHours ?? 17d;
|
||||||
|
|
||||||
|
for (var dayIndex = 0; dayIndex < _timedLayout.VisibleDates.Count; dayIndex++)
|
||||||
|
{
|
||||||
|
var x = dayIndex * dayWidth;
|
||||||
|
|
||||||
|
for (var hour = 0; hour < 24; hour++)
|
||||||
|
{
|
||||||
|
var y = (float)(hour * hourHeight * scaleY);
|
||||||
|
var scaledHourHeight = (float)(hourHeight * scaleY);
|
||||||
|
var fillPaint = hour >= workDayStartHour && hour < workDayEndHour ? workFillPaint : defaultFillPaint;
|
||||||
|
canvas.DrawRect(x, y, dayWidth, scaledHourHeight, fillPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var hour = 0; hour <= 24; hour++)
|
||||||
|
{
|
||||||
|
var y = (float)(hour * hourHeight * scaleY);
|
||||||
|
canvas.DrawLine(0, y, e.Info.Width, y, linePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var index = 0; index <= _timedLayout.VisibleDates.Count; index++)
|
||||||
|
{
|
||||||
|
var x = dayWidth * index;
|
||||||
|
canvas.DrawLine(x, 0, x, e.Info.Height, linePaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MonthStructureCanvasPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
|
||||||
|
{
|
||||||
|
using var linePaint = CreateLinePaint();
|
||||||
|
using var todayPaint = new SKPaint
|
||||||
|
{
|
||||||
|
Style = SKPaintStyle.Fill,
|
||||||
|
Color = new SKColor(0, 120, 215, 26),
|
||||||
|
IsAntialias = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var canvas = e.Surface.Canvas;
|
||||||
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
|
||||||
|
if (_monthLayout.CellWidth <= 0 || _monthLayout.CellHeight <= 0 || MonthViewport.ActualWidth <= 0 || MonthViewport.ActualHeight <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cellWidth = (float)(e.Info.Width / MonthCalendarLayoutCalculator.ColumnCount);
|
||||||
|
var cellHeight = (float)(e.Info.Height / MonthCalendarLayoutCalculator.RowCount);
|
||||||
|
var today = DateOnly.FromDateTime(DateTime.Now.Date);
|
||||||
|
|
||||||
|
foreach (var cell in _monthLayout.Cells)
|
||||||
|
{
|
||||||
|
if (cell.Date != today)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.DrawRect((float)cell.Bounds.X, (float)cell.Bounds.Y, (float)cell.Bounds.Width, (float)cell.Bounds.Height, todayPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var row = 0; row <= MonthCalendarLayoutCalculator.RowCount; row++)
|
||||||
|
{
|
||||||
|
var y = row * cellHeight;
|
||||||
|
canvas.DrawLine(0, y, e.Info.Width, y, linePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var column = 0; column <= MonthCalendarLayoutCalculator.ColumnCount; column++)
|
||||||
|
{
|
||||||
|
var x = column * cellWidth;
|
||||||
|
canvas.DrawLine(x, 0, x, e.Info.Height, linePaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReplaceCollection<T>(ObservableCollection<T> target, IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
target.Clear();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
target.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTimedItems()
|
||||||
|
{
|
||||||
|
TimedItemsCanvas.Children.Clear();
|
||||||
|
|
||||||
|
foreach (var item in TimedItemsCollection)
|
||||||
|
{
|
||||||
|
var presenter = new ContentPresenter
|
||||||
|
{
|
||||||
|
Width = item.Bounds.Width,
|
||||||
|
Height = item.Bounds.Height,
|
||||||
|
Content = item.Item,
|
||||||
|
ContentTemplate = item.Template
|
||||||
|
};
|
||||||
|
|
||||||
|
Canvas.SetLeft(presenter, item.Bounds.X);
|
||||||
|
Canvas.SetTop(presenter, item.Bounds.Y);
|
||||||
|
TimedItemsCanvas.Children.Add(presenter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderMonthCellLabels()
|
||||||
|
{
|
||||||
|
MonthCellLabelsCanvas.Children.Clear();
|
||||||
|
|
||||||
|
foreach (var label in MonthCellLabelsCollection)
|
||||||
|
{
|
||||||
|
var textBlock = new TextBlock
|
||||||
|
{
|
||||||
|
Width = label.Bounds.Width,
|
||||||
|
Height = label.Bounds.Height,
|
||||||
|
Opacity = label.LabelOpacity,
|
||||||
|
Text = label.DayText
|
||||||
|
};
|
||||||
|
|
||||||
|
Canvas.SetLeft(textBlock, label.Bounds.X);
|
||||||
|
Canvas.SetTop(textBlock, label.Bounds.Y);
|
||||||
|
MonthCellLabelsCanvas.Children.Add(textBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderMonthItems()
|
||||||
|
{
|
||||||
|
MonthItemsCanvas.Children.Clear();
|
||||||
|
|
||||||
|
foreach (var item in MonthItemsCollection)
|
||||||
|
{
|
||||||
|
var presenter = new ContentPresenter
|
||||||
|
{
|
||||||
|
Width = item.Bounds.Width,
|
||||||
|
Height = item.Bounds.Height,
|
||||||
|
Content = item.Item,
|
||||||
|
ContentTemplate = item.Template
|
||||||
|
};
|
||||||
|
|
||||||
|
Canvas.SetLeft(presenter, item.Bounds.X);
|
||||||
|
Canvas.SetTop(presenter, item.Bounds.Y);
|
||||||
|
MonthItemsCanvas.Children.Add(presenter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CalendarTransitionInfo GetTransitionInfo()
|
||||||
|
{
|
||||||
|
if (!_hasPresentedState || VisibleRange is null || CalendarSettings is null)
|
||||||
|
{
|
||||||
|
return new CalendarTransitionInfo(CalendarTransitionKind.None, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastDisplayMode != VisibleRange.DisplayType)
|
||||||
|
{
|
||||||
|
return new CalendarTransitionInfo(CalendarTransitionKind.ModeChange, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastDisplayDate != VisibleRange.AnchorDate)
|
||||||
|
{
|
||||||
|
return new CalendarTransitionInfo(
|
||||||
|
CalendarTransitionKind.Navigation,
|
||||||
|
VisibleRange.AnchorDate.CompareTo(_lastDisplayDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastFirstDayOfWeek != CalendarSettings.FirstDayOfWeek)
|
||||||
|
{
|
||||||
|
return new CalendarTransitionInfo(CalendarTransitionKind.Refresh, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CalendarTransitionInfo(CalendarTransitionKind.None, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunTransition(CalendarTransitionInfo transition)
|
||||||
|
{
|
||||||
|
if (transition.Kind == CalendarTransitionKind.None || VisibleRange is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VisibleRange.DisplayType != CalendarDisplayType.Month)
|
||||||
|
{
|
||||||
|
RunTimedTransition(transition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var target = (UIElement)MonthRoot;
|
||||||
|
var visual = ElementCompositionPreview.GetElementVisual(target);
|
||||||
|
var compositor = visual.Compositor;
|
||||||
|
|
||||||
|
visual.CenterPoint = new Vector3((float)(target.RenderSize.Width * 0.5), (float)(target.RenderSize.Height * 0.5), 0f);
|
||||||
|
visual.StopAnimation(nameof(visual.Offset));
|
||||||
|
visual.StopAnimation(nameof(visual.Opacity));
|
||||||
|
visual.StopAnimation(nameof(visual.Scale));
|
||||||
|
|
||||||
|
switch (transition.Kind)
|
||||||
|
{
|
||||||
|
case CalendarTransitionKind.Navigation:
|
||||||
|
StartNavigationTransition(compositor, visual, transition.Direction, target.RenderSize.Width);
|
||||||
|
break;
|
||||||
|
case CalendarTransitionKind.ModeChange:
|
||||||
|
StartModeTransition(compositor, visual);
|
||||||
|
break;
|
||||||
|
case CalendarTransitionKind.Refresh:
|
||||||
|
StartRefreshTransition(compositor, visual);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunTimedTransition(CalendarTransitionInfo transition)
|
||||||
|
{
|
||||||
|
var headerVisual = ElementCompositionPreview.GetElementVisual(TimedHeaderHost);
|
||||||
|
var contentVisual = ElementCompositionPreview.GetElementVisual(TimedScrollViewer);
|
||||||
|
var compositor = headerVisual.Compositor;
|
||||||
|
|
||||||
|
PrepareAnimatedVisual(headerVisual, TimedHeaderHost);
|
||||||
|
PrepareAnimatedVisual(contentVisual, TimedScrollViewer);
|
||||||
|
|
||||||
|
switch (transition.Kind)
|
||||||
|
{
|
||||||
|
case CalendarTransitionKind.Navigation:
|
||||||
|
StartTimedNavigationTransition(compositor, transition.Direction);
|
||||||
|
break;
|
||||||
|
case CalendarTransitionKind.ModeChange:
|
||||||
|
StartTimedModeTransition(compositor);
|
||||||
|
break;
|
||||||
|
case CalendarTransitionKind.Refresh:
|
||||||
|
StartTimedRefreshTransition(compositor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartNavigationTransition(Compositor compositor, Visual visual, int direction, double width)
|
||||||
|
{
|
||||||
|
var travel = (float)Math.Max(48d, Math.Min(160d, width * 0.08d));
|
||||||
|
var startX = direction >= 0 ? travel : -travel;
|
||||||
|
|
||||||
|
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
|
||||||
|
offsetAnimation.InsertKeyFrame(0f, new Vector3(startX, 0f, 0f));
|
||||||
|
offsetAnimation.InsertKeyFrame(1f, Vector3.Zero);
|
||||||
|
offsetAnimation.Duration = TimeSpan.FromMilliseconds(220);
|
||||||
|
|
||||||
|
var opacityAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||||
|
opacityAnimation.InsertKeyFrame(0f, 0.72f);
|
||||||
|
opacityAnimation.InsertKeyFrame(1f, 1f);
|
||||||
|
opacityAnimation.Duration = offsetAnimation.Duration;
|
||||||
|
|
||||||
|
visual.StartAnimation(nameof(visual.Offset), offsetAnimation);
|
||||||
|
visual.StartAnimation(nameof(visual.Opacity), opacityAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartTimedNavigationTransition(Compositor compositor, int direction)
|
||||||
|
{
|
||||||
|
var width = Math.Max(TimedRoot.RenderSize.Width, ActualWidth);
|
||||||
|
var travel = (float)Math.Max(56d, Math.Min(184d, width * 0.09d));
|
||||||
|
var signedTravel = direction >= 0 ? travel : -travel;
|
||||||
|
var clipInset = (float)Math.Max(18d, Math.Min(64d, width * 0.05d));
|
||||||
|
|
||||||
|
StartTimedElementTransition(compositor, TimedHeaderHost, signedTravel * 0.45f, 0f, 0.78f, TimeSpan.FromMilliseconds(180), direction >= 0 ? 0f : clipInset, direction >= 0 ? clipInset : 0f);
|
||||||
|
StartTimedElementTransition(compositor, TimedScrollViewer, signedTravel, 0f, 0.68f, TimeSpan.FromMilliseconds(240), direction >= 0 ? 0f : clipInset, direction >= 0 ? clipInset : 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartModeTransition(Compositor compositor, Visual visual)
|
||||||
|
{
|
||||||
|
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
|
||||||
|
offsetAnimation.InsertKeyFrame(0f, new Vector3(0f, 18f, 0f));
|
||||||
|
offsetAnimation.InsertKeyFrame(1f, Vector3.Zero);
|
||||||
|
offsetAnimation.Duration = TimeSpan.FromMilliseconds(260);
|
||||||
|
|
||||||
|
var opacityAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||||
|
opacityAnimation.InsertKeyFrame(0f, 0f);
|
||||||
|
opacityAnimation.InsertKeyFrame(1f, 1f);
|
||||||
|
opacityAnimation.Duration = offsetAnimation.Duration;
|
||||||
|
|
||||||
|
var scaleAnimation = compositor.CreateVector3KeyFrameAnimation();
|
||||||
|
scaleAnimation.InsertKeyFrame(0f, new Vector3(0.985f, 0.985f, 1f));
|
||||||
|
scaleAnimation.InsertKeyFrame(1f, new Vector3(1f, 1f, 1f));
|
||||||
|
scaleAnimation.Duration = offsetAnimation.Duration;
|
||||||
|
|
||||||
|
visual.StartAnimation(nameof(visual.Offset), offsetAnimation);
|
||||||
|
visual.StartAnimation(nameof(visual.Opacity), opacityAnimation);
|
||||||
|
visual.StartAnimation(nameof(visual.Scale), scaleAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartTimedModeTransition(Compositor compositor)
|
||||||
|
{
|
||||||
|
StartTimedElementTransition(compositor, TimedHeaderHost, 0f, 10f, 0f, TimeSpan.FromMilliseconds(180), 0f, 0f);
|
||||||
|
StartTimedElementTransition(compositor, TimedScrollViewer, 0f, 18f, 0f, TimeSpan.FromMilliseconds(240), 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartRefreshTransition(Compositor compositor, Visual visual)
|
||||||
|
{
|
||||||
|
var opacityAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||||
|
opacityAnimation.InsertKeyFrame(0f, 0.82f);
|
||||||
|
opacityAnimation.InsertKeyFrame(1f, 1f);
|
||||||
|
opacityAnimation.Duration = TimeSpan.FromMilliseconds(160);
|
||||||
|
|
||||||
|
visual.StartAnimation(nameof(visual.Opacity), opacityAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartTimedRefreshTransition(Compositor compositor)
|
||||||
|
{
|
||||||
|
StartOpacityTransition(compositor, ElementCompositionPreview.GetElementVisual(TimedHeaderHost), 0.86f, TimeSpan.FromMilliseconds(140));
|
||||||
|
StartOpacityTransition(compositor, ElementCompositionPreview.GetElementVisual(TimedScrollViewer), 0.8f, TimeSpan.FromMilliseconds(160));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrepareAnimatedVisual(Visual visual, UIElement target)
|
||||||
|
{
|
||||||
|
visual.CenterPoint = new Vector3((float)(target.RenderSize.Width * 0.5), (float)(target.RenderSize.Height * 0.5), 0f);
|
||||||
|
visual.StopAnimation(nameof(visual.Offset));
|
||||||
|
visual.StopAnimation(nameof(visual.Opacity));
|
||||||
|
visual.StopAnimation(nameof(visual.Scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartTimedElementTransition(Compositor compositor, UIElement target, float offsetX, float offsetY, float startingOpacity, TimeSpan duration, float leftInset, float rightInset)
|
||||||
|
{
|
||||||
|
var visual = ElementCompositionPreview.GetElementVisual(target);
|
||||||
|
PrepareAnimatedVisual(visual, target);
|
||||||
|
|
||||||
|
var easing = compositor.CreateCubicBezierEasingFunction(new Vector2(0.16f, 1f), new Vector2(0.3f, 1f));
|
||||||
|
var fadeEasing = compositor.CreateCubicBezierEasingFunction(new Vector2(0.2f, 0f), new Vector2(0f, 1f));
|
||||||
|
var clip = visual.Clip as InsetClip ?? compositor.CreateInsetClip();
|
||||||
|
|
||||||
|
clip.LeftInset = leftInset;
|
||||||
|
clip.RightInset = rightInset;
|
||||||
|
visual.Clip = clip;
|
||||||
|
|
||||||
|
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
|
||||||
|
offsetAnimation.InsertKeyFrame(0f, new Vector3(offsetX, offsetY, 0f));
|
||||||
|
offsetAnimation.InsertKeyFrame(1f, Vector3.Zero, easing);
|
||||||
|
offsetAnimation.Duration = duration;
|
||||||
|
|
||||||
|
var opacityAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||||
|
opacityAnimation.InsertKeyFrame(0f, startingOpacity);
|
||||||
|
opacityAnimation.InsertKeyFrame(1f, 1f, fadeEasing);
|
||||||
|
opacityAnimation.Duration = duration;
|
||||||
|
|
||||||
|
var scaleAnimation = compositor.CreateVector3KeyFrameAnimation();
|
||||||
|
scaleAnimation.InsertKeyFrame(0f, new Vector3(0.996f, 0.996f, 1f));
|
||||||
|
scaleAnimation.InsertKeyFrame(1f, new Vector3(1f, 1f, 1f), easing);
|
||||||
|
scaleAnimation.Duration = duration;
|
||||||
|
|
||||||
|
var leftInsetAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||||
|
leftInsetAnimation.InsertKeyFrame(1f, 0f, easing);
|
||||||
|
leftInsetAnimation.Duration = duration;
|
||||||
|
|
||||||
|
var rightInsetAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||||
|
rightInsetAnimation.InsertKeyFrame(1f, 0f, easing);
|
||||||
|
rightInsetAnimation.Duration = duration;
|
||||||
|
|
||||||
|
visual.StartAnimation(nameof(visual.Offset), offsetAnimation);
|
||||||
|
visual.StartAnimation(nameof(visual.Opacity), opacityAnimation);
|
||||||
|
visual.StartAnimation(nameof(visual.Scale), scaleAnimation);
|
||||||
|
clip.StartAnimation(nameof(clip.LeftInset), leftInsetAnimation);
|
||||||
|
clip.StartAnimation(nameof(clip.RightInset), rightInsetAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void StartOpacityTransition(Compositor compositor, Visual visual, float startingOpacity, TimeSpan duration)
|
||||||
|
{
|
||||||
|
visual.StopAnimation(nameof(visual.Opacity));
|
||||||
|
|
||||||
|
var easing = compositor.CreateCubicBezierEasingFunction(new Vector2(0.2f, 0f), new Vector2(0f, 1f));
|
||||||
|
var opacityAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||||
|
opacityAnimation.InsertKeyFrame(0f, startingOpacity);
|
||||||
|
opacityAnimation.InsertKeyFrame(1f, 1f, easing);
|
||||||
|
opacityAnimation.Duration = duration;
|
||||||
|
|
||||||
|
visual.StartAnimation(nameof(visual.Opacity), opacityAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SKPaint CreateLinePaint()
|
||||||
|
{
|
||||||
|
var strokeColor = GetStrokeColor();
|
||||||
|
|
||||||
|
return new SKPaint
|
||||||
|
{
|
||||||
|
Color = new SKColor(strokeColor.R, strokeColor.G, strokeColor.B, (byte)Math.Max(40, strokeColor.A / 2)),
|
||||||
|
IsAntialias = false,
|
||||||
|
StrokeWidth = 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SKPaint CreateFillPaint(Brush brush)
|
||||||
|
{
|
||||||
|
return new SKPaint
|
||||||
|
{
|
||||||
|
Color = ToSkColor(brush),
|
||||||
|
Style = SKPaintStyle.Fill,
|
||||||
|
IsAntialias = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SKColor ToSkColor(Brush brush)
|
||||||
|
{
|
||||||
|
return brush is SolidColorBrush solidColorBrush
|
||||||
|
? new SKColor(solidColorBrush.Color.R, solidColorBrush.Color.G, solidColorBrush.Color.B, solidColorBrush.Color.A)
|
||||||
|
: SKColors.Transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Brush GetDefaultHourBackground()
|
||||||
|
{
|
||||||
|
if (Application.Current.Resources.TryGetValue("LayerFillColorDefaultBrush", out var resource) && resource is Brush brush)
|
||||||
|
{
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SolidColorBrush(Color.FromArgb(255, 28, 34, 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Brush GetWorkHourBackground()
|
||||||
|
{
|
||||||
|
if (Application.Current.Resources.TryGetValue("SolidBackgroundFillColorBaseBrush", out var resource) && resource is SolidColorBrush solidBrush)
|
||||||
|
{
|
||||||
|
return new SolidColorBrush(Color.FromArgb(64, solidBrush.Color.R, solidBrush.Color.G, solidBrush.Color.B));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SolidColorBrush(Color.FromArgb(255, 34, 40, 52));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetHourHeight() => CalendarSettings?.HourHeight ?? 60d;
|
||||||
|
|
||||||
|
private static Color GetStrokeColor()
|
||||||
|
{
|
||||||
|
if (Application.Current.Resources.TryGetValue("CardStrokeColorDefaultBrush", out var resource) &&
|
||||||
|
resource is SolidColorBrush solidBrush)
|
||||||
|
{
|
||||||
|
return solidBrush.Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Color.FromArgb(96, 210, 210, 210);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct CalendarTransitionInfo(CalendarTransitionKind Kind, int Direction);
|
||||||
|
|
||||||
|
private enum CalendarTransitionKind
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Navigation,
|
||||||
|
ModeChange,
|
||||||
|
Refresh
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public sealed class HeaderTextLayout
|
||||||
|
{
|
||||||
|
public HeaderTextLayout(string text, double width)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text { get; set; }
|
||||||
|
public double Width { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public readonly record struct LayoutRect(double X, double Y, double Width, double Height);
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Wino.Calendar.ViewModels.Data;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public sealed class MonthCellLayout
|
||||||
|
{
|
||||||
|
public MonthCellLayout(DateOnly date, int index, int row, int column, LayoutRect bounds)
|
||||||
|
{
|
||||||
|
Date = date;
|
||||||
|
Index = index;
|
||||||
|
Row = row;
|
||||||
|
Column = column;
|
||||||
|
Bounds = bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateOnly Date { get; set; }
|
||||||
|
public int Index { get; set; }
|
||||||
|
public int Row { get; set; }
|
||||||
|
public int Column { get; set; }
|
||||||
|
public LayoutRect Bounds { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MonthItemLayout
|
||||||
|
{
|
||||||
|
public MonthItemLayout(CalendarItemViewModel item, int cellIndex, DateOnly date, LayoutRect bounds, DataTemplate? template = null)
|
||||||
|
{
|
||||||
|
Item = item;
|
||||||
|
CellIndex = cellIndex;
|
||||||
|
Date = date;
|
||||||
|
Bounds = bounds;
|
||||||
|
Template = template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalendarItemViewModel Item { get; set; }
|
||||||
|
public int CellIndex { get; set; }
|
||||||
|
public DateOnly Date { get; set; }
|
||||||
|
public LayoutRect Bounds { get; set; }
|
||||||
|
public DataTemplate? Template { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MonthCellLabelLayout
|
||||||
|
{
|
||||||
|
public MonthCellLabelLayout(string dayText, double labelOpacity, LayoutRect bounds)
|
||||||
|
{
|
||||||
|
DayText = dayText;
|
||||||
|
LabelOpacity = labelOpacity;
|
||||||
|
Bounds = bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DayText { get; set; }
|
||||||
|
public double LabelOpacity { get; set; }
|
||||||
|
public LayoutRect Bounds { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record MonthCalendarLayoutResult(double CellWidth, double CellHeight, IReadOnlyList<MonthCellLayout> Cells, IReadOnlyList<MonthItemLayout> Items);
|
||||||
|
|
||||||
|
internal static class MonthCalendarLayoutCalculator
|
||||||
|
{
|
||||||
|
public const int ColumnCount = 7;
|
||||||
|
public const int RowCount = 6;
|
||||||
|
|
||||||
|
private const double CellPadding = 4d;
|
||||||
|
private const double DayLabelHeight = 20d;
|
||||||
|
private const double ItemHeight = 18d;
|
||||||
|
private const double ItemGap = 2d;
|
||||||
|
|
||||||
|
public static MonthCalendarLayoutResult Calculate(VisibleDateRange range, IEnumerable<CalendarItemViewModel> items, double availableWidth, double availableHeight)
|
||||||
|
{
|
||||||
|
var cellWidth = availableWidth <= 0 ? 0d : availableWidth / ColumnCount;
|
||||||
|
var cellHeight = availableHeight <= 0 ? 0d : availableHeight / RowCount;
|
||||||
|
var cells = range.Dates
|
||||||
|
.Select((date, index) =>
|
||||||
|
{
|
||||||
|
var row = index / ColumnCount;
|
||||||
|
var column = index % ColumnCount;
|
||||||
|
return new MonthCellLayout(
|
||||||
|
date,
|
||||||
|
index,
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
new LayoutRect(column * cellWidth, row * cellHeight, cellWidth, cellHeight));
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var itemLayouts = new List<MonthItemLayout>();
|
||||||
|
|
||||||
|
foreach (var cell in cells)
|
||||||
|
{
|
||||||
|
var cellItems = GetCellItems(items, cell.Date).ToList();
|
||||||
|
|
||||||
|
for (var index = 0; index < cellItems.Count; index++)
|
||||||
|
{
|
||||||
|
var y = cell.Bounds.Y + DayLabelHeight + CellPadding + (index * (ItemHeight + ItemGap));
|
||||||
|
|
||||||
|
if (y + ItemHeight > cell.Bounds.Y + cell.Bounds.Height - CellPadding)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemLayouts.Add(new MonthItemLayout(
|
||||||
|
cellItems[index],
|
||||||
|
cell.Index,
|
||||||
|
cell.Date,
|
||||||
|
new LayoutRect(
|
||||||
|
cell.Bounds.X + CellPadding,
|
||||||
|
y,
|
||||||
|
Math.Max(0, cell.Bounds.Width - (CellPadding * 2)),
|
||||||
|
ItemHeight)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MonthCalendarLayoutResult(cellWidth, cellHeight, cells, itemLayouts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<CalendarItemViewModel> GetCellItems(IEnumerable<CalendarItemViewModel> items, DateOnly date)
|
||||||
|
{
|
||||||
|
var dayStart = date.ToDateTime(TimeOnly.MinValue);
|
||||||
|
var dayEnd = dayStart.AddDays(1);
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (!CalendarItemAccessor.TryGetTimeRange(item, out var start, out var end))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var localStart = start.LocalDateTime;
|
||||||
|
var localEnd = end.LocalDateTime;
|
||||||
|
|
||||||
|
if (localEnd <= localStart)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localStart < dayEnd && localEnd > dayStart)
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Wino.Calendar.ViewModels.Data;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public sealed class TimedItemLayout
|
||||||
|
{
|
||||||
|
public TimedItemLayout(CalendarItemViewModel item, int dayIndex, DateOnly date, LayoutRect bounds, DataTemplate? template = null)
|
||||||
|
{
|
||||||
|
Item = item;
|
||||||
|
DayIndex = dayIndex;
|
||||||
|
Date = date;
|
||||||
|
Bounds = bounds;
|
||||||
|
Template = template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalendarItemViewModel Item { get; set; }
|
||||||
|
public int DayIndex { get; set; }
|
||||||
|
public DateOnly Date { get; set; }
|
||||||
|
public LayoutRect Bounds { get; set; }
|
||||||
|
public DataTemplate? Template { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed record TimedCalendarLayoutResult(IReadOnlyList<DateOnly> VisibleDates, double DayWidth, IReadOnlyList<TimedItemLayout> Items);
|
||||||
|
|
||||||
|
internal static class TimedCalendarLayoutCalculator
|
||||||
|
{
|
||||||
|
public static double GetTimelineHeight(double hourHeight) => hourHeight * 24d;
|
||||||
|
|
||||||
|
public static TimedCalendarLayoutResult Calculate(VisibleDateRange range, IEnumerable<CalendarItemViewModel> items, double availableWidth, double hourHeight)
|
||||||
|
{
|
||||||
|
var visibleDates = range.Dates;
|
||||||
|
var dayWidth = visibleDates.Count == 0 ? 0d : availableWidth / visibleDates.Count;
|
||||||
|
var layouts = new List<TimedItemLayout>();
|
||||||
|
|
||||||
|
for (var dayIndex = 0; dayIndex < visibleDates.Count; dayIndex++)
|
||||||
|
{
|
||||||
|
var date = visibleDates[dayIndex];
|
||||||
|
var daySegments = BuildDaySegments(items, date)
|
||||||
|
.OrderBy(segment => segment.StartMinute)
|
||||||
|
.ThenBy(segment => segment.EndMinute)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var cluster in BuildClusters(daySegments))
|
||||||
|
{
|
||||||
|
AssignColumns(cluster);
|
||||||
|
var columnCount = cluster.Max(segment => segment.ColumnIndex) + 1;
|
||||||
|
var subColumnWidth = columnCount == 0 ? dayWidth : dayWidth / columnCount;
|
||||||
|
|
||||||
|
foreach (var segment in cluster)
|
||||||
|
{
|
||||||
|
var x = (dayIndex * dayWidth) + (segment.ColumnIndex * subColumnWidth) + 2;
|
||||||
|
var width = Math.Max(0, subColumnWidth - 4);
|
||||||
|
var y = (segment.StartMinute / 60d) * hourHeight;
|
||||||
|
var height = Math.Max(1, ((segment.EndMinute - segment.StartMinute) / 60d) * hourHeight);
|
||||||
|
|
||||||
|
layouts.Add(new TimedItemLayout(segment.Item, dayIndex, date, new LayoutRect(x, y, width, height)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TimedCalendarLayoutResult(visibleDates, dayWidth, layouts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Segment> BuildDaySegments(IEnumerable<CalendarItemViewModel> items, DateOnly date)
|
||||||
|
{
|
||||||
|
var dayStart = date.ToDateTime(TimeOnly.MinValue);
|
||||||
|
var dayEnd = dayStart.AddDays(1);
|
||||||
|
var segments = new List<Segment>();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (!CalendarItemAccessor.TryGetTimeRange(item, out var start, out var end))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var localStart = start.LocalDateTime;
|
||||||
|
var localEnd = end.LocalDateTime;
|
||||||
|
|
||||||
|
if (localEnd <= localStart)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var segmentStart = localStart > dayStart ? localStart : dayStart;
|
||||||
|
var segmentEnd = localEnd < dayEnd ? localEnd : dayEnd;
|
||||||
|
|
||||||
|
if (segmentEnd <= segmentStart)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
segments.Add(new Segment(item, (segmentStart - dayStart).TotalMinutes, (segmentEnd - dayStart).TotalMinutes));
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<List<Segment>> BuildClusters(List<Segment> segments)
|
||||||
|
{
|
||||||
|
if (segments.Count == 0)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cluster = new List<Segment> { segments[0] };
|
||||||
|
var clusterEnd = segments[0].EndMinute;
|
||||||
|
|
||||||
|
for (var index = 1; index < segments.Count; index++)
|
||||||
|
{
|
||||||
|
var segment = segments[index];
|
||||||
|
|
||||||
|
if (segment.StartMinute < clusterEnd)
|
||||||
|
{
|
||||||
|
cluster.Add(segment);
|
||||||
|
clusterEnd = Math.Max(clusterEnd, segment.EndMinute);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return cluster;
|
||||||
|
cluster = [segment];
|
||||||
|
clusterEnd = segment.EndMinute;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return cluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssignColumns(List<Segment> segments)
|
||||||
|
{
|
||||||
|
var columnEnds = new List<double>();
|
||||||
|
|
||||||
|
foreach (var segment in segments)
|
||||||
|
{
|
||||||
|
var assignedColumn = -1;
|
||||||
|
|
||||||
|
for (var columnIndex = 0; columnIndex < columnEnds.Count; columnIndex++)
|
||||||
|
{
|
||||||
|
if (columnEnds[columnIndex] <= segment.StartMinute)
|
||||||
|
{
|
||||||
|
assignedColumn = columnIndex;
|
||||||
|
columnEnds[columnIndex] = segment.EndMinute;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assignedColumn < 0)
|
||||||
|
{
|
||||||
|
assignedColumn = columnEnds.Count;
|
||||||
|
columnEnds.Add(segment.EndMinute);
|
||||||
|
}
|
||||||
|
|
||||||
|
segment.ColumnIndex = assignedColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record Segment(CalendarItemViewModel Item, double StartMinute, double EndMinute)
|
||||||
|
{
|
||||||
|
public int ColumnIndex { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
<ResourceDictionary
|
<ResourceDictionary
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="using:Wino.Mail.WinUI.Styles">
|
xmlns:data="using:Wino.Calendar.ViewModels.Data"
|
||||||
|
xmlns:local="using:Wino.Mail.WinUI.Styles"
|
||||||
|
xmlns:selectors1="using:Wino.Selectors">
|
||||||
|
|
||||||
<Style x:Key="WinoCalendarViewStyle" TargetType="CalendarView">
|
<Style x:Key="WinoCalendarViewStyle" TargetType="CalendarView">
|
||||||
<Setter Property="BlackoutStrikethroughBrush" Value="{ThemeResource CalendarViewBlackoutStrikethroughBrush}" />
|
<Setter Property="BlackoutStrikethroughBrush" Value="{ThemeResource CalendarViewBlackoutStrikethroughBrush}" />
|
||||||
@@ -701,34 +703,6 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!--<Style x:Name="WinoCalendarViewDayItemStyle" TargetType="CalendarViewDayItem">
|
|
||||||
<Setter Property="MinWidth" Value="20" />
|
|
||||||
<Setter Property="MinHeight" Value="20" />
|
|
||||||
<Setter Property="Margin" Value="1" />
|
|
||||||
<Setter Property="Padding" Value="0,0,0,4" />
|
|
||||||
<Setter Property="BorderBrush" Value="Red" />
|
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
|
||||||
<Setter Property="UseSystemFocusVisuals" Value="True" />
|
|
||||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
|
||||||
-->
|
|
||||||
<!--<Setter Property="FocusVisualPrimaryBrush" Value="{ThemeResource CalendarViewFocusVisualPrimaryBrush}" />-->
|
|
||||||
<!--
|
|
||||||
<Setter Property="FocusVisualPrimaryThickness" Value="2" />
|
|
||||||
-->
|
|
||||||
<!--<Setter Property="FocusVisualSecondaryBrush" Value="{ThemeResource CalendarViewFocusVisualSecondaryBrush}" />-->
|
|
||||||
<!--
|
|
||||||
<Setter Property="FocusVisualSecondaryThickness" Value="1" />
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="CalendarViewDayItem">
|
|
||||||
<Grid>
|
|
||||||
-->
|
|
||||||
<!--<TextBlock Text="a" />-->
|
|
||||||
<!--
|
|
||||||
</Grid>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>-->
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -8,12 +8,14 @@
|
|||||||
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
|
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
|
||||||
xmlns:coreSelectors="using:Wino.Mail.WinUI.Selectors"
|
xmlns:coreSelectors="using:Wino.Mail.WinUI.Selectors"
|
||||||
xmlns:coreViewModelData="using:Wino.Core.ViewModels.Data"
|
xmlns:coreViewModelData="using:Wino.Core.ViewModels.Data"
|
||||||
|
xmlns:data="using:Wino.Calendar.ViewModels.Data"
|
||||||
xmlns:domain="using:Wino.Core.Domain"
|
xmlns:domain="using:Wino.Core.Domain"
|
||||||
xmlns:helpers="using:Wino.Helpers"
|
xmlns:helpers="using:Wino.Helpers"
|
||||||
xmlns:local="using:Wino.Mail.WinUI.Styles"
|
xmlns:local="using:Wino.Mail.WinUI.Styles"
|
||||||
xmlns:menu="using:Wino.Core.Domain.MenuItems"
|
xmlns:menu="using:Wino.Core.Domain.MenuItems"
|
||||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||||
xmlns:personalization="using:Wino.Mail.WinUI.Models.Personalization"
|
xmlns:personalization="using:Wino.Mail.WinUI.Models.Personalization"
|
||||||
|
xmlns:selectors1="using:Wino.Selectors"
|
||||||
xmlns:shared="using:Wino.Core.Domain.Entities.Shared"
|
xmlns:shared="using:Wino.Core.Domain.Entities.Shared"
|
||||||
xmlns:viewModelData="using:Wino.Mail.ViewModels.Data"
|
xmlns:viewModelData="using:Wino.Mail.ViewModels.Data"
|
||||||
xmlns:winuiControls="using:CommunityToolkit.WinUI.Controls">
|
xmlns:winuiControls="using:CommunityToolkit.WinUI.Controls">
|
||||||
@@ -326,6 +328,36 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<!-- ShowAs Status Stripe Templates -->
|
||||||
|
<DataTemplate x:Key="FreeStripeTemplate" x:DataType="data:CalendarItemViewModel">
|
||||||
|
<Border Background="#4CAF50" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="TentativeStripeTemplate" x:DataType="data:CalendarItemViewModel">
|
||||||
|
<Border Background="#FFC107" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="BusyStripeTemplate" x:DataType="data:CalendarItemViewModel">
|
||||||
|
<Border Background="#F44336" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="OutOfOfficeStripeTemplate" x:DataType="data:CalendarItemViewModel">
|
||||||
|
<Border Background="#9C27B0" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="WorkingElsewhereStripeTemplate" x:DataType="data:CalendarItemViewModel">
|
||||||
|
<Border Background="#2196F3" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<!-- ShowAs Status Stripe Selector -->
|
||||||
|
<selectors1:CalendarItemShowAsStripeTemplateSelector
|
||||||
|
x:Key="ShowAsStripeSelector"
|
||||||
|
BusyTemplate="{StaticResource BusyStripeTemplate}"
|
||||||
|
FreeTemplate="{StaticResource FreeStripeTemplate}"
|
||||||
|
OutOfOfficeTemplate="{StaticResource OutOfOfficeStripeTemplate}"
|
||||||
|
TentativeTemplate="{StaticResource TentativeStripeTemplate}"
|
||||||
|
WorkingElsewhereTemplate="{StaticResource WorkingElsewhereStripeTemplate}" />
|
||||||
|
|
||||||
<!--#endregion-->
|
<!--#endregion-->
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -3,10 +3,17 @@
|
|||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:abstract="using:Wino.Calendar.Views.Abstract"
|
xmlns:abstract="using:Wino.Calendar.Views.Abstract"
|
||||||
|
xmlns:calendarControls="using:Wino.Calendar.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:domain="using:Wino.Core.Domain"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<Grid />
|
<Grid>
|
||||||
|
<calendarControls:CalendarPeriodControl
|
||||||
|
x:Name="CalendarSurface"
|
||||||
|
IsEnabled="{x:Bind ViewModel.IsCalendarEnabled, Mode=OneWay}"
|
||||||
|
CalendarItems="{x:Bind ViewModel.CalendarItems, Mode=OneWay}"
|
||||||
|
CalendarSettings="{x:Bind ViewModel.CurrentSettings, Mode=OneWay}"
|
||||||
|
VisibleRange="{x:Bind ViewModel.CurrentVisibleRange, Mode=OneWay}" />
|
||||||
|
</Grid>
|
||||||
</abstract:CalendarPageAbstract>
|
</abstract:CalendarPageAbstract>
|
||||||
|
|||||||
@@ -115,6 +115,7 @@
|
|||||||
<None Remove="BackgroundImages\Nighty.jpg" />
|
<None Remove="BackgroundImages\Nighty.jpg" />
|
||||||
<None Remove="BackgroundImages\Snowflake.jpg" />
|
<None Remove="BackgroundImages\Snowflake.jpg" />
|
||||||
<None Remove="Controls\ListView\WinoListViewStyles.xaml" />
|
<None Remove="Controls\ListView\WinoListViewStyles.xaml" />
|
||||||
|
<None Remove="Controls\Calendar\CalendarPeriodControl.xaml" />
|
||||||
<None Remove="ShellWindow.xaml" />
|
<None Remove="ShellWindow.xaml" />
|
||||||
<None Remove="Styles\CalendarShellNavigationViewStyle.xaml" />
|
<None Remove="Styles\CalendarShellNavigationViewStyle.xaml" />
|
||||||
<None Remove="Styles\CalendarThemeResources.xaml" />
|
<None Remove="Styles\CalendarThemeResources.xaml" />
|
||||||
@@ -218,6 +219,7 @@
|
|||||||
<PackageReference Include="sqlite-net-pcl" />
|
<PackageReference Include="sqlite-net-pcl" />
|
||||||
<PackageReference Include="EmailValidation" />
|
<PackageReference Include="EmailValidation" />
|
||||||
<PackageReference Include="System.Drawing.Common" />
|
<PackageReference Include="System.Drawing.Common" />
|
||||||
|
<PackageReference Include="SkiaSharp.Views.WinUI" />
|
||||||
<PackageReference Include="WinUIEx" />
|
<PackageReference Include="WinUIEx" />
|
||||||
<PackageReference Include="Wino.Mail.Contracts" />
|
<PackageReference Include="Wino.Mail.Contracts" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -341,6 +343,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Views\Mail\" />
|
<Folder Include="Views\Mail\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Controls\Calendar\CalendarPeriodControl.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Update="Styles\CalendarViewStyles.xaml">
|
<Page Update="Styles\CalendarViewStyles.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
|||||||
Reference in New Issue
Block a user