|
|
@@ -17,6 +17,7 @@ using System.Windows;
|
|
|
using System.Windows.Controls;
|
|
|
using System.Windows.Controls.Primitives;
|
|
|
using System.Windows.Data;
|
|
|
+using System.Windows.Documents;
|
|
|
using System.Windows.Input;
|
|
|
using System.Windows.Media;
|
|
|
using System.Windows.Media.Animation;
|
|
|
@@ -462,6 +463,11 @@ public class CalendarControl : ContentControl
|
|
|
MainScroll.SizeChanged += MainScroll_SizeChanged;
|
|
|
MainScroll.ScrollChanged += MainScroll_ScrollChanged;
|
|
|
MainScroll.PreviewMouseWheel += MainScroll_PreviewMouseWheel;
|
|
|
+ MainCanvas.AllowDrop = true;
|
|
|
+
|
|
|
+ MainCanvas.DragEnter += MainCanvas_DragEnter;
|
|
|
+ MainCanvas.DragOver += MainCanvas_DragOver;
|
|
|
+ MainCanvas.Drop += MainCanvas_Drop;
|
|
|
|
|
|
grid.AddChild(
|
|
|
new Border
|
|
|
@@ -607,6 +613,49 @@ public class CalendarControl : ContentControl
|
|
|
Render();
|
|
|
}
|
|
|
|
|
|
+ private class BlockDragData(Block block, Point offset)
|
|
|
+ {
|
|
|
+ public Block Block { get; } = block;
|
|
|
+
|
|
|
+ public Point Offset { get; } = offset;
|
|
|
+ }
|
|
|
+
|
|
|
+ private class BlockDragAdorner : Adorner
|
|
|
+ {
|
|
|
+ private Point _offset;
|
|
|
+ public Point Offset
|
|
|
+ {
|
|
|
+ get => _offset;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _offset = value;
|
|
|
+ InvalidateVisual();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Size Size { get; set; }
|
|
|
+
|
|
|
+ public BlockDragData Data { get; set; }
|
|
|
+
|
|
|
+ public BlockDragAdorner(UIElement adornedElement, BlockDragData data) : base(adornedElement)
|
|
|
+ {
|
|
|
+ IsHitTestVisible = false;
|
|
|
+ Data = data;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnMouseMove(MouseEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnMouseMove(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnRender(DrawingContext drawingContext)
|
|
|
+ {
|
|
|
+ var rect = new Rect((Offset - Data.Offset).ToPoint(), Size);
|
|
|
+
|
|
|
+ drawingContext.DrawRoundedRectangle(Colors.Gray.ToBrush(0.5), new Pen(Colors.Black.ToBrush(), 1), rect, 5, 5);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private class Block : Border, INotifyPropertyChanged
|
|
|
{
|
|
|
public static readonly DependencyProperty ColumnProperty =
|
|
|
@@ -673,25 +722,28 @@ public class CalendarControl : ContentControl
|
|
|
private Thumb _bottomThumb;
|
|
|
private TimeSpan _thumbStart;
|
|
|
private TimeSpan _thumbFinish;
|
|
|
+ private bool _thumbDragging = false;
|
|
|
|
|
|
private CalendarControl _parent;
|
|
|
+ private bool _clicked;
|
|
|
+
|
|
|
+ private BlockDragAdorner? _dragAdorner;
|
|
|
+ private AdornerLayer? _adornerLayer;
|
|
|
|
|
|
public Block(CalendarControl parent, object content)
|
|
|
{
|
|
|
_parent = parent;
|
|
|
|
|
|
- var grid = new Grid();
|
|
|
- var topRow = grid.AddRow(GridUnitType.Auto);
|
|
|
- grid.AddRow(GridUnitType.Star);
|
|
|
- grid.AddRow(GridUnitType.Auto);
|
|
|
+ var canvas = new Canvas();
|
|
|
|
|
|
_contentControl = new ContentControl
|
|
|
{
|
|
|
- Content = content
|
|
|
+ Content = content,
|
|
|
+ Background = Colors.Transparent.ToBrush()
|
|
|
};
|
|
|
_contentControl.Bind(ContentControl.ContentTemplateProperty, parent, x => x.ItemTemplate);
|
|
|
|
|
|
- grid.AddChild(_contentControl, 0, 0, rowSpan: 3);
|
|
|
+ canvas.Children.Add(_contentControl);
|
|
|
|
|
|
_topThumb = new Thumb
|
|
|
{
|
|
|
@@ -710,32 +762,142 @@ public class CalendarControl : ContentControl
|
|
|
|
|
|
_topThumb.DragStarted += Thumb_DragStarted;
|
|
|
_topThumb.DragDelta += TopThumb_DragDelta;
|
|
|
- _topThumb.DragCompleted += Thumb_DragCompleted;
|
|
|
+ _topThumb.DragCompleted += TopThumb_DragCompleted;
|
|
|
|
|
|
_bottomThumb.DragStarted += Thumb_DragStarted;
|
|
|
_bottomThumb.DragDelta += BottomThumb_DragDelta;
|
|
|
- _bottomThumb.DragCompleted += Thumb_DragCompleted;
|
|
|
+ _bottomThumb.DragCompleted += BottomThumb_DragCompleted;
|
|
|
+
|
|
|
+ Canvas.SetTop(_topThumb, 0);
|
|
|
+ Canvas.SetTop(_bottomThumb, 0);
|
|
|
+ canvas.Children.Add(_topThumb);
|
|
|
+ canvas.Children.Add(_bottomThumb);
|
|
|
+
|
|
|
+ canvas.SizeChanged += (o, e) =>
|
|
|
+ {
|
|
|
+ _topThumb.Width = canvas.ActualWidth;
|
|
|
+ _bottomThumb.Width = canvas.ActualWidth;
|
|
|
+ _contentControl.Width = canvas.ActualWidth;
|
|
|
+
|
|
|
+ if (!_thumbDragging)
|
|
|
+ {
|
|
|
+ _contentControl.Height = canvas.ActualHeight;
|
|
|
+
|
|
|
+ if (_thumbReversed)
|
|
|
+ {
|
|
|
+ Canvas.SetTop(_bottomThumb, 0);
|
|
|
+ Canvas.SetTop(_topThumb, canvas.ActualHeight - _bottomThumb.Height);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Canvas.SetTop(_topThumb, 0);
|
|
|
+ Canvas.SetTop(_bottomThumb, canvas.ActualHeight - _bottomThumb.Height);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
- grid.AddChild(_topThumb, 0, 0);
|
|
|
- grid.AddChild(_bottomThumb, 2, 0);
|
|
|
+ MouseMove += Block_MouseMove;
|
|
|
+ GiveFeedback += Block_GiveFeedback;
|
|
|
|
|
|
- Child = grid;
|
|
|
+ ContentControl.MouseLeftButtonDown += (o, e) =>
|
|
|
+ {
|
|
|
+ _clicked = true;
|
|
|
+ if (CanAdjust)
|
|
|
+ {
|
|
|
+ e.Handled = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ parent.Block_MouseLeftButtonDown(this, e);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ ContentControl.MouseLeftButtonUp += (o, e) =>
|
|
|
+ {
|
|
|
+ _clicked = false;
|
|
|
+ ClearAdorner();
|
|
|
+ parent.Block_MouseLeftButtonUp(this, e);
|
|
|
+ };
|
|
|
+ ContentControl.MouseRightButtonUp += (o, e) =>
|
|
|
+ {
|
|
|
+ parent.Block_MouseRightButtonUp(this, e);
|
|
|
+ };
|
|
|
+ ContentControl.MouseLeave += ContentControl_MouseLeave;
|
|
|
+
|
|
|
+ Child = canvas;
|
|
|
}
|
|
|
|
|
|
- private void ReverseThumb()
|
|
|
+ private void Block_GiveFeedback(object sender, GiveFeedbackEventArgs e)
|
|
|
{
|
|
|
- _thumbReversed = !_thumbReversed;
|
|
|
- if (_thumbReversed)
|
|
|
+ if(_dragAdorner is not null)
|
|
|
{
|
|
|
- Grid.SetRow(_topThumb, 2);
|
|
|
- Grid.SetRow(_bottomThumb, 0);
|
|
|
+ var mPos = System.Windows.Forms.Control.MousePosition;
|
|
|
+
|
|
|
+ var thisPos = _parent.MainCanvas.PointFromScreen(PointToScreen(new()));
|
|
|
+ var point = _parent.MainCanvas.PointFromScreen(new(mPos.X, mPos.Y));
|
|
|
+
|
|
|
+ if(_parent.TryGetColumnFromPosition(point, out var _date, out var _column, out var x, out var width))
|
|
|
+ {
|
|
|
+ var rowIdx = (int)Math.Floor(point.Y / _parent._rowHeight);
|
|
|
+
|
|
|
+ _dragAdorner.Size = new(width, ActualHeight);
|
|
|
+ _dragAdorner.Offset = (new Point(x + _dragAdorner.Data.Offset.X, rowIdx * _parent._rowHeight + _dragAdorner.Data.Offset.Y) - thisPos).ToPoint();
|
|
|
+ }
|
|
|
}
|
|
|
- else
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ContentControl_MouseLeave(object sender, MouseEventArgs e)
|
|
|
+ {
|
|
|
+ _clicked = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Block_MouseMove(object sender, MouseEventArgs e)
|
|
|
+ {
|
|
|
+ if (!CanAdjust || !_clicked) return;
|
|
|
+
|
|
|
+ if(e.LeftButton == MouseButtonState.Pressed)
|
|
|
{
|
|
|
- Grid.SetRow(_topThumb, 0);
|
|
|
- Grid.SetRow(_bottomThumb, 2);
|
|
|
+ ClearAdorner();
|
|
|
+
|
|
|
+ var data = new BlockDragData(this, e.GetPosition(this));
|
|
|
+ _dragAdorner = new(this, data);
|
|
|
+ _adornerLayer = AdornerLayer.GetAdornerLayer(this);
|
|
|
+ _adornerLayer.Add(_dragAdorner);
|
|
|
+ DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
|
|
|
+ ClearAdorner();
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private void ClearAdorner()
|
|
|
+ {
|
|
|
+ if (_adornerLayer is null || _dragAdorner is null) return;
|
|
|
+
|
|
|
+ _adornerLayer.Remove(_dragAdorner);
|
|
|
+ _dragAdorner = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CancelThumb()
|
|
|
+ {
|
|
|
+ _thumbDragging = false;
|
|
|
+
|
|
|
+ var topY = _parent.GetRow(StartTime) * _parent._rowHeight;
|
|
|
+ var bottomY = _parent.GetRow(EndTime) * _parent._rowHeight;
|
|
|
+
|
|
|
+ var (topThumb, bottomThumb) = _thumbReversed ? (_bottomThumb, _topThumb) : (_topThumb, _bottomThumb);
|
|
|
+
|
|
|
+ Canvas.SetTop(this, topY);
|
|
|
+ Height = bottomY - topY;
|
|
|
+
|
|
|
+ Canvas.SetTop(topThumb, 0);
|
|
|
+ Canvas.SetTop(bottomThumb, Height - bottomThumb.Height);
|
|
|
+
|
|
|
+ Canvas.SetTop(_contentControl, 0);
|
|
|
+ _contentControl.Height = Height;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ReverseThumb()
|
|
|
+ {
|
|
|
+ _thumbReversed = !_thumbReversed;
|
|
|
+ }
|
|
|
private void MoveThumb(bool isTop, double amount)
|
|
|
{
|
|
|
if (_thumbReversed)
|
|
|
@@ -765,16 +927,62 @@ public class CalendarControl : ContentControl
|
|
|
var topY = _parent.GetRow(_thumbStart) * _parent._rowHeight;
|
|
|
var bottomY = _parent.GetRow(_thumbFinish) * _parent._rowHeight;
|
|
|
|
|
|
+ var roundedTopY = !isTop ? topY : Math.Floor(_parent.GetRow(_thumbStart)) * _parent._rowHeight;
|
|
|
+ var roundedBottomY = isTop ? bottomY : Math.Ceiling(_parent.GetRow(_thumbFinish)) * _parent._rowHeight;
|
|
|
+
|
|
|
+ var y = Math.Min(topY, roundedTopY);
|
|
|
+ Canvas.SetTop(this, y);
|
|
|
+ Height = Math.Max(bottomY, roundedBottomY) - y;
|
|
|
+
|
|
|
+ var (topThumb, bottomThumb) = _thumbReversed ? (_bottomThumb, _topThumb) : (_topThumb, _bottomThumb);
|
|
|
+
|
|
|
+ Canvas.SetTop(topThumb, topY - y);
|
|
|
+ Canvas.SetTop(bottomThumb, bottomY - y - bottomThumb.Height);
|
|
|
+
|
|
|
+ Canvas.SetTop(_contentControl, roundedTopY - y);
|
|
|
+ _contentControl.Height = roundedBottomY - roundedTopY;
|
|
|
+
|
|
|
+ Logger.Send(LogType.Information, "", $"{Height} - {_contentControl.Height}");
|
|
|
+ }
|
|
|
+ private void DragCompleted(bool isTop)
|
|
|
+ {
|
|
|
+ if (_thumbReversed)
|
|
|
+ {
|
|
|
+ isTop = !isTop;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isTop)
|
|
|
+ {
|
|
|
+ _thumbStart = Math.Floor(_parent.GetRow(_thumbStart)) * _parent.RowInterval + _parent.StartHour;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _thumbFinish = Math.Ceiling(_parent.GetRow(_thumbFinish)) * _parent.RowInterval + _parent.StartHour;
|
|
|
+ }
|
|
|
+ _thumbDragging = false;
|
|
|
+ StartTime = _thumbStart;
|
|
|
+ EndTime = _thumbFinish;
|
|
|
+
|
|
|
+ var topY = _parent.GetRow(_thumbStart) * _parent._rowHeight;
|
|
|
+ var bottomY = _parent.GetRow(_thumbFinish) * _parent._rowHeight;
|
|
|
+
|
|
|
+ var (topThumb, bottomThumb) = _thumbReversed ? (_bottomThumb, _topThumb) : (_topThumb, _bottomThumb);
|
|
|
+
|
|
|
Canvas.SetTop(this, topY);
|
|
|
Height = bottomY - topY;
|
|
|
+
|
|
|
+ Canvas.SetTop(topThumb, 0);
|
|
|
+ Canvas.SetTop(bottomThumb, Height - bottomThumb.Height);
|
|
|
+
|
|
|
+ Canvas.SetTop(_contentControl, 0);
|
|
|
+ _contentControl.Height = Height;
|
|
|
}
|
|
|
|
|
|
private void Thumb_DragStarted(object sender, DragStartedEventArgs e)
|
|
|
{
|
|
|
_thumbStart = StartTime;
|
|
|
_thumbFinish = EndTime;
|
|
|
-
|
|
|
- _thumbReversed = false;
|
|
|
+ _thumbDragging = true;
|
|
|
}
|
|
|
|
|
|
private void TopThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
|
|
|
@@ -782,10 +990,13 @@ public class CalendarControl : ContentControl
|
|
|
MoveThumb(true, e.VerticalChange);
|
|
|
}
|
|
|
|
|
|
- private void Thumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
|
|
|
+ private void TopThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
|
|
|
{
|
|
|
- StartTime = _thumbStart;
|
|
|
- EndTime = _thumbFinish;
|
|
|
+ DragCompleted(true);
|
|
|
+ }
|
|
|
+ private void BottomThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
|
|
|
+ {
|
|
|
+ DragCompleted(false);
|
|
|
}
|
|
|
|
|
|
private void BottomThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
|
|
|
@@ -875,19 +1086,6 @@ public class CalendarControl : ContentControl
|
|
|
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;
|
|
|
@@ -1330,6 +1528,43 @@ public class CalendarControl : ContentControl
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private bool TryGetColumnFromPosition(Point point, out DateTime date, [NotNullWhen(true)] out object? column, out double columnX, out double width)
|
|
|
+ {
|
|
|
+ column = null;
|
|
|
+ date = DateTime.MinValue;
|
|
|
+ columnX = 0.0;
|
|
|
+ width = 0.0;
|
|
|
+
|
|
|
+ var x = point.X;
|
|
|
+ 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(x < colWidth)
|
|
|
+ {
|
|
|
+ column = columnKey;
|
|
|
+ date = columnDate;
|
|
|
+ width = colWidth;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ columnX += colWidth;
|
|
|
+ x -= colWidth;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return column is not null;
|
|
|
+ }
|
|
|
+
|
|
|
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);
|
|
|
@@ -1419,6 +1654,52 @@ public class CalendarControl : ContentControl
|
|
|
CallBlockEvent(e, block, args.GetPosition(MainCanvas));
|
|
|
}
|
|
|
|
|
|
+ private void MainCanvas_DragOver(object sender, DragEventArgs e)
|
|
|
+ {
|
|
|
+ e.Effects = DragDropEffects.None;
|
|
|
+
|
|
|
+ if (e.Data.GetDataPresent(typeof(BlockDragData)))
|
|
|
+ {
|
|
|
+ e.Effects = DragDropEffects.Move;
|
|
|
+ // var data = e.Data.GetData(typeof(Block));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void MainCanvas_Drop(object sender, DragEventArgs e)
|
|
|
+ {
|
|
|
+ if (e.Data.GetDataPresent(typeof(BlockDragData)))
|
|
|
+ {
|
|
|
+ var pos = e.GetPosition(MainCanvas);
|
|
|
+ if (e.Data.GetData(typeof(BlockDragData)) is not BlockDragData data) return;
|
|
|
+
|
|
|
+ if (TryGetBlockFromPosition(pos, out var date, out var column, out var start, out var end, out var index))
|
|
|
+ {
|
|
|
+ var time = StartHour + RowInterval * Math.Floor(pos.Y / _rowHeight);
|
|
|
+ var duration = data.Block.EndTime - data.Block.StartTime;
|
|
|
+ var endTime = time + duration;
|
|
|
+ var min = TimeSpan.Zero.Add(TimeSpan.FromTicks(1)); // This is really, really stupid, but needs to be so that the assignment doesn't fall back to booked time.
|
|
|
+ var max = TimeSpan.FromHours(24);
|
|
|
+ if(time < min)
|
|
|
+ {
|
|
|
+ time = min;
|
|
|
+ }
|
|
|
+ if(endTime > max)
|
|
|
+ {
|
|
|
+ endTime = max;
|
|
|
+ }
|
|
|
+
|
|
|
+ data.Block.Column = column;
|
|
|
+ data.Block.Date = date;
|
|
|
+ data.Block.StartTime = time;
|
|
|
+ data.Block.EndTime = endTime;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void MainCanvas_DragEnter(object sender, DragEventArgs e)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
private void Block_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
|
{
|
|
|
if (sender is not Block block) return;
|