Просмотр исходного кода

CalendarControl: Added way of overriding filling empty space

Kenric Nugteren 2 недель назад
Родитель
Сommit
9ea66f6865
1 измененных файлов с 156 добавлено и 70 удалено
  1. 156 70
      inabox.wpf/Forms/CalendarControl/CalendarControl.cs

+ 156 - 70
inabox.wpf/Forms/CalendarControl/CalendarControl.cs

@@ -24,13 +24,14 @@ using System.Windows.Shapes;
 
 namespace InABox.Wpf;
 
-public class CalendarBlockEventArgs(object? value, object column, DateTime date, TimeSpan start, TimeSpan end) : EventArgs
+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;
 
@@ -271,6 +272,11 @@ public class CalendarControl : ContentControl
     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;
@@ -1030,6 +1036,7 @@ public class CalendarControl : ContentControl
                     };
                     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);
@@ -1172,9 +1179,8 @@ public class CalendarControl : ContentControl
         }
     }
 
-    private bool TryGetBlockFromPosition(MouseEventArgs e, out DateTime blockDate, [NotNullWhen(true)] out object? column, out TimeSpan start, out TimeSpan end, out int index)
+    private bool TryGetBlockFromPosition(Point point, 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 = StartHour + RowInterval * rowIdx;
@@ -1218,6 +1224,10 @@ public class CalendarControl : ContentControl
         }
         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;
 
@@ -1249,9 +1259,13 @@ public class CalendarControl : ContentControl
             menu.IsOpen = true;
         }
     }
-    private void CallBlockEvent(EventHandler<CalendarBlockEventArgs>? e, Block block)
+    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, new CalendarBlockEventArgs(block.Content, block.Column, block.Date, block.StartTime, block.EndTime));
+        CallBlockEvent(e, block, args.GetPosition(MainCanvas));
     }
 
     private void Block_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
@@ -1259,7 +1273,7 @@ public class CalendarControl : ContentControl
         if (sender is not Block block) return;
         e.Handled = true;
 
-        PressedAction(() => CallBlockEvent(BlockHeld, block));
+        PressedAction(() => CallBlockEvent(BlockHeld, block, e));
     }
 
     private void Block_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
@@ -1267,7 +1281,7 @@ public class CalendarControl : ContentControl
         if (sender is not Block block) return;
         e.Handled = true;
 
-        ReleasedAction(() => CallBlockEvent(BlockClicked, block));
+        ReleasedAction(() => CallBlockEvent(BlockClicked, block, e));
     }
 
     private void Block_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
@@ -1275,7 +1289,7 @@ public class CalendarControl : ContentControl
         if (sender is not Block block) return;
         e.Handled = true;
 
-        CallBlockEvent(BlockRightClicked, block);
+        CallBlockEvent(BlockRightClicked, block, e);
     }
 
     private Border? _heldSelection;
@@ -1286,6 +1300,90 @@ public class CalendarControl : ContentControl
         _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;
@@ -1319,64 +1417,12 @@ public class CalendarControl : ContentControl
                 MainCanvas.Children.Add(_heldSelection);
             }
 
-            var blockStart = StartHour;
-            var blockEnd = TimeSpan.MinValue;
-
-            start = StartHour + (pos.Y / _rowHeight) * RowInterval;
-
-            var width = 0.0;
-
-            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 += index * subColWidth;
+            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 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));
-                            }
-                        }
-                    }
-
-                    width = subColWidth;
-
-                    break;
-                }
-                x += colWidth;
-            }
-
-            if(blockEnd == TimeSpan.MinValue)
-            {
-                blockStart = start;
-                blockEnd = end;
-            }
             var top = ((blockStart - StartHour).TotalHours / RowInterval.TotalHours) * _rowHeight;
             var height = ((blockEnd - blockStart).TotalHours / RowInterval.TotalHours) * _rowHeight;
 
@@ -1385,7 +1431,7 @@ public class CalendarControl : ContentControl
             var leftAnimation = new DoubleAnimation
             {
                 From = pos.X,
-                To = x,
+                To = region.X,
                 Duration = duration
             };
             Storyboard.SetTarget(leftAnimation, _heldSelection);
@@ -1419,15 +1465,32 @@ public class CalendarControl : ContentControl
             storyBoard.Children.Add(topAnimation);
             storyBoard.Children.Add(widthAnimation);
             storyBoard.Children.Add(heightAnimation);
-            storyBoard.Completed += (o, e) =>
+            storyBoard.Completed += (o, _) =>
             {
-                CallBlockEvent(BlockHeld, element, new(null, column, date, blockStart, blockEnd));
+                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()
     {
@@ -1438,24 +1501,47 @@ public class CalendarControl : ContentControl
         }
     }
 
+    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;
-        CallBlockEvent(BlockRightClicked, sender as UIElement, new(null, column, date, start, end));
+
+        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(null, column, date, start, end));
+            CallBlockEvent(BlockClicked, sender as UIElement, new CalendarBlockEventArgs(null, column, date, start, end, pos));
         });
     }