|
|
@@ -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));
|
|
|
});
|
|
|
}
|
|
|
|