| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037 |
- using Avalonia;
- using Avalonia.Animation;
- using Avalonia.Controls;
- using Avalonia.Controls.Primitives;
- using Avalonia.Controls.Shapes;
- using Avalonia.Controls.Templates;
- using Avalonia.Data;
- using Avalonia.Input;
- using Avalonia.Interactivity;
- using Avalonia.Layout;
- using Avalonia.Markup.Xaml;
- using Avalonia.Media;
- using Avalonia.Media.Transformation;
- using Avalonia.Metadata;
- using Avalonia.Styling;
- using Avalonia.Threading;
- using Avalonia.VisualTree;
- using CommunityToolkit.Mvvm.ComponentModel;
- using InABox.Core;
- using ReactiveUI;
- using System.Collections;
- using System.Collections.Specialized;
- using System.Diagnostics.CodeAnalysis;
- using System.Reactive.Linq;
- namespace InABox.Avalonia.Components;
- public class MonthViewCellEventArgs(DateTime date) : EventArgs
- {
- public DateTime Date { get; set; } = date;
- }
- public class MonthViewBlockEventArgs(object? value) : EventArgs
- {
- public object? Value { get; set; } = value;
- }
- public class MonthViewDateRangeEventArgs(DateTime startDate, DateTime endDate) : EventArgs
- {
- public DateTime StartDate { get; set; } = startDate;
- public DateTime EndDate { get; set; } = endDate;
- }
- public partial class MonthView : UserControl
- {
- public static readonly StyledProperty<double> MinimumBlockHeightProperty =
- AvaloniaProperty.Register<CalendarView, double>(nameof(MinimumBlockHeight), defaultValue: 15);
- public static readonly StyledProperty<IBinding?> StartDateMappingProperty =
- AvaloniaProperty.Register<MonthView, IBinding?>(nameof(StartDateMapping));
- public static readonly StyledProperty<IBinding?> EndDateMappingProperty =
- AvaloniaProperty.Register<MonthView, IBinding?>(nameof(EndDateMapping));
- public static readonly StyledProperty<IBinding?> ColourMappingProperty =
- AvaloniaProperty.Register<MonthView, IBinding?>(nameof(ColourMapping));
- public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
- AvaloniaProperty.Register<MonthView, IEnumerable?>(nameof(ItemsSource));
- public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
- AvaloniaProperty.Register<MonthView, IDataTemplate?>(nameof(ItemTemplate));
- public static readonly StyledProperty<DayOfWeek> FirstDayOfWeekProperty =
- AvaloniaProperty.Register<MonthView, DayOfWeek>(nameof(FirstDayOfWeek), DayOfWeek.Sunday);
- public static readonly StyledProperty<DateTime> CurrentDateProperty =
- AvaloniaProperty.Register<MonthView, DateTime>(nameof(CurrentDate), DateTime.Now);
- public double MinimumBlockHeight
- {
- get => GetValue(MinimumBlockHeightProperty);
- set => SetValue(MinimumBlockHeightProperty, value);
- }
- [AssignBinding]
- [InheritDataTypeFromItems(nameof(ItemsSource))]
- public IBinding? StartDateMapping
- {
- get => GetValue(StartDateMappingProperty);
- set => SetValue(StartDateMappingProperty, value);
- }
- [AssignBinding]
- [InheritDataTypeFromItems(nameof(ItemsSource))]
- public IBinding? EndDateMapping
- {
- get => GetValue(EndDateMappingProperty);
- set => SetValue(EndDateMappingProperty, value);
- }
- [AssignBinding]
- [InheritDataTypeFromItems(nameof(ItemsSource))]
- public IBinding? ColourMapping
- {
- get => GetValue(ColourMappingProperty);
- set => SetValue(ColourMappingProperty, value);
- }
- public IEnumerable? ItemsSource
- {
- get => GetValue(ItemsSourceProperty);
- set => SetValue(ItemsSourceProperty, value);
- }
- [InheritDataTypeFromItems(nameof(ItemsSource))]
- public IDataTemplate? ItemTemplate
- {
- get => GetValue(ItemTemplateProperty);
- set => SetValue(ItemTemplateProperty, value);
- }
- public DayOfWeek FirstDayOfWeek
- {
- get => GetValue(FirstDayOfWeekProperty);
- set => SetValue(FirstDayOfWeekProperty, value);
- }
- public DateTime CurrentDate
- {
- get => GetValue(CurrentDateProperty);
- set => SetValue(CurrentDateProperty, value);
- }
- public event EventHandler<MonthViewCellEventArgs>? CellClicked;
- public event EventHandler<MonthViewCellEventArgs>? CellHeld;
- public event EventHandler<MonthViewBlockEventArgs>? BlockClicked;
- public event EventHandler<MonthViewBlockEventArgs>? BlockHeld;
- public event EventHandler<MonthViewDateRangeEventArgs>? DateRangeChanged;
- static MonthView()
- {
- MinimumBlockHeightProperty.Changed.AddClassHandler<MonthView>(Render_Changed);
- ItemsSourceProperty.Changed.AddClassHandler<MonthView>(ItemsSource_Changed);
- FirstDayOfWeekProperty.Changed.AddClassHandler<MonthView>(Render_Changed);
- CurrentDateProperty.Changed.AddClassHandler<MonthView>(Render_Changed);
- }
- private static void Render_Changed(MonthView view, AvaloniaPropertyChangedEventArgs args)
- {
- view.Render();
- }
- public MonthView()
- {
- InitializeComponent();
- }
- private static void ItemsSource_Changed(MonthView view, AvaloniaPropertyChangedEventArgs args)
- {
- if(args.OldValue is INotifyCollectionChanged oldNotify)
- {
- oldNotify.CollectionChanged -= view.Collection_Changed;
- }
- view.Render(itemsChanged: true);
- if(view.ItemsSource is INotifyCollectionChanged notify)
- {
- notify.CollectionChanged += view.Collection_Changed;
- }
- }
- private void Collection_Changed(object? sender, NotifyCollectionChangedEventArgs e)
- {
- Render(itemsChanged: true);
- }
- private void Canvas_SizeChanged(object? sender, SizeChangedEventArgs e)
- {
- Render();
- }
- private DateTime _startDate;
- private class Block : StyledElement
- {
- public static readonly StyledProperty<DateTime> StartTimeProperty =
- AvaloniaProperty.Register<Block, DateTime>(nameof(StartDate));
- public static readonly StyledProperty<DateTime> EndTimeProperty =
- AvaloniaProperty.Register<Block, DateTime>(nameof(EndDate));
- public static readonly StyledProperty<bool> HoveredProperty =
- AvaloniaProperty.Register<Block, bool>(nameof(Hovered));
- public int NColumns { get; set; } = -1;
- public DateTime StartDate
- {
- get => GetValue(StartTimeProperty);
- set => SetValue(StartTimeProperty, value);
- }
- public DateTime EndDate
- {
- get => GetValue(EndTimeProperty);
- set => SetValue(EndTimeProperty, value);
- }
- public bool Hovered
- {
- get => GetValue(HoveredProperty);
- set => SetValue(HoveredProperty, value);
- }
- public object? Content { get; set; }
- public override string ToString()
- {
- return $"Block({StartDate:yyyy-MM-dd} - {EndDate:yyyy-MM-dd})";
- }
- }
- private List<IDisposable> _oldSubscriptions = new();
- private List<Block> _blocks = new();
- private void RecreateBlocksList()
- {
- if (ItemsSource is null) return;
- var startBinding = StartDateMapping;
- var endBinding = EndDateMapping;
- if(startBinding is null || endBinding is null)
- {
- return;
- }
- foreach(var subscription in _oldSubscriptions)
- {
- subscription.Dispose();
- }
- _oldSubscriptions.Clear();
- _blocks.Clear();
- foreach(var item in ItemsSource)
- {
- if (item is null) continue;
- var block = new Block
- {
- [!Block.StartTimeProperty] = startBinding,
- [!Block.EndTimeProperty] = endBinding,
- [Block.DataContextProperty] = item,
- Content = item
- };
- _oldSubscriptions.Add(block.GetObservable(Block.StartTimeProperty).Skip(1).Subscribe(x => UpdateBlock(block)));
- _oldSubscriptions.Add(block.GetObservable(Block.EndTimeProperty).Skip(1).Subscribe(x => UpdateBlock(block)));
- _blocks.Add(block);
- }
- _blocks.SortBy(x => x.StartDate);
- }
- private void UpdateBlock(Block block)
- {
- Render();
- }
- private static int FloorMod(int a, int b)
- {
- return ((a % b) + b) % b;
- }
- private class DayBlockEntry(Block block, int level)
- {
- public Block Block { get; set; } = block;
- public int Level { get; set; } = level;
- }
- private class DayBlock : StyledElement
- {
- public static readonly StyledProperty<int> OverflowProperty =
- AvaloniaProperty.Register<Block, int>(nameof(Overflow), 0);
- public DateTime Date { get; set; }
- public List<DayBlockEntry> Blocks { get; set; } = new();
- public int Level { get; set; } = 0;
- public int Overflow
- {
- get => GetValue(OverflowProperty);
- set => SetValue(OverflowProperty, value);
- }
- }
- private bool _itemsChanged = false;
- private bool _rerendering = false;
- private void Render(bool itemsChanged = false)
- {
- _itemsChanged = _itemsChanged || itemsChanged;
- if (!_rerendering)
- {
- _rerendering = true;
- Dispatcher.UIThread.InvokeAsync(DoRender);
- }
- }
- private double _colWidth;
- private double _colSpace;
- private double _rowHeight;
- private (DateTime, DateTime)? _lastDateRange;
- private const double _dayLabelWidth = 18;
- private List<Border> _blockControls = new();
- private void DoRender()
- {
- _rerendering = false;
- var itemsChanged = _itemsChanged;
- _itemsChanged = false;
- if (itemsChanged)
- {
- RecreateBlocksList();
- }
- var firstDayOfMonth = new DateTime(CurrentDate.Year, CurrentDate.Month, 1);
- var dayDiff = FloorMod(firstDayOfMonth.DayOfWeek - FirstDayOfWeek, 7);
- var startDate = firstDayOfMonth.AddDays(-dayDiff);
- _startDate = startDate;
- var dateRange = (start: _startDate, end: _startDate.AddDays(41)); // 41 because 6 rows, 7 columns, but minus 1.
- if(_lastDateRange != dateRange)
- {
- _lastDateRange = dateRange;
- Dispatcher.UIThread.InvokeAsync(() =>
- {
- DateRangeChanged?.Invoke(this, new(dateRange.start, dateRange.end));
- });
- }
- Canvas.Children.Clear();
- HeaderCanvas.Children.Clear();
- var colSpace = 1;
- if (Canvas.Bounds.Width == 0 || Canvas.Bounds.Height == 0) return;
- var columnWidth = (Canvas.Bounds.Width - 6 * colSpace) / 7;
- var rowHeight = (Canvas.Bounds.Height - 5 * colSpace) / 6;
- _colSpace = colSpace;
- _rowHeight = rowHeight;
- _colWidth = columnWidth;
- var minBlockHeight = 15;
- var dayLabelSpace = 22;
- var usableRowSpace = rowHeight - dayLabelSpace - 1;
- var nBlocksPerRow = (int)Math.Floor(usableRowSpace / minBlockHeight);
- var blockHeight = nBlocksPerRow == 0 ? minBlockHeight : usableRowSpace / nBlocksPerRow;
- // Row Separators
- for(int week = 1; week < 6; ++week)
- {
- var rowSeparator = new Rectangle
- {
- Width = Canvas.Bounds.Width,
- Height = 0.75,
- Fill = new SolidColorBrush(Colors.LightGray)
- };
- Canvas.SetTop(rowSeparator, (rowHeight + colSpace) * week - colSpace);
- Canvas.Children.Add(rowSeparator);
- }
- // Column Separators
- for(int day = 1; day < 7; ++day)
- {
- var colSeparator = new Rectangle
- {
- Height = Canvas.Bounds.Height,
- Width = 0.75,
- Fill = new SolidColorBrush(Colors.LightGray)
- };
- Canvas.SetLeft(colSeparator, (columnWidth + colSpace) * day - colSpace);
- Canvas.Children.Add(colSeparator);
- }
- // Day of Week Header Labels
- for(int day = 0; day < 7; ++day)
- {
- var dayOfWeek = (DayOfWeek)FloorMod((int)FirstDayOfWeek + day, 7);
- var block = new TextBlock
- {
- Text = dayOfWeek.ToString()[0..3],
- Margin = new(0, 0),
- Foreground = new SolidColorBrush(
- dayOfWeek == DateTime.Today.DayOfWeek && DateTime.Today.Month == CurrentDate.Month
- ? Colors.CornflowerBlue
- : Colors.Black),
- Width = columnWidth,
- TextAlignment = TextAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }.WithClasses("ExtraSmall", "Bold");
- block.SizeChanged += HeaderBlock_SizeChanged;
- Canvas.SetLeft(block, day * (columnWidth + colSpace));
- HeaderCanvas.Children.Add(block);
- }
- var dayBlocks = new Dictionary<DateTime, DayBlock>();
- // Cells and Day Labels
- for(int week = 0; week < 6; ++week)
- {
- for(int day = 0; day < 7; ++day)
- {
- var date = _startDate.AddDays(day + week * 7);
- var dayBlock = dayBlocks.GetValueOrAdd(date);
- dayBlock.Date = date;
- var cell = new Rectangle
- {
- Fill = new SolidColorBrush(Colors.Transparent),
- Width = _colWidth,
- Height = _rowHeight
- };
- cell.PointerPressed += Cell_PointerPressed;
- cell.PointerReleased += Cell_PointerReleased;
- Canvas.SetLeft(cell, day * (columnWidth + colSpace));
- Canvas.SetTop(cell, week * (rowHeight + colSpace));
- Canvas.Children.Add(cell);
- var border = new Border
- {
- Margin = new(2),
- Background = new SolidColorBrush(date == DateTime.Today && CurrentDate.Month == date.Month ? Colors.CornflowerBlue : Colors.Transparent),
- Width = _dayLabelWidth,
- Height = _dayLabelWidth,
- CornerRadius = new(_dayLabelWidth / 2),
- IsHitTestVisible = false
- };
- var block = new TextBlock
- {
- Text = date.Day.ToString(),
- Foreground = new SolidColorBrush(date.Month == CurrentDate.Month ? Colors.Black : Colors.Gray),
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- FontSize = 10
- }.WithClass("Bold");
- border.Child = block;
- Canvas.SetLeft(border, day * (columnWidth + colSpace));
- Canvas.SetTop(border, week * (rowHeight + colSpace));
- Canvas.Children.Add(border);
- var overflowBorder = new Border
- {
- Margin = new(2),
- Background = new SolidColorBrush(Colors.Red),
- Width = 18,
- Height = 18,
- CornerRadius = new(9)
- };
- var overflowText = new TextBlock
- {
- [!TextBlock.TextProperty] = dayBlock.GetObservable(DayBlock.OverflowProperty)
- .Select(x => $"+{x}")
- .ToBinding(),
- Foreground = new SolidColorBrush(Colors.White),
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- FontSize = 10
- }.WithClass("Bold");
- overflowBorder.Child = overflowText;
- overflowBorder[!Border.IsVisibleProperty] = dayBlock.GetObservable(DayBlock.OverflowProperty)
- .Select(x => x > 0)
- .ToBinding();
- overflowBorder.Tag = dayBlock;
- overflowBorder.PointerPressed += OverflowBorder_PointerPressed;
- overflowBorder.PointerReleased += OverflowBorder_PointerReleased;
- Canvas.SetRight(overflowBorder, Canvas.Bounds.Width - (day * (columnWidth + colSpace) + columnWidth));
- Canvas.SetTop(overflowBorder, week * (rowHeight + colSpace));
- Canvas.Children.Add(overflowBorder);
- }
- }
- // Blocks
- _blockControls.Clear();
- var colourBinding = ColourMapping;
- foreach(var block in _blocks)
- {
- for(var day = block.StartDate; day <= block.EndDate; day = day.AddDays(1))
- {
- dayBlocks.GetValueOrAdd(day).Blocks.Add(new(block, -1));
- }
- }
- foreach(var block in _blocks)
- {
- var startWeek = (int)Math.Floor((block.StartDate.Date - _startDate).Days / 7.0);
- var endWeek = (int)Math.Floor((block.EndDate.Date - _startDate).Days / 7.0);
- if (startWeek > 5) continue;
- if (endWeek < 0) continue;
- for (var week = Math.Max(startWeek, 0); week <= Math.Min(endWeek, 5); ++week)
- {
- // So, for each week, we need to find what level the block fits in. If any of the days are already full,
- // they don't count for working out the level.
- var startDay = week == startWeek ? FloorMod(block.StartDate.DayOfWeek - FirstDayOfWeek, 7) : 0;
- var endDay = week == endWeek ? FloorMod(block.EndDate.DayOfWeek - FirstDayOfWeek, 7) : 6;
- // Because the blocks are sorted, we actually only need to check the first day of the block. If that's full,
- // then we check the next day, etc.
- var startDateForWeek = _startDate.AddDays(week * 7 + startDay);
- var endDateForWeek = _startDate.AddDays(week * 7 + endDay);
- var level = -1;
- for(var checkDay = startDateForWeek; checkDay <= endDateForWeek; checkDay = checkDay.AddDays(1))
- {
- var dayBlock = dayBlocks.GetValueOrAdd(checkDay);
- var n = 0;
- while (true)
- {
- if(!dayBlock.Blocks.Any(x => x.Level == n))
- {
- break;
- }
- ++n;
- }
- if(n < nBlocksPerRow)
- {
- level = n;
- break;
- }
- else
- {
- // Skip this day.
- ++startDay;
- dayBlock.Overflow = Math.Max(dayBlock.Blocks.Count - nBlocksPerRow, 0);
- }
- }
- if (level < 0)
- {
- continue;
- }
- else
- {
- for(var day = startDateForWeek; day <= endDateForWeek; day = day.AddDays(1))
- {
- var entry = dayBlocks.GetValueOrAdd(day).Blocks.First(x => x.Block == block);
- entry.Level = level;
- }
- }
- var border = CreateControl(block, new Thickness(1, 1, endDateForWeek == block.EndDate ? 3 : 0, 1));
- Canvas.SetLeft(border, startDay * (columnWidth + colSpace));
- Canvas.SetTop(border, level * blockHeight + dayLabelSpace + week * (rowHeight + colSpace));
- border.Width = (endDay - startDay) * (columnWidth + colSpace) + columnWidth;
- border.Height = blockHeight;
- Canvas.Children.Add(border);
- }
- }
- /*var minY = double.MaxValue;
- var colX = 0.0;
- var i = 0;
- foreach(var columnKey in _columns)
- {
- if(!_blocks.TryGetValue(columnKey, out var columnBlocks))
- {
- continue;
- }
- var contentControl = new ContentControl
- {
- Content = columnKey,
- Width = colWidth * columnBlocks.Columns!.Count,
- [!Block.ContentTemplateProperty] = this[!HeaderTemplateProperty],
- };
- Canvas.SetLeft(contentControl, colX);
- HeaderCanvas.Children.Add(contentControl);
- contentControl.SizeChanged += ContentControl_SizeChanged;
- foreach(var column in columnBlocks.Columns!)
- {
- foreach(var block in column)
- {
- var blockY = GetRow(block.StartTime) * rowHeight;
- minY = Math.Min(blockY, minY);
- Canvas.SetTop(block, blockY);
- Canvas.SetLeft(block, colX);
- block.Height = Math.Max((GetRow(block.EndTime) - GetRow(block.StartTime)) * rowHeight, 5);
- block.Width = colWidth * block.NColumns;
- Canvas.Children.Add(block);
- }
- colX += colWidth;
- }
- if(i < _blocks.Count - 1)
- {
- var rectangle = new Rectangle
- {
- Width = 0.75,
- Height = Canvas.Height,
- Fill = new SolidColorBrush(Colors.LightGray)
- };
- Canvas.SetLeft(rectangle, colX);
- Canvas.Children.Add(rectangle);
- var headRectangle = new Rectangle
- {
- Width = 0.75,
- [!Rectangle.HeightProperty] = HeaderCanvas.WhenValueChanged(x => x.Bounds)
- .Select(x => x.Height)
- .ToBinding(),
- Fill = new SolidColorBrush(Colors.LightGray)
- };
- Canvas.SetLeft(headRectangle, colX);
- HeaderCanvas.Children.Add(headRectangle);
- colX += colSpace;
- }
- ++i;
- }
- Canvas.Width = colX;
- HeaderCanvas.Width = colX;
- if(minY == double.MaxValue)
- {
- ScrollViewer.Offset = new(0, 0);
- }
- else
- {
- ScrollViewer.Offset = new(0, Math.Max(minY - RowHeight / 2, 0));
- }
- var lines = new List<Control>();
- LabelCanvas.Children.Clear();
- LabelCanvas.Height = Canvas.Height;
- var y = rowHeight;
- for(var time = RowInterval; time < TimeSpan.FromHours(24); time += RowInterval)
- {
- var rectangle = new Rectangle
- {
- Width = Canvas.Width,
- Height = 0.75,
- Fill = new SolidColorBrush(Colors.LightGray)
- };
- Canvas.SetLeft(rectangle, 0);
- Canvas.SetTop(rectangle, y);
- lines.Add(rectangle);
- var block = new TextBlock
- {
- Text = time.ToString("hh\\:mm"),
- Margin = new(0, -5, 0, 0)
- }.WithClass("ExtraSmall");
- block.SizeChanged += Block_SizeChanged;
- Canvas.SetTop(block, y);
- LabelCanvas.Children.Add(block);
- y += rowHeight;
- }
- Canvas.Children.InsertRange(0, lines);*/
- }
- private Rectangle? _selectedRectangle;
- private void Cell_PointerPressed(object? sender, PointerPressedEventArgs e)
- {
- if (sender is not Rectangle rectangle) return;
- if(_selectedRectangle is not null)
- {
- _selectedRectangle.Fill = new SolidColorBrush(Colors.Transparent);
- _selectedRectangle = null;
- }
- if (ClosePopup()) return;
- if (!TryGetDateFromPosition(e, out var date)) return;
- _selectedRectangle = rectangle;
- _selectedRectangle.Fill = new SolidColorBrush(Colors.LightBlue, opacity: 0.5);
- PressedAction(() => CellHeld?.Invoke(this, new(date)));
- }
- private void Cell_PointerReleased(object? sender, PointerReleasedEventArgs e)
- {
- if (sender is not Rectangle rectangle) return;
- if (!TryGetDateFromPosition(e, out var date)) return;
- ReleasedAction(() =>
- {
- if(_selectedRectangle is not null)
- {
- _selectedRectangle.Fill = new SolidColorBrush(Colors.Transparent);
- _selectedRectangle = null;
- }
- CellClicked?.Invoke(this, new(date));
- });
- }
- private void RootPointerPressed(object? sender, PointerPressedEventArgs e)
- {
- }
- private Control? _magnifiedDayBackground;
- private Control? _magnifiedDayControl;
- private DayBlock? _magnifiedDay;
- private bool ClosePopup()
- {
- if(_magnifiedDayControl is not null)
- {
- _magnifiedDay = null;
- Canvas.Children.Remove(_magnifiedDayControl);
- Canvas.Children.Remove(_magnifiedDayBackground!);
- _magnifiedDayControl = null;
- _magnifiedDayBackground = null;
- return true;
- }
- else
- {
- return false;
- }
- }
- private void OverflowBorder_PointerReleased(object? sender, PointerReleasedEventArgs e)
- {
- if (sender is not Border control
- || control.Tag is not DayBlock dayBlock) return;
- e.Handled = true;
- if(_magnifiedDay == dayBlock)
- {
- return;
- }
- if(_magnifiedDayControl is not null)
- {
- Canvas.Children.Remove(_magnifiedDayControl);
- Canvas.Children.Remove(_magnifiedDayBackground!);
- }
- _magnifiedDay = dayBlock;
- _magnifiedDayBackground = new Rectangle
- {
- Fill = new SolidColorBrush(Colors.Black, opacity: 0.2),
- Width = Canvas.Bounds.Width,
- Height = Canvas.Bounds.Height,
- };
- Canvas.Children.Add(_magnifiedDayBackground);
- _magnifiedDayBackground.PointerReleased += _magnifiedDayBackground_PointerReleased;
- var popoutBorder = new Border
- {
- BorderBrush = new SolidColorBrush(Colors.LightGray),
- BorderThickness = new(0.75),
- Background = new SolidColorBrush(Colors.White),
- Width = _colWidth * 1.5,
- BoxShadow = new(new BoxShadow
- {
- Color = Colors.Gray,
- OffsetX = 0,
- OffsetY = 0,
- Blur = 10
- })
- };
- popoutBorder.PointerPressed += PopoutBorder_PointerPressed;
- var list = new StackPanel();
- // Day label
- {
- var dayOfWeek = dayBlock.Date.DayOfWeek.ToString()[..3];
- var dayLabel = new TextBlock
- {
- Text = $"{dayOfWeek} {dayBlock.Date.Day} {dayBlock.Date:MMM}",
- Foreground = new SolidColorBrush(dayBlock.Date.Month == DateTime.Today.Month ? Colors.Black : Colors.Gray),
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Center,
- Margin = new(2),
- FontSize = 10
- }.WithClass("Bold");
- list.Children.Add(dayLabel);
- }
- foreach(var blockEntry in dayBlock.Blocks)
- {
- var blockControl = CreateControl(blockEntry.Block, new(1));
- list.Children.Add(blockControl);
- }
- popoutBorder.Child = list;
- _magnifiedDayControl = popoutBorder;
- var day = (dayBlock.Date - _startDate).Days;
- var rowIdx = (int)Math.Floor(day / 7.0);
- var colIdx = day - rowIdx * 7;
- var centre = new Point(
- colIdx * (_colWidth + _colSpace) + _colWidth / 2,
- rowIdx * (_rowHeight + _colSpace) + _rowHeight / 2);
- popoutBorder.SizeChanged += (o, e) =>
- {
- Canvas.SetLeft(popoutBorder,
- Math.Clamp(centre.X - popoutBorder.Bounds.Width / 2, 0, Canvas.Bounds.Width - popoutBorder.Bounds.Width));
- Canvas.SetTop(popoutBorder,
- Math.Clamp(centre.Y - popoutBorder.Bounds.Height / 2, 0, Canvas.Bounds.Height - popoutBorder.Bounds.Height));
- };
- var animation = new Animation();
- {
- animation.Duration = TimeSpan.FromSeconds(0.1);
- var startFrame = new KeyFrame
- {
- Cue = new(0),
- };
- startFrame.Setters.Add(new Setter()
- {
- Property = Border.OpacityProperty,
- Value = 0.0
- });
- startFrame.Setters.Add(new Setter()
- {
- Property = ScaleTransform.ScaleXProperty,
- Value = 0.0
- });
- startFrame.Setters.Add(new Setter()
- {
- Property = ScaleTransform.ScaleYProperty,
- Value = 0.0
- });
- animation.Children.Add(startFrame);
- var endFrame = new KeyFrame
- {
- Cue = new(1.0),
- };
- endFrame.Setters.Add(new Setter()
- {
- Property = Border.OpacityProperty,
- Value = 1.0
- });
- endFrame.Setters.Add(new Setter()
- {
- Property = ScaleTransform.ScaleXProperty,
- Value = 1.0
- });
- endFrame.Setters.Add(new Setter()
- {
- Property = ScaleTransform.ScaleYProperty,
- Value = 1.0
- });
- animation.Children.Add(endFrame);
- }
- var backgroundAnimation = new Animation();
- {
- backgroundAnimation.Duration = TimeSpan.FromSeconds(0.1);
- var startFrame = new KeyFrame
- {
- Cue = new(0),
- };
- startFrame.Setters.Add(new Setter()
- {
- Property = Border.OpacityProperty,
- Value = 0.0
- });
- backgroundAnimation.Children.Add(startFrame);
- var endFrame = new KeyFrame
- {
- Cue = new(1.0),
- };
- endFrame.Setters.Add(new Setter()
- {
- Property = Border.OpacityProperty,
- Value = 1.0
- });
- backgroundAnimation.Children.Add(endFrame);
- }
- animation.RunAsync(popoutBorder).LogIfFail();
- backgroundAnimation.RunAsync(_magnifiedDayBackground).LogIfFail();
- Canvas.Children.Add(_magnifiedDayControl);
- }
- private void _magnifiedDayBackground_PointerReleased(object? sender, PointerReleasedEventArgs e)
- {
- ClosePopup();
- }
- private void OverflowBorder_PointerPressed(object? sender, PointerPressedEventArgs e)
- {
- if (sender is not Border border
- || border.Tag is not DayBlock dayBlock) return;
- e.Handled = true;
- }
- private void PopoutBorder_PointerPressed(object? sender, PointerPressedEventArgs e)
- {
- e.Handled = true;
- }
- private Control CreateControl(Block block, Thickness margin)
- {
- var border = new Border
- {
- };
- var innerBorder = new Border
- {
- [Border.DataContextProperty] = block.Content,
- Margin = margin,
- Tag = block
- };
- if(ColourMapping is not null)
- {
- innerBorder[!Border.BackgroundProperty] = ColourMapping;
- }
- innerBorder.PointerEntered += InnerBorder_PointerEntered;
- innerBorder.PointerExited += InnerBorder_PointerExited;
- innerBorder.PointerPressed += InnerBorder_PointerPressed;
- innerBorder.PointerReleased += InnerBorder_PointerReleased;
- innerBorder[!Border.BorderBrushProperty] = block.GetObservable(Block.HoveredProperty)
- .Select(x => x ? new SolidColorBrush(Colors.Black) : new SolidColorBrush(Colors.Transparent))
- .ToBinding();
- innerBorder.BorderThickness = new(1);
- border.Child = innerBorder;
- var contentControl = new ContentControl();
- contentControl.Content = block.Content;
- contentControl[!ContentControl.ContentTemplateProperty] = this[!ItemTemplateProperty];
- innerBorder.Child = contentControl;
- _blockControls.Add(innerBorder);
- return border;
- }
- private void InnerBorder_PointerPressed(object? sender, PointerPressedEventArgs e)
- {
- if (sender is not Border border
- || border.Tag is not Block block) return;
- if(_magnifiedDayControl is not null
- && !border.HasParent(_magnifiedDayControl))
- {
- ClosePopup();
- }
- e.Handled = true;
- PressedAction(() => BlockHeld?.Invoke(this, new(block.Content)));
- }
- private void InnerBorder_PointerReleased(object? sender, PointerReleasedEventArgs e)
- {
- if (sender is not Border border
- || border.Tag is not Block block) return;
- e.Handled = true;
- ReleasedAction(() => BlockClicked?.Invoke(this, new(block.Content)));
- }
- private void InnerBorder_PointerExited(object? sender, PointerEventArgs e)
- {
- if (sender is not Border border
- || border.Tag is not Block block) return;
- block.Hovered = _blockControls.Where(x => x.Tag == block).Any(x => x.IsPointerOver);
- }
- private void InnerBorder_PointerEntered(object? sender, PointerEventArgs e)
- {
- if (sender is not Border border
- || border.Tag is not Block block) return;
- block.Hovered = _blockControls.Where(x => x.Tag == block).Any(x => x.IsPointerOver);
- }
- private bool TryGetDateFromPosition(PointerEventArgs e, out DateTime date)
- {
- var point = e.GetPosition(Canvas);
- var rowIdx = Math.Clamp((int)Math.Floor(point.Y / _rowHeight), 0, 5);
- var colIdx = Math.Clamp((int)Math.Floor(point.X / (_colWidth + _colSpace)), 0, 6);
- date = _startDate.AddDays(rowIdx * 7 + colIdx);
- return true;
- }
- private CancellationTokenSource? cts = null;
- private void PressedAction(Action onHeld)
- {
- cts?.Cancel();
- cts = new();
- Task.Delay(1000).ContinueWith(task =>
- {
- cts = null;
- onHeld();
- }, cts.Token, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
- }
- private void ReleasedAction(Action onRelease)
- {
- if(cts is not null)
- {
- cts.Cancel();
- onRelease();
- }
- }
- private void HeaderBlock_SizeChanged(object? sender, SizeChangedEventArgs e)
- {
- if (sender is not TextBlock block) return;
- Canvas.SetTop(block, HeaderCanvas.Bounds.Height / 2 - block.Bounds.Height / 2);
- }
- }
|