Files
Wino-Mail/Wino.Mail.WinUI/Controls/Calendar/MonthCalendarLayout.cs
2026-03-25 15:49:14 +01:00

156 lines
4.8 KiB
C#

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 RegularItemHeight = 18d;
private const double ExpandedItemHeight = 30d;
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();
var nextItemY = cell.Bounds.Y + DayLabelHeight + CellPadding;
for (var index = 0; index < cellItems.Count; index++)
{
var itemHeight = GetItemHeight(cellItems[index]);
if (nextItemY + 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,
nextItemY,
Math.Max(0, cell.Bounds.Width - (CellPadding * 2)),
itemHeight)));
nextItemY += itemHeight + ItemGap;
}
}
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;
}
}
}
private static double GetItemHeight(CalendarItemViewModel item)
=> item.IsAllDayEvent || item.IsMultiDayEvent
? ExpandedItemHeight
: RegularItemHeight;
}