| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631 |
- using InABox.Core;
- using InABox.WPF;
- 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.Printing;
- using System.Reactive.Linq;
- using System.Runtime.CompilerServices;
- 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, Point point) : 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 Point Point { get; set; } = point;
- private ContextMenu? _menu;
- public ContextMenu Menu
- {
- get
- {
- _menu ??= new ContextMenu();
- return _menu;
- }
- }
- public ContextMenu? GetContextMenu()
- {
- return _menu;
- }
- }
- public class CalendarRegion : INotifyPropertyChanged
- {
- private DateTime _date;
- public DateTime Date
- {
- get => _date;
- set
- {
- _date = value;
- OnPropertyChanged();
- }
- }
- private object? _column;
- public object? Column
- {
- get => _column;
- set
- {
- _column = value;
- OnPropertyChanged();
- }
- }
- private TimeSpan _start;
- public TimeSpan Start
- {
- get => _start;
- set
- {
- _start = value;
- OnPropertyChanged();
- }
- }
- private TimeSpan _end;
- public TimeSpan End
- {
- get => _end;
- set
- {
- _end = value;
- OnPropertyChanged();
- }
- }
- private Brush? _background;
- public Brush? Background
- {
- get => _background;
- set
- {
- _background = value;
- OnPropertyChanged();
- }
- }
- public event PropertyChangedEventHandler? PropertyChanged;
- private void OnPropertyChanged([CallerMemberName] string? name = null)
- {
- PropertyChanged?.Invoke(this, new(name));
- }
- }
- public enum CalendarColumnWidthMode
- {
- ConstantColumns,
- ConstantSubColumns
- }
- public class CalendarControl : ContentControl
- {
- public static readonly DependencyProperty StartHourProperty =
- DependencyProperty.Register(nameof(StartHour), typeof(TimeSpan), typeof(CalendarControl), new(TimeSpan.Zero, Render_Changed));
- public static readonly DependencyProperty EndHourProperty =
- DependencyProperty.Register(nameof(EndHour), typeof(TimeSpan), typeof(CalendarControl), new(TimeSpan.FromHours(24), Render_Changed));
- public static readonly DependencyProperty RowHeightProperty =
- DependencyProperty.Register(nameof(RowHeight), typeof(double), typeof(CalendarControl), new(100.0, Render_Changed));
- public static readonly DependencyProperty ZoomProperty =
- DependencyProperty.Register(nameof(Zoom), typeof(double), typeof(CalendarControl), new(1.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 ColumnWidthModeProperty =
- DependencyProperty.Register(nameof(ColumnWidthMode), typeof(CalendarColumnWidthMode), typeof(CalendarControl), new(CalendarColumnWidthMode.ConstantSubColumns, Render_Changed));
- public static readonly DependencyProperty MinimumBlockHeightProperty =
- DependencyProperty.Register(nameof(MinimumBlockHeight), typeof(double), typeof(CalendarControl), new(5.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 static readonly DependencyProperty DatesProperty =
- DependencyProperty.Register(nameof(Dates), typeof(IEnumerable<DateTime>), typeof(CalendarControl), new(Dates_Changed));
- public static readonly DependencyProperty RegionsProperty =
- DependencyProperty.Register(nameof(Regions), typeof(IEnumerable<CalendarRegion>), typeof(CalendarControl), new(Regions_Changed));
- public TimeSpan StartHour
- {
- get => (TimeSpan)GetValue(StartHourProperty);
- set => SetValue(StartHourProperty, value);
- }
- public TimeSpan EndHour
- {
- get => (TimeSpan)GetValue(EndHourProperty);
- set => SetValue(EndHourProperty, value);
- }
- public double MinimumColumnWidth
- {
- get => (double)GetValue(MinimumColumnWidthProperty);
- set => SetValue(MinimumColumnWidthProperty, value);
- }
- public CalendarColumnWidthMode ColumnWidthMode
- {
- get => (CalendarColumnWidthMode)GetValue(ColumnWidthModeProperty);
- set => SetValue(ColumnWidthModeProperty, value);
- }
- public double MinimumBlockHeight
- {
- get => (double)GetValue(MinimumBlockHeightProperty);
- set => SetValue(MinimumBlockHeightProperty, value);
- }
- public double RowHeight
- {
- get => (double)GetValue(RowHeightProperty);
- set => SetValue(RowHeightProperty, value);
- }
- /// <summary>
- /// Zoom level for the calendar; the calculated row height will be multiplied by this number to get the rendered row height.
- /// </summary>
- public double Zoom
- {
- get => (double)GetValue(ZoomProperty);
- set => SetValue(ZoomProperty, 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 IEnumerable<DateTime>? Dates
- {
- get => GetValue(DatesProperty) as IEnumerable<DateTime>;
- set => SetValue(DatesProperty, value);
- }
- public IEnumerable<CalendarRegion>? Regions
- {
- get => GetValue(RegionsProperty) as IEnumerable<CalendarRegion>;
- set => SetValue(RegionsProperty, value);
- }
- public event EventHandler<CalendarBlockEventArgs>? BlockClicked;
- public event EventHandler<CalendarBlockEventArgs>? BlockRightClicked;
- public event EventHandler<CalendarBlockEventArgs>? BlockHeld;
- public delegate TimeSpan? GetFillBlockHandler(DateTime date, object? column, TimeSpan time);
- public GetFillBlockHandler? GetFillBlockStart;
- public GetFillBlockHandler? GetFillBlockEnd;
- 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 static void Dates_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is not CalendarControl calendar) return;
- if(e.OldValue is INotifyCollectionChanged oldNotify)
- {
- oldNotify.CollectionChanged -= calendar.DatesCollection_Changed;
- }
- calendar.Render(columnsChanged: true);
- if(e.NewValue is INotifyCollectionChanged notify)
- {
- notify.CollectionChanged += calendar.DatesCollection_Changed;
- }
- }
- private static void Regions_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is not CalendarControl calendar) return;
- if(e.OldValue is IEnumerable<CalendarRegion> regions)
- {
- foreach(var item in regions)
- {
- item.PropertyChanged -= calendar.Region_PropertyChanged;
- }
- }
- if(e.OldValue is INotifyCollectionChanged oldNotify)
- {
- oldNotify.CollectionChanged -= calendar.RegionsCollection_Changed;
- }
- calendar.UpdateRegionBinding();
- calendar.Render();
- if(e.NewValue is INotifyCollectionChanged notify)
- {
- notify.CollectionChanged += calendar.RegionsCollection_Changed;
- }
- }
- private void UpdateRegionBinding()
- {
- if(Regions is null)
- {
- return;
- }
- foreach(var region in Regions)
- {
- region.PropertyChanged -= Region_PropertyChanged;
- region.PropertyChanged += Region_PropertyChanged;
- }
- }
- private void Region_PropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if(e.PropertyName == nameof(CalendarRegion.Start)
- || e.PropertyName == nameof(CalendarRegion.End)
- || e.PropertyName == nameof(CalendarRegion.Date)
- || e.PropertyName == nameof(CalendarRegion.Column))
- {
- Render();
- }
- }
- private void RegionsCollection_Changed(object? sender, NotifyCollectionChangedEventArgs e)
- {
- UpdateRegionBinding();
- Render();
- }
- private void DatesCollection_Changed(object? sender, NotifyCollectionChangedEventArgs e)
- {
- Render(columnsChanged: true);
- }
- 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;
- this.PreviewMouseLeftButtonDown += CalendarControl_PreviewMouseLeftButtonDown;
- }
- private void CalendarControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
- {
- ClearSelectedRectangle();
- }
- 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), new(OnPropertyChangedHandler));
- public static readonly DependencyProperty EndTimeProperty =
- DependencyProperty.Register(nameof(EndTime), typeof(TimeSpan), typeof(Block), new(OnPropertyChangedHandler));
- 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);
- };
- block.ContentControl.MouseRightButtonUp += (o, e) =>
- {
- Block_MouseRightButtonUp(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);
- }
- }
- var autoGenerateDates = true;
- if(Dates is not null)
- {
- autoGenerateDates = false;
- foreach(var date in Dates)
- {
- 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 {col} in Calendar");
- }
- }
- }
- }
- }
- }
- 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))
- {
- if (!autoGenerateDates) continue;
- dateBlocks = new();
- _blocks.Add(date, dateBlocks);
- if (!autoGenerateColumns)
- {
- foreach(var col in _columns)
- {
- if(!dateBlocks.TryAdd(col, new()))
- {
- throw new Exception($"Duplicate column {col} 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)
- || e.PropertyName == nameof(Block.Date))
- {
- 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 CalendarColumnWidthMode _lastColumnWidthMode;
- 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 = ((EndHour - StartHour).TotalHours / RowInterval.TotalHours);
- var rowHeight = Math.Max(RowHeight, MainScroll.ActualHeight / nRows) * Zoom;
- MainCanvas.Children.Clear();
- MainCanvas.Height = rowHeight * nRows;
- var minColWidth = MinimumColumnWidth;
- var colSpace = 1;
- var nColumns = 0;
- var nSizingColumns = 0;
- foreach (var (column, columnBlocks) in _allColumns)
- {
- columnBlocks.Columns ??= RecalculateBlockPositionsForDay(columnBlocks.Blocks);
- nColumns += columnBlocks.Columns.Count;
- nSizingColumns += ColumnWidthMode switch
- {
- CalendarColumnWidthMode.ConstantSubColumns => columnBlocks.Columns.Count,
- CalendarColumnWidthMode.ConstantColumns or _ => 1,
- };
- }
- var colWidth = (Math.Max((MainScroll.ActualWidth - colSpace * (_blocks.Sum(x => x.Value.Count) - 1)) / nSizingColumns, minColWidth));
- var lastColWidth = _lastColWidth;
- _lastColWidth = colWidth;
- var lastColWidthMode = _lastColumnWidthMode;
- _lastColumnWidthMode = ColumnWidthMode;
- var updateHeaders = colWidth != lastColWidth || lastColWidthMode != ColumnWidthMode || 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);
- var regions = (Regions ?? Enumerable.Empty<CalendarRegion>())
- .GroupByDictionary(x => (x.Date, x.Column));
- _columnList.Clear();
- var columnIdx = 0;
- foreach(var date in dates)
- {
- if (!_blocks.TryGetValue(date, out var dateBlocks)) continue;
- if (updateHeaders)
- {
- var nDateColumns = ColumnWidthMode switch
- {
- CalendarColumnWidthMode.ConstantSubColumns => dateBlocks.Sum(x => x.Value.Columns!.Count),
- CalendarColumnWidthMode.ConstantColumns or _ => dateBlocks.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));
- var (nCols, subColWidth) = ColumnWidthMode switch
- {
- CalendarColumnWidthMode.ConstantSubColumns => (columnBlocks.Columns!.Count, colWidth),
- CalendarColumnWidthMode.ConstantColumns or _ => (1, colWidth / columnBlocks.Columns!.Count),
- };
- if (updateHeaders)
- {
- var columnHeader = new ContentControl
- {
- Content = columnKey,
- Width = colWidth * nCols,
- };
- columnHeader.Bind(ContentControl.ContentTemplateProperty, this, x => x.HeaderTemplate);
- Canvas.SetLeft(columnHeader, colX);
- HeaderCanvas.Children.Add(columnHeader);
- columnHeader.SizeChanged += ColumnHeader_SizeChanged;
- }
- // Add regions
- if(regions.TryGetValue((date, columnKey), out var columnRegions))
- {
- foreach(var region in columnRegions)
- {
- var rectangle = new Rectangle
- {
- Width = colWidth * nCols,
- Height = ((region.End - region.Start).TotalHours / RowInterval.TotalHours) * rowHeight,
- };
- rectangle.Bind(Rectangle.FillProperty, region, x => x.Background);
- Canvas.SetLeft(rectangle, colX);
- Canvas.SetTop(rectangle, ((region.Start - StartHour).TotalHours / RowInterval.TotalHours) * rowHeight);
- MainCanvas.Children.Add(rectangle);
- }
- }
- // Add cell placeholders
- var rowIdx = 0;
- for(var time = StartHour; time < EndHour; time += RowInterval)
- {
- var rectangle = new Rectangle
- {
- Width = colWidth * nCols,
- Height = rowHeight,
- Fill = new SolidColorBrush(Colors.Transparent),
- };
- rectangle.MouseEnter += (o, e) =>
- {
- if(rectangle != _selectedRectangle)
- {
- rectangle.Fill = Colors.LightBlue.ToBrush(0.5);
- }
- };
- rectangle.MouseLeave += (o, e) =>
- {
- if(rectangle != _selectedRectangle)
- {
- rectangle.Fill = Colors.Transparent.ToBrush();
- }
- };
- rectangle.MouseLeftButtonDown += Rectangle_MouseLeftButtonDown;
- rectangle.MouseLeftButtonUp += Rectangle_MouseLeftButtonUp;
- rectangle.MouseRightButtonDown += Rectangle_MouseRightButtonDown;
- rectangle.MouseRightButtonUp += Rectangle_MouseRightButtonUp;
- Canvas.SetLeft(rectangle, colX);
- Canvas.SetTop(rectangle, rowIdx * rowHeight);
- MainCanvas.Children.Add(rectangle);
- ++rowIdx;
- }
- var startColX = colX;
- 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, MinimumBlockHeight);
- block.Width = subColWidth * block.NColumns;
- MainCanvas.Children.Add(block);
- }
- if(ColumnWidthMode == CalendarColumnWidthMode.ConstantSubColumns)
- {
- colX += colWidth;
- }
- else
- {
- colX += subColWidth;
- }
- }
- if(ColumnWidthMode == CalendarColumnWidthMode.ConstantColumns)
- {
- colX = startColX + 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 = StartHour + RowInterval; time < EndHour; 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(Point point, out DateTime blockDate, [NotNullWhen(true)] out object? column, out TimeSpan start, out TimeSpan end, out int index)
- {
- var rowIdx = (int)Math.Floor(point.Y / _rowHeight);
- start = StartHour + RowInterval * rowIdx;
- end = StartHour + RowInterval * (rowIdx + 1);
- if(start < StartHour)
- {
- start = StartHour;
- }
- if(end > EndHour)
- {
- end = EndHour;
- }
- 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 (nCols, subColWidth) = ColumnWidthMode switch
- {
- CalendarColumnWidthMode.ConstantSubColumns => (columnBlocks.Columns!.Count, _colWidth),
- CalendarColumnWidthMode.ConstantColumns => (1, _colWidth / columnBlocks.Columns!.Count),
- _ => throw new InvalidEnumException<CalendarColumnWidthMode>(ColumnWidthMode)
- };
- var colWidth = nCols * _colWidth + _colSpace;
- if(x < colWidth)
- {
- column = columnKey;
- index = Math.Min((int)Math.Floor(x / subColWidth), columnBlocks.Columns!.Count - 1);
- blockDate = date;
- break;
- }
- else
- {
- x -= colWidth;
- }
- }
- return column is not null;
- }
- private bool TryGetBlockFromPosition(MouseEventArgs e, out DateTime blockDate, [NotNullWhen(true)] out object? column, out TimeSpan start, out TimeSpan end, out int index)
- {
- return TryGetBlockFromPosition(e.GetPosition(MainCanvas), out blockDate, out column, out start, out end, out index);
- }
- private CancellationTokenSource? cts = null;
- private void PressedAction(Action onHeld)
- {
- cts?.Cancel();
- cts = new();
- Task.Delay(500).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 CallBlockEvent(EventHandler<CalendarBlockEventArgs>? e, UIElement? parent, CalendarBlockEventArgs args)
- {
- e?.Invoke(this, args);
- if(args.GetContextMenu() is ContextMenu menu && menu.Items.Count > 0)
- {
- menu.PlacementTarget = parent;
- menu.IsOpen = true;
- }
- }
- private void CallBlockEvent(EventHandler<CalendarBlockEventArgs>? e, Block block, Point point)
- {
- CallBlockEvent(e, block, new CalendarBlockEventArgs(block.Content, block.Column, block.Date, block.StartTime, block.EndTime, point));
- }
- private void CallBlockEvent(EventHandler<CalendarBlockEventArgs>? e, Block block, MouseEventArgs args)
- {
- CallBlockEvent(e, block, args.GetPosition(MainCanvas));
- }
- private void Block_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- if (sender is not Block block) return;
- e.Handled = true;
- PressedAction(() => CallBlockEvent(BlockHeld, block, e));
- }
- private void Block_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- if (sender is not Block block) return;
- e.Handled = true;
- ReleasedAction(() => CallBlockEvent(BlockClicked, block, e));
- }
- private void Block_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- if (sender is not Block block) return;
- e.Handled = true;
- CallBlockEvent(BlockRightClicked, block, e);
- }
- private Border? _heldSelection;
- public void ClearHeldSelection()
- {
- MainCanvas.Children.Remove(_heldSelection);
- _heldSelection = null;
- }
- public class Region(TimeSpan? start, TimeSpan? end, double x, double width)
- {
- public TimeSpan? Start { get; } = start;
- public TimeSpan? End { get; } = end;
- public double X { get; } = x;
- public double Width { get; } = width;
- }
- private Region GetEmptySpace(DateTime date, object? column, int subColumnIndex, TimeSpan time, IEnumerable<object?>? exclude = null)
- {
- TimeSpan? blockStart = null;
- TimeSpan? blockEnd = null;
- var start = time;
- var width = 0.0;
- var excludedItems = exclude?.ToHashSet() ?? [];
- 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 (nCols, subColWidth) = ColumnWidthMode switch
- {
- CalendarColumnWidthMode.ConstantSubColumns => (columnBlocks.Columns!.Count, _colWidth),
- CalendarColumnWidthMode.ConstantColumns => (1, _colWidth / columnBlocks.Columns!.Count),
- _ => throw new InvalidEnumException<CalendarColumnWidthMode>(ColumnWidthMode)
- };
- var colWidth = nCols * _colWidth + _colSpace;
- if(date == columnDate && column == columnKey)
- {
- x += subColumnIndex * subColWidth;
- var list = columnBlocks.Blocks.Where(x =>
- {
- return !excludedItems.Contains(x.Content) && x.ColumnIndex <= subColumnIndex && subColumnIndex < 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;
- }
- }
- width = subColWidth;
- break;
- }
- x += colWidth;
- }
- return new(blockStart, blockEnd, x, width);
- }
- public TimeSpan GetTime(Point point)
- {
- return StartHour + (point.Y / _rowHeight) * RowInterval;
- }
- public bool GetEmptySpace(Point pos, out TimeSpan? start, out TimeSpan? end, TimeSpan? time = null, IEnumerable<object?>? exclude = null)
- {
- if (!TryGetBlockFromPosition(pos, out var date, out var column, out var _start, out var _end, out var index))
- {
- start = default;
- end = default;
- return false;
- }
- var region = GetEmptySpace(date, column, index, time ?? (StartHour + (pos.Y / _rowHeight) * RowInterval), exclude);
- start = region.Start;
- end = region.End;
- return true;
- }
- 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.LightBlue.ToBrush(0.7)
- };
- }
- 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 mouseTime = StartHour + (pos.Y / _rowHeight) * RowInterval;
- var region = GetEmptySpace(date, column, index, mouseTime);
- var blockStart = region.Start ?? GetFillBlockStart?.Invoke(date, column, mouseTime) ?? start;
- var blockEnd = region.End ?? GetFillBlockEnd?.Invoke(date, column, mouseTime) ?? end;
- var width = region.Width;
- var top = ((blockStart - StartHour).TotalHours / RowInterval.TotalHours) * _rowHeight;
- var height = ((blockEnd - blockStart).TotalHours / RowInterval.TotalHours) * _rowHeight;
- var duration = TimeSpan.FromSeconds(0.2);
- var leftAnimation = new DoubleAnimation
- {
- From = pos.X,
- To = region.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, _) =>
- {
- CallBlockEvent(BlockHeld, element, new CalendarBlockEventArgs(null, column, date, blockStart, blockEnd, pos));
- };
- storyBoard.Begin();
- });
- }
- private Rectangle? _selectedRectangle;
- private CellPosition? _selectedRectanglePosition;
- private class CellPosition(DateTime date, object? column, TimeSpan start, TimeSpan end)
- {
- public DateTime Date { get; } = date;
- public object? Column { get; } = column;
- public TimeSpan Start { get; } = start;
- public TimeSpan End { get; } = end;
- public bool Equals(DateTime date, object? column, TimeSpan start, TimeSpan end)
- {
- return date == Date && Equals(column, Column) && start == Start && end == End;
- }
- }
- private void ClearSelectedRectangle()
- {
- if(_selectedRectangle is not null)
- {
- _selectedRectangle.Fill = _selectedRectangle.IsMouseOver ? Colors.LightBlue.ToBrush(0.5) : Colors.Transparent.ToBrush();
- _selectedRectangle = null;
- }
- }
- private void Rectangle_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
- {
- if (!TryGetBlockFromPosition(e, out var date, out var column, out var start, out var end, out var index)) return;
- if(_selectedRectanglePosition?.Equals(date, column, start, end) == false)
- {
- ClearSelectedRectangle();
- }
- }
- private void Rectangle_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
- {
- if (!TryGetBlockFromPosition(e, out var date, out var column, out var start, out var end, out var index)) return;
- var pos = e.GetPosition(MainCanvas);
- ClearSelectedRectangle();
- if(sender is Rectangle rect)
- {
- _selectedRectangle = rect;
- _selectedRectanglePosition = new(date, column, start, end);
- rect.Fill = Colors.LightBlue.ToBrush(0.7);
- }
- CallBlockEvent(BlockRightClicked, sender as UIElement, new CalendarBlockEventArgs(null, column, date, start, end, pos));
- }
- 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;
- var pos = e.GetPosition(MainCanvas);
- ReleasedAction(() =>
- {
- ClearSelectedRectangle();
- if(sender is Rectangle rect)
- {
- _selectedRectangle = rect;
- _selectedRectanglePosition = new(date, column, start, end);
- rect.Fill = Colors.LightBlue.ToBrush(0.7);
- }
- CallBlockEvent(BlockClicked, sender as UIElement, new CalendarBlockEventArgs(null, column, date, start, end, pos));
- });
- }
- 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 - StartHour).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);
- }
- }
|