From 384d4dbf2900c711b7347bfbcbfa38713ff9bb78 Mon Sep 17 00:00:00 2001 From: chinboy Date: Thu, 16 Jan 2025 15:49:03 +0800 Subject: [PATCH] Add DataGridColumnHeader --- .../Colum/DataGridColumn.cs | 4 +- .../Colum/DataGridColumnHeader.cs | 854 ++++++++++++++++++ 2 files changed, 856 insertions(+), 2 deletions(-) create mode 100644 src/AtomUI.Controls.DataGrid/Colum/DataGridColumnHeader.cs diff --git a/src/AtomUI.Controls.DataGrid/Colum/DataGridColumn.cs b/src/AtomUI.Controls.DataGrid/Colum/DataGridColumn.cs index 630bf58f9..fe97b78c6 100644 --- a/src/AtomUI.Controls.DataGrid/Colum/DataGridColumn.cs +++ b/src/AtomUI.Controls.DataGrid/Colum/DataGridColumn.cs @@ -113,7 +113,7 @@ public abstract class DataGridColumn : AvaloniaObject internal bool HasHeaderCell => _headerCell != null; - internal DataGridColumnHeader HeaderCell + internal DataGridColumnHeader? HeaderCell { get { @@ -852,7 +852,7 @@ public abstract class DataGridColumn : AvaloniaObject { if (OwningGrid != null && OwningGrid.UseLayoutRounding) { - var scale = LayoutHelper.GetLayoutScale(HeaderCell); + var scale = LayoutHelper.GetLayoutScale(HeaderCell!); var roundSize = LayoutHelper.RoundLayoutSizeUp(new Size(leftEdge + ActualWidth, 1), scale, scale); LayoutRoundedWidth = roundSize.Width - leftEdge; } diff --git a/src/AtomUI.Controls.DataGrid/Colum/DataGridColumnHeader.cs b/src/AtomUI.Controls.DataGrid/Colum/DataGridColumnHeader.cs new file mode 100644 index 000000000..024ac5643 --- /dev/null +++ b/src/AtomUI.Controls.DataGrid/Colum/DataGridColumnHeader.cs @@ -0,0 +1,854 @@ +using System.ComponentModel; +using System.Diagnostics; +using AtomUI.Controls.Automation.Peers; +using AtomUI.Controls.Collections; +using AtomUI.Controls.Utils; +using Avalonia; +using Avalonia.Automation; +using Avalonia.Automation.Peers; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Mixins; +using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Utilities; + +namespace AtomUI.Controls; + +/// +/// Represents an individual column header. +/// +[PseudoClasses(":dragIndicator", ":pressed", ":sortascending", ":sortdescending")] +public class DataGridColumnHeader : ContentControl +{ + private enum DragMode + { + None = 0, + MouseDown = 1, + Drag = 2, + Resize = 3, + Reorder = 4 + } + + private const int DATAGRIDCOLUMNHEADER_resizeRegionWidth = 5; + private const int DATAGRIDCOLUMNHEADER_columnsDragTreshold = 5; + + private bool _areHandlersSuspended; + private static DragMode _dragMode; + private static Point? _lastMousePositionHeaders; + private static Cursor? _originalCursor; + private static double _originalHorizontalOffset; + private static double _originalWidth; + private bool _desiredSeparatorVisibility = true; + private static Point? _dragStart; + private static DataGridColumn? _dragColumn; + private static double _frozenColumnsWidth; + private static Lazy _resizeCursor = new Lazy(() => new Cursor(StandardCursorType.SizeWestEast)); + + public static readonly StyledProperty SeparatorBrushProperty = + AvaloniaProperty.Register(nameof(SeparatorBrush)); + + public IBrush SeparatorBrush + { + get => GetValue(SeparatorBrushProperty); + set => SetValue(SeparatorBrushProperty, value); + } + + public static readonly StyledProperty AreSeparatorsVisibleProperty = + AvaloniaProperty.Register( + nameof(AreSeparatorsVisible), + defaultValue: true); + + public bool AreSeparatorsVisible + { + get => GetValue(AreSeparatorsVisibleProperty); + set => SetValue(AreSeparatorsVisibleProperty, value); + } + + static DataGridColumnHeader() + { + AreSeparatorsVisibleProperty.Changed.AddClassHandler((x, e) => + x.OnAreSeparatorsVisibleChanged(e)); + PressedMixin.Attach(); + IsTabStopProperty.OverrideDefaultValue(false); + AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior + .FromClip); + } + + /// + /// Initializes a new instance of the class. + /// + //TODO Implement + public DataGridColumnHeader() + { + PointerPressed += DataGridColumnHeader_PointerPressed; + PointerReleased += DataGridColumnHeader_PointerReleased; + PointerMoved += DataGridColumnHeader_PointerMoved; + PointerEntered += DataGridColumnHeader_PointerEntered; + PointerExited += DataGridColumnHeader_PointerExited; + } + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new DataGridColumnHeaderAutomationPeer(this); + } + + private void OnAreSeparatorsVisibleChanged(AvaloniaPropertyChangedEventArgs e) + { + if (!_areHandlersSuspended) + { + _desiredSeparatorVisibility = e.NewValue != null && (bool)e.NewValue; + if (OwningGrid != null) + { + UpdateSeparatorVisibility(OwningGrid.ColumnsInternal.LastVisibleColumn); + } + else + { + UpdateSeparatorVisibility(null); + } + } + } + + internal DataGridColumn? OwningColumn { get; set; } + internal DataGrid? OwningGrid => OwningColumn?.OwningGrid; + + internal int ColumnIndex + { + get + { + if (OwningColumn == null) + { + return -1; + } + + return OwningColumn.Index; + } + } + + internal ListSortDirection? CurrentSortingState { get; private set; } + + private bool IsMouseOver { get; set; } + + private bool IsPressed { get; set; } + + private void SetValueNoCallback(AvaloniaProperty property, T value, + BindingPriority priority = BindingPriority.LocalValue) + { + _areHandlersSuspended = true; + try + { + SetValue(property, value, priority); + } + finally + { + _areHandlersSuspended = false; + } + } + + internal void UpdatePseudoClasses() + { + CurrentSortingState = null; + if (OwningGrid != null + && OwningGrid.DataConnection != null + && OwningGrid.DataConnection.AllowSort) + { + var sort = OwningColumn?.GetSortDescription(); + if (sort != null) + { + CurrentSortingState = sort.Direction; + } + } + + PseudoClasses.Set(":sortascending", + CurrentSortingState == ListSortDirection.Ascending); + PseudoClasses.Set(":sortdescending", + CurrentSortingState == ListSortDirection.Descending); + } + + internal void UpdateSeparatorVisibility(DataGridColumn? lastVisibleColumn) + { + bool newVisibility = _desiredSeparatorVisibility; + + // Collapse separator for the last column if there is no filler column + if (OwningColumn != null && + OwningGrid != null && + _desiredSeparatorVisibility && + OwningColumn == lastVisibleColumn && + !OwningGrid.ColumnsInternal.FillerColumn.IsActive) + { + newVisibility = false; + } + + // Update the public property if it has changed + if (AreSeparatorsVisible != newVisibility) + { + SetValueNoCallback(AreSeparatorsVisibleProperty, newVisibility); + } + } + + public event EventHandler? LeftClick; + + internal void OnMouseLeftButtonUp_Click(KeyModifiers keyModifiers, ref bool handled) + { + LeftClick?.Invoke(this, keyModifiers); + + // completed a click without dragging, so we're sorting + InvokeProcessSort(keyModifiers); + handled = true; + } + + internal void InvokeProcessSort(KeyModifiers keyModifiers, ListSortDirection? forcedDirection = null) + { + Debug.Assert(OwningGrid != null); + if (OwningGrid.WaitForLostFocus(() => InvokeProcessSort(keyModifiers, forcedDirection))) + { + return; + } + + if (OwningGrid.CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true)) + { + Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(keyModifiers, forcedDirection)); + } + } + + //TODO GroupSorting + internal void ProcessSort(KeyModifiers keyModifiers, ListSortDirection? forcedDirection = null) + { + // if we can sort: + // - AllowUserToSortColumns and CanSort are true, and + // - OwningColumn is bound + // then try to sort + if (OwningColumn != null + && OwningGrid != null + && OwningGrid.EditingRow == null + && OwningColumn != OwningGrid.ColumnsInternal.FillerColumn + && OwningGrid.CanUserSortColumns + && OwningColumn.CanUserSort) + { + var ea = new DataGridColumnEventArgs(OwningColumn); + OwningGrid.OnColumnSorting(ea); + + if (!ea.Handled && OwningGrid.DataConnection!.AllowSort && + OwningGrid.DataConnection.SortDescriptions != null) + { + // - DataConnection.AllowSort is true, and + // - SortDescriptionsCollection exists, and + // - the column's data type is comparable + + DataGrid owningGrid = OwningGrid; + DataGridSortDescription newSort; + + KeyboardHelper.GetMetaKeyState(this, keyModifiers, out bool ctrl, out bool shift); + + var sort = OwningColumn.GetSortDescription(); + var collectionView = owningGrid.DataConnection.CollectionView; + Debug.Assert(collectionView != null); + + using (collectionView.DeferRefresh()) + { + // if shift is held down, we multi-sort, therefore if it isn't, we'll clear the sorts beforehand + if (!shift || owningGrid.DataConnection.SortDescriptions.Count == 0) + { + owningGrid.DataConnection.SortDescriptions.Clear(); + } + + // if ctrl is held down, we only clear the sort directions + if (!ctrl) + { + if (sort != null) + { + if (forcedDirection == null || sort.Direction != forcedDirection) + { + newSort = sort.SwitchSortDirection(); + } + else + { + newSort = sort; + } + + // changing direction should not affect sort order, so we replace this column's + // sort description instead of just adding it to the end of the collection + int oldIndex = owningGrid.DataConnection.SortDescriptions.IndexOf(sort); + if (oldIndex >= 0) + { + owningGrid.DataConnection.SortDescriptions.Remove(sort); + owningGrid.DataConnection.SortDescriptions.Insert(oldIndex, newSort); + } + else + { + owningGrid.DataConnection.SortDescriptions.Add(newSort); + } + } + else if (OwningColumn.CustomSortComparer != null) + { + newSort = forcedDirection != null + ? DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer, + forcedDirection.Value) + : DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer); + + owningGrid.DataConnection.SortDescriptions.Add(newSort); + } + else + { + var propertyName = OwningColumn.GetSortPropertyName(); + // no-opt if we couldn't find a property to sort on + if (string.IsNullOrEmpty(propertyName)) + { + return; + } + + newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture); + if (forcedDirection != null && newSort.Direction != forcedDirection) + { + newSort = newSort.SwitchSortDirection(); + } + + owningGrid.DataConnection.SortDescriptions.Add(newSort); + } + } + } + } + } + } + + private bool CanReorderColumn(DataGridColumn column) + { + return OwningGrid != null && OwningGrid.CanUserReorderColumns + && !(column is DataGridFillerColumn) + && (column.CanUserReorderInternal.HasValue && column.CanUserReorderInternal.Value || + !column.CanUserReorderInternal.HasValue); + } + + /// + /// Determines whether a column can be resized by dragging the border of its header. If star sizing + /// is being used, there are special conditions that can prevent a column from being resized: + /// 1. The column is the last visible column. + /// 2. All columns are constrained by either their maximum or minimum values. + /// + /// Column to check. + /// Whether or not the column can be resized by dragging its header. + private static bool CanResizeColumn(DataGridColumn column) + { + if (column.OwningGrid != null && column.OwningGrid.ColumnsInternal != null && + column.OwningGrid.UsesStarSizing && + (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || + !MathUtilities.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, + column.OwningGrid.CellsWidth))) + { + return false; + } + + return column.ActualCanUserResize; + } + + private static bool TrySetResizeColumn(DataGridColumn column) + { + // If datagrid.CanUserResizeColumns == false, then the column can still override it + if (CanResizeColumn(column)) + { + _dragColumn = column; + _dragMode = DragMode.Resize; + return true; + } + + return false; + } + + //TODO DragDrop + + internal void OnMouseLeftButtonDown(ref bool handled, PointerEventArgs args, Point mousePosition) + { + IsPressed = true; + if (OwningGrid != null && OwningGrid.ColumnHeaders != null) + { + _dragMode = DragMode.MouseDown; + _frozenColumnsWidth = OwningGrid.ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth(); + _lastMousePositionHeaders = this.Translate(OwningGrid.ColumnHeaders, mousePosition); + + double distanceFromLeft = mousePosition.X; + double distanceFromRight = Bounds.Width - distanceFromLeft; + var currentColumn = OwningColumn; + DataGridColumn? previousColumn = null; + if (!(OwningColumn is DataGridFillerColumn)) + { + previousColumn = OwningGrid.ColumnsInternal.GetPreviousVisibleNonFillerColumn(currentColumn); + } + + if (_dragMode == DragMode.MouseDown && _dragColumn == null && + distanceFromRight <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && + currentColumn != null) + { + handled = TrySetResizeColumn(currentColumn); + } + else if (_dragMode == DragMode.MouseDown && _dragColumn == null && + distanceFromLeft <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && previousColumn != null) + { + handled = TrySetResizeColumn(previousColumn); + } + + if (_dragMode == DragMode.Resize && _dragColumn != null) + { + _dragStart = _lastMousePositionHeaders; + _originalWidth = _dragColumn.ActualWidth; + _originalHorizontalOffset = OwningGrid.HorizontalOffset; + + handled = true; + } + } + } + + //TODO DragEvents + //TODO MouseCapture + internal void OnMouseLeftButtonUp(ref bool handled, PointerEventArgs args, Point mousePosition, + Point mousePositionHeaders) + { + IsPressed = false; + + if (OwningGrid != null && OwningGrid.ColumnHeaders != null) + { + if (_dragMode == DragMode.MouseDown) + { + OnMouseLeftButtonUp_Click(args.KeyModifiers, ref handled); + } + else if (_dragMode == DragMode.Reorder) + { + // Find header we're hovering over + int targetIndex = GetReorderingTargetDisplayIndex(mousePositionHeaders); + + if (OwningColumn is not null) + { + if ((!OwningColumn.IsFrozen && targetIndex >= OwningGrid.FrozenColumnCount) + || (OwningColumn.IsFrozen && targetIndex < OwningGrid.FrozenColumnCount)) + { + OwningColumn.DisplayIndex = targetIndex; + + var ea = new DataGridColumnEventArgs(OwningColumn); + OwningGrid.OnColumnReordered(ea); + } + } + } + + SetDragCursor(mousePosition); + + // Variables that track drag mode states get reset in DataGridColumnHeader_LostMouseCapture + args.Pointer.Capture(null); + OnLostMouseCapture(); + _dragMode = DragMode.None; + handled = true; + } + } + + //TODO DragEvents + internal void OnMouseMove(PointerEventArgs args, Point mousePosition, Point mousePositionHeaders) + { + var handled = args.Handled; + if (handled || OwningGrid == null || OwningGrid.ColumnHeaders == null) + { + return; + } + + Debug.Assert(OwningGrid.Parent is InputElement); + + OnMouseMove_Resize(ref handled, mousePositionHeaders); + + OnMouseMove_Reorder(ref handled, mousePosition, mousePositionHeaders); + + SetDragCursor(mousePosition); + } + + private void DataGridColumnHeader_PointerEntered(object? sender, PointerEventArgs e) + { + if (!IsEnabled) + { + return; + } + + Point mousePosition = e.GetPosition(this); + OnMouseEnter(mousePosition); + UpdatePseudoClasses(); + } + + private void DataGridColumnHeader_PointerExited(object? sender, PointerEventArgs e) + { + if (!IsEnabled) + { + return; + } + + OnMouseLeave(); + UpdatePseudoClasses(); + } + + private void DataGridColumnHeader_PointerPressed(object? sender, PointerPressedEventArgs e) + { + if (OwningColumn == null || e.Handled || !IsEnabled || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) + { + return; + } + + Point mousePosition = e.GetPosition(this); + bool handled = e.Handled; + OnMouseLeftButtonDown(ref handled, e, mousePosition); + e.Handled = handled; + + UpdatePseudoClasses(); + } + + private void DataGridColumnHeader_PointerReleased(object? sender, PointerReleasedEventArgs e) + { + if (OwningColumn == null || e.Handled || !IsEnabled || e.InitialPressMouseButton != MouseButton.Left) + { + return; + } + + Point mousePosition = e.GetPosition(this); + Point mousePositionHeaders = e.GetPosition(OwningGrid!.ColumnHeaders); + bool handled = e.Handled; + OnMouseLeftButtonUp(ref handled, e, mousePosition, mousePositionHeaders); + e.Handled = handled; + + UpdatePseudoClasses(); + } + + private void DataGridColumnHeader_PointerMoved(object? sender, PointerEventArgs e) + { + if (OwningGrid == null || !IsEnabled) + { + return; + } + + Point mousePosition = e.GetPosition(this); + Point mousePositionHeaders = e.GetPosition(OwningGrid.ColumnHeaders); + + OnMouseMove(e, mousePosition, mousePositionHeaders); + } + + /// + /// Returns the column against whose top-left the reordering caret should be positioned + /// + /// Mouse position within the ColumnHeadersPresenter + /// Whether or not to scroll horizontally when a column is dragged out of bounds + /// If scroll is true, returns the horizontal amount that was scrolled + /// + private DataGridColumn? GetReorderingTargetColumn(Point mousePositionHeaders, bool scroll, out double scrollAmount) + { + scrollAmount = 0; + double leftEdge = OwningGrid?.ColumnsInternal.RowGroupSpacerColumn.IsRepresented + ? OwningGrid?.ColumnsInternal.RowGroupSpacerColumn.ActualWidth + : 0; + var rightEdge = OwningGrid!.CellsWidth; + if (OwningColumn!.IsFrozen) + { + rightEdge = Math.Min(rightEdge, _frozenColumnsWidth); + } + else if (OwningGrid.FrozenColumnCount > 0) + { + leftEdge = _frozenColumnsWidth; + } + + if (mousePositionHeaders.X < leftEdge) + { + if (scroll && + OwningGrid.HorizontalScrollBar != null && + OwningGrid.HorizontalScrollBar.IsVisible && + OwningGrid.HorizontalScrollBar.Value > 0) + { + double newVal = mousePositionHeaders.X - leftEdge; + scrollAmount = Math.Min(newVal, OwningGrid.HorizontalScrollBar.Value); + OwningGrid.UpdateHorizontalOffset(scrollAmount + OwningGrid.HorizontalScrollBar.Value); + } + + mousePositionHeaders = mousePositionHeaders.WithX(leftEdge); + } + else if (mousePositionHeaders.X >= rightEdge) + { + if (scroll && + OwningGrid.HorizontalScrollBar != null && + OwningGrid.HorizontalScrollBar.IsVisible && + OwningGrid.HorizontalScrollBar.Value < OwningGrid.HorizontalScrollBar.Maximum) + { + double newVal = mousePositionHeaders.X - rightEdge; + scrollAmount = Math.Min(newVal, + OwningGrid.HorizontalScrollBar.Maximum - OwningGrid.HorizontalScrollBar.Value); + OwningGrid.UpdateHorizontalOffset(scrollAmount + OwningGrid.HorizontalScrollBar.Value); + } + + mousePositionHeaders = mousePositionHeaders.WithX(rightEdge - 1); + } + + foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetDisplayedColumns()) + { + Point mousePosition = OwningGrid.ColumnHeaders!.Translate(column.HeaderCell!, mousePositionHeaders); + double columnMiddle = column.HeaderCell!.Bounds.Width / 2; + if (mousePosition.X >= 0 && mousePosition.X <= columnMiddle) + { + return column; + } + else if (mousePosition.X > columnMiddle && mousePosition.X < column.HeaderCell.Bounds.Width) + { + return OwningGrid.ColumnsInternal.GetNextVisibleColumn(column); + } + } + + return null; + } + + /// + /// Returns the display index to set the column to + /// + /// Mouse position relative to the column headers presenter + /// + private int GetReorderingTargetDisplayIndex(Point mousePositionHeaders) + { + var targetColumn = + GetReorderingTargetColumn(mousePositionHeaders, false /*scroll*/, out double scrollAmount); + if (targetColumn != null) + { + return targetColumn.DisplayIndex > OwningColumn!.DisplayIndex + ? targetColumn.DisplayIndex - 1 + : targetColumn.DisplayIndex; + } + else + { + return OwningGrid!.Columns.Count - 1; + } + } + + /// + /// Returns true if the mouse is + /// - to the left of the element, or within the left half of the element + /// and + /// - within the vertical range of the element, or ignoreVertical == true + /// + /// + /// + /// + /// + private bool IsReorderTargeted(Point mousePosition, Control element, bool ignoreVertical) + { + Point position = this.Translate(element, mousePosition); + + return (position.X < 0 || (position.X >= 0 && position.X <= element.Bounds.Width / 2)) + && (ignoreVertical || (position.Y >= 0 && position.Y <= element.Bounds.Height)); + } + + /// + /// Resets the static DataGridColumnHeader properties when a header loses mouse capture + /// + private void OnLostMouseCapture() + { + // When we stop interacting with the column headers, we need to reset the drag mode + // and close any popups if they are open. + + if (_dragColumn != null && _dragColumn.HeaderCell != null) + { + _dragColumn.HeaderCell.Cursor = _originalCursor; + } + + _dragMode = DragMode.None; + _dragColumn = null; + _dragStart = null; + _lastMousePositionHeaders = null; + + if (OwningGrid != null && OwningGrid.ColumnHeaders != null) + { + OwningGrid.ColumnHeaders.DragColumn = null; + OwningGrid.ColumnHeaders.DragIndicator = null; + OwningGrid.ColumnHeaders.DropLocationIndicator = null; + } + } + + /// + /// Sets up the DataGridColumnHeader for the MouseEnter event + /// + /// mouse position relative to the DataGridColumnHeader + private void OnMouseEnter(Point mousePosition) + { + IsMouseOver = true; + SetDragCursor(mousePosition); + } + + /// + /// Sets up the DataGridColumnHeader for the MouseLeave event + /// + private void OnMouseLeave() + { + IsMouseOver = false; + } + + private void OnMouseMove_BeginReorder(Point mousePosition) + { + var dragIndicator = new DataGridColumnHeader + { + OwningColumn = OwningColumn, + IsEnabled = false, + Content = Content, + ContentTemplate = ContentTemplate + }; + if (OwningGrid!.ColumnHeaderTheme is { } columnHeaderTheme) + { + dragIndicator.SetValue(ThemeProperty, columnHeaderTheme, BindingPriority.Template); + } + + dragIndicator.PseudoClasses.Add(":dragIndicator"); + + Control? dropLocationIndicator = OwningGrid?.DropLocationIndicatorTemplate.Build(); + + // If the user didn't style the dropLocationIndicator's Height, default to the column header's height + if (dropLocationIndicator != null && double.IsNaN(dropLocationIndicator.Height)) + { + dropLocationIndicator.Height = Bounds.Height; + } + + // pass the caret's data template to the user for modification + var columnReorderingEventArgs = new DataGridColumnReorderingEventArgs(OwningColumn!) + { + DropLocationIndicator = dropLocationIndicator, + DragIndicator = dragIndicator + }; + OwningGrid!.OnColumnReordering(columnReorderingEventArgs); + if (columnReorderingEventArgs.Cancel) + { + return; + } + + // The user didn't cancel, so prepare for the reorder + _dragColumn = OwningColumn; + _dragMode = DragMode.Reorder; + _dragStart = mousePosition; + + // Display the reordering thumb + OwningGrid.ColumnHeaders!.DragColumn = OwningColumn; + OwningGrid.ColumnHeaders.DragIndicator = columnReorderingEventArgs.DragIndicator; + OwningGrid.ColumnHeaders.DropLocationIndicator = columnReorderingEventArgs.DropLocationIndicator; + + // If the user didn't style the dragIndicator's Width, default it to the column header's width + if (double.IsNaN(dragIndicator.Width)) + { + dragIndicator.Width = Bounds.Width; + } + } + + //TODO DragEvents + private void OnMouseMove_Reorder(ref bool handled, Point mousePosition, Point mousePositionHeaders) + { + if (handled) + { + return; + } + + //handle entry into reorder mode + if (_dragMode == DragMode.MouseDown && _dragColumn == null && _lastMousePositionHeaders != null) + { + var distanceFromInitial = (Vector)(mousePositionHeaders - _lastMousePositionHeaders); + if (distanceFromInitial.Length > DATAGRIDCOLUMNHEADER_columnsDragTreshold) + { + handled = CanReorderColumn(OwningColumn!); + + if (handled) + { + OnMouseMove_BeginReorder(mousePosition); + } + } + } + + //handle reorder mode (eg, positioning of the popup) + if (_dragMode == DragMode.Reorder && OwningGrid?.ColumnHeaders?.DragIndicator != null) + { + // Find header we're hovering over + var targetColumn = GetReorderingTargetColumn(mousePositionHeaders, !OwningColumn!.IsFrozen /*scroll*/, + out double scrollAmount); + + OwningGrid.ColumnHeaders.DragIndicatorOffset = mousePosition.X - _dragStart!.Value.X + scrollAmount; + OwningGrid.ColumnHeaders.InvalidateArrange(); + + if (OwningGrid.ColumnHeaders.DropLocationIndicator != null) + { + Point targetPosition = new Point(0, 0); + if (targetColumn == null || targetColumn == OwningGrid.ColumnsInternal.FillerColumn || + targetColumn.IsFrozen != OwningColumn.IsFrozen) + { + targetColumn = + OwningGrid.ColumnsInternal.GetLastColumn( + isVisible: true, + isFrozen: OwningColumn.IsFrozen, + isReadOnly: null); + targetPosition = targetColumn.HeaderCell!.Translate(OwningGrid.ColumnHeaders, targetPosition); + + targetPosition = targetPosition.WithX(targetPosition.X + targetColumn.ActualWidth); + } + else + { + targetPosition = targetColumn.HeaderCell!.Translate(OwningGrid.ColumnHeaders, targetPosition); + } + + OwningGrid.ColumnHeaders.DropLocationIndicatorOffset = targetPosition.X - scrollAmount; + } + + handled = true; + } + } + + private void OnMouseMove_Resize(ref bool handled, Point mousePositionHeaders) + { + if (handled) + { + return; + } + + if (_dragMode == DragMode.Resize && _dragColumn != null && _dragStart.HasValue) + { + // resize column + + double mouseDelta = mousePositionHeaders.X - _dragStart.Value.X; + double desiredWidth = _originalWidth + mouseDelta; + + desiredWidth = Math.Max(_dragColumn.ActualMinWidth, Math.Min(_dragColumn.ActualMaxWidth, desiredWidth)); + _dragColumn.Resize(_dragColumn.Width, + new(_dragColumn.Width.Value, _dragColumn.Width.UnitType, _dragColumn.Width.DesiredValue, desiredWidth), + true); + + OwningGrid?.UpdateHorizontalOffset(_originalHorizontalOffset); + + handled = true; + } + } + + private void SetDragCursor(Point mousePosition) + { + if (_dragMode != DragMode.None || OwningGrid == null || OwningColumn == null) + { + return; + } + + // set mouse if we can resize column + + double distanceFromLeft = mousePosition.X; + double distanceFromRight = Bounds.Width - distanceFromLeft; + DataGridColumn? currentColumn = OwningColumn; + DataGridColumn? previousColumn = null; + + if (!(OwningColumn is DataGridFillerColumn)) + { + previousColumn = OwningGrid.ColumnsInternal.GetPreviousVisibleNonFillerColumn(currentColumn); + } + + if ((distanceFromRight <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && currentColumn != null && + CanResizeColumn(currentColumn)) || + (distanceFromLeft <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && previousColumn != null && + CanResizeColumn(previousColumn))) + { + var resizeCursor = _resizeCursor.Value; + if (Cursor != resizeCursor) + { + _originalCursor = Cursor; + Cursor = resizeCursor; + } + } + else + { + Cursor = _originalCursor; + } + } +} \ No newline at end of file