| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162 |
- using InABox.Core;
- using InABox.Wpf;
- using InABox.WPF;
- using Microsoft.CodeAnalysis.VisualBasic.Syntax;
- using NPOI.OpenXmlFormats.Spreadsheet;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Collections.Specialized;
- using System.ComponentModel;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using System.Reactive.Linq;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Controls.Primitives;
- using System.Windows.Data;
- using System.Windows.Input;
- using System.Windows.Media;
- using System.Windows.Media.Animation;
- using System.Windows.Shapes;
- namespace InABox.WPF;
- public class CalendarBlockEventArgs(object? value, object column, DateTime date, TimeSpan start, TimeSpan end) : EventArgs
- {
- public object? Value { get; set; } = value;
- public DateTime Date { get; set; } = date;
- public object Column { get; set; } = column;
- public TimeSpan Start { get; set; } = start;
- public TimeSpan End { get; set; } = end;
- }
- public class CalendarControl : ContentControl
- {
- public static readonly DependencyProperty RowHeightProperty =
- DependencyProperty.Register(nameof(RowHeight), typeof(double), typeof(CalendarControl), new(100.0, Render_Changed));
- public static readonly DependencyProperty MinimumColumnWidthProperty =
- DependencyProperty.Register(nameof(MinimumColumnWidth), typeof(double), typeof(CalendarControl), new(50.0, Render_Changed));
- public static readonly DependencyProperty RowIntervalProperty =
- DependencyProperty.Register(nameof(RowInterval), typeof(TimeSpan), typeof(CalendarControl), new(TimeSpan.FromHours(1), Render_Changed));
- public static readonly DependencyProperty ItemsSourceProperty =
- DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(CalendarControl), new(ItemsSource_Changed));
- public static readonly DependencyProperty ItemTemplateProperty =
- DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(CalendarControl));
- public static readonly DependencyProperty DateTemplateProperty =
- DependencyProperty.Register(nameof(DateTemplate), typeof(DataTemplate), typeof(CalendarControl));
- public static readonly DependencyProperty HeaderTemplateProperty =
- DependencyProperty.Register(nameof(HeaderTemplate), typeof(DataTemplate), typeof(CalendarControl));
- public static readonly DependencyProperty ColumnsProperty =
- DependencyProperty.Register(nameof(Columns), typeof(IEnumerable), typeof(CalendarControl), new(Columns_Changed));
- public double MinimumColumnWidth
- {
- get => (double)GetValue(MinimumColumnWidthProperty);
- set => SetValue(MinimumColumnWidthProperty, value);
- }
- public double RowHeight
- {
- get => (double)GetValue(RowHeightProperty);
- set => SetValue(RowHeightProperty, value);
- }
- public TimeSpan RowInterval
- {
- get => (TimeSpan)GetValue(RowIntervalProperty);
- set => SetValue(RowIntervalProperty, value);
- }
- public BindingBase? DateMapping { get; set; }
- public BindingBase? ColumnMapping { get; set; }
- public BindingBase? StartTimeMapping { get; set; }
- public BindingBase? EndTimeMapping { get; set; }
- public IEnumerable? ItemsSource
- {
- get => GetValue(ItemsSourceProperty) as IEnumerable;
- set => SetValue(ItemsSourceProperty, value);
- }
- public DataTemplate? ItemTemplate
- {
- get => GetValue(ItemTemplateProperty) as DataTemplate;
- set => SetValue(ItemTemplateProperty, value);
- }
- public DataTemplate? DateTemplate
- {
- get => GetValue(DateTemplateProperty) as DataTemplate;
- set => SetValue(DateTemplateProperty, value);
- }
- public DataTemplate? HeaderTemplate
- {
- get => GetValue(HeaderTemplateProperty) as DataTemplate;
- set => SetValue(HeaderTemplateProperty, value);
- }
- public IEnumerable? Columns
- {
- get => GetValue(ColumnsProperty) as IEnumerable;
- set => SetValue(ColumnsProperty, value);
- }
- public event EventHandler<CalendarBlockEventArgs>? BlockClicked;
- public event EventHandler<CalendarBlockEventArgs>? BlockHeld;
- private static void Columns_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is not CalendarControl calendar) return;
- if(e.OldValue is INotifyCollectionChanged oldNotify)
- {
- oldNotify.CollectionChanged -= calendar.ColumnsCollection_Changed;
- }
- calendar.Render(columnsChanged: true);
- if(e.NewValue is INotifyCollectionChanged notify)
- {
- notify.CollectionChanged += calendar.ColumnsCollection_Changed;
- }
- }
- private void ColumnsCollection_Changed(object? sender, NotifyCollectionChangedEventArgs e)
- {
- Render(columnsChanged: true);
- }
- private static void Render_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is not CalendarControl calendar) return;
- calendar.Render();
- }
- private ScrollViewer DateScroll;
- private ScrollViewer HeaderScroll;
- private ScrollViewer LabelScroll;
- private ScrollViewer MainScroll;
- private ScrollBar VerticalScroll;
- private ScrollBar HorizontalScroll;
- private Canvas DateCanvas;
- private Canvas HeaderCanvas;
- private Canvas LabelCanvas;
- private Canvas MainCanvas;
- private Border HeaderBorder;
- public CalendarControl()
- {
- var grid = new Grid();
- grid.AddRow(GridUnitType.Auto); // Date
- grid.AddRow(GridUnitType.Auto); // Column
- grid.AddRow(GridUnitType.Star);
- grid.AddRow(GridUnitType.Auto); // Scroll Bar
- grid.AddColumn(GridUnitType.Auto); // Times
- grid.AddColumn(GridUnitType.Star);
- grid.AddColumn(GridUnitType.Auto); // ScrollBar
- DateScroll = new ScrollViewer
- {
- HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
- VerticalScrollBarVisibility = ScrollBarVisibility.Disabled
- };
- DateCanvas = new Canvas
- {
- };
- DateScroll.Content = DateCanvas;
- HeaderScroll = new ScrollViewer
- {
- HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
- VerticalScrollBarVisibility = ScrollBarVisibility.Disabled
- };
- HeaderCanvas = new Canvas
- {
- };
- HeaderScroll.Content = HeaderCanvas;
- LabelScroll = new ScrollViewer
- {
- VerticalScrollBarVisibility = ScrollBarVisibility.Hidden,
- HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled
- };
- LabelCanvas = new Canvas
- {
- Margin = new(2, 0, 2, 0)
- };
- LabelScroll.Content = LabelCanvas;
- MainScroll = new ScrollViewer
- {
- HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
- VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
- };
- MainCanvas = new Canvas
- {
- };
- MainScroll.Content = MainCanvas;
- MainScroll.SizeChanged += MainScroll_SizeChanged;
- MainScroll.ScrollChanged += MainScroll_ScrollChanged;
- MainScroll.PreviewMouseWheel += MainScroll_PreviewMouseWheel;
- grid.AddChild(
- new Border
- {
- BorderBrush = Colors.LightGray.ToBrush(),
- BorderThickness = new(0, 0, 1, 0)
- },
- row: 0, rowSpan: 2,
- column: 0);
- grid.AddChild(
- new Border
- {
- BorderBrush = Colors.LightGray.ToBrush(),
- BorderThickness = new(0, 0, 0, 1),
- Child = DateScroll
- },
- row: 0,
- column: 1, colSpan: 2);
- HeaderBorder = new Border
- {
- BorderBrush = Colors.LightGray.ToBrush(),
- BorderThickness = new(0, 0, 0, 1),
- Child = HeaderScroll
- };
- grid.AddChild(
- HeaderBorder,
- row: 1,
- column: 1, colSpan: 2);
- grid.AddChild(
- new Border
- {
- BorderBrush = Colors.LightGray.ToBrush(),
- BorderThickness = new(0, 0, 1, 0),
- Child = LabelScroll
- },
- row: 2, rowSpan: 2,
- column: 0);
- grid.AddChild(
- new Border
- {
- Child = MainScroll
- },
- row: 2, rowSpan: 2,
- column: 1, colSpan: 2);
- VerticalScroll = new ScrollBar();
- VerticalScroll.Scroll += VerticalScroll_Scroll;
- VerticalScroll.Opacity = 0.5;
- VerticalScroll.MouseEnter += Scroll_MouseEnter;
- VerticalScroll.MouseLeave += Scroll_MouseLeave;
- HorizontalScroll = new ScrollBar
- {
- Orientation = Orientation.Horizontal
- };
- HorizontalScroll.Scroll += HorizontalScroll_Scroll;
- HorizontalScroll.Opacity = 0.5;
- HorizontalScroll.MouseEnter += Scroll_MouseEnter;
- HorizontalScroll.MouseLeave += Scroll_MouseLeave;
- grid.AddChild(VerticalScroll, 2, 2);
- grid.AddChild(HorizontalScroll, 3, 1);
- Content = grid;
- }
- private (double x, double y) _currentScroll;
- private void Scroll_MouseLeave(object sender, MouseEventArgs e)
- {
- if(sender is ScrollBar bar)
- {
- bar.Opacity = 0.5;
- }
- }
- private void Scroll_MouseEnter(object sender, MouseEventArgs e)
- {
- if(sender is ScrollBar bar)
- {
- bar.Opacity = 1;
- }
- }
- private void MainScroll_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
- {
- if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
- {
- MainScroll.ScrollToHorizontalOffset(MainScroll.HorizontalOffset - e.Delta);
- e.Handled = true;
- }
- }
- private void HorizontalScroll_Scroll(object sender, ScrollEventArgs e)
- {
- MainScroll.ScrollToHorizontalOffset(e.NewValue);
- }
- private void VerticalScroll_Scroll(object sender, ScrollEventArgs e)
- {
- MainScroll.ScrollToVerticalOffset(e.NewValue);
- }
- private void MainScroll_ScrollChanged(object sender, ScrollChangedEventArgs e)
- {
- LabelScroll.ScrollToVerticalOffset(e.VerticalOffset);
- HeaderScroll.ScrollToHorizontalOffset(e.HorizontalOffset);
- DateScroll.ScrollToHorizontalOffset(e.HorizontalOffset);
- HorizontalScroll.Value = e.HorizontalOffset;
- VerticalScroll.Value = e.VerticalOffset;
- _currentScroll = (e.HorizontalOffset, e.VerticalOffset);
- }
- private static void ItemsSource_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is not CalendarControl calendar) return;
- if(e.OldValue is INotifyCollectionChanged oldNotify)
- {
- oldNotify.CollectionChanged -= calendar.Collection_Changed;
- }
- calendar.Render(itemsChanged: true);
- if(calendar.ItemsSource is INotifyCollectionChanged notify)
- {
- notify.CollectionChanged += calendar.Collection_Changed;
- }
- }
- private void Collection_Changed(object? sender, NotifyCollectionChangedEventArgs e)
- {
- Render(itemsChanged: true);
- }
- private void MainScroll_SizeChanged(object sender, SizeChangedEventArgs e)
- {
- Render();
- }
- private class Block : Border, INotifyPropertyChanged
- {
- public static readonly DependencyProperty ColumnProperty =
- DependencyProperty.Register(nameof(Column), typeof(object), typeof(Block), new(OnPropertyChangedHandler));
- public static readonly DependencyProperty DateProperty =
- DependencyProperty.Register(nameof(Date), typeof(DateTime), typeof(Block), new(OnPropertyChangedHandler));
- public static readonly DependencyProperty StartTimeProperty =
- DependencyProperty.Register(nameof(StartTime), typeof(TimeSpan), typeof(Block));
- public static readonly DependencyProperty EndTimeProperty =
- DependencyProperty.Register(nameof(EndTime), typeof(TimeSpan), typeof(Block));
- public int ColumnIndex { get; set; } = -1;
- public int NColumns { get; set; } = -1;
- public object Column
- {
- get => GetValue(ColumnProperty);
- set => SetValue(ColumnProperty, value);
- }
- public DateTime Date
- {
- get => (DateTime)GetValue(DateProperty);
- set => SetValue(DateProperty, value);
- }
- public TimeSpan StartTime
- {
- get => (TimeSpan)GetValue(StartTimeProperty);
- set => SetValue(StartTimeProperty, value);
- }
- public TimeSpan EndTime
- {
- get => (TimeSpan)GetValue(EndTimeProperty);
- set => SetValue(EndTimeProperty, value);
- }
- private ContentControl _contentControl;
- public ContentControl ContentControl => _contentControl;
- public object? Content => _contentControl.Content;
- public override string ToString()
- {
- return $"Block({Column}: {StartTime:hh\\:mm} - {EndTime:hh\\:mm})";
- }
- public Block(CalendarControl parent, object content)
- {
- _contentControl = new ContentControl
- {
- Content = content
- };
- _contentControl.Bind(ContentControl.ContentTemplateProperty, parent, x => x.ItemTemplate);
- Child = _contentControl;
- }
- public event PropertyChangedEventHandler? PropertyChanged;
- private static void OnPropertyChangedHandler(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is not Block block) return;
- block.PropertyChanged?.Invoke(block, new(e.Property.Name));
- }
- }
- private class Column
- {
- public List<Block> Blocks { get; set; } = new();
- public List<List<Block>>? Columns { get; set; } = null;
- }
- private List<Block> _blockList = new();
- private Dictionary<DateTime, Dictionary<object, Column>> _blocks = new();
- private List<IDisposable> _oldSubscriptions = new();
- private List<object> _columns = new();
- private List<(DateTime, object)> _columnList = new();
- private IEnumerable<KeyValuePair<(DateTime, object), Column>> _allColumns =>
- _blocks.SelectMany(x => x.Value.Select(y => new KeyValuePair<(DateTime, object), Column>((x.Key, y.Key), y.Value)));
- private class ActionDisposable(Action onDispose) : IDisposable
- {
- public void Dispose()
- {
- onDispose();
- }
- }
- private bool RecreateBlocksList()
- {
- if (ItemsSource is null) return false;
- var columnBinding = ColumnMapping;
- var dateBinding = DateMapping;
- var startBinding = StartTimeMapping;
- var endBinding = EndTimeMapping;
- if(columnBinding is null || dateBinding is null || startBinding is null || endBinding is null)
- {
- return false;
- }
- foreach(var subscription in _oldSubscriptions)
- {
- subscription.Dispose();
- }
- _oldSubscriptions.Clear();
- _blockList.Clear();
- foreach(var item in ItemsSource)
- {
- if (item is null) continue;
- var block = new Block(this, item);
- block.SetBinding(Block.ColumnProperty, columnBinding);
- block.SetBinding(Block.StartTimeProperty, startBinding);
- block.SetBinding(Block.EndTimeProperty, endBinding);
- block.SetBinding(Block.DateProperty, dateBinding);
- block.DataContext = item;
- block.Background = Colors.Transparent.ToBrush();
- block.PropertyChanged += Block_PropertyChanged;
- _oldSubscriptions.Add(new ActionDisposable(() => block.PropertyChanged -= Block_PropertyChanged));
- block.ContentControl.MouseLeftButtonDown += (o, e) =>
- {
- Block_MouseLeftButtonDown(block, e);
- };
- block.ContentControl.MouseLeftButtonUp += (o, e) =>
- {
- Block_MouseLeftButtonUp(block, e);
- };
- _blockList.Add(block);
- }
- return true;
- }
- private void RefreshColumns(bool itemsChanged = false)
- {
- if (itemsChanged)
- {
- if (!RecreateBlocksList()) return;
- }
- _blocks.Clear();
- _columns.Clear();
- var autoGenerateColumns = true;
- if(Columns is not null)
- {
- autoGenerateColumns = false;
- foreach(var column in Columns)
- {
- if(column is null) continue;
- _columns.Add(column);
- }
- }
- foreach(var block in _blockList)
- {
- var column = block.Column;
- var date = block.Date;
- if(column is null)
- {
- continue;
- }
- if(!_blocks.TryGetValue(date, out var dateBlocks))
- {
- dateBlocks = new();
- _blocks.Add(date, dateBlocks);
- if (!autoGenerateColumns)
- {
- foreach(var col in _columns)
- {
- if(!dateBlocks.TryAdd(col, new()))
- {
- throw new Exception($"Duplicate column {column} in Calendar");
- }
- }
- }
- }
- if(!dateBlocks.TryGetValue(column, out var columnBlocks))
- {
- if (!autoGenerateColumns) continue;
- columnBlocks = new();
- dateBlocks.Add(column, columnBlocks);
- _columns.Add(column);
- }
- columnBlocks.Blocks.Add(block);
- }
- HeaderBorder.Visibility = _columns.Count <= 1 ? Visibility.Collapsed : Visibility.Visible;
- }
- private void Block_PropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if (sender is not Block block) return;
- if(e.PropertyName == nameof(Block.Column))
- {
- Render(columnsChanged: true);
- }
- else if(e.PropertyName == nameof(Block.StartTime)
- || e.PropertyName == nameof(Block.EndTime))
- {
- UpdateBlock(block);
- }
- }
- private double _colWidth;
- private double _colSpace;
- private double _rowHeight;
- private bool _columnsChanged = false;
- private bool _itemsChanged = false;
- private bool _recalculatePositions = false;
- private bool _rerendering = false;
- private void Render(bool columnsChanged = false, bool itemsChanged = false, bool recalculatePositions = false)
- {
- _columnsChanged = _columnsChanged || columnsChanged;
- _itemsChanged = _itemsChanged || itemsChanged;
- _recalculatePositions = _recalculatePositions || recalculatePositions;
- if (!_rerendering)
- {
- _rerendering = true;
- Dispatcher.BeginInvoke(DoRender);
- }
- }
- private double _lastColWidth;
- private void DoRender()
- {
- _rerendering = false;
- var itemsChanged = _itemsChanged;
- var columnsChanged = _columnsChanged;
- var recalculatePositions = _recalculatePositions;
- _columnsChanged = false;
- _itemsChanged = false;
- _recalculatePositions = false;
- if (itemsChanged || columnsChanged)
- {
- RefreshColumns(itemsChanged: itemsChanged);
- }
- if (recalculatePositions)
- {
- foreach(var (column, columnBlocks) in _allColumns)
- {
- columnBlocks.Columns = null;
- }
- }
- var nRows = (24 / RowInterval.TotalHours);
- var rowHeight = Math.Max(RowHeight, MainScroll.ActualHeight / nRows);
- MainCanvas.Children.Clear();
- MainCanvas.Height = rowHeight * nRows;
- var minColWidth = MinimumColumnWidth;
- var colSpace = 1;
- var nColumns = 0;
- foreach (var (column, columnBlocks) in _allColumns)
- {
- columnBlocks.Columns ??= RecalculateBlockPositionsForDay(columnBlocks.Blocks);
- // columnsPerDay = Math.Max(columnsPerDay, columnBlocks.Columns.Count);
- nColumns += columnBlocks.Columns.Count;
- }
- // nColumns = columnsPerDay * _blocks.Count
- var colWidth = (Math.Max((MainScroll.ActualWidth - colSpace * (_blocks.Sum(x => x.Value.Count) - 1)) / nColumns, minColWidth));
- var lastColWidth = _lastColWidth;
- _lastColWidth = colWidth;
- var updateHeaders = colWidth != lastColWidth || columnsChanged || itemsChanged;
- if (updateHeaders)
- {
- HeaderCanvas.Children.Clear();
- DateCanvas.Children.Clear();
- }
- ClearHeldSelection();
- _rowHeight = rowHeight;
- _colWidth = colWidth;
- _colSpace = colSpace;
- var minY = double.MaxValue;
- var colX = 0.0;
- var dates = _blocks.Keys.ToArray();
- Array.Sort(dates);
- _columnList.Clear();
- var columnIdx = 0;
- foreach(var date in dates)
- {
- if (!_blocks.TryGetValue(date, out var dateBlocks)) continue;
- if (updateHeaders)
- {
- var nDateColumns = dateBlocks.Sum(x => x.Value.Columns!.Count);
- var dateHeader = new ContentControl
- {
- Content = date,
- Width = colWidth * nDateColumns + colSpace * (nDateColumns - 1)
- };
- dateHeader.Bind(ContentControl.ContentTemplateProperty, this, x => x.DateTemplate);
- Canvas.SetLeft(dateHeader, colX);
- DateCanvas.Children.Add(dateHeader);
- dateHeader.SizeChanged += DateHeader_SizeChanged;
- }
- var dateColumnIndex = 0;
- foreach(var columnKey in _columns)
- {
- if(!dateBlocks.TryGetValue(columnKey, out var columnBlocks))
- {
- continue;
- }
- _columnList.Add((date, columnKey));
- if (updateHeaders)
- {
- var columnHeader = new ContentControl
- {
- Content = columnKey,
- Width = colWidth * columnBlocks.Columns!.Count,
- };
- columnHeader.Bind(ContentControl.ContentTemplateProperty, this, x => x.HeaderTemplate);
- Canvas.SetLeft(columnHeader, colX);
- HeaderCanvas.Children.Add(columnHeader);
- columnHeader.SizeChanged += ColumnHeader_SizeChanged;
- }
- // Add cell placeholders
- var rowIdx = 0;
- for(var time = TimeSpan.Zero; time < TimeSpan.FromHours(24); time += RowInterval)
- {
- var rectangle = new Rectangle
- {
- Width = colWidth * columnBlocks.Columns!.Count,
- Height = RowHeight,
- Fill = new SolidColorBrush(Colors.Transparent),
- };
- rectangle.MouseEnter += (o, e) =>
- {
- rectangle.Fill = Colors.LightBlue.ToBrush();
- };
- rectangle.MouseLeave += (o, e) =>
- {
- rectangle.Fill = Colors.Transparent.ToBrush();
- };
- rectangle.MouseLeftButtonDown += Rectangle_MouseLeftButtonDown;
- rectangle.MouseLeftButtonUp += Rectangle_MouseLeftButtonUp;
- Canvas.SetLeft(rectangle, colX);
- Canvas.SetTop(rectangle, rowIdx * rowHeight);
- MainCanvas.Children.Add(rectangle);
- ++rowIdx;
- }
- 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;
- MainCanvas.Children.Add(block);
- }
- colX += colWidth;
- }
- // Add Header separators
- if(columnIdx < nColumns - 1)
- {
- var rectangle = new Rectangle
- {
- Width = 0.75,
- Height = MainCanvas.Height,
- Fill = new SolidColorBrush(Colors.LightGray)
- };
- Canvas.SetLeft(rectangle, colX);
- MainCanvas.Children.Add(rectangle);
- if (updateHeaders)
- {
- var headRectangle = new Rectangle
- {
- Width = 0.75,
- Fill = new SolidColorBrush(Colors.LightGray)
- };
- headRectangle.Bind(Rectangle.HeightProperty, HeaderCanvas, x => x.ActualHeight);
- Canvas.SetLeft(headRectangle, colX);
- HeaderCanvas.Children.Add(headRectangle);
- if(dateColumnIndex == dateBlocks.Count - 1)
- {
- var dateRectangle = new Rectangle
- {
- Width = 0.75,
- Fill = new SolidColorBrush(Colors.LightGray)
- };
- dateRectangle.Bind(Rectangle.HeightProperty, DateCanvas, x => x.ActualHeight);
- Canvas.SetLeft(dateRectangle, colX);
- DateCanvas.Children.Add(dateRectangle);
- }
- }
- colX += colSpace;
- }
- ++dateColumnIndex;
- ++columnIdx;
- }
- }
- MainCanvas.Width = Math.Floor(colX);
- HeaderCanvas.Width = Math.Floor(colX);
- DateCanvas.Width = Math.Floor(colX);
- VerticalScroll.Minimum = 0;
- VerticalScroll.Maximum = MainCanvas.Height - MainScroll.ActualHeight;
- VerticalScroll.ViewportSize = MainScroll.ActualHeight;
- VerticalScroll.Visibility = VerticalScroll.Maximum < 1 ? Visibility.Collapsed : Visibility.Visible;
- HorizontalScroll.Minimum = 0;
- HorizontalScroll.Maximum = MainCanvas.Width - MainScroll.ActualWidth;
- HorizontalScroll.ViewportSize = MainScroll.ActualWidth;
- HorizontalScroll.Visibility = HorizontalScroll.Maximum < 1 ? Visibility.Collapsed : Visibility.Visible;
- if(minY == double.MaxValue)
- {
- MainScroll.ScrollToHorizontalOffset(_currentScroll.x);
- MainScroll.ScrollToVerticalOffset(_currentScroll.y);
- }
- else
- {
- MainScroll.ScrollToHorizontalOffset(_currentScroll.x);
- MainScroll.ScrollToVerticalOffset(Math.Max(minY - RowHeight / 2, _currentScroll.y));
- }
- var lines = new List<FrameworkElement>();
- LabelCanvas.Children.Clear();
- LabelCanvas.Height = MainCanvas.Height;
- var y = rowHeight;
- for(var time = RowInterval; time < TimeSpan.FromHours(24); time += RowInterval)
- {
- var rectangle = new Rectangle
- {
- Width = MainCanvas.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),
- FontSize = 10
- };
- block.SizeChanged += Block_SizeChanged;
- Canvas.SetTop(block, y);
- LabelCanvas.Children.Add(block);
- y += rowHeight;
- }
- for(var i = 0; i < lines.Count; ++i)
- {
- MainCanvas.Children.Insert(i, lines[i]);
- }
- }
- private bool TryGetBlockFromPosition(MouseEventArgs e, out DateTime blockDate, [NotNullWhen(true)] out object? column, out TimeSpan start, out TimeSpan end, out int index)
- {
- var point = e.GetPosition(MainCanvas);
- var rowIdx = (int)Math.Floor(point.Y / _rowHeight);
- start = RowInterval * rowIdx;
- end = RowInterval * (rowIdx + 1);
- if(start.TotalHours < 0)
- {
- start = TimeSpan.Zero;
- }
- if(end.TotalHours >= 24)
- {
- end = TimeSpan.FromHours(24).Subtract(TimeSpan.FromTicks(1));
- }
- column = null;
- index = -1;
- blockDate = DateTime.MinValue;
- var x = point.X;
- foreach(var (date, columnKey) in _columnList)
- {
- if (!_blocks.TryGetValue(date, out var dateBlocks)
- || !dateBlocks.TryGetValue(columnKey, out var columnBlocks)) continue;
- var colWidth = columnBlocks.Columns!.Count * _colWidth + _colSpace;
- if(x < colWidth)
- {
- column = columnKey;
- index = Math.Min((int)Math.Floor(x / _colWidth), columnBlocks.Columns!.Count - 1);
- blockDate = date;
- break;
- }
- else
- {
- x -= colWidth;
- }
- }
- return column is not null;
- }
- 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 Block_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- if (sender is not Block block) return;
- e.Handled = true;
- PressedAction(() => BlockHeld?.Invoke(this, new(block.Content, block.Column, block.Date, block.StartTime, block.EndTime)));
- }
- private void Block_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- if (sender is not Block block) return;
- e.Handled = true;
- ReleasedAction(() => BlockClicked?.Invoke(this, new(block.Content, block.Column, block.Date, block.StartTime, block.EndTime)));
- }
- private Border? _heldSelection;
- public void ClearHeldSelection()
- {
- MainCanvas.Children.Remove(_heldSelection);
- _heldSelection = null;
- }
- private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
- {
- if (sender is not UIElement element) return;
- if (!TryGetBlockFromPosition(e, out var date, out var column, out var start, out var end, out var index)) return;
- var pos = e.GetPosition(MainCanvas);
- ClearHeldSelection();
- PressedAction(() =>
- {
- if(_heldSelection is null)
- {
- _heldSelection = new Border
- {
- Background = Colors.Black.ToBrush(0.2)
- };
- }
- else
- {
- MainCanvas.Children.Remove(_heldSelection);
- }
- var element = MainCanvas.Children.OfType<Block>().FirstOrDefault(x => x.Date == date && x.Column == column);
- if(element is not null)
- {
- var idx = MainCanvas.Children.IndexOf(element);
- MainCanvas.Children.Insert(idx + 1, _heldSelection);
- }
- else
- {
- MainCanvas.Children.Add(_heldSelection);
- }
- var blockStart = start;
- var blockEnd = TimeSpan.MinValue;
- var x = 0.0;
- foreach(var (columnDate, columnKey) in _columnList)
- {
- if (!_blocks.TryGetValue(columnDate, out var dateBlocks)
- || !dateBlocks.TryGetValue(columnKey, out var columnBlocks)) continue;
- var colWidth = columnBlocks.Columns!.Count * _colWidth + _colSpace;
- if(date == columnDate && column == columnKey)
- {
- x += index * _colWidth;
- var list = columnBlocks.Blocks.Where(x =>
- {
- return x.ColumnIndex <= index && index < x.ColumnIndex + x.NColumns;
- }).ToList();
- list.SortBy(x => x.StartTime);
- for(int i = 0; i < list.Count; ++i)
- {
- if(start < list[i].StartTime)
- {
- blockEnd = list[i].StartTime;
- break;
- }
- else
- {
- blockStart = list[i].EndTime;
- if(i == list.Count - 1)
- {
- blockEnd = TimeSpan.FromHours(24).Subtract(TimeSpan.FromTicks(1));
- }
- }
- }
- break;
- }
- x += colWidth;
- }
- if(blockEnd == TimeSpan.MinValue)
- {
- blockStart = start;
- blockEnd = end;
- }
- var top = (blockStart.TotalHours / RowInterval.TotalHours) * RowHeight;
- var height = ((blockEnd - blockStart).TotalHours / RowInterval.TotalHours) * RowHeight;
- var width = _colWidth;
- var duration = TimeSpan.FromSeconds(0.2);
- var leftAnimation = new DoubleAnimation
- {
- From = pos.X,
- To = x,
- Duration = duration
- };
- Storyboard.SetTarget(leftAnimation, _heldSelection);
- Storyboard.SetTargetProperty(leftAnimation, new PropertyPath("(Canvas.Left)"));
- var widthAnimation = new DoubleAnimation
- {
- From = 0,
- To = width,
- Duration = duration
- };
- Storyboard.SetTarget(widthAnimation, _heldSelection);
- Storyboard.SetTargetProperty(widthAnimation, new PropertyPath("Width"));
- var topAnimation = new DoubleAnimation
- {
- From = pos.Y,
- To = top,
- Duration = duration
- };
- Storyboard.SetTarget(topAnimation, _heldSelection);
- Storyboard.SetTargetProperty(topAnimation, new PropertyPath("(Canvas.Top)"));
- var heightAnimation = new DoubleAnimation
- {
- From = 0,
- To = height,
- Duration = duration
- };
- Storyboard.SetTarget(heightAnimation, _heldSelection);
- Storyboard.SetTargetProperty(heightAnimation, new PropertyPath("Height"));
- var storyBoard = new Storyboard();
- storyBoard.Children.Add(leftAnimation);
- storyBoard.Children.Add(topAnimation);
- storyBoard.Children.Add(widthAnimation);
- storyBoard.Children.Add(heightAnimation);
- storyBoard.Completed += (o, e) =>
- {
- BlockHeld?.Invoke(this, new(null, column, date, blockStart, blockEnd));
- };
- storyBoard.Begin();
- });
- }
- private void Rectangle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
- {
- if (!TryGetBlockFromPosition(e, out var date, out var column, out var start, out var end, out var index)) return;
- ReleasedAction(() =>
- {
- BlockClicked?.Invoke(this, new(null, column, date, start, end));
- });
- }
- private void ColumnHeader_SizeChanged(object? sender, SizeChangedEventArgs e)
- {
- HeaderCanvas.Height = HeaderCanvas.Children.OfType<FrameworkElement>().Select(x => x.ActualHeight).Max();
- }
- private void DateHeader_SizeChanged(object sender, SizeChangedEventArgs e)
- {
- DateCanvas.Height = DateCanvas.Children.OfType<FrameworkElement>().Select(x => x.ActualHeight).Max();
- }
- private void Block_SizeChanged(object? sender, SizeChangedEventArgs e)
- {
- LabelCanvas.Width = LabelCanvas.Children.OfType<FrameworkElement>().Select(x => x.ActualWidth).Max();
- }
- private double GetRow(TimeSpan time)
- {
- return time.TotalHours / RowInterval.TotalHours;
- }
- private static List<List<Block>> RecalculateBlockPositionsForDay(List<Block> dayBlocks)
- {
- dayBlocks.SortBy(x => x.StartTime);
- var columns = new List<List<Block>>();
- var remainingBlocks = dayBlocks;
- while(remainingBlocks.Count > 0)
- {
- // At least one block will be moved, so we can use 1 less than the remaining as capacity.
- var tempRemainingBlocks = new List<Block>(remainingBlocks.Count - 1);
- var newBlocks = new List<Block>(remainingBlocks.Count);
- var curTime = TimeSpan.MinValue;
- Block? curBlock = null;
- foreach(var block in remainingBlocks)
- {
- if(curBlock is not null && block.StartTime < curTime)
- {
- tempRemainingBlocks.Add(block);
- }
- else
- {
- newBlocks.Add(block);
- curTime = block.EndTime;
- curBlock = block;
- }
- }
- columns.Add(newBlocks);
- remainingBlocks = tempRemainingBlocks;
- }
- for(int i = 0; i < columns.Count; ++i)
- {
- foreach(var block in columns[i])
- {
- var nColumns = -1;
- for(int j = i + 1; j < columns.Count; ++j)
- {
- foreach(var block2 in columns[j])
- {
- if(block.StartTime < block2.EndTime && block.EndTime > block2.StartTime)
- {
- nColumns = j - i;
- break;
- }
- }
- if(nColumns > -1)
- {
- break;
- }
- }
- block.NColumns = nColumns > -1 ? nColumns : columns.Count - i;
- block.ColumnIndex = i;
- }
- }
- if(columns.Count == 0)
- {
- columns.Add(new());
- }
- return columns;
- }
- private void UpdateBlock(Block block)
- {
- Render(recalculatePositions: true);
- }
- }
|