Files
PowerToys/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/DockEditor.xaml.cs

392 lines
12 KiB
C#
Raw Normal View History

2025-12-04 13:40:51 +01:00
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using Microsoft.CmdPal.UI.ViewModels.Dock;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Media;
2025-12-04 14:43:54 +01:00
using Windows.ApplicationModel.DataTransfer;
2025-12-04 13:40:51 +01:00
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.Controls;
public sealed partial class DockEditor : UserControl
{
2025-12-04 14:43:54 +01:00
private DockBandSettingsViewModel? _draggedItem;
private ObservableCollection<DockBandSettingsViewModel>? _sourceCollection;
private DockEditorPinArea _targetArea;
2025-12-04 16:52:51 +01:00
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(
nameof(Orientation),
typeof(Orientation),
typeof(DockEditor),
new PropertyMetadata(Orientation.Horizontal, OnOrientationChanged));
2025-12-04 14:43:54 +01:00
/// <summary>
/// Gets or sets the collection of all dock band items to display and manage.
/// </summary>
public IList<DockBandSettingsViewModel> DockItems
2025-12-04 13:40:51 +01:00
{
2025-12-04 14:43:54 +01:00
get => (IList<DockBandSettingsViewModel>)GetValue(DockItemsProperty);
set => SetValue(DockItemsProperty, value);
2025-12-04 13:40:51 +01:00
}
2025-12-04 14:43:54 +01:00
public static readonly DependencyProperty DockItemsProperty =
DependencyProperty.Register(
nameof(DockItems),
typeof(IList<DockBandSettingsViewModel>),
typeof(DockEditor),
new PropertyMetadata(new ObservableCollection<DockBandSettingsViewModel>(), OnDockItemsChanged));
/// <summary>
/// Gets items pinned to the start (left) area of the dock.
/// </summary>
public ObservableCollection<DockBandSettingsViewModel> StartItems { get; } = new();
/// <summary>
/// Gets items pinned to the center area of the dock (UI concept, stored as Start).
/// </summary>
public ObservableCollection<DockBandSettingsViewModel> CenterItems { get; } = new();
/// <summary>
/// Gets items pinned to the end (right) area of the dock.
/// </summary>
public ObservableCollection<DockBandSettingsViewModel> EndItems { get; } = new();
/// <summary>
/// Gets items available to be added to the dock (not currently pinned).
/// </summary>
public ObservableCollection<DockBandSettingsViewModel> AvailableItems { get; } = new();
2025-12-04 13:40:51 +01:00
public DockEditor()
{
InitializeComponent();
}
2025-12-04 14:43:54 +01:00
private static void OnDockItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DockEditor editor)
{
editor.LoadItems();
}
}
2025-12-04 16:52:51 +01:00
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DockEditor editor)
{
if (editor.Orientation == Orientation.Horizontal)
{
VisualStateManager.GoToState(editor, "HorizontalState", true);
}
else
{
VisualStateManager.GoToState(editor, "VerticalState", true);
}
}
}
2025-12-04 14:43:54 +01:00
private void LoadItems()
{
StartItems.Clear();
CenterItems.Clear();
EndItems.Clear();
if (DockItems == null)
{
return;
}
foreach (var item in DockItems)
{
2025-12-04 16:52:51 +01:00
// PinSideIndex: 0 = None, 1 = Start, 2 = End
2025-12-04 14:43:54 +01:00
switch (item.PinSideIndex)
{
case 1: // Start
StartItems.Add(item);
break;
case 2: // End
EndItems.Add(item);
break;
default: // None (0) - available for adding
break;
}
}
RefreshAvailableItems();
}
private void RefreshAvailableItems()
{
AvailableItems.Clear();
if (DockItems == null)
{
return;
}
foreach (var item in DockItems)
{
// Items with PinSideIndex == 0 (None) are available
if (item.PinSideIndex == 0)
{
AvailableItems.Add(item);
}
}
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
if (sender is not Button button)
{
return;
}
// Determine which area to add to based on button name
_targetArea = button.Name switch
{
"StartAddButton" => DockEditorPinArea.Start,
"CenterAddButton" or "CenterAddButtonLeft" => DockEditorPinArea.Center,
"EndAddButton" => DockEditorPinArea.End,
_ => DockEditorPinArea.None,
};
// Refresh available items before showing flyout
RefreshAvailableItems();
// Show the flyout
var flyout = button.Flyout as Flyout;
flyout?.ShowAt(button);
}
private void AvailableItemsListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is not DockBandSettingsViewModel selectedItem)
{
return;
}
// Set the pin side based on target area
// PinSideIndex: 0 = None, 1 = Start, 2 = End
selectedItem.PinSideIndex = _targetArea switch
{
DockEditorPinArea.Start => 1,
DockEditorPinArea.End => 2,
_ => 0,
};
// Add to the appropriate UI collection
var targetCollection = _targetArea switch
{
DockEditorPinArea.Start => StartItems,
DockEditorPinArea.Center => CenterItems,
DockEditorPinArea.End => EndItems,
_ => null,
};
targetCollection?.Add(selectedItem);
// Refresh available items
RefreshAvailableItems();
// Close the flyout
CloseFlyoutFromSender(sender);
}
private void UnpinButton_Click(object sender, RoutedEventArgs e)
{
2025-12-04 21:01:39 +01:00
if (sender is Button btn && btn.DataContext is DockBandSettingsViewModel contextItem)
2025-12-04 14:43:54 +01:00
{
2025-12-04 21:01:39 +01:00
// Remove from all UI collections
StartItems.Remove(contextItem);
CenterItems.Remove(contextItem);
EndItems.Remove(contextItem);
2025-12-04 14:43:54 +01:00
2025-12-04 21:01:39 +01:00
// Refresh available items
RefreshAvailableItems();
2025-12-04 14:43:54 +01:00
2025-12-04 21:01:39 +01:00
// Close the flyout
CloseFlyoutFromSender(sender);
}
2025-12-04 14:43:54 +01:00
}
private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
if (e.Items.Count > 0 && e.Items[0] is DockBandSettingsViewModel item)
{
_draggedItem = item;
_sourceCollection = (sender as ListView)?.ItemsSource as ObservableCollection<DockBandSettingsViewModel>;
e.Data.RequestedOperation = DataPackageOperation.Move;
}
}
private void ListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
// Reordering within the same list is handled automatically by ListView
// The PinSideIndex doesn't need to change
_draggedItem = null;
_sourceCollection = null;
}
private void ListView_DragOver(object sender, DragEventArgs e)
{
e.AcceptedOperation = DataPackageOperation.Move;
e.DragUIOverride.Caption = "Move";
e.DragUIOverride.IsCaptionVisible = true;
e.DragUIOverride.IsContentVisible = true;
e.DragUIOverride.IsGlyphVisible = true;
}
private void StartListView_Drop(object sender, DragEventArgs e)
{
HandleDrop(sender, DockEditorPinArea.Start, e);
}
private void CenterListView_Drop(object sender, DragEventArgs e)
{
HandleDrop(sender, DockEditorPinArea.Center, e);
}
private void EndListView_Drop(object sender, DragEventArgs e)
{
HandleDrop(sender, DockEditorPinArea.End, e);
}
private void HandleDrop(object sender, DockEditorPinArea targetArea, DragEventArgs e)
{
if (_draggedItem == null)
{
return;
}
var targetListView = sender as ListView;
var targetCollection = targetListView?.ItemsSource as ObservableCollection<DockBandSettingsViewModel>;
if (targetCollection == null || _sourceCollection == null)
{
return;
}
// If dropping to a different collection, move the item
if (targetCollection != _sourceCollection)
{
// Remove from source collection
_sourceCollection.Remove(_draggedItem);
// Calculate the drop index based on the position
var dropIndex = GetDropIndex(targetListView!, e);
// Insert at the drop position
if (dropIndex >= targetCollection.Count)
{
targetCollection.Add(_draggedItem);
}
else
{
targetCollection.Insert(dropIndex, _draggedItem);
}
// Update the pin side
// PinSideIndex: 0 = None, 1 = Start, 2 = End
_draggedItem.PinSideIndex = targetArea switch
{
DockEditorPinArea.Start => 1,
DockEditorPinArea.End => 2,
_ => 0,
};
}
// Clear references
_draggedItem = null;
_sourceCollection = null;
}
private int GetDropIndex(ListView listView, DragEventArgs e)
{
var position = e.GetPosition(listView);
var items = listView.ItemsSource as ObservableCollection<DockBandSettingsViewModel>;
if (items == null || items.Count == 0)
{
return 0;
}
// Find the drop position based on X coordinate for horizontal layout
for (var i = 0; i < items.Count; i++)
{
var container = listView.ContainerFromIndex(i) as ListViewItem;
if (container != null)
{
var containerPosition = container.TransformToVisual(listView).TransformPoint(new Point(0, 0));
var containerCenter = containerPosition.X + (container.ActualWidth / 2);
if (position.X < containerCenter)
{
return i;
}
}
}
return items.Count;
}
private static void CloseFlyoutFromSender(object sender)
{
// Walk up the visual tree to find and close the flyout
var element = sender as DependencyObject;
while (element != null)
{
if (element is Popup popup)
{
popup.IsOpen = false;
return;
}
if (element is FlyoutPresenter)
{
// Find the parent popup
var parent = VisualTreeHelper.GetParent(element);
while (parent != null && parent is not Popup)
{
parent = VisualTreeHelper.GetParent(parent);
}
if (parent is Popup flyoutPopup)
{
flyoutPopup.IsOpen = false;
}
return;
}
element = VisualTreeHelper.GetParent(element);
}
}
2025-12-04 16:52:51 +01:00
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
AvailableItemsListView.ItemsSource = AvailableItems;
}
2025-12-04 14:43:54 +01:00
}
/// <summary>
/// Pin area enum for the DockEditor UI.
/// </summary>
public enum DockEditorPinArea
{
None,
Start,
Center,
End,
2025-12-04 13:40:51 +01:00
}