mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
Merge branch 'niels9001/cmdpal-dock/drag' into dev/migrie/f/powerdock
This commit is contained in:
@@ -19,6 +19,7 @@ public sealed partial class DockViewModel : IDisposable,
|
||||
IPageContext
|
||||
{
|
||||
private readonly TopLevelCommandManager _topLevelCommandManager;
|
||||
private readonly SettingsModel _settingsModel;
|
||||
|
||||
private DockSettings _settings;
|
||||
|
||||
@@ -36,6 +37,7 @@ public sealed partial class DockViewModel : IDisposable,
|
||||
TaskScheduler scheduler)
|
||||
{
|
||||
_topLevelCommandManager = tlcManager;
|
||||
_settingsModel = settings;
|
||||
_settings = settings.DockSettings;
|
||||
Scheduler = scheduler;
|
||||
WeakReferenceMessenger.Default.Register<CommandsReloadedMessage>(this);
|
||||
@@ -135,6 +137,176 @@ public sealed partial class DockViewModel : IDisposable,
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syncs the band position in settings after a same-list reorder.
|
||||
/// Does not save to disk - call SaveBandOrder() when done editing.
|
||||
/// </summary>
|
||||
public void SyncBandPosition(DockBandViewModel band, DockPinSide targetSide, int targetIndex)
|
||||
{
|
||||
var bandId = band.Id;
|
||||
var dockSettings = _settingsModel.DockSettings;
|
||||
|
||||
var bandSettings = dockSettings.StartBands.FirstOrDefault(b => b.Id == bandId)
|
||||
?? dockSettings.EndBands.FirstOrDefault(b => b.Id == bandId);
|
||||
|
||||
if (bandSettings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from both settings lists
|
||||
dockSettings.StartBands.RemoveAll(b => b.Id == bandId);
|
||||
dockSettings.EndBands.RemoveAll(b => b.Id == bandId);
|
||||
|
||||
// Add to target settings list at the correct index
|
||||
var targetSettings = targetSide == DockPinSide.Start ? dockSettings.StartBands : dockSettings.EndBands;
|
||||
var insertIndex = Math.Min(targetIndex, targetSettings.Count);
|
||||
targetSettings.Insert(insertIndex, bandSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves a dock band to a new position (cross-list drop).
|
||||
/// Does not save to disk - call SaveBandOrder() when done editing.
|
||||
/// </summary>
|
||||
public void MoveBandWithoutSaving(DockBandViewModel band, DockPinSide targetSide, int targetIndex)
|
||||
{
|
||||
var bandId = band.Id;
|
||||
var dockSettings = _settingsModel.DockSettings;
|
||||
|
||||
var bandSettings = dockSettings.StartBands.FirstOrDefault(b => b.Id == bandId)
|
||||
?? dockSettings.EndBands.FirstOrDefault(b => b.Id == bandId);
|
||||
|
||||
if (bandSettings == null)
|
||||
{
|
||||
Logger.LogWarning($"Could not find band settings for band {bandId}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from both sides (settings and UI)
|
||||
dockSettings.StartBands.RemoveAll(b => b.Id == bandId);
|
||||
dockSettings.EndBands.RemoveAll(b => b.Id == bandId);
|
||||
StartItems.Remove(band);
|
||||
EndItems.Remove(band);
|
||||
|
||||
// Add to the target side at the specified index
|
||||
switch (targetSide)
|
||||
{
|
||||
case DockPinSide.Start:
|
||||
{
|
||||
var settingsIndex = Math.Min(targetIndex, dockSettings.StartBands.Count);
|
||||
dockSettings.StartBands.Insert(settingsIndex, bandSettings);
|
||||
|
||||
var uiIndex = Math.Min(targetIndex, StartItems.Count);
|
||||
StartItems.Insert(uiIndex, band);
|
||||
break;
|
||||
}
|
||||
|
||||
case DockPinSide.End:
|
||||
{
|
||||
var settingsIndex = Math.Min(targetIndex, dockSettings.EndBands.Count);
|
||||
dockSettings.EndBands.Insert(settingsIndex, bandSettings);
|
||||
|
||||
var uiIndex = Math.Min(targetIndex, EndItems.Count);
|
||||
EndItems.Insert(uiIndex, band);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogDebug($"Moved band {bandId} to {targetSide} at index {targetIndex} (not saved yet)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current band order to settings.
|
||||
/// Call this when exiting edit mode.
|
||||
/// </summary>
|
||||
public void SaveBandOrder()
|
||||
{
|
||||
_snapshotStartBands = null;
|
||||
_snapshotEndBands = null;
|
||||
SettingsModel.SaveSettings(_settingsModel);
|
||||
Logger.LogDebug("Saved band order to settings");
|
||||
}
|
||||
|
||||
private List<DockBandSettings>? _snapshotStartBands;
|
||||
private List<DockBandSettings>? _snapshotEndBands;
|
||||
|
||||
/// <summary>
|
||||
/// Takes a snapshot of the current band order before editing.
|
||||
/// Call this when entering edit mode.
|
||||
/// </summary>
|
||||
public void SnapshotBandOrder()
|
||||
{
|
||||
var dockSettings = _settingsModel.DockSettings;
|
||||
_snapshotStartBands = dockSettings.StartBands.Select(b => new DockBandSettings { Id = b.Id }).ToList();
|
||||
_snapshotEndBands = dockSettings.EndBands.Select(b => new DockBandSettings { Id = b.Id }).ToList();
|
||||
Logger.LogDebug($"Snapshot taken: {_snapshotStartBands.Count} start bands, {_snapshotEndBands.Count} end bands");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the band order from the snapshot taken when entering edit mode.
|
||||
/// Call this when discarding edit mode changes.
|
||||
/// </summary>
|
||||
public void RestoreBandOrder()
|
||||
{
|
||||
if (_snapshotStartBands == null || _snapshotEndBands == null)
|
||||
{
|
||||
Logger.LogWarning("No snapshot to restore from");
|
||||
return;
|
||||
}
|
||||
|
||||
var dockSettings = _settingsModel.DockSettings;
|
||||
|
||||
// Restore settings from snapshot
|
||||
dockSettings.StartBands.Clear();
|
||||
dockSettings.EndBands.Clear();
|
||||
|
||||
foreach (var bandSnapshot in _snapshotStartBands)
|
||||
{
|
||||
var bandSettings = new DockBandSettings { Id = bandSnapshot.Id };
|
||||
dockSettings.StartBands.Add(bandSettings);
|
||||
}
|
||||
|
||||
foreach (var bandSnapshot in _snapshotEndBands)
|
||||
{
|
||||
var bandSettings = new DockBandSettings { Id = bandSnapshot.Id };
|
||||
dockSettings.EndBands.Add(bandSettings);
|
||||
}
|
||||
|
||||
// Rebuild UI collections from restored settings
|
||||
RebuildUICollections();
|
||||
|
||||
_snapshotStartBands = null;
|
||||
_snapshotEndBands = null;
|
||||
Logger.LogDebug("Restored band order from snapshot");
|
||||
}
|
||||
|
||||
private void RebuildUICollections()
|
||||
{
|
||||
var dockSettings = _settingsModel.DockSettings;
|
||||
|
||||
// Create a lookup of all current band ViewModels
|
||||
var allBands = StartItems.Concat(EndItems).ToDictionary(b => b.Id);
|
||||
|
||||
StartItems.Clear();
|
||||
EndItems.Clear();
|
||||
|
||||
foreach (var bandSettings in dockSettings.StartBands)
|
||||
{
|
||||
if (allBands.TryGetValue(bandSettings.Id, out var bandVM))
|
||||
{
|
||||
StartItems.Add(bandVM);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var bandSettings in dockSettings.EndBands)
|
||||
{
|
||||
if (allBands.TryGetValue(bandSettings.Id, out var bandVM))
|
||||
{
|
||||
EndItems.Add(bandVM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowException(Exception ex, string? extensionHint = null)
|
||||
{
|
||||
var extensionText = extensionHint ?? "<unknown>";
|
||||
@@ -162,18 +334,29 @@ public sealed partial class DockViewModel : IDisposable,
|
||||
{
|
||||
public DockContextMenuItem()
|
||||
{
|
||||
var editDockCommand = new AnonymousCommand(
|
||||
action: () =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new EnterDockEditModeMessage());
|
||||
})
|
||||
{
|
||||
Name = "Edit dock", // TODO!Loc
|
||||
Icon = Icons.EditIcon,
|
||||
};
|
||||
|
||||
var openSettingsCommand = new AnonymousCommand(
|
||||
action: () =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new OpenSettingsMessage("Dock"));
|
||||
})
|
||||
{
|
||||
Name = "Customize", // TODO!Loc
|
||||
Name = "Dock settings", // TODO!Loc
|
||||
Icon = Icons.SettingsIcon,
|
||||
};
|
||||
|
||||
MoreCommands = new CommandContextItem[]
|
||||
{
|
||||
new CommandContextItem(editDockCommand),
|
||||
new CommandContextItem(openSettingsCommand),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,4 +13,6 @@ internal sealed class Icons
|
||||
internal static IconInfo UnpinIcon => new("\uE77A"); // Unpin icon
|
||||
|
||||
internal static IconInfo SettingsIcon => new("\uE713"); // Settings icon
|
||||
|
||||
internal static IconInfo EditIcon => new("\uE70F"); // Edit icon
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
|
||||
public record EnterDockEditModeMessage();
|
||||
@@ -20,6 +20,7 @@
|
||||
<ResourceDictionary Source="ms-appx:///Controls/Tag.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Controls/KeyVisual/KeyVisual.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Controls/IsEnabledTextBlock.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Dock/DockItemControl.xaml" />
|
||||
<!-- Default theme dictionary -->
|
||||
<ResourceDictionary Source="ms-appx:///Styles/Theme.Normal.xaml" />
|
||||
<services:MutableOverridesDictionary />
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Dock;
|
||||
|
||||
internal sealed partial class BandAlignmentConverter : Microsoft.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
public DockControl? Control { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is ObservableCollection<DockItemViewModel> items && Control is not null)
|
||||
{
|
||||
return Control.GetBandAlignment(items);
|
||||
}
|
||||
|
||||
return HorizontalAlignment.Center;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Dock;
|
||||
|
||||
internal sealed partial class DockBandTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DockControl? Control { get; set; }
|
||||
|
||||
public DataTemplate? HorizontalTemplate { get; set; }
|
||||
|
||||
public DataTemplate? VerticalTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
if (Control is null)
|
||||
{
|
||||
return HorizontalTemplate;
|
||||
}
|
||||
|
||||
return Control.ItemsOrientation == Orientation.Horizontal
|
||||
? HorizontalTemplate
|
||||
: VerticalTemplate;
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<StackLayout
|
||||
x:Key="ItemsOrientation"
|
||||
Orientation="{x:Bind ItemsOrientation, Mode=OneWay}"
|
||||
Spacing="4" />
|
||||
<ItemsPanelTemplate x:Key="HorizontalItemsPanel">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="VerticalItemsPanel">
|
||||
<StackPanel Orientation="Vertical" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
|
||||
<Style x:Key="ResizingIconStyle" TargetType="cpcontrols:IconBox">
|
||||
<Setter Property="Height" Value="{x:Bind IconSize, Mode=OneWay}" />
|
||||
@@ -32,88 +34,75 @@
|
||||
</Style>
|
||||
|
||||
<local:IconInfoVisibilityConverter x:Key="IconInfoVisibilityConverter" />
|
||||
<local:BandAlignmentConverter
|
||||
x:Key="BandAlignmentConverter"
|
||||
x:Name="BandAlignmentConverter"
|
||||
Control="{x:Bind}" />
|
||||
<local:BandAlignmentConverter x:Key="BandAlignmentConverter" Control="{x:Bind}" />
|
||||
|
||||
<DataTemplate x:Key="DeskbandTemplate" x:DataType="dockVm:DockItemViewModel">
|
||||
<Button
|
||||
VerticalAlignment="Stretch"
|
||||
DataContext="{x:Bind}"
|
||||
RightTapped="BandItem_RightTapped"
|
||||
Style="{StaticResource TaskBarButtonStyle}"
|
||||
Tapped="BandItem_Tapped"
|
||||
ToolTipService.ToolTip="{x:Bind Tooltip, Mode=OneWay}">
|
||||
|
||||
<Grid AutomationProperties.Name="{x:Bind Title, Mode=OneWay}" Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Visibility="{x:Bind Icon, Converter={StaticResource IconInfoVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid>
|
||||
<TextBlock Opacity="0" Style="{StaticResource ResizingTitleTextBlock}" />
|
||||
<!-- Removing this breaks the app.. and I have no idea why -->
|
||||
<local:DockItemControl
|
||||
Title="{x:Bind Title, Mode=OneWay}"
|
||||
RightTapped="BandItem_RightTapped"
|
||||
Subtitle="{x:Bind Subtitle, Mode=OneWay}"
|
||||
Tapped="BandItem_Tapped"
|
||||
ToolTip="{x:Bind Tooltip, Mode=OneWay}">
|
||||
<local:DockItemControl.Icon>
|
||||
<cpcontrols:IconBox
|
||||
x:Name="IconBorder"
|
||||
Width="16"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}"
|
||||
Style="{StaticResource ResizingIconStyle}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="8,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{x:Bind HasText, Mode=OneWay}">
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Style="{StaticResource ResizingTitleTextBlock}"
|
||||
Text="{x:Bind Title, Mode=OneWay}"
|
||||
TextAlignment="Left"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
x:Name="SubTitleTextBlock"
|
||||
MaxWidth="100"
|
||||
Margin="0,-4,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorTertiary}"
|
||||
Text="{x:Bind Subtitle, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind Subtitle, Mode=OneWay, Converter={StaticResource StringNotEmptyToVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button>
|
||||
</local:DockItemControl.Icon>
|
||||
</local:DockItemControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="DockBandTemplate" x:DataType="dockVm:DockBandViewModel">
|
||||
<ItemsRepeater
|
||||
x:Name="BandItemsRepeater"
|
||||
<DataTemplate x:Key="HorizontalDockBandTemplate" x:DataType="dockVm:DockBandViewModel">
|
||||
<ItemsControl
|
||||
HorizontalAlignment="{x:Bind Items, Converter={StaticResource BandAlignmentConverter}, Mode=OneWay}"
|
||||
ItemTemplate="{StaticResource DeskbandTemplate}"
|
||||
ItemsSource="{x:Bind Items, Mode=OneWay}"
|
||||
Layout="{StaticResource ItemsOrientation}">
|
||||
<ItemsRepeater.Transitions>
|
||||
<TransitionCollection />
|
||||
</ItemsRepeater.Transitions>
|
||||
</ItemsRepeater>
|
||||
ItemsPanel="{StaticResource HorizontalItemsPanel}"
|
||||
ItemsSource="{x:Bind Items, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="VerticalDockBandTemplate" x:DataType="dockVm:DockBandViewModel">
|
||||
<ItemsControl
|
||||
HorizontalAlignment="{x:Bind Items, Converter={StaticResource BandAlignmentConverter}, Mode=OneWay}"
|
||||
ItemTemplate="{StaticResource DeskbandTemplate}"
|
||||
ItemsPanel="{StaticResource VerticalItemsPanel}"
|
||||
ItemsSource="{x:Bind Items, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
|
||||
<local:DockBandTemplateSelector
|
||||
x:Key="DockBandTemplateSelector"
|
||||
Control="{x:Bind}"
|
||||
HorizontalTemplate="{StaticResource HorizontalDockBandTemplate}"
|
||||
VerticalTemplate="{StaticResource VerticalDockBandTemplate}" />
|
||||
|
||||
<Style x:Key="DockBandListViewStyle" TargetType="ListView">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="IsItemClickEnabled" Value="False" />
|
||||
<Setter Property="SelectionMode" Value="None" />
|
||||
<!-- Drag properties controlled by code-behind based on IsEditMode -->
|
||||
<Setter Property="CanDragItems" Value="False" />
|
||||
<Setter Property="CanReorderItems" Value="False" />
|
||||
<Setter Property="AllowDrop" Value="False" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DockBandListViewItemStyle" TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0,0,4,0" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Name="ContextMenuFlyoutStyle"
|
||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
|
||||
@@ -143,10 +132,18 @@
|
||||
BorderBrush="{ThemeResource SurfaceStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
RightTapped="RootGrid_RightTapped">
|
||||
<!-- Edit Mode Overlay - shown when in edit mode -->
|
||||
<Border
|
||||
x:Name="EditModeOverlay"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0" />
|
||||
|
||||
<Grid x:Name="ContentGrid" Padding="0,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition x:Name="EndColumn" Width="Auto" />
|
||||
<ColumnDefinition x:Name="DoneButtonColumn" Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
@@ -159,16 +156,19 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<cpcontrols:ScrollContainer.Source>
|
||||
<ItemsRepeater
|
||||
x:Name="StartItemsRepeater"
|
||||
<ListView
|
||||
x:Name="StartItemsListView"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemTemplate="{StaticResource DockBandTemplate}"
|
||||
DragItemsCompleted="BandListView_DragItemsCompleted"
|
||||
DragItemsStarting="BandListView_DragItemsStarting"
|
||||
DragOver="BandListView_DragOver"
|
||||
Drop="StartItemsListView_Drop"
|
||||
ItemContainerStyle="{StaticResource DockBandListViewItemStyle}"
|
||||
ItemTemplateSelector="{StaticResource DockBandTemplateSelector}"
|
||||
ItemsPanel="{StaticResource HorizontalItemsPanel}"
|
||||
ItemsSource="{x:Bind ViewModel.StartItems, Mode=OneWay}"
|
||||
Layout="{StaticResource ItemsOrientation}">
|
||||
<ItemsRepeater.Transitions>
|
||||
<TransitionCollection />
|
||||
</ItemsRepeater.Transitions>
|
||||
</ItemsRepeater>
|
||||
SelectionMode="None"
|
||||
Style="{StaticResource DockBandListViewStyle}" />
|
||||
</cpcontrols:ScrollContainer.Source>
|
||||
</cpcontrols:ScrollContainer>
|
||||
|
||||
@@ -177,17 +177,46 @@
|
||||
Grid.Column="1"
|
||||
ContentAlignment="End">
|
||||
<cpcontrols:ScrollContainer.Source>
|
||||
<ItemsRepeater
|
||||
x:Name="EndItemsRepeater"
|
||||
ItemTemplate="{StaticResource DockBandTemplate}"
|
||||
<ListView
|
||||
x:Name="EndItemsListView"
|
||||
DragItemsCompleted="BandListView_DragItemsCompleted"
|
||||
DragItemsStarting="BandListView_DragItemsStarting"
|
||||
DragOver="BandListView_DragOver"
|
||||
Drop="EndItemsListView_Drop"
|
||||
ItemContainerStyle="{StaticResource DockBandListViewItemStyle}"
|
||||
ItemTemplateSelector="{StaticResource DockBandTemplateSelector}"
|
||||
ItemsPanel="{StaticResource HorizontalItemsPanel}"
|
||||
ItemsSource="{x:Bind ViewModel.EndItems, Mode=OneWay}"
|
||||
Layout="{StaticResource ItemsOrientation}">
|
||||
<ItemsRepeater.Transitions>
|
||||
SelectionMode="None"
|
||||
Style="{StaticResource DockBandListViewStyle}">
|
||||
<ListView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</ItemsRepeater.Transitions>
|
||||
</ItemsRepeater>
|
||||
</ListView.ItemContainerTransitions>
|
||||
</ListView>
|
||||
</cpcontrols:ScrollContainer.Source>
|
||||
</cpcontrols:ScrollContainer>
|
||||
|
||||
<!-- To do: remove the X button -->
|
||||
<TeachingTip
|
||||
x:Name="EditButtonsTeachingTip"
|
||||
MinWidth="0"
|
||||
PreferredPlacement="Bottom"
|
||||
ShouldConstrainToRootBounds="False"
|
||||
Target="{x:Bind ContentGrid}">
|
||||
<TeachingTip.Content>
|
||||
<StackPanel
|
||||
x:Name="EditButtonsPanel"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<Button
|
||||
Click="DoneEditingButton_Click"
|
||||
Content="Save"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button Click="DiscardEditingButton_Click" Content="Discard" />
|
||||
</StackPanel>
|
||||
</TeachingTip.Content>
|
||||
</TeachingTip>
|
||||
</Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="DockOrientation">
|
||||
@@ -268,6 +297,16 @@
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<!-- Edit Mode Visual States -->
|
||||
<VisualStateGroup x:Name="EditModeStates">
|
||||
<VisualState x:Name="EditModeOff" />
|
||||
<VisualState x:Name="EditModeOn">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="EditModeOverlay.Opacity" Value="0.4" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.UI;
|
||||
@@ -16,13 +17,12 @@ using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Dock;
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
public sealed partial class DockControl : UserControl, INotifyPropertyChanged, IRecipient<CloseContextMenuMessage>
|
||||
public sealed partial class DockControl : UserControl, INotifyPropertyChanged, IRecipient<CloseContextMenuMessage>, IRecipient<EnterDockEditModeMessage>
|
||||
{
|
||||
private DockViewModel _viewModel;
|
||||
|
||||
@@ -39,10 +39,31 @@ public sealed partial class DockControl : UserControl, INotifyPropertyChanged, I
|
||||
{
|
||||
field = value;
|
||||
PropertyChanged?.Invoke(this, new(nameof(ItemsOrientation)));
|
||||
UpdateBandTemplates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBandTemplates()
|
||||
{
|
||||
var panelKey = ItemsOrientation == Orientation.Horizontal
|
||||
? "HorizontalItemsPanel"
|
||||
: "VerticalItemsPanel";
|
||||
|
||||
var panel = (ItemsPanelTemplate)Resources[panelKey];
|
||||
|
||||
StartItemsListView.ItemsPanel = panel;
|
||||
EndItemsListView.ItemsPanel = panel;
|
||||
|
||||
// Force the selector to re-evaluate by refreshing ItemsSource
|
||||
var startItems = StartItemsListView.ItemsSource;
|
||||
var endItems = EndItemsListView.ItemsSource;
|
||||
StartItemsListView.ItemsSource = null;
|
||||
EndItemsListView.ItemsSource = null;
|
||||
StartItemsListView.ItemsSource = startItems;
|
||||
EndItemsListView.ItemsSource = endItems;
|
||||
}
|
||||
|
||||
public DockSide DockSide
|
||||
{
|
||||
get => field;
|
||||
@@ -56,6 +77,20 @@ public sealed partial class DockControl : UserControl, INotifyPropertyChanged, I
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEditMode
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (field != value)
|
||||
{
|
||||
field = value;
|
||||
PropertyChanged?.Invoke(this, new(nameof(IsEditMode)));
|
||||
UpdateEditMode(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double IconSize
|
||||
{
|
||||
get => field;
|
||||
@@ -109,6 +144,81 @@ public sealed partial class DockControl : UserControl, INotifyPropertyChanged, I
|
||||
_viewModel = viewModel;
|
||||
InitializeComponent();
|
||||
WeakReferenceMessenger.Default.Register<CloseContextMenuMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<EnterDockEditModeMessage>(this);
|
||||
|
||||
// Start with edit mode disabled - normal click behavior
|
||||
UpdateEditMode(false);
|
||||
}
|
||||
|
||||
public void Receive(EnterDockEditModeMessage message)
|
||||
{
|
||||
// Message may arrive from a background thread, dispatch to UI thread
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
EnterEditMode();
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateEditMode(bool isEditMode)
|
||||
{
|
||||
// Enable/disable drag-and-drop based on edit mode
|
||||
StartItemsListView.CanDragItems = isEditMode;
|
||||
StartItemsListView.CanReorderItems = isEditMode;
|
||||
StartItemsListView.AllowDrop = isEditMode;
|
||||
|
||||
EndItemsListView.CanDragItems = isEditMode;
|
||||
EndItemsListView.CanReorderItems = isEditMode;
|
||||
EndItemsListView.AllowDrop = isEditMode;
|
||||
|
||||
if (isEditMode)
|
||||
{
|
||||
EditButtonsTeachingTip.PreferredPlacement = DockSide switch
|
||||
{
|
||||
DockSide.Left => TeachingTipPlacementMode.Right,
|
||||
DockSide.Right => TeachingTipPlacementMode.Left,
|
||||
DockSide.Top => TeachingTipPlacementMode.Bottom,
|
||||
DockSide.Bottom => TeachingTipPlacementMode.Top,
|
||||
_ => TeachingTipPlacementMode.Auto,
|
||||
};
|
||||
}
|
||||
|
||||
EditButtonsTeachingTip.IsOpen = isEditMode;
|
||||
|
||||
// Update visual state
|
||||
VisualStateManager.GoToState(this, isEditMode ? "EditModeOn" : "EditModeOff", true);
|
||||
}
|
||||
|
||||
internal void EnterEditMode()
|
||||
{
|
||||
// Snapshot current state so we can restore on discard
|
||||
ViewModel.SnapshotBandOrder();
|
||||
IsEditMode = true;
|
||||
}
|
||||
|
||||
internal void ExitEditMode()
|
||||
{
|
||||
IsEditMode = false;
|
||||
|
||||
// Save all changes when exiting edit mode
|
||||
ViewModel.SaveBandOrder();
|
||||
}
|
||||
|
||||
internal void DiscardEditMode()
|
||||
{
|
||||
IsEditMode = false;
|
||||
|
||||
// Restore the original band order from snapshot
|
||||
ViewModel.RestoreBandOrder();
|
||||
}
|
||||
|
||||
private void DoneEditingButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ExitEditMode();
|
||||
}
|
||||
|
||||
private void DiscardEditingButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DiscardEditMode();
|
||||
}
|
||||
|
||||
internal void UpdateSettings(DockSettings settings)
|
||||
@@ -127,52 +237,58 @@ public sealed partial class DockControl : UserControl, INotifyPropertyChanged, I
|
||||
{
|
||||
RootGrid.BorderBrush = new SolidColorBrush(Colors.Transparent);
|
||||
}
|
||||
|
||||
// Ensure templates are updated on initial load (setter only updates on change)
|
||||
UpdateBandTemplates();
|
||||
}
|
||||
|
||||
private void BandItem_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
|
||||
{
|
||||
var pos = e.GetPosition(null);
|
||||
var button = sender as Button;
|
||||
var item = button?.DataContext as DockItemViewModel;
|
||||
|
||||
if (item is not null)
|
||||
// Ignore clicks when in edit mode - allow drag behavior instead
|
||||
if (IsEditMode)
|
||||
{
|
||||
// Use the center of the button as the point to open at. This is
|
||||
// more reliable than using the tap position. This allows multiple
|
||||
// clicks anywhere in the button to open the palette in a consistent
|
||||
// location.
|
||||
var buttonPos = button!.TransformToVisual(null).TransformPoint(new Point(0, 0));
|
||||
var buttonCenter = new Point(
|
||||
buttonPos.X + (button.ActualWidth / 2),
|
||||
buttonPos.Y + (button.ActualHeight / 2));
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeItem(item, buttonCenter);
|
||||
if (sender is DockItemControl dockItem && dockItem.DataContext is DockItemViewModel item)
|
||||
{
|
||||
// Use the center of the border as the point to open at
|
||||
var borderPos = dockItem.TransformToVisual(null).TransformPoint(new Point(0, 0));
|
||||
var borderCenter = new Point(
|
||||
borderPos.X + (dockItem.ActualWidth / 2),
|
||||
borderPos.Y + (dockItem.ActualHeight / 2));
|
||||
|
||||
InvokeItem(item, borderCenter);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void BandItem_RightTapped(object sender, Microsoft.UI.Xaml.Input.RightTappedRoutedEventArgs e)
|
||||
{
|
||||
var pos = e.GetPosition(null);
|
||||
var button = sender as Button;
|
||||
var item = button?.DataContext as DockItemViewModel;
|
||||
if (item is not null)
|
||||
// Ignore right-clicks when in edit mode
|
||||
if (IsEditMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender is DockItemControl dockItem && dockItem.DataContext is DockItemViewModel item)
|
||||
{
|
||||
if (item.HasMoreCommands)
|
||||
{
|
||||
ContextControl.ViewModel.SelectedItem = item;
|
||||
ContextMenuFlyout.ShowAt(
|
||||
button,
|
||||
new FlyoutShowOptions()
|
||||
{
|
||||
ShowMode = FlyoutShowMode.Standard,
|
||||
Placement = FlyoutPlacementMode.TopEdgeAlignedRight,
|
||||
});
|
||||
dockItem,
|
||||
new FlyoutShowOptions()
|
||||
{
|
||||
ShowMode = FlyoutShowMode.Standard,
|
||||
Placement = FlyoutPlacementMode.TopEdgeAlignedRight,
|
||||
});
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InvokeItem(DockItemViewModel item, global::Windows.Foundation.Point pos)
|
||||
private void InvokeItem(DockItemViewModel item, Point pos)
|
||||
{
|
||||
var command = item.Command;
|
||||
try
|
||||
@@ -236,7 +352,7 @@ public sealed partial class DockControl : UserControl, INotifyPropertyChanged, I
|
||||
}
|
||||
|
||||
var requestedTheme = ActualTheme;
|
||||
var isLight = requestedTheme == Microsoft.UI.Xaml.ElementTheme.Light;
|
||||
var isLight = requestedTheme == ElementTheme.Light;
|
||||
|
||||
// Check if any of the items have both an icon and a label.
|
||||
//
|
||||
@@ -256,26 +372,118 @@ public sealed partial class DockControl : UserControl, INotifyPropertyChanged, I
|
||||
|
||||
return HorizontalAlignment.Center;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class BandAlignmentConverter : Microsoft.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
public DockControl? Control { get; set; }
|
||||
private DockBandViewModel? _draggedBand;
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
private void BandListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
|
||||
{
|
||||
if (value is ObservableCollection<DockItemViewModel> items && Control is not null)
|
||||
if (e.Items.Count > 0 && e.Items[0] is DockBandViewModel band)
|
||||
{
|
||||
return Control.GetBandAlignment(items);
|
||||
_draggedBand = band;
|
||||
e.Data.RequestedOperation = DataPackageOperation.Move;
|
||||
}
|
||||
}
|
||||
|
||||
private void BandListView_DragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
if (_draggedBand != null)
|
||||
{
|
||||
e.AcceptedOperation = DataPackageOperation.Move;
|
||||
}
|
||||
}
|
||||
|
||||
private void BandListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
||||
{
|
||||
// Reordering within the same list is handled automatically by ListView
|
||||
// We just need to sync the ViewModel order without saving
|
||||
if (args.DropResult == DataPackageOperation.Move && _draggedBand != null)
|
||||
{
|
||||
var isStartList = sender == StartItemsListView;
|
||||
var targetSide = isStartList ? DockPinSide.Start : DockPinSide.End;
|
||||
var targetCollection = isStartList ? ViewModel.StartItems : ViewModel.EndItems;
|
||||
|
||||
// Find the new index and sync ViewModel (without saving)
|
||||
var newIndex = targetCollection.IndexOf(_draggedBand);
|
||||
if (newIndex >= 0)
|
||||
{
|
||||
ViewModel.SyncBandPosition(_draggedBand, targetSide, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return HorizontalAlignment.Center;
|
||||
_draggedBand = null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
private void StartItemsListView_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
HandleCrossListDrop(DockPinSide.Start, e);
|
||||
}
|
||||
|
||||
private void EndItemsListView_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
HandleCrossListDrop(DockPinSide.End, e);
|
||||
}
|
||||
|
||||
private void HandleCrossListDrop(DockPinSide targetSide, DragEventArgs e)
|
||||
{
|
||||
if (_draggedBand == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a cross-list drop (dragging from the other list)
|
||||
var isInStart = ViewModel.StartItems.Contains(_draggedBand);
|
||||
var isInEnd = ViewModel.EndItems.Contains(_draggedBand);
|
||||
|
||||
var sourceIsStart = isInStart;
|
||||
var targetIsStart = targetSide == DockPinSide.Start;
|
||||
|
||||
// Only handle cross-list drops here; same-list reorders are handled in DragItemsCompleted
|
||||
if (sourceIsStart != targetIsStart)
|
||||
{
|
||||
// Calculate drop index based on drop position
|
||||
var targetListView = targetIsStart ? StartItemsListView : EndItemsListView;
|
||||
var targetCollection = targetIsStart ? ViewModel.StartItems : ViewModel.EndItems;
|
||||
|
||||
var dropIndex = GetDropIndex(targetListView, e, targetCollection.Count);
|
||||
|
||||
// Move the band to the new side (without saving - save happens on Done)
|
||||
ViewModel.MoveBandWithoutSaving(_draggedBand, targetSide, dropIndex);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetDropIndex(ListView listView, DragEventArgs e, int itemCount)
|
||||
{
|
||||
var position = e.GetPosition(listView);
|
||||
|
||||
// Find the item at the drop position
|
||||
for (var i = 0; i < itemCount; i++)
|
||||
{
|
||||
if (listView.ContainerFromIndex(i) is ListViewItem container)
|
||||
{
|
||||
var itemBounds = container.TransformToVisual(listView).TransformBounds(
|
||||
new Rect(0, 0, container.ActualWidth, container.ActualHeight));
|
||||
|
||||
if (ItemsOrientation == Orientation.Horizontal)
|
||||
{
|
||||
// For horizontal layout, check X position
|
||||
if (position.X < itemBounds.X + (itemBounds.Width / 2))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For vertical layout, check Y position
|
||||
if (position.Y < itemBounds.Y + (itemBounds.Height / 2))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're past all items, insert at the end
|
||||
return itemCount;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
|
||||
137
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockItemControl.xaml
Normal file
137
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockItemControl.xaml
Normal file
@@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI.Dock">
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<SolidColorBrush x:Key="DockItemBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="DockItemBorderBrush" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="DockItemBackgroundPointerOver" Color="#0FFFFFFF" />
|
||||
<SolidColorBrush x:Key="DockItemBackgroundPressed" Color="#0BFFFFFF" />
|
||||
<LinearGradientBrush x:Key="DockItemBorderBrushPointerOver" MappingMode="Absolute" StartPoint="0,0" EndPoint="0,3">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Offset="0.33" Color="#0FFFFFFF" />
|
||||
<GradientStop Offset="1.0" Color="#19FFFFFF" />
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="DockItemBorderBrushPressed" Color="#0BFFFFFF" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="DockItemBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="DockItemBorderBrush" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="DockItemBackgroundPointerOver" Color="#80FFFFFF" />
|
||||
<SolidColorBrush x:Key="DockItemBackgroundPressed" Color="#4DFFFFFF" />
|
||||
<LinearGradientBrush x:Key="DockItemBorderBrushPointerOver" MappingMode="Absolute" StartPoint="0,0" EndPoint="0,3">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Offset="0.33" Color="#08000000" />
|
||||
<GradientStop Offset="1.0" Color="#17000000" />
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="DockItemBorderBrushPressed" Color="#05000000" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="DockItemBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="DockItemBorderBrush" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="DockItemBackgroundPointerOver" Color="{StaticResource SystemColorHighlightTextColor}" />
|
||||
<SolidColorBrush x:Key="DockItemBackgroundPressed" Color="{StaticResource SystemColorHighlightTextColor}" />
|
||||
<SolidColorBrush x:Key="DockItemBorderBrushPointerOver" Color="{StaticResource SystemColorHighlightColor}" />
|
||||
<SolidColorBrush x:Key="DockItemBorderBrushPressed" Color="{StaticResource SystemColorHighlightTextColor}" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultDockItemControlStyle}" TargetType="local:DockItemControl" />
|
||||
|
||||
<Style x:Key="DefaultDockItemControlStyle" TargetType="local:DockItemControl">
|
||||
<Style.Setters>
|
||||
<Setter Property="Background" Value="{ThemeResource DockItemBackground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource DockItemBorderBrush}" />
|
||||
<Setter Property="Padding" Value="4,2,4,2" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:DockItemControl">
|
||||
<Grid
|
||||
x:Name="PART_RootGrid"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
ToolTipService.ToolTip="{TemplateBinding ToolTip}">
|
||||
<Grid AutomationProperties.Name="{TemplateBinding Title}" Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<ContentPresenter
|
||||
x:Name="IconPresenter"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding Icon}" />
|
||||
|
||||
<!-- Text (Title + Subtitle) -->
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="8,0,8,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
x:Name="TitleText"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Text="{TemplateBinding Title}"
|
||||
TextAlignment="Left"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
x:Name="SubtitleText"
|
||||
MaxWidth="100"
|
||||
Margin="0,-4,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorTertiary}"
|
||||
Text="{TemplateBinding Subtitle}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_RootGrid.Background" Value="{ThemeResource DockItemBackgroundPointerOver}" />
|
||||
<Setter Target="PART_RootGrid.BorderBrush" Value="{ThemeResource DockItemBorderBrushPointerOver}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_RootGrid.Background" Value="{ThemeResource DockItemBackgroundPointerOver}" />
|
||||
<Setter Target="PART_RootGrid.BorderBrush" Value="{ThemeResource DockItemBorderBrushPressed}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_RootGrid.Background" Value="Transparent" />
|
||||
<Setter Target="PART_RootGrid.BorderBrush" Value="Transparent" />
|
||||
<Setter Target="IconPresenter.Foreground" Value="{ThemeResource ButtonForegroundDisabled}" />
|
||||
<Setter Target="TitleText.Foreground" Value="{ThemeResource ButtonForegroundDisabled}" />
|
||||
<Setter Target="SubtitleText.Foreground" Value="{ThemeResource ButtonForegroundDisabled}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,151 @@
|
||||
// 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 Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Dock;
|
||||
|
||||
[ContentProperty(Name = nameof(Icon))]
|
||||
public sealed partial class DockItemControl : Control
|
||||
{
|
||||
public DockItemControl()
|
||||
{
|
||||
DefaultStyleKey = typeof(DockItemControl);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ToolTipProperty =
|
||||
DependencyProperty.Register(nameof(ToolTip), typeof(string), typeof(DockItemControl), new PropertyMetadata(null));
|
||||
|
||||
public string ToolTip
|
||||
{
|
||||
get => (string)GetValue(ToolTipProperty);
|
||||
set => SetValue(ToolTipProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TitleProperty =
|
||||
DependencyProperty.Register(nameof(Title), typeof(string), typeof(DockItemControl), new PropertyMetadata(null, OnTextPropertyChanged));
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => (string)GetValue(TitleProperty);
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SubtitleProperty =
|
||||
DependencyProperty.Register(nameof(Subtitle), typeof(string), typeof(DockItemControl), new PropertyMetadata(null, OnTextPropertyChanged));
|
||||
|
||||
public string Subtitle
|
||||
{
|
||||
get => (string)GetValue(SubtitleProperty);
|
||||
set => SetValue(SubtitleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IconProperty =
|
||||
DependencyProperty.Register(nameof(Icon), typeof(object), typeof(DockItemControl), new PropertyMetadata(null, OnIconPropertyChanged));
|
||||
|
||||
public object Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
private const string IconPresenterName = "IconPresenter";
|
||||
private const string TitleTextName = "TitleText";
|
||||
private const string SubtitleTextName = "SubtitleText";
|
||||
|
||||
private FrameworkElement? _iconPresenter;
|
||||
private FrameworkElement? _titleText;
|
||||
private FrameworkElement? _subtitleText;
|
||||
|
||||
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DockItemControl control)
|
||||
{
|
||||
control.UpdateTextVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIconPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DockItemControl control)
|
||||
{
|
||||
control.UpdateIconVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNullOrEmpty(string? value) => string.IsNullOrEmpty(value);
|
||||
|
||||
private void UpdateTextVisibility()
|
||||
{
|
||||
if (_titleText is not null)
|
||||
{
|
||||
_titleText.Visibility = IsNullOrEmpty(Title) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
if (_subtitleText is not null)
|
||||
{
|
||||
_subtitleText.Visibility = IsNullOrEmpty(Subtitle) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIconVisibility()
|
||||
{
|
||||
if (_iconPresenter is not null)
|
||||
{
|
||||
_iconPresenter.Visibility = Icon is null ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAllVisibility()
|
||||
{
|
||||
UpdateTextVisibility();
|
||||
UpdateIconVisibility();
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
IsEnabledChanged -= OnIsEnabledChanged;
|
||||
|
||||
PointerEntered += Control_PointerEntered;
|
||||
PointerExited += Control_PointerExited;
|
||||
|
||||
IsEnabledChanged += OnIsEnabledChanged;
|
||||
|
||||
// Get template children for visibility updates
|
||||
_iconPresenter = GetTemplateChild(IconPresenterName) as FrameworkElement;
|
||||
_titleText = GetTemplateChild(TitleTextName) as FrameworkElement;
|
||||
_subtitleText = GetTemplateChild(SubtitleTextName) as FrameworkElement;
|
||||
|
||||
// Set initial visibility
|
||||
UpdateAllVisibility();
|
||||
}
|
||||
|
||||
private void Control_PointerEntered(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "PointerOver", true);
|
||||
}
|
||||
|
||||
private void Control_PointerExited(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Normal", true);
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerRoutedEventArgs e)
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
base.OnPointerPressed(e);
|
||||
VisualStateManager.GoToState(this, "Pressed", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- For debugging purposes, uncomment this block to enable AOT builds -->
|
||||
<PropertyGroup>
|
||||
<!-- <PropertyGroup>
|
||||
<EnableCmdPalAOT>true</EnableCmdPalAOT>
|
||||
<GeneratePackageLocally>true</GeneratePackageLocally>
|
||||
</PropertyGroup>
|
||||
@@ -40,7 +40,7 @@
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
<DisableRuntimeMarshalling>false</DisableRuntimeMarshalling>
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
</PropertyGroup> -->
|
||||
|
||||
<PropertyGroup Condition="'$(CIBuild)' == 'true' or '$(GeneratePackageLocally)' == 'true'">
|
||||
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
|
||||
@@ -79,6 +79,7 @@
|
||||
<None Remove="Controls\ScreenPreview.xaml" />
|
||||
<None Remove="Controls\ScrollContainer.xaml" />
|
||||
<None Remove="Controls\SearchBar.xaml" />
|
||||
<None Remove="Dock\DockItemControl.xaml" />
|
||||
<None Remove="IsEnabledTextBlock.xaml" />
|
||||
<None Remove="ListDetailPage.xaml" />
|
||||
<None Remove="LoadingPage.xaml" />
|
||||
@@ -220,6 +221,12 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Dock\DockItemControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\ScrollContainer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
<RepositionThemeTransition IsStaggeringEnabled="False" />
|
||||
</StackPanel.ChildrenTransitions>-->
|
||||
|
||||
<!-- Enable Dock -->
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_EnableDock_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.EnableDock, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Appearance Section -->
|
||||
<TextBlock x:Uid="DockAppearanceSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
|
||||
@@ -85,10 +85,6 @@
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.ShowSystemTrayIcon, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_EnableDock_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.EnableDock, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- 'For Developers' section -->
|
||||
|
||||
<TextBlock x:Uid="ForDevelopersSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
@@ -390,7 +390,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<value>Extensions</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_NavigationViewItem_Dock.Content" xml:space="preserve">
|
||||
<value>Dock</value>
|
||||
<value>Dock (Preview)</value>
|
||||
</data>
|
||||
<data name="SettingsButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Open Command Palette settings</value>
|
||||
|
||||
Reference in New Issue
Block a user