mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 02:36:19 +02:00
CmdPal: Add a dock (#45824)
Add support for a "dock" window in CmdPal. The dock is a toolbar powered by the `APPBAR` APIs. This gives you a persistent region to display commands for quick shortcuts or glanceable widgets. The dock can be pinned to any side of the screen. The dock can be independently styled with any of the theming controls cmdpal already has The dock has three "regions" to pin to - the "start", the "center", and the "end". Elements on the dock are grouped as "bands", which contains a set of "items". Each "band" is one atomic unit. For example, the Media Player extension produces 4 items, but one _band_. The dock has only one size (for now) The dock will only appear on your primary display (for now) This PR includes support for pinning arbitrary top-level commands to the dock - however, we're planning on replacing that with a more universal ability to pin any command to the dock or top level. (see #45191). This is at least usable for now. This is definitely still _even more preview_ than usual PowerToys features, but it's more than usable. I'd love to get it out there and start collecting feedback on where to improve next. I'll probably add a follow-up issue for tracking the remaining bugs & nits. closes #45201 --------- Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI"
|
||||
xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls"
|
||||
xmlns:services="using:Microsoft.CmdPal.UI.Services">
|
||||
@@ -12,7 +13,7 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
<ResourceDictionary Source="ms-appx:///Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Styles/TeachingTip.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Styles/TextBlock.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Styles/TextBox.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Styles/Settings.xaml" />
|
||||
@@ -21,6 +22,7 @@
|
||||
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/KeyVisual/KeyCharPresenter.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Dock/DockItemControl.xaml" />
|
||||
<!-- Default theme dictionary -->
|
||||
<ResourceDictionary Source="ms-appx:///Styles/Theme.Normal.xaml" />
|
||||
<services:MutableOverridesDictionary />
|
||||
@@ -29,6 +31,12 @@
|
||||
|
||||
<x:Double x:Key="SettingActionControlMinWidth">240</x:Double>
|
||||
<Style BasedOn="{StaticResource DefaultCheckBoxStyle}" TargetType="ptcontrols:CheckBoxWithDescriptionControl" />
|
||||
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringNotEmptyToVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
NotEmptyValue="Visible" />
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -28,6 +28,7 @@ using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -220,6 +221,7 @@ public partial class App : Application, IDisposable
|
||||
|
||||
// ViewModels
|
||||
services.AddSingleton<ShellViewModel>();
|
||||
services.AddSingleton<DockViewModel>();
|
||||
services.AddSingleton<IContextMenuFactory, CommandPaletteContextMenuFactory>();
|
||||
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
|
||||
@@ -23,22 +24,45 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
_topLevelCommandManager = topLevelCommandManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the view models for the MoreCommands of a
|
||||
/// CommandItemViewModel. In our case, we can use our settings to add a
|
||||
/// contextually-relevant pin/unpin command to this item.
|
||||
///
|
||||
/// This is called on all CommandItemViewModels. There are however some
|
||||
/// weird edge cases we need to handle, concerning
|
||||
/// </summary>
|
||||
public List<IContextItemViewModel> UnsafeBuildAndInitMoreCommands(
|
||||
IContextItem[] items,
|
||||
CommandItemViewModel commandItem)
|
||||
{
|
||||
var results = DefaultContextMenuFactory.Instance.UnsafeBuildAndInitMoreCommands(items, commandItem);
|
||||
|
||||
IPageContext? page = null;
|
||||
var succeeded = commandItem.PageContext.TryGetTarget(out page);
|
||||
if (!succeeded || page is null)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
var isTopLevelItem = page is TopLevelItemPageContext;
|
||||
if (isTopLevelItem)
|
||||
{
|
||||
// Bail early. We'll handle it below.
|
||||
return results;
|
||||
}
|
||||
|
||||
List<IContextItem> moreCommands = [];
|
||||
var itemId = commandItem.Command.Id;
|
||||
var providerContext = page.ProviderContext;
|
||||
var supportsPinning = providerContext.SupportsPinning;
|
||||
|
||||
if (commandItem.PageContext.TryGetTarget(out var page) &&
|
||||
page.ProviderContext.SupportsPinning &&
|
||||
if (supportsPinning &&
|
||||
!string.IsNullOrEmpty(itemId))
|
||||
{
|
||||
// Add pin/unpin commands for pinning items to the top-level or to
|
||||
// the dock.
|
||||
var providerId = page.ProviderContext.ProviderId;
|
||||
var providerId = providerContext.ProviderId;
|
||||
if (_topLevelCommandManager.LookupProvider(providerId) is CommandProviderWrapper provider)
|
||||
{
|
||||
var providerSettings = _settingsModel.GetProviderSettings(provider);
|
||||
@@ -51,8 +75,6 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
// We can't look up if this command item is in the top level
|
||||
// items in the manager, because we are being called _before_ we
|
||||
// get added to the manager's list of commands.
|
||||
var isTopLevelItem = page is TopLevelItemPageContext;
|
||||
|
||||
if (!isTopLevelItem || alreadyPinnedToTopLevel)
|
||||
{
|
||||
var pinToTopLevelCommand = new PinToCommand(
|
||||
@@ -66,6 +88,8 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
var contextItem = new PinToContextItem(pinToTopLevelCommand, commandItem);
|
||||
moreCommands.Add(contextItem);
|
||||
}
|
||||
|
||||
TryAddPinToDockCommand(providerSettings, itemId, providerId, moreCommands, commandItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +103,99 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to create the context menu on TopLevelViewModels.
|
||||
///
|
||||
/// These are handled differently from everyone else. With
|
||||
/// TopLevelViewModels, the ID isn't on the Command, it is on the
|
||||
/// TopLevelViewModel itself. Basically, we can't figure out how to add
|
||||
/// pin/unpin commands directly attached to the ICommandItems that we get
|
||||
/// from the API.
|
||||
///
|
||||
/// Instead, this method is used to extend the set of IContextItems that are
|
||||
/// added to the TopLevelViewModel itself. This lets us pin/unpin the
|
||||
/// generated ID of the TopLevelViewModel, even if the command didn't have
|
||||
/// one.
|
||||
/// </summary>
|
||||
public void AddMoreCommandsToTopLevel(
|
||||
TopLevelViewModel topLevelItem,
|
||||
ICommandProviderContext providerContext,
|
||||
List<IContextItem?> contextItems)
|
||||
{
|
||||
var itemId = topLevelItem.Id;
|
||||
var supportsPinning = providerContext.SupportsPinning;
|
||||
List<IContextItem> moreCommands = [];
|
||||
var commandItem = topLevelItem.ItemViewModel;
|
||||
|
||||
// Add pin/unpin commands for pinning items to the top-level or to
|
||||
// the dock.
|
||||
var providerId = providerContext.ProviderId;
|
||||
if (_topLevelCommandManager.LookupProvider(providerId) is CommandProviderWrapper provider)
|
||||
{
|
||||
var providerSettings = _settingsModel.GetProviderSettings(provider);
|
||||
|
||||
var isPinnedSubCommand = providerSettings.PinnedCommandIds.Contains(itemId);
|
||||
if (isPinnedSubCommand)
|
||||
{
|
||||
var pinToTopLevelCommand = new PinToCommand(
|
||||
commandId: itemId,
|
||||
providerId: providerId,
|
||||
pin: !isPinnedSubCommand,
|
||||
PinLocation.TopLevel,
|
||||
_settingsModel,
|
||||
_topLevelCommandManager);
|
||||
|
||||
var contextItem = new PinToContextItem(pinToTopLevelCommand, commandItem);
|
||||
moreCommands.Add(contextItem);
|
||||
}
|
||||
|
||||
TryAddPinToDockCommand(providerSettings, itemId, providerId, moreCommands, commandItem);
|
||||
}
|
||||
|
||||
if (moreCommands.Count > 0)
|
||||
{
|
||||
moreCommands.Insert(0, new Separator());
|
||||
|
||||
// var moreResults = DefaultContextMenuFactory.Instance.UnsafeBuildAndInitMoreCommands(moreCommands.ToArray(), commandItem);
|
||||
contextItems.AddRange(moreCommands);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryAddPinToDockCommand(
|
||||
ProviderSettings providerSettings,
|
||||
string itemId,
|
||||
string providerId,
|
||||
List<IContextItem> moreCommands,
|
||||
CommandItemViewModel commandItem)
|
||||
{
|
||||
if (!_settingsModel.EnableDock)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var inStartBands = _settingsModel.DockSettings.StartBands.Any(band => MatchesBand(band, itemId, providerId));
|
||||
var inCenterBands = _settingsModel.DockSettings.CenterBands.Any(band => MatchesBand(band, itemId, providerId));
|
||||
var inEndBands = _settingsModel.DockSettings.EndBands.Any(band => MatchesBand(band, itemId, providerId));
|
||||
var alreadyPinned = inStartBands || inCenterBands || inEndBands; /** &&
|
||||
_settingsModel.DockSettings.PinnedCommands.Contains(this.Id)**/
|
||||
var pinToTopLevelCommand = new PinToCommand(
|
||||
commandId: itemId,
|
||||
providerId: providerId,
|
||||
pin: !alreadyPinned,
|
||||
PinLocation.Dock,
|
||||
_settingsModel,
|
||||
_topLevelCommandManager);
|
||||
|
||||
var contextItem = new PinToContextItem(pinToTopLevelCommand, commandItem);
|
||||
moreCommands.Add(contextItem);
|
||||
}
|
||||
|
||||
internal static bool MatchesBand(DockBandSettings bandSettings, string commandId, string providerId)
|
||||
{
|
||||
return bandSettings.CommandId == commandId &&
|
||||
bandSettings.ProviderId == providerId;
|
||||
}
|
||||
|
||||
internal enum PinLocation
|
||||
{
|
||||
TopLevel,
|
||||
@@ -119,9 +236,13 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
private readonly bool _pin;
|
||||
private readonly PinLocation _pinLocation;
|
||||
|
||||
private bool IsPinToDock => _pinLocation == PinLocation.Dock;
|
||||
|
||||
public override IconInfo Icon => _pin ? Icons.PinIcon : Icons.UnpinIcon;
|
||||
|
||||
public override string Name => _pin ? RS_.GetString("top_level_pin_command_name") : RS_.GetString("top_level_unpin_command_name");
|
||||
public override string Name => _pin ?
|
||||
(IsPinToDock ? RS_.GetString("dock_pin_command_name") : RS_.GetString("top_level_pin_command_name")) :
|
||||
(IsPinToDock ? RS_.GetString("dock_unpin_command_name") : RS_.GetString("top_level_unpin_command_name"));
|
||||
|
||||
internal event EventHandler? PinStateChanged;
|
||||
|
||||
@@ -152,10 +273,9 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
PinToTopLevel();
|
||||
break;
|
||||
|
||||
// TODO: After dock is added:
|
||||
// case PinLocation.Dock:
|
||||
// PinToDock();
|
||||
// break;
|
||||
case PinLocation.Dock:
|
||||
PinToDock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -166,9 +286,9 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
UnpinFromTopLevel();
|
||||
break;
|
||||
|
||||
// case PinLocation.Dock:
|
||||
// UnpinFromDock();
|
||||
// break;
|
||||
case PinLocation.Dock:
|
||||
UnpinFromDock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,5 +308,17 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
UnpinCommandItemMessage message = new(_providerId, _commandId);
|
||||
WeakReferenceMessenger.Default.Send(message);
|
||||
}
|
||||
|
||||
private void PinToDock()
|
||||
{
|
||||
PinToDockMessage message = new(_providerId, _commandId, true);
|
||||
WeakReferenceMessenger.Default.Send(message);
|
||||
}
|
||||
|
||||
private void UnpinFromDock()
|
||||
{
|
||||
PinToDockMessage message = new(_providerId, _commandId, false);
|
||||
WeakReferenceMessenger.Default.Send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<Grid
|
||||
x:Name="IconRoot"
|
||||
Margin="3,0,-5,0"
|
||||
Visibility="{x:Bind CurrentPageViewModel.IsNested, Mode=OneWay}">
|
||||
Visibility="{x:Bind CurrentPageViewModel.IsRootPage, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
|
||||
<Button
|
||||
x:Name="StatusMessagesButton"
|
||||
x:Uid="StatusMessagesButton"
|
||||
@@ -134,7 +134,7 @@
|
||||
x:Uid="SettingsButton"
|
||||
Click="SettingsIcon_Clicked"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="{x:Bind CurrentPageViewModel.IsNested, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
|
||||
Visibility="{x:Bind CurrentPageViewModel.IsRootPage, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon
|
||||
VerticalAlignment="Center"
|
||||
@@ -153,7 +153,7 @@
|
||||
Text="{x:Bind CurrentPageViewModel.Title, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind CurrentPageViewModel.IsNested, Mode=OneWay}" />
|
||||
Visibility="{x:Bind CurrentPageViewModel.IsRootPage, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
Padding="0,0,4,0"
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CmdPal.UI.Controls.ScrollContainer"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<Style x:Key="ScrollButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{ThemeResource FlipViewNextPreviousButtonBackground}" />
|
||||
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource FlipViewNextPreviousButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AnimatedIcon.State="Normal"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<ContentPresenter.BackgroundTransition>
|
||||
<BrushTransition Duration="0:0:0.083" />
|
||||
</ContentPresenter.BackgroundTransition>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource FlipViewNextPreviousButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource FlipViewNextPreviousButtonBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource FlipViewNextPreviousArrowForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource FlipViewNextPreviousButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource FlipViewNextPreviousButtonBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource FlipViewNextPreviousArrowForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
|
||||
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</ContentPresenter>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
Background="{x:Bind Background, Mode=OneWay}"
|
||||
BorderBrush="{x:Bind BorderBrush, Mode=OneWay}"
|
||||
BorderThickness="{x:Bind BorderThickness, Mode=OneWay}"
|
||||
CornerRadius="{x:Bind CornerRadius, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition x:Name="Row2" Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Action button - position controlled by visual states -->
|
||||
<ContentPresenter
|
||||
x:Name="ActionButtonPresenter"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Margin="4,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Content="{x:Bind ActionButton, Mode=OneWay}"
|
||||
Visibility="{x:Bind ActionButtonVisibility, Mode=OneWay}" />
|
||||
|
||||
<Grid
|
||||
x:Name="ScrollerContainer"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1">
|
||||
<ScrollViewer
|
||||
x:Name="scroller"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
HorizontalScrollMode="Enabled"
|
||||
SizeChanged="Scroller_SizeChanged"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
VerticalScrollMode="Disabled"
|
||||
ViewChanging="Scroller_ViewChanging">
|
||||
<Grid x:Name="ContentGrid">
|
||||
<ContentPresenter Content="{x:Bind Source, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
<Button
|
||||
x:Name="ScrollBackBtn"
|
||||
Margin="8,0,0,0"
|
||||
Padding="2,8,2,8"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="Scroll left"
|
||||
Click="ScrollBackBtn_Click"
|
||||
Style="{StaticResource ScrollButtonStyle}"
|
||||
ToolTipService.ToolTip="Scroll left"
|
||||
Visibility="Collapsed">
|
||||
<FontIcon
|
||||
x:Name="ScrollBackIcon"
|
||||
FontSize="{ThemeResource FlipViewButtonFontSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="ScrollForwardBtn"
|
||||
Margin="0,0,8,0"
|
||||
Padding="2,8,2,8"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="Scroll right"
|
||||
Click="ScrollForwardBtn_Click"
|
||||
Style="{StaticResource ScrollButtonStyle}"
|
||||
ToolTipService.ToolTip="Scroll right">
|
||||
<FontIcon
|
||||
x:Name="ScrollForwardIcon"
|
||||
FontSize="{ThemeResource FlipViewButtonFontSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="OrientationStates">
|
||||
<VisualState x:Name="HorizontalState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="scroller.HorizontalScrollBarVisibility" Value="Hidden" />
|
||||
<Setter Target="scroller.HorizontalScrollMode" Value="Enabled" />
|
||||
<Setter Target="scroller.VerticalScrollBarVisibility" Value="Hidden" />
|
||||
<Setter Target="scroller.VerticalScrollMode" Value="Disabled" />
|
||||
<Setter Target="ScrollBackBtn.Padding" Value="4,12,4,12" />
|
||||
<Setter Target="ScrollBackBtn.Margin" Value="8,0,0,0" />
|
||||
<Setter Target="ScrollBackBtn.HorizontalAlignment" Value="Left" />
|
||||
<Setter Target="ScrollBackBtn.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="ScrollBackBtn.(AutomationProperties.Name)" Value="Scroll left" />
|
||||
<Setter Target="ScrollBackBtn.(ToolTipService.ToolTip)" Value="Scroll left" />
|
||||
<Setter Target="ScrollBackIcon.Glyph" Value="" />
|
||||
<Setter Target="ScrollForwardBtn.Padding" Value="4,12,4,12" />
|
||||
<Setter Target="ScrollForwardBtn.Margin" Value="0,0,8,0" />
|
||||
<Setter Target="ScrollForwardBtn.HorizontalAlignment" Value="Right" />
|
||||
<Setter Target="ScrollForwardBtn.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="ScrollForwardBtn.(AutomationProperties.Name)" Value="Scroll right" />
|
||||
<Setter Target="ScrollForwardBtn.(ToolTipService.ToolTip)" Value="Scroll right" />
|
||||
<Setter Target="ScrollForwardIcon.Glyph" Value="" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="VerticalState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="scroller.HorizontalScrollBarVisibility" Value="Hidden" />
|
||||
<Setter Target="scroller.HorizontalScrollMode" Value="Disabled" />
|
||||
<Setter Target="scroller.VerticalScrollBarVisibility" Value="Hidden" />
|
||||
<Setter Target="scroller.VerticalScrollMode" Value="Enabled" />
|
||||
<Setter Target="ScrollBackBtn.Padding" Value="12,4,12,4" />
|
||||
<Setter Target="ScrollBackBtn.Margin" Value="0,8,0,0" />
|
||||
<Setter Target="ScrollBackBtn.HorizontalAlignment" Value="Center" />
|
||||
<Setter Target="ScrollBackBtn.VerticalAlignment" Value="Top" />
|
||||
<Setter Target="ScrollBackBtn.(AutomationProperties.Name)" Value="Scroll up" />
|
||||
<Setter Target="ScrollBackBtn.(ToolTipService.ToolTip)" Value="Scroll up" />
|
||||
<Setter Target="ScrollBackIcon.Glyph" Value="" />
|
||||
<Setter Target="ScrollForwardBtn.Padding" Value="12,4,12,4" />
|
||||
<Setter Target="ScrollForwardBtn.Margin" Value="0,0,0,8" />
|
||||
<Setter Target="ScrollForwardBtn.HorizontalAlignment" Value="Center" />
|
||||
<Setter Target="ScrollForwardBtn.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="ScrollForwardBtn.(AutomationProperties.Name)" Value="Scroll down" />
|
||||
<Setter Target="ScrollForwardBtn.(ToolTipService.ToolTip)" Value="Scroll down" />
|
||||
<Setter Target="ScrollForwardIcon.Glyph" Value="" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="LayoutStates">
|
||||
<!-- Horizontal + Start: button on right -->
|
||||
<VisualState x:Name="HorizontalStartState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ScrollerContainer.(Grid.Row)" Value="0" />
|
||||
<Setter Target="ScrollerContainer.(Grid.RowSpan)" Value="3" />
|
||||
<Setter Target="ScrollerContainer.(Grid.Column)" Value="1" />
|
||||
<Setter Target="ScrollerContainer.(Grid.ColumnSpan)" Value="1" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.Row)" Value="0" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.RowSpan)" Value="3" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.Column)" Value="2" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.ColumnSpan)" Value="1" />
|
||||
<Setter Target="ActionButtonPresenter.Margin" Value="4,0,0,0" />
|
||||
<Setter Target="ActionButtonPresenter.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="ActionButtonPresenter.VerticalAlignment" Value="Center" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<!-- Horizontal + End: button on left -->
|
||||
<VisualState x:Name="HorizontalEndState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ScrollerContainer.(Grid.Row)" Value="0" />
|
||||
<Setter Target="ScrollerContainer.(Grid.RowSpan)" Value="3" />
|
||||
<Setter Target="ScrollerContainer.(Grid.Column)" Value="1" />
|
||||
<Setter Target="ScrollerContainer.(Grid.ColumnSpan)" Value="1" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.Row)" Value="0" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.RowSpan)" Value="3" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.Column)" Value="0" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.ColumnSpan)" Value="1" />
|
||||
<Setter Target="ActionButtonPresenter.Margin" Value="0,0,4,0" />
|
||||
<Setter Target="ActionButtonPresenter.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="ActionButtonPresenter.VerticalAlignment" Value="Center" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<!-- Vertical + Start: button on bottom -->
|
||||
<VisualState x:Name="VerticalStartState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ScrollerContainer.(Grid.Row)" Value="1" />
|
||||
<Setter Target="ScrollerContainer.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="ScrollerContainer.(Grid.Column)" Value="0" />
|
||||
<Setter Target="ScrollerContainer.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.Row)" Value="2" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.Column)" Value="0" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="ActionButtonPresenter.Margin" Value="0,4,0,0" />
|
||||
<Setter Target="ActionButtonPresenter.HorizontalAlignment" Value="Center" />
|
||||
<Setter Target="ActionButtonPresenter.VerticalAlignment" Value="Stretch" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<!-- Vertical + End: button on top -->
|
||||
<VisualState x:Name="VerticalEndState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ScrollerContainer.(Grid.Row)" Value="1" />
|
||||
<Setter Target="ScrollerContainer.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="ScrollerContainer.(Grid.Column)" Value="0" />
|
||||
<Setter Target="ScrollerContainer.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.Row)" Value="0" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.Column)" Value="0" />
|
||||
<Setter Target="ActionButtonPresenter.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="ActionButtonPresenter.Margin" Value="0,0,0,4" />
|
||||
<Setter Target="ActionButtonPresenter.HorizontalAlignment" Value="Center" />
|
||||
<Setter Target="ActionButtonPresenter.VerticalAlignment" Value="Stretch" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,222 @@
|
||||
// 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.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class ScrollContainer : UserControl
|
||||
{
|
||||
public enum ScrollContentAlignment
|
||||
{
|
||||
Start,
|
||||
End,
|
||||
}
|
||||
|
||||
public ScrollContainer()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += ScrollContainer_Loaded;
|
||||
}
|
||||
|
||||
private void ScrollContainer_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateOrientationState();
|
||||
UpdateLayoutState();
|
||||
}
|
||||
|
||||
public object Source
|
||||
{
|
||||
get => (object)GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SourceProperty =
|
||||
DependencyProperty.Register(nameof(Source), typeof(object), typeof(ScrollContainer), new PropertyMetadata(null));
|
||||
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => (Orientation)GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty OrientationProperty =
|
||||
DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(ScrollContainer), new PropertyMetadata(Orientation.Horizontal, OnOrientationChanged));
|
||||
|
||||
public ScrollContentAlignment ContentAlignment
|
||||
{
|
||||
get => (ScrollContentAlignment)GetValue(ContentAlignmentProperty);
|
||||
set => SetValue(ContentAlignmentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContentAlignmentProperty =
|
||||
DependencyProperty.Register(nameof(ContentAlignment), typeof(ScrollContentAlignment), typeof(ScrollContainer), new PropertyMetadata(ScrollContentAlignment.Start, OnContentAlignmentChanged));
|
||||
|
||||
public object ActionButton
|
||||
{
|
||||
get => (object)GetValue(ActionButtonProperty);
|
||||
set => SetValue(ActionButtonProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ActionButtonProperty =
|
||||
DependencyProperty.Register(nameof(ActionButton), typeof(object), typeof(ScrollContainer), new PropertyMetadata(null));
|
||||
|
||||
public Visibility ActionButtonVisibility
|
||||
{
|
||||
get => (Visibility)GetValue(ActionButtonVisibilityProperty);
|
||||
set => SetValue(ActionButtonVisibilityProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ActionButtonVisibilityProperty =
|
||||
DependencyProperty.Register(nameof(ActionButtonVisibility), typeof(Visibility), typeof(ScrollContainer), new PropertyMetadata(Visibility.Collapsed));
|
||||
|
||||
private static void OnContentAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollContainer control)
|
||||
{
|
||||
control.UpdateLayoutState();
|
||||
control.ScrollToAlignment();
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollToAlignment()
|
||||
{
|
||||
// Reset button visibility
|
||||
ScrollBackBtn.Visibility = Visibility.Collapsed;
|
||||
ScrollForwardBtn.Visibility = Visibility.Collapsed;
|
||||
|
||||
if (ContentAlignment == ScrollContentAlignment.End)
|
||||
{
|
||||
// Scroll to the end
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
scroller.ChangeView(scroller.ScrollableWidth, null, null, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
scroller.ChangeView(null, scroller.ScrollableHeight, null, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scroll to the beginning
|
||||
scroller.ChangeView(0, 0, null, true);
|
||||
}
|
||||
|
||||
// Defer visibility update until after layout
|
||||
void OnLayoutUpdated(object? sender, object args)
|
||||
{
|
||||
scroller.LayoutUpdated -= OnLayoutUpdated;
|
||||
UpdateScrollButtonsVisibility();
|
||||
}
|
||||
|
||||
scroller.LayoutUpdated += OnLayoutUpdated;
|
||||
}
|
||||
|
||||
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollContainer control)
|
||||
{
|
||||
control.UpdateOrientationState();
|
||||
control.UpdateLayoutState();
|
||||
control.ScrollToAlignment();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOrientationState()
|
||||
{
|
||||
var stateName = Orientation == Orientation.Horizontal ? "HorizontalState" : "VerticalState";
|
||||
VisualStateManager.GoToState(this, stateName, true);
|
||||
}
|
||||
|
||||
private void UpdateLayoutState()
|
||||
{
|
||||
var isHorizontal = Orientation == Orientation.Horizontal;
|
||||
var isStart = ContentAlignment == ScrollContentAlignment.Start;
|
||||
|
||||
var stateName = (isHorizontal, isStart) switch
|
||||
{
|
||||
(true, true) => "HorizontalStartState",
|
||||
(true, false) => "HorizontalEndState",
|
||||
(false, true) => "VerticalStartState",
|
||||
(false, false) => "VerticalEndState",
|
||||
};
|
||||
|
||||
VisualStateManager.GoToState(this, stateName, true);
|
||||
}
|
||||
|
||||
private void Scroller_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
|
||||
{
|
||||
UpdateScrollButtonsVisibility(e.FinalView.HorizontalOffset, e.FinalView.VerticalOffset);
|
||||
}
|
||||
|
||||
private void ScrollBackBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
scroller.ChangeView(scroller.HorizontalOffset - scroller.ViewportWidth, null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
scroller.ChangeView(null, scroller.VerticalOffset - scroller.ViewportHeight, null);
|
||||
}
|
||||
|
||||
// Manually focus to ScrollForwardBtn since this button disappears after scrolling to the end.
|
||||
ScrollForwardBtn.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
private void ScrollForwardBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
scroller.ChangeView(scroller.HorizontalOffset + scroller.ViewportWidth, null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
scroller.ChangeView(null, scroller.VerticalOffset + scroller.ViewportHeight, null);
|
||||
}
|
||||
|
||||
// Manually focus to ScrollBackBtn since this button disappears after scrolling to the end.
|
||||
ScrollBackBtn.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
private void Scroller_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateScrollButtonsVisibility();
|
||||
}
|
||||
|
||||
private void UpdateScrollButtonsVisibility(double? horizontalOffset = null, double? verticalOffset = null)
|
||||
{
|
||||
var hOffset = horizontalOffset ?? scroller.HorizontalOffset;
|
||||
var vOffset = verticalOffset ?? scroller.VerticalOffset;
|
||||
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
ScrollBackBtn.Visibility = hOffset > 1 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ScrollForwardBtn.Visibility = scroller.ScrollableWidth > 0 && hOffset < scroller.ScrollableWidth - 1
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScrollBackBtn.Visibility = vOffset > 1 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ScrollForwardBtn.Visibility = scroller.ScrollableHeight > 0 && vOffset < scroller.ScrollableHeight - 1
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
449
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockControl.xaml
Normal file
449
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockControl.xaml
Normal file
@@ -0,0 +1,449 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CmdPal.UI.Dock.DockControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:coreVm="using:Microsoft.CmdPal.Core.ViewModels"
|
||||
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dockVm="using:Microsoft.CmdPal.UI.ViewModels.Dock"
|
||||
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI.Dock"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:vm="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<StackLayout
|
||||
x:Key="ItemsOrientationLayout"
|
||||
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>
|
||||
|
||||
<DataTemplate x:Key="DockBandTemplate" x:DataType="dockVm:DockBandViewModel">
|
||||
<ItemsRepeater ItemsSource="{x:Bind Items, Mode=OneWay}" Layout="{StaticResource ItemsOrientationLayout}">
|
||||
<ItemsRepeater.Transitions>
|
||||
<TransitionCollection />
|
||||
</ItemsRepeater.Transitions>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate x:DataType="dockVm:DockItemViewModel">
|
||||
<local:DockItemControl
|
||||
Title="{x:Bind Title, Mode=OneWay}"
|
||||
RightTapped="BandItem_RightTapped"
|
||||
Subtitle="{x:Bind Subtitle, Mode=OneWay}"
|
||||
Tag="{x:Bind}"
|
||||
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.SourceRequested16}" />
|
||||
</local:DockItemControl.Icon>
|
||||
</local:DockItemControl>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</DataTemplate>
|
||||
|
||||
<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}"
|
||||
TargetType="FlyoutPresenter">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Background" Value="{ThemeResource DesktopAcrylicTransparentBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<!-- Backdrop requires ShouldConstrainToRootBounds="False" -->
|
||||
<Flyout
|
||||
x:Name="ContextMenuFlyout"
|
||||
FlyoutPresenterStyle="{StaticResource ContextMenuFlyoutStyle}"
|
||||
Opened="ContextMenuFlyout_Opened"
|
||||
ShouldConstrainToRootBounds="False"
|
||||
SystemBackdrop="{ThemeResource AcrylicBackgroundFillColorDefaultBackdrop}">
|
||||
<cpcontrols:ContextMenu x:Name="ContextControl" />
|
||||
</Flyout>
|
||||
|
||||
<!-- Edit mode context menu for dock bands -->
|
||||
<MenuFlyout x:Name="EditModeContextMenu" ShouldConstrainToRootBounds="False">
|
||||
<MenuFlyoutSubItem x:Name="LabelsSubMenu" Text="Labels">
|
||||
<MenuFlyoutSubItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</MenuFlyoutSubItem.Icon>
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="ShowTitlesMenuItem"
|
||||
Click="ShowTitlesMenuItem_Click"
|
||||
Text="Show titles" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="ShowSubtitlesMenuItem"
|
||||
Click="ShowSubtitlesMenuItem_Click"
|
||||
Text="Show subtitles" />
|
||||
</MenuFlyoutSubItem>
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Name="UnpinBandMenuItem"
|
||||
Click="UnpinBandMenuItem_Click"
|
||||
Text="Unpin">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
</MenuFlyout>
|
||||
|
||||
<!-- Add band flyout - used in edit mode to add bands to dock sections -->
|
||||
<Flyout
|
||||
x:Name="AddBandFlyout"
|
||||
Placement="Bottom"
|
||||
ShouldConstrainToRootBounds="False">
|
||||
<StackPanel Width="320">
|
||||
<TextBlock
|
||||
x:Name="NoAvailableBandsText"
|
||||
Padding="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="No commands available to pin"
|
||||
TextAlignment="Center"
|
||||
Visibility="Collapsed" />
|
||||
<ListView
|
||||
x:Name="AddBandListView"
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="AddBandListView_ItemClick"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TopLevelViewModel">
|
||||
<Grid Padding="4" ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<cpcontrols:IconBox
|
||||
Width="20"
|
||||
Height="20"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
SourceKey="{x:Bind IconViewModel, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested20}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Title, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
BorderThickness="0,0,0,1"
|
||||
RightTapped="RootGrid_RightTapped">
|
||||
<!-- Edit Mode Overlay - shown when in edit mode -->
|
||||
<Grid
|
||||
x:Name="ContentGrid"
|
||||
Margin="4"
|
||||
Padding="4,0,4,0"
|
||||
Background="Transparent"
|
||||
RightTapped="RootGrid_RightTapped">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<cpcontrols:ScrollContainer
|
||||
x:Name="StartScroller"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch">
|
||||
<cpcontrols:ScrollContainer.ActionButton>
|
||||
<Button
|
||||
x:Name="StartAddButton"
|
||||
Click="AddBandButton_Click"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Tag="Start"
|
||||
ToolTipService.ToolTip="Add band to Start">
|
||||
<FontIcon FontSize="12" Glyph="" />
|
||||
</Button>
|
||||
</cpcontrols:ScrollContainer.ActionButton>
|
||||
<cpcontrols:ScrollContainer.Source>
|
||||
<ListView
|
||||
x:Name="StartListView"
|
||||
HorizontalAlignment="Stretch"
|
||||
DragItemsCompleted="BandListView_DragItemsCompleted"
|
||||
DragItemsStarting="BandListView_DragItemsStarting"
|
||||
DragOver="BandListView_DragOver"
|
||||
Drop="StartListView_Drop"
|
||||
ItemContainerStyle="{StaticResource DockBandListViewItemStyle}"
|
||||
ItemTemplate="{StaticResource DockBandTemplate}"
|
||||
ItemsPanel="{StaticResource HorizontalItemsPanel}"
|
||||
ItemsSource="{x:Bind ViewModel.StartItems, Mode=OneWay}"
|
||||
SelectionMode="None"
|
||||
Style="{StaticResource DockBandListViewStyle}" />
|
||||
</cpcontrols:ScrollContainer.Source>
|
||||
</cpcontrols:ScrollContainer>
|
||||
|
||||
<cpcontrols:ScrollContainer
|
||||
x:Name="CenterScroller"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
MinWidth="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Stretch">
|
||||
<cpcontrols:ScrollContainer.ActionButton>
|
||||
<Button
|
||||
x:Name="CenterAddButton"
|
||||
Click="AddBandButton_Click"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Tag="Center"
|
||||
ToolTipService.ToolTip="Add band to Center">
|
||||
<FontIcon FontSize="12" Glyph="" />
|
||||
</Button>
|
||||
</cpcontrols:ScrollContainer.ActionButton>
|
||||
<cpcontrols:ScrollContainer.Source>
|
||||
<ListView
|
||||
x:Name="CenterListView"
|
||||
HorizontalAlignment="Stretch"
|
||||
DragItemsCompleted="BandListView_DragItemsCompleted"
|
||||
DragItemsStarting="BandListView_DragItemsStarting"
|
||||
DragOver="BandListView_DragOver"
|
||||
Drop="CenterListView_Drop"
|
||||
ItemContainerStyle="{StaticResource DockBandListViewItemStyle}"
|
||||
ItemTemplate="{StaticResource DockBandTemplate}"
|
||||
ItemsPanel="{StaticResource HorizontalItemsPanel}"
|
||||
ItemsSource="{x:Bind ViewModel.CenterItems, Mode=OneWay}"
|
||||
SelectionMode="None"
|
||||
Style="{StaticResource DockBandListViewStyle}" />
|
||||
</cpcontrols:ScrollContainer.Source>
|
||||
</cpcontrols:ScrollContainer>
|
||||
|
||||
<cpcontrols:ScrollContainer
|
||||
x:Name="EndScroller"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Stretch"
|
||||
ContentAlignment="End">
|
||||
<cpcontrols:ScrollContainer.ActionButton>
|
||||
<Button
|
||||
x:Name="EndAddButton"
|
||||
Click="AddBandButton_Click"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Tag="End"
|
||||
ToolTipService.ToolTip="Add band to End">
|
||||
<FontIcon FontSize="12" Glyph="" />
|
||||
</Button>
|
||||
</cpcontrols:ScrollContainer.ActionButton>
|
||||
<cpcontrols:ScrollContainer.Source>
|
||||
<ListView
|
||||
x:Name="EndListView"
|
||||
DragItemsCompleted="BandListView_DragItemsCompleted"
|
||||
DragItemsStarting="BandListView_DragItemsStarting"
|
||||
DragOver="BandListView_DragOver"
|
||||
Drop="EndListView_Drop"
|
||||
ItemContainerStyle="{StaticResource DockBandListViewItemStyle}"
|
||||
ItemTemplate="{StaticResource DockBandTemplate}"
|
||||
ItemsPanel="{StaticResource HorizontalItemsPanel}"
|
||||
ItemsSource="{x:Bind ViewModel.EndItems, Mode=OneWay}"
|
||||
SelectionMode="None"
|
||||
Style="{StaticResource DockBandListViewStyle}">
|
||||
<ListView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</ListView.ItemContainerTransitions>
|
||||
</ListView>
|
||||
</cpcontrols:ScrollContainer.Source>
|
||||
</cpcontrols:ScrollContainer>
|
||||
<TeachingTip
|
||||
x:Name="EditButtonsTeachingTip"
|
||||
MinWidth="0"
|
||||
PreferredPlacement="Bottom"
|
||||
ShouldConstrainToRootBounds="False"
|
||||
Style="{StaticResource TeachingTipWithoutCloseButtonStyle}"
|
||||
Target="{x:Bind ContentGrid}">
|
||||
|
||||
<TeachingTip.Content>
|
||||
<StackPanel
|
||||
x:Name="EditButtonsPanel"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="DoneEditingButton_Click"
|
||||
Content="Save"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="DiscardEditingButton_Click"
|
||||
Content="Discard" />
|
||||
</StackPanel>
|
||||
</TeachingTip.Content>
|
||||
</TeachingTip>
|
||||
</Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="DockOrientation">
|
||||
<VisualState x:Name="DockOnTop">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind DockSide, Mode=OneWay}" To="Top" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
<VisualState x:Name="DockOnBottom">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind DockSide, Mode=OneWay}" To="Bottom" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootGrid.BorderThickness" Value="0,1,0,0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="DockOnLeft">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind DockSide, Mode=OneWay}" To="Left" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="StartScroller.(Grid.Row)" Value="0" />
|
||||
<Setter Target="StartScroller.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="StartScroller.(Grid.Column)" Value="0" />
|
||||
<Setter Target="StartScroller.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="StartScroller.Orientation" Value="Vertical" />
|
||||
<Setter Target="StartScroller.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="StartScroller.VerticalAlignment" Value="Top" />
|
||||
<Setter Target="StartScroller.Orientation" Value="Vertical" />
|
||||
|
||||
<Setter Target="CenterScroller.(Grid.Row)" Value="1" />
|
||||
<Setter Target="CenterScroller.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="CenterScroller.(Grid.Column)" Value="0" />
|
||||
<Setter Target="CenterScroller.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="CenterScroller.Orientation" Value="Vertical" />
|
||||
<Setter Target="CenterScroller.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="CenterScroller.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="CenterScroller.Orientation" Value="Vertical" />
|
||||
|
||||
<Setter Target="EndScroller.Orientation" Value="Vertical" />
|
||||
<Setter Target="EndScroller.(Grid.Row)" Value="2" />
|
||||
<Setter Target="EndScroller.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="EndScroller.(Grid.Column)" Value="0" />
|
||||
<Setter Target="EndScroller.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="EndScroller.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="EndScroller.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="ContentGrid.Padding" Value="4,8,4,8" />
|
||||
<Setter Target="RootGrid.BorderThickness" Value="0,0,1,0" />
|
||||
|
||||
<Setter Target="StartListView.ItemsPanel" Value="{StaticResource VerticalItemsPanel}" />
|
||||
<Setter Target="CenterListView.ItemsPanel" Value="{StaticResource VerticalItemsPanel}" />
|
||||
<Setter Target="EndListView.ItemsPanel" Value="{StaticResource VerticalItemsPanel}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="DockOnRight">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind DockSide, Mode=OneWay}" To="Right" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="StartScroller.(Grid.Row)" Value="0" />
|
||||
<Setter Target="StartScroller.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="StartScroller.(Grid.Column)" Value="0" />
|
||||
<Setter Target="StartScroller.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="StartScroller.Orientation" Value="Vertical" />
|
||||
<Setter Target="StartScroller.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="StartScroller.VerticalAlignment" Value="Top" />
|
||||
<Setter Target="StartScroller.Orientation" Value="Vertical" />
|
||||
|
||||
<Setter Target="CenterScroller.(Grid.Row)" Value="1" />
|
||||
<Setter Target="CenterScroller.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="CenterScroller.(Grid.Column)" Value="0" />
|
||||
<Setter Target="CenterScroller.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="CenterScroller.Orientation" Value="Vertical" />
|
||||
<Setter Target="CenterScroller.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="CenterScroller.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="CenterScroller.Orientation" Value="Vertical" />
|
||||
|
||||
<Setter Target="EndScroller.Orientation" Value="Vertical" />
|
||||
<Setter Target="EndScroller.(Grid.Row)" Value="2" />
|
||||
<Setter Target="EndScroller.(Grid.RowSpan)" Value="1" />
|
||||
<Setter Target="EndScroller.(Grid.Column)" Value="0" />
|
||||
<Setter Target="EndScroller.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="EndScroller.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="EndScroller.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="ContentGrid.Padding" Value="4,8,4,8" />
|
||||
<Setter Target="RootGrid.BorderThickness" Value="1,0,0,0" />
|
||||
|
||||
<Setter Target="StartListView.ItemsPanel" Value="{StaticResource VerticalItemsPanel}" />
|
||||
<Setter Target="CenterListView.ItemsPanel" Value="{StaticResource VerticalItemsPanel}" />
|
||||
<Setter Target="EndListView.ItemsPanel" Value="{StaticResource VerticalItemsPanel}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<!-- Edit Mode Visual States -->
|
||||
<VisualStateGroup x:Name="EditModeStates">
|
||||
<VisualState x:Name="EditModeOff" />
|
||||
<VisualState x:Name="EditModeOn">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="StartScroller.BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Target="StartScroller.Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Target="StartScroller.BorderThickness" Value="1" />
|
||||
<Setter Target="StartScroller.CornerRadius" Value="{StaticResource OverlayCornerRadius}" />
|
||||
<Setter Target="CenterScroller.BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Target="CenterScroller.Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Target="CenterScroller.BorderThickness" Value="1" />
|
||||
<Setter Target="CenterScroller.CornerRadius" Value="{StaticResource OverlayCornerRadius}" />
|
||||
<Setter Target="EndScroller.BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Target="EndScroller.Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Target="EndScroller.BorderThickness" Value="1" />
|
||||
<Setter Target="EndScroller.CornerRadius" Value="{StaticResource OverlayCornerRadius}" />
|
||||
<Setter Target="StartScroller.ActionButtonVisibility" Value="Visible" />
|
||||
<Setter Target="CenterScroller.ActionButtonVisibility" Value="Visible" />
|
||||
<Setter Target="EndScroller.ActionButtonVisibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
510
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockControl.xaml.cs
Normal file
510
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockControl.xaml.cs
Normal file
@@ -0,0 +1,510 @@
|
||||
// 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 System.Runtime.InteropServices;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
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;
|
||||
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;
|
||||
|
||||
public sealed partial class DockControl : UserControl, IRecipient<CloseContextMenuMessage>, IRecipient<EnterDockEditModeMessage>
|
||||
{
|
||||
private DockViewModel _viewModel;
|
||||
|
||||
internal DockViewModel ViewModel => _viewModel;
|
||||
|
||||
public static readonly DependencyProperty ItemsOrientationProperty =
|
||||
DependencyProperty.Register(nameof(ItemsOrientation), typeof(Orientation), typeof(DockControl), new PropertyMetadata(Orientation.Horizontal));
|
||||
|
||||
public Orientation ItemsOrientation
|
||||
{
|
||||
get => (Orientation)GetValue(ItemsOrientationProperty);
|
||||
set => SetValue(ItemsOrientationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DockSideProperty =
|
||||
DependencyProperty.Register(nameof(DockSide), typeof(DockSide), typeof(DockControl), new PropertyMetadata(DockSide.Top));
|
||||
|
||||
public DockSide DockSide
|
||||
{
|
||||
get => (DockSide)GetValue(DockSideProperty);
|
||||
set => SetValue(DockSideProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsEditModeProperty =
|
||||
DependencyProperty.Register(nameof(IsEditMode), typeof(bool), typeof(DockControl), new PropertyMetadata(false, OnIsEditModeChanged));
|
||||
|
||||
public bool IsEditMode
|
||||
{
|
||||
get => (bool)GetValue(IsEditModeProperty);
|
||||
set => SetValue(IsEditModeProperty, value);
|
||||
}
|
||||
|
||||
private static void OnIsEditModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DockControl control && e.NewValue is bool isEditMode)
|
||||
{
|
||||
control.UpdateEditMode(isEditMode);
|
||||
}
|
||||
}
|
||||
|
||||
internal DockControl(DockViewModel viewModel)
|
||||
{
|
||||
_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
|
||||
StartListView.CanDragItems = isEditMode;
|
||||
StartListView.CanReorderItems = isEditMode;
|
||||
StartListView.AllowDrop = isEditMode;
|
||||
|
||||
CenterListView.CanDragItems = isEditMode;
|
||||
CenterListView.CanReorderItems = isEditMode;
|
||||
CenterListView.AllowDrop = isEditMode;
|
||||
|
||||
EndListView.CanDragItems = isEditMode;
|
||||
EndListView.CanReorderItems = isEditMode;
|
||||
EndListView.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)
|
||||
{
|
||||
DockSide = settings.Side;
|
||||
|
||||
var isHorizontal = settings.Side == DockSide.Top || settings.Side == DockSide.Bottom;
|
||||
|
||||
ItemsOrientation = isHorizontal ? Orientation.Horizontal : Orientation.Vertical;
|
||||
|
||||
if (settings.Backdrop == DockBackdrop.Transparent)
|
||||
{
|
||||
RootGrid.BorderBrush = new SolidColorBrush(Colors.Transparent);
|
||||
}
|
||||
}
|
||||
|
||||
private void BandItem_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
|
||||
{
|
||||
// Ignore clicks when in edit mode - allow drag behavior instead
|
||||
if (IsEditMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender is DockItemControl dockItem && dockItem.DataContext is DockBandViewModel band && dockItem.Tag 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Stores the band that was right-clicked for edit mode context menu
|
||||
private DockBandViewModel? _editModeContextBand;
|
||||
|
||||
private void BandItem_RightTapped(object sender, Microsoft.UI.Xaml.Input.RightTappedRoutedEventArgs e)
|
||||
{
|
||||
if (sender is DockItemControl dockItem && dockItem.DataContext is DockBandViewModel band && dockItem.Tag is DockItemViewModel item)
|
||||
{
|
||||
// In edit mode, show the edit mode context menu (show/hide labels)
|
||||
if (IsEditMode)
|
||||
{
|
||||
// Find the parent DockBandViewModel for this item
|
||||
_editModeContextBand = band;
|
||||
if (_editModeContextBand != null)
|
||||
{
|
||||
// Update toggle menu item checked state based on current settings
|
||||
ShowTitlesMenuItem.IsChecked = _editModeContextBand.ShowTitles;
|
||||
ShowSubtitlesMenuItem.IsChecked = _editModeContextBand.ShowSubtitles;
|
||||
|
||||
EditModeContextMenu.ShowAt(
|
||||
dockItem,
|
||||
new FlyoutShowOptions()
|
||||
{
|
||||
ShowMode = FlyoutShowMode.Standard,
|
||||
Placement = FlyoutPlacementMode.TopEdgeAlignedRight,
|
||||
});
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal mode - show the command context menu
|
||||
if (item.HasMoreCommands)
|
||||
{
|
||||
ContextControl.ViewModel.SelectedItem = item;
|
||||
ContextMenuFlyout.ShowAt(
|
||||
dockItem,
|
||||
new FlyoutShowOptions()
|
||||
{
|
||||
ShowMode = FlyoutShowMode.Standard,
|
||||
Placement = FlyoutPlacementMode.TopEdgeAlignedRight,
|
||||
});
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowTitlesMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_editModeContextBand != null)
|
||||
{
|
||||
_editModeContextBand.ShowTitles = ShowTitlesMenuItem.IsChecked;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowSubtitlesMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_editModeContextBand != null)
|
||||
{
|
||||
_editModeContextBand.ShowSubtitles = ShowSubtitlesMenuItem.IsChecked;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnpinBandMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_editModeContextBand != null)
|
||||
{
|
||||
ViewModel.UnpinBand(_editModeContextBand);
|
||||
_editModeContextBand = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void InvokeItem(DockItemViewModel item, Point pos)
|
||||
{
|
||||
var command = item.Command;
|
||||
try
|
||||
{
|
||||
PerformCommandMessage m = new(command.Model);
|
||||
m.WithAnimation = false;
|
||||
m.TransientPage = true;
|
||||
WeakReferenceMessenger.Default.Send(m);
|
||||
|
||||
var isPage = command.Model.Unsafe is not IInvokableCommand invokable;
|
||||
if (isPage)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<RequestShowPaletteAtMessage>(new(pos));
|
||||
}
|
||||
}
|
||||
catch (COMException e)
|
||||
{
|
||||
Logger.LogError("Error invoking dock command", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void ContextMenuFlyout_Opened(object sender, object e)
|
||||
{
|
||||
// We need to wait until our flyout is opened to try and toss focus
|
||||
// at its search box. The control isn't in the UI tree before that
|
||||
ContextControl.FocusSearchBox();
|
||||
}
|
||||
|
||||
public void Receive(CloseContextMenuMessage message)
|
||||
{
|
||||
if (ContextMenuFlyout.IsOpen)
|
||||
{
|
||||
ContextMenuFlyout.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void RootGrid_RightTapped(object sender, Microsoft.UI.Xaml.Input.RightTappedRoutedEventArgs e)
|
||||
{
|
||||
var pos = e.GetPosition(null);
|
||||
var item = this.ViewModel.GetContextMenuForDock();
|
||||
if (item.HasMoreCommands)
|
||||
{
|
||||
ContextControl.ViewModel.SelectedItem = item;
|
||||
ContextMenuFlyout.ShowAt(
|
||||
this.RootGrid,
|
||||
new FlyoutShowOptions()
|
||||
{
|
||||
ShowMode = FlyoutShowMode.Standard,
|
||||
Placement = FlyoutPlacementMode.TopEdgeAlignedRight,
|
||||
Position = pos,
|
||||
});
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private DockBandViewModel? _draggedBand;
|
||||
|
||||
private void BandListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
|
||||
{
|
||||
if (e.Items.Count > 0 && e.Items[0] is DockBandViewModel band)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
DockPinSide targetSide;
|
||||
ObservableCollection<DockBandViewModel> targetCollection;
|
||||
|
||||
if (sender == StartListView)
|
||||
{
|
||||
targetSide = DockPinSide.Start;
|
||||
targetCollection = ViewModel.StartItems;
|
||||
}
|
||||
else if (sender == CenterListView)
|
||||
{
|
||||
targetSide = DockPinSide.Center;
|
||||
targetCollection = ViewModel.CenterItems;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetSide = DockPinSide.End;
|
||||
targetCollection = ViewModel.EndItems;
|
||||
}
|
||||
|
||||
// Find the new index and sync ViewModel (without saving)
|
||||
var newIndex = targetCollection.IndexOf(_draggedBand);
|
||||
if (newIndex >= 0)
|
||||
{
|
||||
ViewModel.SyncBandPosition(_draggedBand, targetSide, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
_draggedBand = null;
|
||||
}
|
||||
|
||||
private void StartListView_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
HandleCrossListDrop(DockPinSide.Start, e);
|
||||
}
|
||||
|
||||
private void CenterListView_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
HandleCrossListDrop(DockPinSide.Center, e);
|
||||
}
|
||||
|
||||
private void EndListView_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
HandleCrossListDrop(DockPinSide.End, e);
|
||||
}
|
||||
|
||||
private void HandleCrossListDrop(DockPinSide targetSide, DragEventArgs e)
|
||||
{
|
||||
if (_draggedBand == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check which list the band is currently in
|
||||
var isInStart = ViewModel.StartItems.Contains(_draggedBand);
|
||||
var isInCenter = ViewModel.CenterItems.Contains(_draggedBand);
|
||||
var isInEnd = ViewModel.EndItems.Contains(_draggedBand);
|
||||
|
||||
DockPinSide sourceSide;
|
||||
if (isInStart)
|
||||
{
|
||||
sourceSide = DockPinSide.Start;
|
||||
}
|
||||
else if (isInCenter)
|
||||
{
|
||||
sourceSide = DockPinSide.Center;
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceSide = DockPinSide.End;
|
||||
}
|
||||
|
||||
// Only handle cross-list drops here; same-list reorders are handled in DragItemsCompleted
|
||||
if (sourceSide != targetSide)
|
||||
{
|
||||
// Calculate drop index based on drop position
|
||||
var targetListView = targetSide switch
|
||||
{
|
||||
DockPinSide.Start => StartListView,
|
||||
DockPinSide.Center => CenterListView,
|
||||
_ => EndListView,
|
||||
};
|
||||
var targetCollection = targetSide switch
|
||||
{
|
||||
DockPinSide.Start => ViewModel.StartItems,
|
||||
DockPinSide.Center => ViewModel.CenterItems,
|
||||
_ => 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;
|
||||
}
|
||||
|
||||
// Tracks which section (Start/Center/End) the add button was clicked for
|
||||
private DockPinSide _addBandTargetSide;
|
||||
|
||||
private void AddBandButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button && button.Tag is string sideTag)
|
||||
{
|
||||
_addBandTargetSide = sideTag switch
|
||||
{
|
||||
"Start" => DockPinSide.Start,
|
||||
"Center" => DockPinSide.Center,
|
||||
"End" => DockPinSide.End,
|
||||
_ => DockPinSide.Center,
|
||||
};
|
||||
|
||||
// Populate the list with available bands (not already in the dock)
|
||||
var availableBands = ViewModel.GetAvailableBandsToAdd().ToList();
|
||||
AddBandListView.ItemsSource = availableBands;
|
||||
|
||||
// Show/hide empty state text based on whether there are bands to add
|
||||
var hasAvailableBands = availableBands.Count > 0;
|
||||
NoAvailableBandsText.Visibility = hasAvailableBands ? Visibility.Collapsed : Visibility.Visible;
|
||||
AddBandListView.Visibility = hasAvailableBands ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
// Show the flyout
|
||||
AddBandFlyout.ShowAt(button);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddBandListView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is TopLevelViewModel topLevel)
|
||||
{
|
||||
// Add the band to the target section
|
||||
ViewModel.AddBandToSection(topLevel, _addBandTargetSide);
|
||||
|
||||
// Close the flyout
|
||||
AddBandFlyout.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
178
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockItemControl.xaml
Normal file
178
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockItemControl.xaml
Normal file
@@ -0,0 +1,178 @@
|
||||
<?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>
|
||||
|
||||
<CornerRadius x:Key="DockItemCornerRadius">4</CornerRadius>
|
||||
<Thickness x:Key="DockItemPadding">4,0,4,0</Thickness>
|
||||
|
||||
<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="{StaticResource DockItemPadding}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="CornerRadius" Value="{StaticResource DockItemCornerRadius}" />
|
||||
<Setter Property="TextVisibility" Value="Visible" />
|
||||
<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
|
||||
x:Name="ContentGrid"
|
||||
AutomationProperties.Name="{TemplateBinding Title}"
|
||||
Background="Transparent"
|
||||
ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<ContentPresenter
|
||||
x:Name="IconPresenter"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding Icon}" />
|
||||
|
||||
<!-- Text (Title + Subtitle) -->
|
||||
<StackPanel
|
||||
x:Name="TextPanel"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{TemplateBinding TextVisibility}">
|
||||
<TextBlock
|
||||
x:Name="TitleText"
|
||||
MinWidth="24"
|
||||
MaxWidth="100"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="Segoe UI"
|
||||
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"
|
||||
FontFamily="Segoe UI"
|
||||
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="{ThemeResource DockItemBackground}" />
|
||||
<Setter Target="PART_RootGrid.BorderBrush" Value="{ThemeResource DockItemBackground}" />
|
||||
<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>
|
||||
<VisualStateGroup x:Name="TextVisibilityStates">
|
||||
<VisualState x:Name="TextVisible" />
|
||||
<VisualState x:Name="TitleOnly">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SubtitleText.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="SubtitleOnly">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TitleText.Visibility" Value="Collapsed" />
|
||||
<Setter Target="SubtitleText.Margin" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="TextHidden">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentGrid.ColumnSpacing" Value="0" />
|
||||
<Setter Target="TextPanel.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="IconVisibilityStates">
|
||||
<VisualState x:Name="IconVisible" />
|
||||
<VisualState x:Name="IconHidden">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentGrid.ColumnSpacing" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,213 @@
|
||||
// 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.CmdPal.UI.Controls;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||
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);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextVisibilityProperty =
|
||||
DependencyProperty.Register(nameof(TextVisibility), typeof(Visibility), typeof(DockItemControl), new PropertyMetadata(null, OnTextPropertyChanged));
|
||||
|
||||
public Visibility TextVisibility
|
||||
{
|
||||
get => (Visibility)GetValue(TextVisibilityProperty);
|
||||
set => SetValue(TextVisibilityProperty, value);
|
||||
}
|
||||
|
||||
private const string IconPresenterName = "IconPresenter";
|
||||
|
||||
private FrameworkElement? _iconPresenter;
|
||||
|
||||
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DockItemControl control)
|
||||
{
|
||||
control.UpdateTextVisibility();
|
||||
control.UpdateAlignment();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIconPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DockItemControl control)
|
||||
{
|
||||
control.UpdateIconVisibility();
|
||||
control.UpdateAlignment();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HasTitle => !string.IsNullOrEmpty(Title);
|
||||
|
||||
internal bool HasSubtitle => !string.IsNullOrEmpty(Subtitle);
|
||||
|
||||
internal bool HasText => HasTitle || HasSubtitle;
|
||||
|
||||
private void UpdateTextVisibility()
|
||||
{
|
||||
UpdateTextVisibilityState();
|
||||
}
|
||||
|
||||
private void UpdateTextVisibilityState()
|
||||
{
|
||||
// Determine which visual state to use based on title/subtitle presence
|
||||
var stateName = (HasTitle, HasSubtitle) switch
|
||||
{
|
||||
(true, true) => "TextVisible",
|
||||
(true, false) => "TitleOnly",
|
||||
(false, true) => "SubtitleOnly",
|
||||
(false, false) => "TextHidden",
|
||||
};
|
||||
|
||||
VisualStateManager.GoToState(this, stateName, true);
|
||||
}
|
||||
|
||||
private void UpdateIconVisibility()
|
||||
{
|
||||
if (Icon is IconBox icon)
|
||||
{
|
||||
var dt = icon.DataContext;
|
||||
var src = icon.Source;
|
||||
|
||||
if (_iconPresenter is not null)
|
||||
{
|
||||
// n.b. this might be wrong - I think we always have an Icon (an IconBox),
|
||||
// we need to check if the box has an icon
|
||||
_iconPresenter.Visibility = Icon is null ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
UpdateIconVisibilityState();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIconVisibilityState()
|
||||
{
|
||||
var hasIcon = Icon is not null;
|
||||
VisualStateManager.GoToState(this, hasIcon ? "IconVisible" : "IconHidden", true);
|
||||
}
|
||||
|
||||
private void UpdateAlignment()
|
||||
{
|
||||
// If this item has both an icon and a label, left align so that the
|
||||
// icons don't wobble if the text changes.
|
||||
//
|
||||
// Otherwise, center align.
|
||||
var requestedTheme = ActualTheme;
|
||||
var isLight = requestedTheme == ElementTheme.Light;
|
||||
var showText = HasText;
|
||||
if (Icon is IconBox icoBox &&
|
||||
icoBox.DataContext is DockItemViewModel item &&
|
||||
item.Icon is IconInfoViewModel icon)
|
||||
{
|
||||
var showIcon = icon is not null && icon.HasIcon(isLight);
|
||||
if (showText && showIcon)
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Left;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalAlignment = HorizontalAlignment.Center;
|
||||
}
|
||||
|
||||
private void UpdateAllVisibility()
|
||||
{
|
||||
UpdateTextVisibility();
|
||||
UpdateIconVisibility();
|
||||
UpdateAlignment();
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
IsEnabledChanged -= OnIsEnabledChanged;
|
||||
|
||||
PointerEntered -= Control_PointerEntered;
|
||||
PointerExited -= Control_PointerExited;
|
||||
|
||||
PointerEntered += Control_PointerEntered;
|
||||
PointerExited += Control_PointerExited;
|
||||
|
||||
IsEnabledChanged += OnIsEnabledChanged;
|
||||
|
||||
// Get template children for visibility updates
|
||||
_iconPresenter = GetTemplateChild(IconPresenterName) 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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.CmdPal.UI.ViewModels.Settings;
|
||||
using Windows.Win32;
|
||||
using WinUIEx;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Dock;
|
||||
|
||||
internal static class DockSettingsToViews
|
||||
{
|
||||
public static double WidthForSize(DockSize size)
|
||||
{
|
||||
return size switch
|
||||
{
|
||||
DockSize.Small => 128,
|
||||
DockSize.Medium => 192,
|
||||
DockSize.Large => 256,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static double HeightForSize(DockSize size)
|
||||
{
|
||||
return size switch
|
||||
{
|
||||
DockSize.Small => 32,
|
||||
DockSize.Medium => 54,
|
||||
DockSize.Large => 76,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static double IconSizeForSize(DockSize size)
|
||||
{
|
||||
return size switch
|
||||
{
|
||||
DockSize.Small => 32 / 2,
|
||||
DockSize.Medium => 54 / 2,
|
||||
DockSize.Large => 76 / 2,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static Microsoft.UI.Xaml.Media.SystemBackdrop? GetSystemBackdrop(DockBackdrop backdrop)
|
||||
{
|
||||
return backdrop switch
|
||||
{
|
||||
DockBackdrop.Transparent => new TransparentTintBackdrop(),
|
||||
DockBackdrop.Acrylic => null,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static uint GetAppBarEdge(DockSide side)
|
||||
{
|
||||
return side switch
|
||||
{
|
||||
DockSide.Left => PInvoke.ABE_LEFT,
|
||||
DockSide.Top => PInvoke.ABE_TOP,
|
||||
DockSide.Right => PInvoke.ABE_RIGHT,
|
||||
DockSide.Bottom => PInvoke.ABE_BOTTOM,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
49
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindow.xaml
Normal file
49
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindow.xaml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<winuiex:WindowEx
|
||||
x:Class="Microsoft.CmdPal.UI.Dock.DockWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI.Dock"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Title="PowerDock"
|
||||
Closed="DockWindow_Closed"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
x:Name="Root"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
|
||||
<!-- Colorization overlay for transparent backdrop -->
|
||||
<Border
|
||||
x:Name="ColorizationOverlay"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="{x:Bind WindowViewModel.ColorizationOpacity, Mode=OneWay}"
|
||||
Visibility="{x:Bind WindowViewModel.ShowColorizationOverlay, Mode=OneWay}">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{x:Bind WindowViewModel.ColorizationColor, Mode=OneWay}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<cpcontrols:BlurImageControl
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BlurAmount="{x:Bind WindowViewModel.BackgroundImageBlurAmount, Mode=OneWay}"
|
||||
ImageBrightness="{x:Bind WindowViewModel.BackgroundImageBrightness, Mode=OneWay}"
|
||||
ImageOpacity="{x:Bind WindowViewModel.BackgroundImageOpacity, Mode=OneWay}"
|
||||
ImageSource="{x:Bind WindowViewModel.BackgroundImageSource, Mode=OneWay}"
|
||||
ImageStretch="{x:Bind WindowViewModel.BackgroundImageStretch, Mode=OneWay}"
|
||||
IsHitTestVisible="False"
|
||||
IsHoldingEnabled="False"
|
||||
TintColor="{x:Bind WindowViewModel.BackgroundImageTint, Mode=OneWay}"
|
||||
TintIntensity="{x:Bind WindowViewModel.BackgroundImageTintIntensity, Mode=OneWay}"
|
||||
Visibility="{x:Bind WindowViewModel.ShowBackgroundImage, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
698
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindow.xaml.cs
Normal file
698
src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockWindow.xaml.cs
Normal file
@@ -0,0 +1,698 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Foundation;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Graphics.Dwm;
|
||||
using Windows.Win32.UI.Accessibility;
|
||||
using Windows.Win32.UI.Shell;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using WinRT;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Dock;
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
public sealed partial class DockWindow : WindowEx,
|
||||
IRecipient<BringToTopMessage>,
|
||||
IRecipient<RequestShowPaletteAtMessage>,
|
||||
IRecipient<QuitMessage>,
|
||||
IDisposable
|
||||
{
|
||||
#pragma warning disable SA1306 // Field names should begin with lower-case letter
|
||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||
private readonly uint WM_TASKBAR_RESTART;
|
||||
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||
#pragma warning restore SA1306 // Field names should begin with lower-case letter
|
||||
|
||||
private readonly IThemeService _themeService;
|
||||
private readonly DockWindowViewModel _windowViewModel;
|
||||
|
||||
private HWND _hwnd = HWND.Null;
|
||||
private APPBARDATA _appBarData;
|
||||
private uint _callbackMessageId;
|
||||
|
||||
private DockSettings _settings;
|
||||
private DockViewModel viewModel;
|
||||
private DockControl _dock;
|
||||
private DesktopAcrylicController? _acrylicController;
|
||||
private SystemBackdropConfiguration? _configurationSource;
|
||||
private DockSize _lastSize;
|
||||
|
||||
// Store the original WndProc
|
||||
private WNDPROC? _originalWndProc;
|
||||
private WNDPROC? _customWndProc;
|
||||
|
||||
// internal Settings CurrentSettings => _settings;
|
||||
public DockWindow()
|
||||
{
|
||||
var serviceProvider = App.Current.Services;
|
||||
var mainSettings = serviceProvider.GetService<SettingsModel>()!;
|
||||
mainSettings.SettingsChanged += SettingsChangedHandler;
|
||||
_settings = mainSettings.DockSettings;
|
||||
_lastSize = _settings.DockSize;
|
||||
|
||||
viewModel = serviceProvider.GetService<DockViewModel>()!;
|
||||
_themeService = serviceProvider.GetRequiredService<IThemeService>();
|
||||
_themeService.ThemeChanged += ThemeService_ThemeChanged;
|
||||
_windowViewModel = new DockWindowViewModel(_themeService);
|
||||
_dock = new DockControl(viewModel);
|
||||
|
||||
InitializeComponent();
|
||||
Root.Children.Add(_dock);
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
AppWindow.IsShownInSwitchers = false;
|
||||
if (AppWindow.Presenter is OverlappedPresenter overlappedPresenter)
|
||||
{
|
||||
overlappedPresenter.SetBorderAndTitleBar(false, false);
|
||||
overlappedPresenter.IsResizable = false;
|
||||
}
|
||||
|
||||
this.Activated += DockWindow_Activated;
|
||||
|
||||
WeakReferenceMessenger.Default.Register<BringToTopMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<RequestShowPaletteAtMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||
|
||||
_hwnd = GetWindowHandle(this);
|
||||
|
||||
// Subclass the window to intercept messages
|
||||
//
|
||||
// Set up custom window procedure to listen for display changes
|
||||
// LOAD BEARING: If you don't stick the pointer to HotKeyPrc into a
|
||||
// member (and instead like, use a local), then the pointer we marshal
|
||||
// into the WindowLongPtr will be useless after we leave this function,
|
||||
// and our **WindProc will explode**.
|
||||
_customWndProc = CustomWndProc;
|
||||
|
||||
_callbackMessageId = PInvoke.RegisterWindowMessage($"CmdPal_ABM_{_hwnd}");
|
||||
|
||||
// TaskbarCreated is the message that's broadcast when explorer.exe
|
||||
// restarts. We need to know when that happens to be able to bring our
|
||||
// app bar back
|
||||
// And this apparently happens on lock screens / hibernates, too
|
||||
WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated");
|
||||
|
||||
var hotKeyPrcPointer = Marshal.GetFunctionPointerForDelegate(_customWndProc);
|
||||
_originalWndProc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(PInvoke.SetWindowLongPtr(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer));
|
||||
|
||||
// Disable minimize and maximize box
|
||||
var style = (WINDOW_STYLE)PInvoke.GetWindowLong(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
|
||||
style &= ~WINDOW_STYLE.WS_MINIMIZEBOX; // Remove WS_MINIMIZEBOX
|
||||
style &= ~WINDOW_STYLE.WS_MAXIMIZEBOX; // Remove WS_MAXIMIZEBOX
|
||||
_ = PInvoke.SetWindowLong(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, (int)style);
|
||||
|
||||
ShowDesktop.AddHook(this);
|
||||
UpdateSettingsOnUiThread();
|
||||
}
|
||||
|
||||
private void SettingsChangedHandler(SettingsModel sender, object? args)
|
||||
{
|
||||
_settings = sender.DockSettings;
|
||||
DispatcherQueue.TryEnqueue(UpdateSettingsOnUiThread);
|
||||
}
|
||||
|
||||
private void DockWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
// These are used for removing the very subtle shadow/border that we get from Windows 11
|
||||
HwndExtensions.ToggleWindowStyle(_hwnd, false, WindowStyle.TiledWindow);
|
||||
unsafe
|
||||
{
|
||||
BOOL value = false;
|
||||
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, &value, (uint)sizeof(BOOL));
|
||||
}
|
||||
}
|
||||
|
||||
private HWND GetWindowHandle(Window window)
|
||||
{
|
||||
var hwnd = WindowNative.GetWindowHandle(window);
|
||||
return new HWND(hwnd);
|
||||
}
|
||||
|
||||
private void UpdateSettingsOnUiThread()
|
||||
{
|
||||
this.viewModel.UpdateSettings(_settings);
|
||||
|
||||
SystemBackdrop = DockSettingsToViews.GetSystemBackdrop(_settings.Backdrop);
|
||||
|
||||
// If the backdrop is acrylic, things are more complicated
|
||||
if (_settings.Backdrop == DockBackdrop.Acrylic)
|
||||
{
|
||||
SetAcrylic();
|
||||
}
|
||||
|
||||
_dock.UpdateSettings(_settings);
|
||||
var side = DockSettingsToViews.GetAppBarEdge(_settings.Side);
|
||||
|
||||
if (_appBarData.hWnd != IntPtr.Zero)
|
||||
{
|
||||
var sameEdge = _appBarData.uEdge == side;
|
||||
var sameSize = _lastSize == _settings.DockSize;
|
||||
if (sameEdge && sameSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyAppBar(_hwnd);
|
||||
}
|
||||
|
||||
CreateAppBar(_hwnd);
|
||||
}
|
||||
|
||||
// We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material
|
||||
// other Shell surfaces are using, this cannot be set in XAML however.
|
||||
private void SetAcrylic()
|
||||
{
|
||||
if (DesktopAcrylicController.IsSupported())
|
||||
{
|
||||
// Hooking up the policy object.
|
||||
_configurationSource = new SystemBackdropConfiguration
|
||||
{
|
||||
// Initial configuration state.
|
||||
IsInputActive = true,
|
||||
};
|
||||
UpdateAcrylic();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAcrylic()
|
||||
{
|
||||
if (_acrylicController != null)
|
||||
{
|
||||
_acrylicController.RemoveAllSystemBackdropTargets();
|
||||
_acrylicController.Dispose();
|
||||
}
|
||||
|
||||
var backdrop = _themeService.CurrentDockTheme.BackdropParameters;
|
||||
_acrylicController = new DesktopAcrylicController
|
||||
{
|
||||
Kind = DesktopAcrylicKind.Thin,
|
||||
TintColor = backdrop.TintColor,
|
||||
TintOpacity = backdrop.EffectiveOpacity,
|
||||
FallbackColor = backdrop.FallbackColor,
|
||||
LuminosityOpacity = backdrop.EffectiveLuminosityOpacity,
|
||||
};
|
||||
|
||||
// Enable the system backdrop.
|
||||
// Note: Be sure to have "using WinRT;" to support the Window.As<...>() call.
|
||||
_acrylicController.AddSystemBackdropTarget(this.As<ICompositionSupportsSystemBackdrop>());
|
||||
_acrylicController.SetSystemBackdropConfiguration(_configurationSource);
|
||||
}
|
||||
|
||||
private void DisposeAcrylic()
|
||||
{
|
||||
if (_acrylicController is not null)
|
||||
{
|
||||
_acrylicController.Dispose();
|
||||
_acrylicController = null!;
|
||||
_configurationSource = null!;
|
||||
}
|
||||
}
|
||||
|
||||
private void ThemeService_ThemeChanged(object? sender, ThemeChangedEventArgs e)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
// We only need to handle acrylic here.
|
||||
// Transparent background is handled directly in XAML by binding to
|
||||
// the DockWindowViewModel's ColorizationColor properties.
|
||||
if (_settings.Backdrop == DockBackdrop.Acrylic)
|
||||
{
|
||||
UpdateAcrylic();
|
||||
}
|
||||
|
||||
// ActualTheme / RequestedTheme sync,
|
||||
// as pilfered from WindowThemeSynchronizer
|
||||
// LOAD BEARING: Changing the RequestedTheme to Dark then Light then target forces
|
||||
// a refresh of the theme.
|
||||
Root.RequestedTheme = ElementTheme.Dark;
|
||||
Root.RequestedTheme = ElementTheme.Light;
|
||||
Root.RequestedTheme = _themeService.CurrentDockTheme.Theme;
|
||||
});
|
||||
}
|
||||
|
||||
private void CreateAppBar(HWND hwnd)
|
||||
{
|
||||
_appBarData = new APPBARDATA
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uCallbackMessage = _callbackMessageId,
|
||||
};
|
||||
|
||||
// Register this window as an app bar
|
||||
PInvoke.SHAppBarMessage(PInvoke.ABM_NEW, ref _appBarData);
|
||||
|
||||
// Stash the last size we created the bar at, so we know when to hot-
|
||||
// reload it
|
||||
_lastSize = _settings.DockSize;
|
||||
|
||||
UpdateWindowPosition();
|
||||
}
|
||||
|
||||
private void DestroyAppBar(HWND hwnd)
|
||||
{
|
||||
PInvoke.SHAppBarMessage(PInvoke.ABM_REMOVE, ref _appBarData);
|
||||
_appBarData = default;
|
||||
}
|
||||
|
||||
private void UpdateWindowPosition()
|
||||
{
|
||||
Logger.LogDebug("UpdateWindowPosition");
|
||||
|
||||
var dpi = PInvoke.GetDpiForWindow(_hwnd);
|
||||
|
||||
var screenWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
|
||||
|
||||
// Get system border metrics
|
||||
var borderWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXBORDER);
|
||||
var edgeWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXEDGE);
|
||||
var frameWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXFRAME);
|
||||
|
||||
UpdateAppBarDataForEdge(_settings.Side, _settings.DockSize, dpi / 96.0);
|
||||
|
||||
// Query and set position
|
||||
PInvoke.SHAppBarMessage(PInvoke.ABM_QUERYPOS, ref _appBarData);
|
||||
PInvoke.SHAppBarMessage(PInvoke.ABM_SETPOS, ref _appBarData);
|
||||
|
||||
// TODO: investigate ABS_AUTOHIDE and auto hide bars.
|
||||
// I think it's something like this, but I don't totally know
|
||||
// _appBarData.lParam = ABS_ALWAYSONTOP;
|
||||
// _appBarData.lParam = (LPARAM)(int)PInvoke.ABS_AUTOHIDE;
|
||||
// PInvoke.SHAppBarMessage(ABM_SETSTATE, ref _appBarData);
|
||||
// PInvoke.SHAppBarMessage(PInvoke.ABM_SETAUTOHIDEBAR, ref _appBarData);
|
||||
|
||||
// Account for system borders when moving the window
|
||||
// Adjust position to account for window frame/border
|
||||
var adjustedLeft = _appBarData.rc.left - frameWidth;
|
||||
var adjustedTop = _appBarData.rc.top - frameWidth;
|
||||
var adjustedWidth = (_appBarData.rc.right - _appBarData.rc.left) + (2 * frameWidth);
|
||||
var adjustedHeight = (_appBarData.rc.bottom - _appBarData.rc.top) + (2 * frameWidth);
|
||||
|
||||
// Move the actual window
|
||||
PInvoke.MoveWindow(
|
||||
_hwnd,
|
||||
adjustedLeft,
|
||||
adjustedTop,
|
||||
adjustedWidth,
|
||||
adjustedHeight,
|
||||
true);
|
||||
}
|
||||
|
||||
private void UpdateAppBarDataForEdge(DockSide side, DockSize size, double scaleFactor)
|
||||
{
|
||||
Logger.LogDebug("UpdateAppBarDataForEdge");
|
||||
var horizontalHeightDips = DockSettingsToViews.HeightForSize(size);
|
||||
var verticalWidthDips = DockSettingsToViews.WidthForSize(size);
|
||||
var screenHeight = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN);
|
||||
var screenWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
|
||||
|
||||
if (side == DockSide.Top)
|
||||
{
|
||||
_appBarData.uEdge = PInvoke.ABE_TOP;
|
||||
_appBarData.rc.left = 0;
|
||||
_appBarData.rc.top = 0;
|
||||
_appBarData.rc.right = screenWidth;
|
||||
_appBarData.rc.bottom = (int)(horizontalHeightDips * scaleFactor);
|
||||
}
|
||||
else if (side == DockSide.Bottom)
|
||||
{
|
||||
var heightPixels = (int)(horizontalHeightDips * scaleFactor);
|
||||
|
||||
_appBarData.uEdge = PInvoke.ABE_BOTTOM;
|
||||
_appBarData.rc.left = 0;
|
||||
_appBarData.rc.top = screenHeight - heightPixels;
|
||||
_appBarData.rc.right = screenWidth;
|
||||
_appBarData.rc.bottom = screenHeight;
|
||||
}
|
||||
else if (side == DockSide.Left)
|
||||
{
|
||||
var widthPixels = (int)(verticalWidthDips * scaleFactor);
|
||||
|
||||
_appBarData.uEdge = PInvoke.ABE_LEFT;
|
||||
_appBarData.rc.left = 0;
|
||||
_appBarData.rc.top = 0;
|
||||
_appBarData.rc.right = widthPixels;
|
||||
_appBarData.rc.bottom = screenHeight;
|
||||
}
|
||||
else if (side == DockSide.Right)
|
||||
{
|
||||
var widthPixels = (int)(verticalWidthDips * scaleFactor);
|
||||
|
||||
_appBarData.uEdge = PInvoke.ABE_RIGHT;
|
||||
_appBarData.rc.left = screenWidth - widthPixels;
|
||||
_appBarData.rc.top = 0;
|
||||
_appBarData.rc.right = screenWidth;
|
||||
_appBarData.rc.bottom = screenHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private LRESULT CustomWndProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
// check settings changed
|
||||
if (msg == PInvoke.WM_SETTINGCHANGE)
|
||||
{
|
||||
if (wParam == (uint)SYSTEM_PARAMETERS_INFO_ACTION.SPI_SETWORKAREA)
|
||||
{
|
||||
Logger.LogDebug($"WM_SETTINGCHANGE(SPI_SETWORKAREA)");
|
||||
|
||||
// Use debounced call to throttle rapid successive calls
|
||||
DispatcherQueue.TryEnqueue(() => UpdateWindowPosition());
|
||||
}
|
||||
}
|
||||
else if (msg == PInvoke.WM_DISPLAYCHANGE)
|
||||
{
|
||||
Logger.LogDebug("WM_DISPLAYCHANGE");
|
||||
|
||||
// Use dispatcher to ensure we're on the UI thread
|
||||
DispatcherQueue.TryEnqueue(() => UpdateWindowPosition());
|
||||
}
|
||||
|
||||
// Intercept WM_SYSCOMMAND to prevent minimize and maximize
|
||||
else if (msg == PInvoke.WM_SYSCOMMAND)
|
||||
{
|
||||
var command = (int)(wParam.Value & 0xFFF0);
|
||||
if (command == PInvoke.SC_MINIMIZE || command == PInvoke.SC_MAXIMIZE)
|
||||
{
|
||||
// Block minimize and maximize commands
|
||||
return new LRESULT(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop min/max on WM_WINDOWPOSCHANGING too
|
||||
else if (msg == PInvoke.WM_WINDOWPOSCHANGING)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var pWindowPos = (WINDOWPOS*)lParam.Value;
|
||||
|
||||
// Check if the window is being hidden (minimized) or if flags suggest minimize/maximize
|
||||
if ((pWindowPos->flags & SET_WINDOW_POS_FLAGS.SWP_HIDEWINDOW) != 0)
|
||||
{
|
||||
// Prevent hiding the window (minimize)
|
||||
pWindowPos->flags &= ~SET_WINDOW_POS_FLAGS.SWP_HIDEWINDOW;
|
||||
pWindowPos->flags |= SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW;
|
||||
}
|
||||
|
||||
// Additional check: if the window position suggests it's being minimized or maximized
|
||||
// by checking for dramatic size changes
|
||||
if (pWindowPos->cx <= 0 || pWindowPos->cy <= 0)
|
||||
{
|
||||
// Prevent zero or negative size changes (minimize)
|
||||
pWindowPos->flags |= SET_WINDOW_POS_FLAGS.SWP_NOSIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle WM_SIZE to prevent minimize/maximize state changes
|
||||
else if (msg == PInvoke.WM_SIZE)
|
||||
{
|
||||
var sizeType = (int)wParam.Value;
|
||||
if (sizeType == PInvoke.SIZE_MINIMIZED || sizeType == PInvoke.SIZE_MAXIMIZED)
|
||||
{
|
||||
// Block the size change by not calling the original window procedure
|
||||
return new LRESULT(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle WM_SHOWWINDOW to prevent hiding (minimize)
|
||||
else if (msg == PInvoke.WM_SHOWWINDOW)
|
||||
{
|
||||
var isBeingShown = wParam.Value != 0;
|
||||
if (!isBeingShown)
|
||||
{
|
||||
// Prevent hiding the window
|
||||
return new LRESULT(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle double-click on title bar (non-client area)
|
||||
else if (msg == PInvoke.WM_NCLBUTTONDBLCLK)
|
||||
{
|
||||
var hitTest = (int)wParam.Value;
|
||||
if (hitTest == PInvoke.HTCAPTION)
|
||||
{
|
||||
// Block double-click on title bar to prevent maximize
|
||||
return new LRESULT(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle WM_GETMINMAXINFO to control window size limits
|
||||
else if (msg == PInvoke.WM_GETMINMAXINFO)
|
||||
{
|
||||
// We can modify the min/max tracking info here if needed
|
||||
// For now, let it pass through but we could restrict max size
|
||||
}
|
||||
|
||||
// Handle the AppBarMessage message
|
||||
// This is needed to update the position when the work area changes.
|
||||
// (notably, when the user toggles auto-hide taskbars)
|
||||
else if (msg == _callbackMessageId)
|
||||
{
|
||||
if (wParam.Value == PInvoke.ABN_POSCHANGED)
|
||||
{
|
||||
UpdateWindowPosition();
|
||||
}
|
||||
}
|
||||
else if (msg == WM_TASKBAR_RESTART)
|
||||
{
|
||||
Logger.LogDebug("WM_TASKBAR_RESTART");
|
||||
|
||||
DispatcherQueue.TryEnqueue(() => CreateAppBar(_hwnd));
|
||||
|
||||
WeakReferenceMessenger.Default.Send<BringToTopMessage>(new(false));
|
||||
}
|
||||
|
||||
// Call the original window procedure for all other messages
|
||||
return PInvoke.CallWindowProc(_originalWndProc, hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
void IRecipient<BringToTopMessage>.Receive(BringToTopMessage message)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
var onTop = message.OnTop ? HWND.HWND_TOPMOST : HWND.HWND_NOTOPMOST;
|
||||
PInvoke.SetWindowPos(_hwnd, onTop, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
|
||||
PInvoke.SetWindowPos(_hwnd, HWND.HWND_NOTOPMOST, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
|
||||
});
|
||||
}
|
||||
|
||||
public void Receive(QuitMessage message)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
DestroyAppBar(_hwnd);
|
||||
|
||||
this.Close();
|
||||
});
|
||||
}
|
||||
|
||||
void IRecipient<RequestShowPaletteAtMessage>.Receive(RequestShowPaletteAtMessage message)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => RequestShowPaletteOnUiThread(message.PosDips));
|
||||
}
|
||||
|
||||
private void RequestShowPaletteOnUiThread(Point posDips)
|
||||
{
|
||||
// pos is relative to our root. We need to convert to screen coords.
|
||||
var rootPosDips = Root.TransformToVisual(null).TransformPoint(new Point(0, 0));
|
||||
var screenPosDips = new Point(rootPosDips.X + posDips.X, rootPosDips.Y + posDips.Y);
|
||||
|
||||
var dpi = PInvoke.GetDpiForWindow(_hwnd);
|
||||
var scaleFactor = dpi / 96.0;
|
||||
var screenPosPixels = new Point(screenPosDips.X * scaleFactor, screenPosDips.Y * scaleFactor);
|
||||
|
||||
var screenWidth = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
|
||||
var screenHeight = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN);
|
||||
|
||||
// Now we're going to find the best position for the palette.
|
||||
|
||||
// We want to anchor the palette on the dock side.
|
||||
// on the top:
|
||||
// - anchor to the top, left if we're on the left half of the screen
|
||||
// - anchor to the top, right if we're on the right half of the screen
|
||||
// On the left:
|
||||
// - anchor to the top, left if we're on the top half of the screen
|
||||
// - anchor to the bottom, left if we're on the bottom half of the screen
|
||||
// On the right:
|
||||
// - anchor to the top, right if we're on the top half of the screen
|
||||
// - anchor to the bottom, right if we're on the bottom half of the screen
|
||||
// On the bottom:
|
||||
// - anchor to the bottom, left if we're on the left half of the screen
|
||||
// - anchor to the bottom, right if we're on the right half of the screen
|
||||
var onTopHalf = screenPosPixels.Y < screenHeight / 2;
|
||||
var onLeftHalf = screenPosPixels.X < screenWidth / 2;
|
||||
var onRightHalf = !onLeftHalf;
|
||||
var onBottomHalf = !onTopHalf;
|
||||
|
||||
var anchorPoint = _settings.Side switch
|
||||
{
|
||||
DockSide.Top => onLeftHalf ? AnchorPoint.TopLeft : AnchorPoint.TopRight,
|
||||
DockSide.Bottom => onLeftHalf ? AnchorPoint.BottomLeft : AnchorPoint.BottomRight,
|
||||
DockSide.Left => onTopHalf ? AnchorPoint.TopLeft : AnchorPoint.BottomLeft,
|
||||
DockSide.Right => onTopHalf ? AnchorPoint.TopRight : AnchorPoint.BottomRight,
|
||||
_ => AnchorPoint.TopLeft,
|
||||
};
|
||||
|
||||
// we also need to slide the anchor point a bit away from the dock
|
||||
var paddingDips = 8;
|
||||
var paddingPixels = paddingDips * scaleFactor;
|
||||
PInvoke.GetWindowRect(_hwnd, out var ourRect);
|
||||
|
||||
// Depending on the side we're on, we need to offset differently
|
||||
switch (_settings.Side)
|
||||
{
|
||||
case DockSide.Top:
|
||||
screenPosPixels.Y = ourRect.bottom + paddingPixels;
|
||||
break;
|
||||
case DockSide.Bottom:
|
||||
screenPosPixels.Y = ourRect.top - paddingPixels;
|
||||
break;
|
||||
case DockSide.Left:
|
||||
screenPosPixels.X = ourRect.right + paddingPixels;
|
||||
break;
|
||||
case DockSide.Right:
|
||||
screenPosPixels.X = ourRect.left - paddingPixels;
|
||||
break;
|
||||
}
|
||||
|
||||
// Now that we know the anchor corner, and where to attempt to place it, we can
|
||||
// ask the palette to show itself there.
|
||||
WeakReferenceMessenger.Default.Send<ShowPaletteAtMessage>(new(screenPosPixels, anchorPoint));
|
||||
}
|
||||
|
||||
public DockWindowViewModel WindowViewModel => _windowViewModel;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeAcrylic();
|
||||
_windowViewModel.Dispose();
|
||||
}
|
||||
|
||||
private void DockWindow_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
var serviceProvider = App.Current.Services;
|
||||
var settings = serviceProvider.GetService<SettingsModel>();
|
||||
settings?.SettingsChanged -= SettingsChangedHandler;
|
||||
_themeService.ThemeChanged -= ThemeService_ThemeChanged;
|
||||
DisposeAcrylic();
|
||||
|
||||
// Remove our app bar registration
|
||||
DestroyAppBar(_hwnd);
|
||||
|
||||
// Unhook the window procedure
|
||||
ShowDesktop.RemoveHook();
|
||||
}
|
||||
}
|
||||
|
||||
// Thank you to https://stackoverflow.com/a/35422795/1481137
|
||||
internal static class ShowDesktop
|
||||
{
|
||||
private const string WORKERW = "WorkerW";
|
||||
private const string PROGMAN = "Progman";
|
||||
|
||||
private static WINEVENTPROC? _hookProc;
|
||||
private static IntPtr _hookHandle = IntPtr.Zero;
|
||||
|
||||
public static void AddHook(Window window)
|
||||
{
|
||||
if (IsHooked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsHooked = true;
|
||||
|
||||
_hookProc = (WINEVENTPROC)WinEventCallback;
|
||||
_hookHandle = PInvoke.SetWinEventHook(PInvoke.EVENT_SYSTEM_FOREGROUND, PInvoke.EVENT_SYSTEM_FOREGROUND, HMODULE.Null, _hookProc, 0, 0, PInvoke.WINEVENT_OUTOFCONTEXT);
|
||||
}
|
||||
|
||||
public static void RemoveHook()
|
||||
{
|
||||
if (!IsHooked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsHooked = false;
|
||||
|
||||
PInvoke.UnhookWinEvent((HWINEVENTHOOK)_hookHandle);
|
||||
_hookProc = null;
|
||||
_hookHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
private static string GetWindowClass(HWND hwnd)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (char* c = new char[32])
|
||||
{
|
||||
_ = PInvoke.GetClassName(hwnd, (PWSTR)c, 32);
|
||||
return new string(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
|
||||
|
||||
private static void WinEventCallback(
|
||||
HWINEVENTHOOK hWinEventHook,
|
||||
uint eventType,
|
||||
HWND hwnd,
|
||||
int idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
uint dwmsEventTime)
|
||||
{
|
||||
if (eventType == PInvoke.EVENT_SYSTEM_FOREGROUND)
|
||||
{
|
||||
var @class = GetWindowClass(hwnd);
|
||||
if (string.Equals(@class, WORKERW, StringComparison.Ordinal) || string.Equals(@class, PROGMAN, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.LogDebug("ShowDesktop invoked. Bring us back");
|
||||
WeakReferenceMessenger.Default.Send<BringToTopMessage>(new(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsHooked { get; private set; }
|
||||
}
|
||||
|
||||
internal sealed record BringToTopMessage(bool OnTop);
|
||||
|
||||
internal sealed record RequestShowPaletteAtMessage(Point PosDips);
|
||||
|
||||
internal sealed record ShowPaletteAtMessage(Point PosPixels, AnchorPoint Anchor);
|
||||
|
||||
internal enum AnchorPoint
|
||||
{
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
@@ -2,10 +2,8 @@
|
||||
// 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.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
@@ -24,6 +24,7 @@ public static partial class IconCacheProvider
|
||||
| 256×256 | 256.0 KB | 64 | 16.0 MB | 1 MB | 64.0 MB | 2.3 MB | 144 MB |
|
||||
*/
|
||||
|
||||
private static IIconSourceProvider _provider16 = null!;
|
||||
private static IIconSourceProvider _provider20 = null!;
|
||||
private static IIconSourceProvider _provider32 = null!;
|
||||
private static IIconSourceProvider _provider64 = null!;
|
||||
@@ -31,6 +32,7 @@ public static partial class IconCacheProvider
|
||||
|
||||
public static void Initialize(IServiceProvider serviceProvider)
|
||||
{
|
||||
_provider16 = serviceProvider.GetRequiredKeyedService<IIconSourceProvider>(WellKnownIconSize.Size16);
|
||||
_provider20 = serviceProvider.GetRequiredKeyedService<IIconSourceProvider>(WellKnownIconSize.Size20);
|
||||
_provider32 = serviceProvider.GetRequiredKeyedService<IIconSourceProvider>(WellKnownIconSize.Size32);
|
||||
_provider64 = serviceProvider.GetRequiredKeyedService<IIconSourceProvider>(WellKnownIconSize.Size64);
|
||||
@@ -64,6 +66,9 @@ public static partial class IconCacheProvider
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0060 // Remove unused parameter
|
||||
public static void SourceRequested16(IconBox sender, SourceRequestedEventArgs args)
|
||||
=> SourceRequestedCore(_provider16, args);
|
||||
|
||||
public static void SourceRequested20(IconBox sender, SourceRequestedEventArgs args)
|
||||
=> SourceRequestedCore(_provider20, args);
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ internal static class IconServiceRegistration
|
||||
services.AddSingleton<IIconLoaderService>(loader);
|
||||
|
||||
// Keyed providers by size
|
||||
services.AddKeyedSingleton<IIconSourceProvider>(
|
||||
WellKnownIconSize.Size16,
|
||||
(_, _) => new IconSourceProvider(loader, 16));
|
||||
|
||||
services.AddKeyedSingleton<IIconSourceProvider>(
|
||||
WellKnownIconSize.Size20,
|
||||
(_, _) => new CachedIconSourceProvider(loader, 20, 1024));
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
internal enum WellKnownIconSize
|
||||
{
|
||||
Size16 = 16,
|
||||
Size20 = 20,
|
||||
Size32 = 32,
|
||||
Size64 = 64,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Messages;
|
||||
using Microsoft.CmdPal.UI.Controls;
|
||||
using Microsoft.CmdPal.UI.Dock;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Messages;
|
||||
@@ -18,6 +19,7 @@ using Microsoft.CmdPal.UI.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CmdPal.ViewModels.Messages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Composition;
|
||||
@@ -45,6 +47,7 @@ namespace Microsoft.CmdPal.UI;
|
||||
public sealed partial class MainWindow : WindowEx,
|
||||
IRecipient<DismissMessage>,
|
||||
IRecipient<ShowWindowMessage>,
|
||||
IRecipient<ShowPaletteAtMessage>,
|
||||
IRecipient<HideWindowMessage>,
|
||||
IRecipient<QuitMessage>,
|
||||
IRecipient<NavigateToPageMessage>,
|
||||
@@ -145,6 +148,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
WeakReferenceMessenger.Default.Register<DismissMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ShowPaletteAtMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<NavigationDepthMessage>(this);
|
||||
@@ -206,7 +210,10 @@ public sealed partial class MainWindow : WindowEx,
|
||||
}
|
||||
}
|
||||
|
||||
private void SettingsChangedHandler(SettingsModel sender, object? args) => HotReloadSettings();
|
||||
private void SettingsChangedHandler(SettingsModel sender, object? args)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(HotReloadSettings);
|
||||
}
|
||||
|
||||
private void RootElementLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
@@ -496,6 +503,78 @@ public sealed partial class MainWindow : WindowEx,
|
||||
}
|
||||
|
||||
private void ShowHwnd(IntPtr hwndValue, MonitorBehavior target)
|
||||
{
|
||||
var positionWindowForTargetMonitor = (HWND hwnd) =>
|
||||
{
|
||||
if (target == MonitorBehavior.ToLast)
|
||||
{
|
||||
var originalScreen = new SizeInt32(_currentWindowPosition.ScreenWidth, _currentWindowPosition.ScreenHeight);
|
||||
var newRect = WindowPositionHelper.AdjustRectForVisibility(_currentWindowPosition.ToPhysicalWindowRectangle(), originalScreen, _currentWindowPosition.Dpi);
|
||||
MoveAndResizeDpiAware(newRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
var display = GetScreen(hwnd, target);
|
||||
PositionCentered(display);
|
||||
}
|
||||
};
|
||||
ShowHwnd(hwndValue, positionWindowForTargetMonitor);
|
||||
}
|
||||
|
||||
private void ShowHwnd(IntPtr hwndValue, Point anchorInPixels, AnchorPoint anchorCorner)
|
||||
{
|
||||
var positionWindowForAnchor = (HWND hwnd) =>
|
||||
{
|
||||
PInvoke.GetWindowRect(hwnd, out var bounds);
|
||||
var swpFlags = SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER;
|
||||
switch (anchorCorner)
|
||||
{
|
||||
case AnchorPoint.TopLeft:
|
||||
PInvoke.SetWindowPos(
|
||||
hwnd,
|
||||
HWND.HWND_TOP,
|
||||
(int)anchorInPixels.X,
|
||||
(int)anchorInPixels.Y,
|
||||
0,
|
||||
0,
|
||||
swpFlags);
|
||||
break;
|
||||
case AnchorPoint.TopRight:
|
||||
PInvoke.SetWindowPos(
|
||||
hwnd,
|
||||
HWND.HWND_TOP,
|
||||
(int)(anchorInPixels.X - bounds.Width),
|
||||
(int)anchorInPixels.Y,
|
||||
0,
|
||||
0,
|
||||
swpFlags);
|
||||
break;
|
||||
case AnchorPoint.BottomLeft:
|
||||
PInvoke.SetWindowPos(
|
||||
hwnd,
|
||||
HWND.HWND_TOP,
|
||||
(int)anchorInPixels.X,
|
||||
(int)(anchorInPixels.Y - bounds.Height),
|
||||
0,
|
||||
0,
|
||||
swpFlags);
|
||||
break;
|
||||
case AnchorPoint.BottomRight:
|
||||
PInvoke.SetWindowPos(
|
||||
hwnd,
|
||||
HWND.HWND_TOP,
|
||||
(int)(anchorInPixels.X - bounds.Width),
|
||||
(int)(anchorInPixels.Y - bounds.Height),
|
||||
0,
|
||||
0,
|
||||
swpFlags);
|
||||
break;
|
||||
}
|
||||
};
|
||||
ShowHwnd(hwndValue, positionWindowForAnchor);
|
||||
}
|
||||
|
||||
private void ShowHwnd(IntPtr hwndValue, Action<HWND>? positionWindow)
|
||||
{
|
||||
StopAutoGoHome();
|
||||
|
||||
@@ -514,16 +593,9 @@ public sealed partial class MainWindow : WindowEx,
|
||||
PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_RESTORE);
|
||||
}
|
||||
|
||||
if (target == MonitorBehavior.ToLast)
|
||||
if (positionWindow is not null)
|
||||
{
|
||||
var originalScreen = new SizeInt32(_currentWindowPosition.ScreenWidth, _currentWindowPosition.ScreenHeight);
|
||||
var newRect = WindowPositionHelper.AdjustRectForVisibility(_currentWindowPosition.ToPhysicalWindowRectangle(), originalScreen, _currentWindowPosition.Dpi);
|
||||
MoveAndResizeDpiAware(newRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
var display = GetScreen(hwnd, target);
|
||||
PositionCentered(display);
|
||||
positionWindow(hwnd);
|
||||
}
|
||||
|
||||
// Check if the debugger is attached. If it is, we don't want to apply the tool window style,
|
||||
@@ -605,6 +677,11 @@ public sealed partial class MainWindow : WindowEx,
|
||||
ShowHwnd(message.Hwnd, settings.SummonOn);
|
||||
}
|
||||
|
||||
internal void Receive(ShowPaletteAtMessage message)
|
||||
{
|
||||
ShowHwnd(HWND.Null, message.PosPixels, message.Anchor);
|
||||
}
|
||||
|
||||
public void Receive(HideWindowMessage message)
|
||||
{
|
||||
// This might come in off the UI thread. Make sure to hop back.
|
||||
@@ -715,6 +792,8 @@ public sealed partial class MainWindow : WindowEx,
|
||||
// Sure, it's not ideal, but at least it's not visible.
|
||||
}
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new WindowHiddenMessage());
|
||||
|
||||
// Start auto-go-home timer
|
||||
RestartAutoGoHome();
|
||||
}
|
||||
@@ -1121,6 +1200,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
// but that's the price to pay for having the HWND not light-dismiss while we're debugging.
|
||||
Cloak();
|
||||
this.Hide();
|
||||
WeakReferenceMessenger.Default.Send(new WindowHiddenMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1178,6 +1258,8 @@ public sealed partial class MainWindow : WindowEx,
|
||||
DisposeAcrylic();
|
||||
}
|
||||
|
||||
void IRecipient<ShowPaletteAtMessage>.Receive(ShowPaletteAtMessage message) => Receive(message);
|
||||
|
||||
public void Receive(ToggleDevRibbonMessage message)
|
||||
{
|
||||
_devRibbon?.Visibility = _devRibbon.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
<Version>$(CmdPalVersion)</Version>
|
||||
|
||||
<!-- For MVVM Toolkit Partial Properties/AOT support -->
|
||||
<LangVersion>preview</LangVersion>
|
||||
|
||||
<!-- OutputPath is set in CmdPal.Branding.props -->
|
||||
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
@@ -27,10 +30,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- For debugging purposes, uncomment this block to enable AOT builds -->
|
||||
<!--<PropertyGroup>
|
||||
<!-- <PropertyGroup>
|
||||
<EnableCmdPalAOT>true</EnableCmdPalAOT>
|
||||
<GeneratePackageLocally>true</GeneratePackageLocally>
|
||||
</PropertyGroup>-->
|
||||
</PropertyGroup> -->
|
||||
|
||||
<PropertyGroup Condition="'$(EnableCmdPalAOT)' == 'true'">
|
||||
<SelfContained>true</SelfContained>
|
||||
@@ -39,13 +42,13 @@
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(CIBuild)' == 'true' or '$(GeneratePackageLocally)' == 'true'">
|
||||
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
|
||||
<AppxBundle>Never</AppxBundle>
|
||||
<AppxPackageTestDir>$(OutputPath)\AppPackages\Microsoft.CmdPal.UI_$(Version)_Test\</AppxPackageTestDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(CIBuild)' == 'true' or '$(GeneratePackageLocally)' == 'true'">
|
||||
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
|
||||
<AppxBundle>Never</AppxBundle>
|
||||
<AppxPackageTestDir>$(OutputPath)\AppPackages\Microsoft.CmdPal.UI_$(Version)_Test\</AppxPackageTestDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- This lets us actually reference types from Microsoft.Terminal.UI and CmdPalKeyboardService -->
|
||||
<CsWinRTIncludes>Microsoft.Terminal.UI;CmdPalKeyboardService</CsWinRTIncludes>
|
||||
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
|
||||
@@ -73,6 +76,7 @@
|
||||
<None Remove="Controls\DevRibbon.xaml" />
|
||||
<None Remove="Controls\FallbackRankerDialog.xaml" />
|
||||
<None Remove="Controls\ScreenPreview.xaml" />
|
||||
<None Remove="Controls\ScrollContainer.xaml" />
|
||||
<None Remove="Controls\SearchBar.xaml" />
|
||||
<None Remove="ListDetailPage.xaml" />
|
||||
<None Remove="LoadingPage.xaml" />
|
||||
@@ -83,8 +87,8 @@
|
||||
<None Remove="Settings\AppearancePage.xaml" />
|
||||
<None Remove="Settings\InternalPage.xaml" />
|
||||
<None Remove="ShellPage.xaml" />
|
||||
<None Remove="Styles\Colors.xaml" />
|
||||
<None Remove="Styles\Settings.xaml" />
|
||||
<None Remove="Styles\TeachingTip.xaml" />
|
||||
<None Remove="Styles\TextBox.xaml" />
|
||||
<None Remove="Styles\Theme.Normal.xaml" />
|
||||
</ItemGroup>
|
||||
@@ -214,6 +218,24 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\TeachingTip.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Dock\DockItemControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\ScrollContainer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\CommandPalettePreview.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -59,15 +59,56 @@ GetModuleHandle
|
||||
GetWindowLong
|
||||
SetWindowLong
|
||||
WINDOW_EX_STYLE
|
||||
|
||||
CreateWindowEx
|
||||
WNDCLASSEXW
|
||||
RegisterClassEx
|
||||
GetStockObject
|
||||
GetModuleHandle
|
||||
|
||||
MoveWindow
|
||||
GetSystemMetrics
|
||||
SHAppBarMessage
|
||||
ABM_NEW
|
||||
ABM_QUERYPOS
|
||||
ABM_SETPOS
|
||||
ABM_REMOVE
|
||||
ABM_SETAUTOHIDEBAR
|
||||
ABS_AUTOHIDE
|
||||
ABN_POSCHANGED
|
||||
APPBARDATA
|
||||
ABE_TOP
|
||||
ABE_BOTTOM
|
||||
ABE_LEFT
|
||||
ABE_RIGHT
|
||||
SYSTEM_METRICS_INDEX
|
||||
GetDpiForWindow
|
||||
SHQueryUserNotificationState
|
||||
SYSTEM_PARAMETERS_INFO_ACTION
|
||||
WINDOWPOS
|
||||
WM_DISPLAYCHANGE
|
||||
WM_SYSCOMMAND
|
||||
WM_SETTINGCHANGE
|
||||
WM_WINDOWPOSCHANGING
|
||||
WM_SHOWWINDOW
|
||||
WM_SIZE
|
||||
WM_GETMINMAXINFO
|
||||
SetWinEventHook
|
||||
WINDOW_STYLE
|
||||
SC_MINIMIZE
|
||||
SC_MAXIMIZE
|
||||
SET_WINDOW_POS_FLAGS
|
||||
SIZE_MAXIMIZED
|
||||
SIZE_MINIMIZED
|
||||
HWND_NOTOPMOST
|
||||
HWND_TOP
|
||||
HTCAPTION
|
||||
GetClassName
|
||||
EVENT_SYSTEM_FOREGROUND
|
||||
WINEVENT_OUTOFCONTEXT
|
||||
GetWindowThreadProcessId
|
||||
AttachThreadInput
|
||||
|
||||
GetWindowPlacement
|
||||
WINDOWPLACEMENT
|
||||
WM_DPICHANGED
|
||||
WM_DPICHANGED
|
||||
|
||||
@@ -200,14 +200,19 @@
|
||||
|
||||
<!-- Back button -->
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<!--
|
||||
This border is to hold a bit of padding we need when
|
||||
the back button is hidden
|
||||
-->
|
||||
<Border Margin="20,0,0,0" Visibility="{x:Bind ViewModel.CurrentPage.HasBackButton, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
|
||||
<Image
|
||||
Width="20"
|
||||
Margin="20,0,6,0"
|
||||
Margin="0,0,6,0"
|
||||
HorizontalAlignment="Center"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Source="ms-appx:///Assets/icon.svg"
|
||||
Visibility="{x:Bind ViewModel.CurrentPage.IsNested, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
|
||||
Visibility="{x:Bind ViewModel.CurrentPage.IsRootPage, Mode=OneWay}">
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:OpacityAnimation
|
||||
EasingMode="EaseIn"
|
||||
@@ -250,7 +255,7 @@
|
||||
FontSize=14}"
|
||||
FontSize="16"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.CurrentPage.IsNested, Mode=OneWay}">
|
||||
Visibility="{x:Bind ViewModel.CurrentPage.HasBackButton, Mode=OneWay}">
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:OpacityAnimation
|
||||
EasingMode="EaseIn"
|
||||
@@ -297,7 +302,7 @@
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
SourceKey="{x:Bind ViewModel.CurrentPage.Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested20}"
|
||||
Visibility="{x:Bind ViewModel.CurrentPage.IsNested, Mode=OneWay}">
|
||||
Visibility="{x:Bind ViewModel.CurrentPage.IsRootPage, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:OpacityAnimation
|
||||
From="0"
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Text;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.Dock;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Messages;
|
||||
@@ -25,6 +26,7 @@ using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Windows.UI.Core;
|
||||
using WinUIEx;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
using VirtualKey = Windows.System.VirtualKey;
|
||||
|
||||
@@ -47,6 +49,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
IRecipient<ShowConfirmationMessage>,
|
||||
IRecipient<ShowToastMessage>,
|
||||
IRecipient<NavigateToPageMessage>,
|
||||
IRecipient<ShowHideDockMessage>,
|
||||
INotifyPropertyChanged,
|
||||
IDisposable
|
||||
{
|
||||
@@ -64,6 +67,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
private readonly CompositeFormat _pageNavigatedAnnouncement;
|
||||
|
||||
private SettingsWindow? _settingsWindow;
|
||||
private DockWindow? _dockWindow;
|
||||
|
||||
private CancellationTokenSource? _focusAfterLoadedCts;
|
||||
private WeakReference<Page>? _lastNavigatedPageRef;
|
||||
@@ -96,6 +100,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
WeakReferenceMessenger.Default.Register<ShowToastMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<ShowHideDockMessage>(this);
|
||||
|
||||
AddHandler(PreviewKeyDownEvent, new KeyEventHandler(ShellPage_OnPreviewKeyDown), true);
|
||||
AddHandler(KeyDownEvent, new KeyEventHandler(ShellPage_OnKeyDown), false);
|
||||
AddHandler(PointerPressedEvent, new PointerEventHandler(ShellPage_OnPointerPressed), true);
|
||||
@@ -104,6 +110,12 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
var pageAnnouncementFormat = ResourceLoaderInstance.GetString("ScreenReader_Announcement_NavigatedToPage0");
|
||||
_pageNavigatedAnnouncement = CompositeFormat.Parse(pageAnnouncementFormat);
|
||||
|
||||
if (App.Current.Services.GetService<SettingsModel>()!.EnableDock)
|
||||
{
|
||||
_dockWindow = new DockWindow();
|
||||
_dockWindow.Show();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -145,7 +157,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
public void Receive(NavigateToPageMessage message)
|
||||
{
|
||||
// TODO GH #526 This needs more better locking too
|
||||
_ = _queue.TryEnqueue(() =>
|
||||
_ = _queue.TryEnqueue(DispatcherQueuePriority.High, () =>
|
||||
{
|
||||
// Also hide our details pane about here, if we had one
|
||||
HideDetails();
|
||||
@@ -250,10 +262,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeConfirmationDialog(ConfirmResultViewModel vm)
|
||||
{
|
||||
vm.SafeInitializePropertiesSynchronous();
|
||||
}
|
||||
private void InitializeConfirmationDialog(ConfirmResultViewModel vm) => vm.SafeInitializePropertiesSynchronous();
|
||||
|
||||
public void Receive(OpenSettingsMessage message)
|
||||
{
|
||||
@@ -346,10 +355,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
public void Receive(ClearSearchMessage message) => SearchBox.ClearSearch();
|
||||
|
||||
public void Receive(HotkeySummonMessage message)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() => SummonOnUiThread(message));
|
||||
}
|
||||
public void Receive(HotkeySummonMessage message) => _ = DispatcherQueue.TryEnqueue(() => SummonOnUiThread(message));
|
||||
|
||||
public void Receive(SettingsWindowClosedMessage message) => _settingsWindow = null;
|
||||
|
||||
@@ -418,10 +424,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
|
||||
public void Receive(GoBackMessage message)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() => GoBack(message.WithAnimation, message.FocusSearch));
|
||||
}
|
||||
public void Receive(GoBackMessage message) => _ = DispatcherQueue.TryEnqueue(() => GoBack(message.WithAnimation, message.FocusSearch));
|
||||
|
||||
private void GoBack(bool withAnimation = true, bool focusSearch = true)
|
||||
{
|
||||
@@ -462,10 +465,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(GoHomeMessage message)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() => GoHome(withAnimation: message.WithAnimation, focusSearch: message.FocusSearch));
|
||||
}
|
||||
public void Receive(GoHomeMessage message) => _ = DispatcherQueue.TryEnqueue(() => GoHome(withAnimation: message.WithAnimation, focusSearch: message.FocusSearch));
|
||||
|
||||
private void GoHome(bool withAnimation = true, bool focusSearch = true)
|
||||
{
|
||||
@@ -483,6 +483,27 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(ShowHideDockMessage message)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (message.ShowDock)
|
||||
{
|
||||
if (_dockWindow is null)
|
||||
{
|
||||
_dockWindow = new DockWindow();
|
||||
}
|
||||
|
||||
_dockWindow.Show();
|
||||
}
|
||||
else if (_dockWindow is not null)
|
||||
{
|
||||
_dockWindow.Close();
|
||||
_dockWindow = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void BackButton_Clicked(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) => WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
|
||||
|
||||
private void RootFrame_Navigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
|
||||
@@ -754,5 +775,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
_focusAfterLoadedCts?.Cancel();
|
||||
_focusAfterLoadedCts?.Dispose();
|
||||
_focusAfterLoadedCts = null;
|
||||
|
||||
_dockWindow?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.
|
||||
|
||||
@@ -7,6 +7,7 @@ using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
@@ -36,11 +37,14 @@ internal sealed partial class ThemeService : IThemeService, IDisposable
|
||||
private bool _isInitialized;
|
||||
private bool _disposed;
|
||||
private InternalThemeState _currentState;
|
||||
private DockThemeSnapshot _currentDockState;
|
||||
|
||||
public event EventHandler<ThemeChangedEventArgs>? ThemeChanged;
|
||||
|
||||
public ThemeSnapshot Current => Volatile.Read(ref _currentState).Snapshot;
|
||||
|
||||
public DockThemeSnapshot CurrentDockTheme => Volatile.Read(ref _currentDockState);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the theme service. Must be called after the application window is activated and on UI thread.
|
||||
/// </summary>
|
||||
@@ -144,6 +148,60 @@ internal sealed partial class ThemeService : IThemeService, IDisposable
|
||||
// Atomic swap
|
||||
Interlocked.Exchange(ref _currentState, newState);
|
||||
|
||||
// Compute DockThemeSnapshot from DockSettings
|
||||
var dockSettings = _settings.DockSettings;
|
||||
var dockIntensity = Math.Clamp(dockSettings.CustomThemeColorIntensity, 0, 100);
|
||||
IThemeProvider dockProvider = dockIntensity > 0 && dockSettings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor or ColorizationMode.Image
|
||||
? _colorfulThemeProvider
|
||||
: _normalThemeProvider;
|
||||
|
||||
var dockTint = dockSettings.ColorizationMode switch
|
||||
{
|
||||
ColorizationMode.CustomColor => dockSettings.CustomThemeColor,
|
||||
ColorizationMode.WindowsAccentColor => _uiSettings.GetColorValue(UIColorType.Accent),
|
||||
ColorizationMode.Image => dockSettings.CustomThemeColor,
|
||||
_ => Colors.Transparent,
|
||||
};
|
||||
var dockEffectiveTheme = GetElementTheme((ElementTheme)dockSettings.Theme);
|
||||
var dockImageSource = dockSettings.ColorizationMode == ColorizationMode.Image
|
||||
? LoadImageSafe(dockSettings.BackgroundImagePath)
|
||||
: null;
|
||||
var dockStretch = dockSettings.BackgroundImageFit switch
|
||||
{
|
||||
BackgroundImageFit.Fill => Stretch.Fill,
|
||||
_ => Stretch.UniformToFill,
|
||||
};
|
||||
var dockOpacity = Math.Clamp(dockSettings.BackgroundImageOpacity, 0, 100) / 100.0;
|
||||
|
||||
var dockContext = new ThemeContext
|
||||
{
|
||||
Tint = dockTint,
|
||||
ColorIntensity = dockIntensity,
|
||||
Theme = dockEffectiveTheme,
|
||||
BackgroundImageSource = dockImageSource,
|
||||
BackgroundImageStretch = dockStretch,
|
||||
BackgroundImageOpacity = dockOpacity,
|
||||
};
|
||||
var dockBackdrop = dockProvider.GetBackdropParameters(dockContext);
|
||||
var dockBlur = dockSettings.BackgroundImageBlurAmount;
|
||||
var dockBrightness = dockSettings.BackgroundImageBrightness;
|
||||
|
||||
var dockSnapshot = new DockThemeSnapshot
|
||||
{
|
||||
Tint = dockTint,
|
||||
TintIntensity = dockIntensity / 100f,
|
||||
Theme = dockEffectiveTheme,
|
||||
Backdrop = dockSettings.Backdrop,
|
||||
BackgroundImageSource = dockImageSource,
|
||||
BackgroundImageStretch = dockStretch,
|
||||
BackgroundImageOpacity = dockOpacity,
|
||||
BackdropParameters = dockBackdrop,
|
||||
BlurAmount = dockBlur,
|
||||
BackgroundBrightness = dockBrightness / 100f,
|
||||
};
|
||||
|
||||
Interlocked.Exchange(ref _currentDockState, dockSnapshot);
|
||||
|
||||
_resourceSwapper.TryActivateTheme(provider.ThemeKey);
|
||||
ThemeChanged?.Invoke(this, new ThemeChangedEventArgs());
|
||||
}
|
||||
@@ -223,6 +281,20 @@ internal sealed partial class ThemeService : IThemeService, IDisposable
|
||||
},
|
||||
Provider = _normalThemeProvider,
|
||||
};
|
||||
|
||||
_currentDockState = new DockThemeSnapshot
|
||||
{
|
||||
Tint = Colors.Transparent,
|
||||
TintIntensity = 1.0f,
|
||||
Theme = ElementTheme.Light,
|
||||
Backdrop = DockBackdrop.Acrylic,
|
||||
BackdropParameters = new BackdropParameters(Colors.Black, Colors.Black, 0.5f, 0.5f),
|
||||
BackgroundImageOpacity = 1,
|
||||
BackgroundImageSource = null,
|
||||
BackgroundImageStretch = Stretch.Fill,
|
||||
BlurAmount = 0,
|
||||
BackgroundBrightness = 0,
|
||||
};
|
||||
}
|
||||
|
||||
private void RequestReload()
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Microsoft.CmdPal.UI.Settings.DockSettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:cpControls="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dockVm="using:Microsoft.CmdPal.UI.ViewModels.Dock"
|
||||
xmlns:helpers="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI.Settings"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<Grid Padding="16">
|
||||
<StackPanel
|
||||
MaxWidth="1000"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<!--
|
||||
I got these from the samples, but they break XAML hot-reloading,
|
||||
so I commented them out.
|
||||
-->
|
||||
|
||||
<!--<StackPanel.ChildrenTransitions>
|
||||
<EntranceThemeTransition FromVerticalOffset="50" />
|
||||
<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}" />
|
||||
|
||||
<!-- Dock Position -->
|
||||
<controls:SettingsExpander x:Uid="DockAppearance_DockPosition_SettingsExpander" IsExpanded="True">
|
||||
<controls:SettingsExpander.HeaderIcon>
|
||||
<SymbolIcon Symbol="MoveToFolder" />
|
||||
</controls:SettingsExpander.HeaderIcon>
|
||||
<ComboBox
|
||||
x:Name="DockPositionComboBox"
|
||||
MinWidth="120"
|
||||
SelectedIndex="{x:Bind SelectedSideIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="DockAppearance_DockPosition_Left" />
|
||||
<ComboBoxItem x:Uid="DockAppearance_DockPosition_Top" />
|
||||
<ComboBoxItem x:Uid="DockAppearance_DockPosition_Right" />
|
||||
<ComboBoxItem x:Uid="DockAppearance_DockPosition_Bottom" />
|
||||
</ComboBox>
|
||||
<controls:SettingsExpander.Items>
|
||||
|
||||
<!-- Show Labels -->
|
||||
<controls:SettingsCard ContentAlignment="Left">
|
||||
<ptcontrols:CheckBoxWithDescriptionControl x:Uid="DockAppearance_ShowLabels_CheckBox" IsChecked="{x:Bind ShowLabels, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<!-- Theme Section -->
|
||||
<TextBlock x:Uid="DockAppearance_ThemeSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="DockAppearance_AppTheme_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.DockAppearance.ThemeIndex, Mode=TwoWay}">
|
||||
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AppTheme_Mode_System_Automation" Tag="Default">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="Settings_GeneralPage_AppTheme_Mode_System" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AppTheme_Mode_Light_Automation" Tag="Light">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="Settings_GeneralPage_AppTheme_Mode_Light" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AppTheme_Mode_Dark_Automation" Tag="Dark">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="Settings_GeneralPage_AppTheme_Mode_Dark" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Backdrop Style -->
|
||||
<controls:SettingsCard x:Uid="DockAppearance_Backdrop_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox
|
||||
x:Name="BackdropComboBox"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
SelectedIndex="{x:Bind ViewModel.DockAppearance.BackdropIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="DockAppearance_Backdrop_Transparent" />
|
||||
<ComboBoxItem x:Uid="DockAppearance_Backdrop_Acrylic" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Background / Colorization Section -->
|
||||
<controls:SettingsExpander
|
||||
x:Uid="DockAppearance_Background_SettingsExpander"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="{x:Bind ViewModel.DockAppearance.IsColorizationDetailsExpanded, Mode=TwoWay}">
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.DockAppearance.ColorizationModeIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_ColorizationMode_None" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_ColorizationMode_WindowsAccent" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_ColorizationMode_CustomColor" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_ColorizationMode_Image" />
|
||||
</ComboBox>
|
||||
<controls:SettingsExpander.Items>
|
||||
<!-- none -->
|
||||
<controls:SettingsCard
|
||||
x:Uid="DockAppearance_NoBackground_SettingsCard"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ContentAlignment="Vertical"
|
||||
Visibility="{x:Bind ViewModel.DockAppearance.IsNoBackgroundVisible, Mode=OneWay}">
|
||||
<TextBlock
|
||||
x:Uid="DockAppearance_NoBackground_DescriptionTextBlock"
|
||||
Margin="24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- system accent color -->
|
||||
<controls:SettingsCard x:Uid="DockAppearance_WindowsAccentColor_SettingsCard" Visibility="{x:Bind ViewModel.DockAppearance.IsAccentColorControlsVisible, Mode=OneWay}">
|
||||
<controls:SettingsCard.Description>
|
||||
<TextBlock>
|
||||
<Run x:Uid="Settings_GeneralPage_WindowsAccentColor_SettingsCard_Description1" />
|
||||
<Hyperlink
|
||||
Click="OpenWindowsColorsSettings_Click"
|
||||
TextDecorations="None"
|
||||
UnderlineStyle="None">
|
||||
<Run x:Uid="Settings_GeneralPage_WindowsAccentColor_OpenWindowsColorsLinkText" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</controls:SettingsCard.Description>
|
||||
<controls:SettingsCard.Content>
|
||||
<Border
|
||||
MinWidth="32"
|
||||
MinHeight="32"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{x:Bind ViewModel.DockAppearance.EffectiveThemeColor, Mode=OneWay}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
</controls:SettingsCard.Content>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- background image -->
|
||||
<controls:SettingsCard
|
||||
x:Uid="DockAppearance_BackgroundImage_SettingsCard"
|
||||
Description="{x:Bind ViewModel.DockAppearance.BackgroundImagePath, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.DockAppearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<Button x:Uid="Settings_GeneralPage_BackgroundImage_ChooseImageButton" Click="PickBackgroundImage_Click" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="DockAppearance_BackgroundImageBrightness_SettingsCard" Visibility="{x:Bind ViewModel.DockAppearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Maximum="100"
|
||||
Minimum="-100"
|
||||
StepFrequency="1"
|
||||
Value="{x:Bind ViewModel.DockAppearance.BackgroundImageBrightness, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="DockAppearance_BackgroundImageBlur_SettingsCard" Visibility="{x:Bind ViewModel.DockAppearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Maximum="50"
|
||||
Minimum="0"
|
||||
StepFrequency="1"
|
||||
Value="{x:Bind ViewModel.DockAppearance.BackgroundImageBlurAmount, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="DockAppearance_BackgroundImageFit_SettingsCard" Visibility="{x:Bind ViewModel.DockAppearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.DockAppearance.BackgroundImageFitIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="BackgroundImageFit_ComboBoxItem_Fill" />
|
||||
<ComboBoxItem x:Uid="BackgroundImageFit_ComboBoxItem_Stretch" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Background tint color and intensity -->
|
||||
<controls:SettingsCard x:Uid="DockAppearance_BackgroundTint_SettingsCard" Visibility="{x:Bind ViewModel.DockAppearance.IsCustomTintVisible, Mode=OneWay}">
|
||||
<cpControls:ColorPickerButton
|
||||
HasSelectedColor="True"
|
||||
IsAlphaEnabled="False"
|
||||
PaletteColors="{x:Bind ViewModel.DockAppearance.Swatches}"
|
||||
SelectedColor="{x:Bind ViewModel.DockAppearance.ThemeColor, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="DockAppearance_BackgroundTintIntensity_SettingsCard" Visibility="{x:Bind ViewModel.DockAppearance.IsCustomTintIntensityVisible, Mode=OneWay}">
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Maximum="100"
|
||||
Minimum="1"
|
||||
StepFrequency="1"
|
||||
Value="{x:Bind ViewModel.DockAppearance.ColorIntensity, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Reset background image properties -->
|
||||
<controls:SettingsCard x:Uid="DockAppearance_BackgroundImage_ResetProperties_SettingsCard" Visibility="{x:Bind ViewModel.DockAppearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<Button x:Uid="Settings_GeneralPage_Background_ResetImagePropertiesButton" Command="{x:Bind ViewModel.DockAppearance.ResetBackgroundImagePropertiesCommand}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<!-- Bands Section -->
|
||||
<TextBlock x:Uid="DockBandsSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<ItemsRepeater ItemsSource="{x:Bind AllDockBandItems, Mode=OneWay}">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="{StaticResource SettingsCardSpacing}" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate x:DataType="dockVm:DockBandSettingsViewModel">
|
||||
<controls:SettingsCard
|
||||
Description="{x:Bind Description, Mode=OneWay}"
|
||||
Header="{x:Bind Title, Mode=OneWay}"
|
||||
IsClickEnabled="False">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<cpControls:ContentIcon>
|
||||
<cpControls:ContentIcon.Content>
|
||||
<cpControls:IconBox
|
||||
Width="20"
|
||||
Height="20"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind helpers:IconCacheProvider.SourceRequested20}" />
|
||||
</cpControls:ContentIcon.Content>
|
||||
</cpControls:ContentIcon>
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
<ToggleSwitch IsOn="{x:Bind IsPinned, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,218 @@
|
||||
// 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.Diagnostics;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.Windows.Storage.Pickers;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Settings;
|
||||
|
||||
public sealed partial class DockSettingsPage : Page
|
||||
{
|
||||
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
|
||||
internal SettingsViewModel ViewModel { get; }
|
||||
|
||||
public List<DockBandSettingsViewModel> AllDockBandItems => GetAllBandSettings();
|
||||
|
||||
public DockSettingsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
var themeService = App.Current.Services.GetService<IThemeService>()!;
|
||||
var topLevelCommandManager = App.Current.Services.GetService<TopLevelCommandManager>()!;
|
||||
|
||||
ViewModel = new SettingsViewModel(settings, topLevelCommandManager, _mainTaskScheduler, themeService);
|
||||
|
||||
// Initialize UI state
|
||||
InitializeSettings();
|
||||
}
|
||||
|
||||
private void InitializeSettings()
|
||||
{
|
||||
// Initialize UI controls to match current settings
|
||||
DockPositionComboBox.SelectedIndex = SelectedSideIndex;
|
||||
BackdropComboBox.SelectedIndex = SelectedBackdropIndex;
|
||||
}
|
||||
|
||||
private async void PickBackgroundImage_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (XamlRoot?.ContentIslandEnvironment is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var windowId = XamlRoot?.ContentIslandEnvironment?.AppWindowId ?? new Microsoft.UI.WindowId(0);
|
||||
|
||||
var picker = new FileOpenPicker(windowId)
|
||||
{
|
||||
CommitButtonText = ViewModels.Properties.Resources.builtin_settings_appearance_pick_background_image_title!,
|
||||
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
|
||||
ViewMode = PickerViewMode.Thumbnail,
|
||||
};
|
||||
|
||||
string[] extensions = [".png", ".bmp", ".jpg", ".jpeg", ".jfif", ".gif", ".tiff", ".tif", ".webp", ".jxr"];
|
||||
foreach (var ext in extensions)
|
||||
{
|
||||
picker.FileTypeFilter!.Add(ext);
|
||||
}
|
||||
|
||||
var file = await picker.PickSingleFileAsync()!;
|
||||
if (file != null)
|
||||
{
|
||||
ViewModel.DockAppearance.BackgroundImagePath = file.Path ?? string.Empty;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to pick background image file for dock", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenWindowsColorsSettings_Click(Hyperlink sender, HyperlinkClickEventArgs args)
|
||||
{
|
||||
// LOAD BEARING (or BEAR LOADING?): Process.Start with UseShellExecute inside a XAML input event can trigger WinUI reentrancy
|
||||
// and cause FailFast crashes. Task.Run moves the call off the UI thread to prevent hard process termination.
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = Process.Start(new ProcessStartInfo("ms-settings:colors") { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to open Windows Settings", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Property bindings for ComboBoxes
|
||||
public int SelectedDockSizeIndex
|
||||
{
|
||||
get => DockSizeToSelectedIndex(ViewModel.Dock_DockSize);
|
||||
set => ViewModel.Dock_DockSize = SelectedIndexToDockSize(value);
|
||||
}
|
||||
|
||||
public int SelectedSideIndex
|
||||
{
|
||||
get => SideToSelectedIndex(ViewModel.Dock_Side);
|
||||
set => ViewModel.Dock_Side = SelectedIndexToSide(value);
|
||||
}
|
||||
|
||||
public int SelectedBackdropIndex
|
||||
{
|
||||
get => BackdropToSelectedIndex(ViewModel.Dock_Backdrop);
|
||||
set => ViewModel.Dock_Backdrop = SelectedIndexToBackdrop(value);
|
||||
}
|
||||
|
||||
public bool ShowLabels
|
||||
{
|
||||
get => ViewModel.Dock_ShowLabels;
|
||||
set => ViewModel.Dock_ShowLabels = value;
|
||||
}
|
||||
|
||||
// Conversion methods for ComboBox bindings
|
||||
private static int DockSizeToSelectedIndex(DockSize size) => size switch
|
||||
{
|
||||
DockSize.Small => 0,
|
||||
DockSize.Medium => 1,
|
||||
DockSize.Large => 2,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
private static DockSize SelectedIndexToDockSize(int index) => index switch
|
||||
{
|
||||
0 => DockSize.Small,
|
||||
1 => DockSize.Medium,
|
||||
2 => DockSize.Large,
|
||||
_ => DockSize.Small,
|
||||
};
|
||||
|
||||
private static int SideToSelectedIndex(DockSide side) => side switch
|
||||
{
|
||||
DockSide.Left => 0,
|
||||
DockSide.Top => 1,
|
||||
DockSide.Right => 2,
|
||||
DockSide.Bottom => 3,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
private static DockSide SelectedIndexToSide(int index) => index switch
|
||||
{
|
||||
0 => DockSide.Left,
|
||||
1 => DockSide.Top,
|
||||
2 => DockSide.Right,
|
||||
3 => DockSide.Bottom,
|
||||
_ => DockSide.Top,
|
||||
};
|
||||
|
||||
private static int BackdropToSelectedIndex(DockBackdrop backdrop) => backdrop switch
|
||||
{
|
||||
DockBackdrop.Transparent => 0,
|
||||
DockBackdrop.Acrylic => 1,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
private static DockBackdrop SelectedIndexToBackdrop(int index) => index switch
|
||||
{
|
||||
0 => DockBackdrop.Transparent,
|
||||
1 => DockBackdrop.Acrylic,
|
||||
_ => DockBackdrop.Acrylic,
|
||||
};
|
||||
|
||||
private List<TopLevelViewModel> GetAllBands()
|
||||
{
|
||||
var allBands = new List<TopLevelViewModel>();
|
||||
|
||||
var tlcManager = App.Current.Services.GetService<TopLevelCommandManager>()!;
|
||||
|
||||
foreach (var item in tlcManager.DockBands)
|
||||
{
|
||||
if (item.IsDockBand)
|
||||
{
|
||||
allBands.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return allBands;
|
||||
}
|
||||
|
||||
private List<DockBandSettingsViewModel> GetAllBandSettings()
|
||||
{
|
||||
var allSettings = new List<DockBandSettingsViewModel>();
|
||||
|
||||
// var allBands = GetAllBands();
|
||||
var tlcManager = App.Current.Services.GetService<TopLevelCommandManager>()!;
|
||||
var settingsModel = App.Current.Services.GetService<SettingsModel>()!;
|
||||
var dockViewModel = App.Current.Services.GetService<DockViewModel>()!;
|
||||
var allBands = tlcManager.DockBands;
|
||||
foreach (var band in allBands)
|
||||
{
|
||||
var setting = band.DockBandSettings;
|
||||
if (setting is not null)
|
||||
{
|
||||
var bandVm = dockViewModel.FindBandByTopLevel(band);
|
||||
allSettings.Add(new(
|
||||
dockSettingsModel: setting,
|
||||
topLevelAdapter: band,
|
||||
bandViewModel: bandVm,
|
||||
settingsModel: settingsModel
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return allSettings;
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,12 @@
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_Extensions"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="Extensions" />
|
||||
<!-- xF596 is HolePunchLandscapeTop -->
|
||||
<NavigationViewItem
|
||||
x:Name="DockSettingsPageNavItem"
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_Dock"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="Dock" />
|
||||
<!-- "Internal Tools" page item is added dynamically from code -->
|
||||
</NavigationView.MenuItems>
|
||||
<Grid>
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
public ObservableCollection<Crumb> BreadCrumbs { get; } = [];
|
||||
|
||||
// Gets or sets optional action invoked after NavigationView is loaded.
|
||||
public Action NavigationViewLoaded { get; set; } = () => { };
|
||||
public Action? NavigationViewLoaded { get; set; }
|
||||
|
||||
public SettingsWindow()
|
||||
{
|
||||
@@ -125,6 +125,9 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
case "Extensions":
|
||||
pageType = typeof(ExtensionsPage);
|
||||
break;
|
||||
case "Dock":
|
||||
pageType = typeof(DockSettingsPage);
|
||||
break;
|
||||
case "Internal":
|
||||
pageType = typeof(InternalPage);
|
||||
break;
|
||||
@@ -142,6 +145,18 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
if (pageType is not null)
|
||||
{
|
||||
NavFrame.Navigate(pageType);
|
||||
|
||||
// Now, make sure to actually select the correct menu item too
|
||||
foreach (var obj in NavView.MenuItems)
|
||||
{
|
||||
if (obj is NavigationViewItem item)
|
||||
{
|
||||
if (item.Tag is string s && s == page)
|
||||
{
|
||||
NavView.SelectedItem = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +307,12 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
var pageType = RS_.GetString("Settings_PageTitles_ExtensionsPage");
|
||||
BreadCrumbs.Add(new(pageType, pageType));
|
||||
}
|
||||
else if (e.SourcePageType == typeof(DockSettingsPage))
|
||||
{
|
||||
NavView.SelectedItem = DockSettingsPageNavItem;
|
||||
var pageType = RS_.GetString("Settings_PageTitles_DockPage");
|
||||
BreadCrumbs.Add(new(pageType, pageType));
|
||||
}
|
||||
else if (e.SourcePageType == typeof(ExtensionPage) && e.Parameter is ProviderSettingsViewModel vm)
|
||||
{
|
||||
NavView.SelectedItem = ExtensionPageNavItem;
|
||||
|
||||
@@ -395,6 +395,9 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="Settings_GeneralPage_NavigationViewItem_Extensions.Content" xml:space="preserve">
|
||||
<value>Extensions</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_NavigationViewItem_Dock.Content" xml:space="preserve">
|
||||
<value>Dock (Preview)</value>
|
||||
</data>
|
||||
<data name="SettingsButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Open Command Palette settings</value>
|
||||
</data>
|
||||
@@ -404,6 +407,12 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="BehaviorSettingsHeader.Text" xml:space="preserve">
|
||||
<value>Behavior</value>
|
||||
</data>
|
||||
<data name="DockAppearanceSettingsHeader.Text" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="DockBandsSettingsHeader.Text" xml:space="preserve">
|
||||
<value>Bands</value>
|
||||
</data>
|
||||
<data name="ContextFilterBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Search commands...</value>
|
||||
</data>
|
||||
@@ -418,6 +427,12 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_DisableAnimations_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Disable animations when switching between pages</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_EnableDock_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Enable Dock</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_EnableDock_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Enable a toolbar with quick access to commands</value>
|
||||
</data>
|
||||
<data name="BackButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Back</value>
|
||||
@@ -625,6 +640,9 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="Settings_PageTitles_ExtensionsPage" xml:space="preserve">
|
||||
<value>Extensions</value>
|
||||
</data>
|
||||
<data name="Settings_PageTitles_DockPage" xml:space="preserve">
|
||||
<value>Dock</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_EscapeKeyBehavior_Option_DismissEmptySearchOrGoBack.Content" xml:space="preserve">
|
||||
<value>Clear search first, then go back</value>
|
||||
</data>
|
||||
@@ -739,6 +757,75 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="Settings_GeneralPage_VersionNo" xml:space="preserve">
|
||||
<value>Version {0}</value>
|
||||
</data>
|
||||
<data name="Settings_NavigationViewItem_DockAppearance.Content" xml:space="preserve">
|
||||
<value>Dock Appearance</value>
|
||||
</data>
|
||||
<data name="Settings_PageTitles_DockAppearancePage" xml:space="preserve">
|
||||
<value>Dock Appearance</value>
|
||||
</data>
|
||||
<data name="DockAppearance_ThemeSettingsHeader.Text" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="DockAppearance_AppTheme_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Dock theme mode</value>
|
||||
</data>
|
||||
<data name="DockAppearance_AppTheme_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Select which theme to display for the dock</value>
|
||||
</data>
|
||||
<data name="DockAppearance_Backdrop_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Material</value>
|
||||
</data>
|
||||
<data name="DockAppearance_Backdrop_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Select the visual material used for the dock</value>
|
||||
</data>
|
||||
<data name="DockAppearance_Backdrop_Mica.Content" xml:space="preserve">
|
||||
<value>Mica</value>
|
||||
</data>
|
||||
<data name="DockAppearance_Backdrop_Transparent.Content" xml:space="preserve">
|
||||
<value>Transparent</value>
|
||||
</data>
|
||||
<data name="DockAppearance_Backdrop_Acrylic.Content" xml:space="preserve">
|
||||
<value>Acrylic</value>
|
||||
</data>
|
||||
<data name="DockAppearance_Background_SettingsExpander.Header" xml:space="preserve">
|
||||
<value>Background</value>
|
||||
</data>
|
||||
<data name="DockAppearance_Background_SettingsExpander.Description" xml:space="preserve">
|
||||
<value>Choose a custom background color or image for the dock</value>
|
||||
</data>
|
||||
<data name="DockAppearance_ColorizationMode.Header" xml:space="preserve">
|
||||
<value>Colorization mode</value>
|
||||
</data>
|
||||
<data name="DockAppearance_NoBackground_SettingsCard.Header" xml:space="preserve">
|
||||
<value>No background</value>
|
||||
</data>
|
||||
<data name="DockAppearance_NoBackground_DescriptionTextBlock.Text" xml:space="preserve">
|
||||
<value>No settings</value>
|
||||
</data>
|
||||
<data name="DockAppearance_WindowsAccentColor_SettingsCard.Header" xml:space="preserve">
|
||||
<value>System accent color</value>
|
||||
</data>
|
||||
<data name="DockAppearance_BackgroundImage_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Background image</value>
|
||||
</data>
|
||||
<data name="DockAppearance_BackgroundImageBrightness_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Background image brightness</value>
|
||||
</data>
|
||||
<data name="DockAppearance_BackgroundImageBlur_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Background image blur</value>
|
||||
</data>
|
||||
<data name="DockAppearance_BackgroundImageFit_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Background image fit</value>
|
||||
</data>
|
||||
<data name="DockAppearance_BackgroundTint_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Color tint</value>
|
||||
</data>
|
||||
<data name="DockAppearance_BackgroundTintIntensity_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Color intensity</value>
|
||||
</data>
|
||||
<data name="DockAppearance_BackgroundImage_ResetProperties_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Restore defaults</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackdropOpacity_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Opacity</value>
|
||||
</data>
|
||||
@@ -807,6 +894,30 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="ConfigureShortcutText.Text" xml:space="preserve">
|
||||
<value>Assign shortcut</value>
|
||||
</data>
|
||||
<data name="DockAppearance_DockPosition_SettingsExpander.Header" xml:space="preserve">
|
||||
<value>Dock position and appearance</value>
|
||||
</data>
|
||||
<data name="DockAppearance_DockPosition_SettingsExpander.Description" xml:space="preserve">
|
||||
<value>Choose where the dock appears on your screen</value>
|
||||
</data>
|
||||
<data name="DockAppearance_DockPosition_Left.Content" xml:space="preserve">
|
||||
<value>Left</value>
|
||||
</data>
|
||||
<data name="DockAppearance_DockPosition_Top.Content" xml:space="preserve">
|
||||
<value>Top</value>
|
||||
</data>
|
||||
<data name="DockAppearance_DockPosition_Right.Content" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
</data>
|
||||
<data name="DockAppearance_DockPosition_Bottom.Content" xml:space="preserve">
|
||||
<value>Bottom</value>
|
||||
</data>
|
||||
<data name="DockAppearance_ShowLabels_CheckBox.Header" xml:space="preserve">
|
||||
<value>Show labels</value>
|
||||
</data>
|
||||
<data name="DockAppearance_ShowLabels_CheckBox.Description" xml:space="preserve">
|
||||
<value>Show labels for dock items by default</value>
|
||||
</data>
|
||||
<data name="top_level_pin_command_name" xml:space="preserve">
|
||||
<value>Pin to home</value>
|
||||
<comment>Command name for pinning an item to the top level list of commands</comment>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?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">
|
||||
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
442
src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/TeachingTip.xaml
Normal file
442
src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/TeachingTip.xaml
Normal file
@@ -0,0 +1,442 @@
|
||||
<?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">
|
||||
|
||||
<!-- TeachingTip doesn't have a simple way to get rid of the close button. It requires a full custom style :( -->
|
||||
<Style x:Key="TeachingTipWithoutCloseButtonStyle" TargetType="TeachingTip">
|
||||
<Setter Property="Background" Value="{ThemeResource TeachingTipBackgroundBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TeachingTipForegroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource TeachingTipBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource TeachingTipContentBorderThicknessUntargeted}" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource OverlayCornerRadius}" />
|
||||
<Setter Property="ActionButtonStyle" Value="{ThemeResource DefaultButtonStyle}" />
|
||||
<Setter Property="CloseButtonStyle" Value="{ThemeResource DefaultButtonStyle}" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TeachingTip">
|
||||
<Border
|
||||
x:Name="Container"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent">
|
||||
<Grid
|
||||
MinWidth="{ThemeResource TeachingTipMinWidth}"
|
||||
MinHeight="{ThemeResource TeachingTipMinHeight}"
|
||||
MaxWidth="{ThemeResource TeachingTipMaxWidth}"
|
||||
MaxHeight="{ThemeResource TeachingTipMaxHeight}"
|
||||
AutomationProperties.Name="{TemplateBinding AutomationProperties.Name}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource TeachingTipTailShortSideLength}" />
|
||||
<ColumnDefinition Width="{StaticResource TeachingTipTailMargin}" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="{StaticResource TeachingTipTailMargin}" />
|
||||
<ColumnDefinition Width="{StaticResource TeachingTipTailShortSideLength}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{StaticResource TeachingTipTailShortSideLength}" />
|
||||
<RowDefinition Height="{StaticResource TeachingTipTailMargin}" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="{StaticResource TeachingTipTailMargin}" />
|
||||
<RowDefinition Height="{StaticResource TeachingTipTailShortSideLength}" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid
|
||||
x:Name="TailOcclusionGrid"
|
||||
Grid.RowSpan="5"
|
||||
Grid.ColumnSpan="5"
|
||||
MinWidth="{ThemeResource TeachingTipMinWidth}"
|
||||
MinHeight="{ThemeResource TeachingTipMinHeight}"
|
||||
MaxWidth="{ThemeResource TeachingTipMaxWidth}"
|
||||
MaxHeight="{ThemeResource TeachingTipMaxHeight}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource TeachingTipTailShortSideLength}" />
|
||||
<ColumnDefinition Width="{StaticResource TeachingTipTailMargin}" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="{StaticResource TeachingTipTailMargin}" />
|
||||
<ColumnDefinition Width="{StaticResource TeachingTipTailShortSideLength}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{StaticResource TeachingTipTailShortSideLength}" />
|
||||
<RowDefinition Height="{StaticResource TeachingTipTailMargin}" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="{StaticResource TeachingTipTailMargin}" />
|
||||
<RowDefinition Height="{StaticResource TeachingTipTailShortSideLength}" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid
|
||||
x:Name="ContentRootGrid"
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
AutomationProperties.LandmarkType="Custom"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
FlowDirection="{TemplateBinding FlowDirection}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border
|
||||
x:Name="HeroContentBorder"
|
||||
Grid.Row="0"
|
||||
Background="{TemplateBinding Background}"
|
||||
Child="{TemplateBinding HeroContent}" />
|
||||
<Grid x:Name="NonHeroContentRootGrid" Grid.Row="1">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="{StaticResource TeachingTipContentMargin}">
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="IconPresenter"
|
||||
Grid.Column="0"
|
||||
Foreground="{TemplateBinding Foreground}">
|
||||
<Border Child="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.IconElement}" />
|
||||
</ContentPresenter>
|
||||
<StackPanel x:Name="TitlesStackPanel" Grid.Column="1">
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
Grid.Column="0"
|
||||
FontFamily="{TemplateBinding FontFamily}"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TeachingTipTitleForegroundBrush}"
|
||||
Text="{TemplateBinding Title}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="Collapsed" />
|
||||
<TextBlock
|
||||
x:Name="SubtitleTextBlock"
|
||||
Grid.Row="1"
|
||||
FontFamily="{TemplateBinding FontFamily}"
|
||||
Foreground="{ThemeResource TeachingTipSubtitleForegroundBrush}"
|
||||
Text="{TemplateBinding Subtitle}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<ContentPresenter
|
||||
x:Name="MainContentPresenter"
|
||||
Grid.Row="1"
|
||||
Background="{TemplateBinding Background}"
|
||||
Content="{TemplateBinding Content}"
|
||||
FontFamily="{TemplateBinding FontFamily}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontStretch="{TemplateBinding FontStretch}"
|
||||
FontStyle="{TemplateBinding FontStyle}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
Foreground="{TemplateBinding Foreground}" />
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
x:Name="ActionButton"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{TemplateBinding ActionButtonCommand}"
|
||||
CommandParameter="{TemplateBinding ActionButtonCommandParameter}"
|
||||
Style="{TemplateBinding ActionButtonStyle}">
|
||||
<ContentPresenter Content="{TemplateBinding ActionButtonContent}" TextWrapping="WrapWholeWords" />
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="CloseButton"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{TemplateBinding CloseButtonCommand}"
|
||||
CommandParameter="{TemplateBinding CloseButtonCommandParameter}"
|
||||
Style="{TemplateBinding CloseButtonStyle}">
|
||||
<ContentPresenter Content="{TemplateBinding CloseButtonContent}" TextWrapping="WrapWholeWords" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<Button
|
||||
x:Name="AlternateCloseButton"
|
||||
Style="{ThemeResource AlternateCloseButtonStyle}"
|
||||
Visibility="Collapsed" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Polygon
|
||||
x:Name="TailPolygon"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{TemplateBinding Background}"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
StrokeThickness="{StaticResource TeachingTipBorderThickness}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="LightDismissStates">
|
||||
<VisualState x:Name="LightDismiss">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Fill" Value="{ThemeResource TeachingTipTransientBackground}" />
|
||||
<Setter Target="ContentRootGrid.Background" Value="{ThemeResource TeachingTipTransientBackground}" />
|
||||
<Setter Target="MainContentPresenter.Background" Value="{ThemeResource TeachingTipTransientBackground}" />
|
||||
<Setter Target="HeroContentBorder.Background" Value="{ThemeResource TeachingTipTransientBackground}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NormalDismiss" />
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="ButtonsStates">
|
||||
<VisualState x:Name="NoButtonsVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="CloseButton.Visibility" Value="Collapsed" />
|
||||
<Setter Target="ActionButton.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="ActionButtonVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="CloseButton.Visibility" Value="Collapsed" />
|
||||
<Setter Target="ActionButton.Visibility" Value="Visible" />
|
||||
<Setter Target="ActionButton.(Grid.ColumnSpan)" Value="2" />
|
||||
<Setter Target="ActionButton.Margin" Value="{ThemeResource TeachingTipButtonPanelMargin}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CloseButtonVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="CloseButton.Visibility" Value="Visible" />
|
||||
<Setter Target="CloseButton.Margin" Value="{ThemeResource TeachingTipButtonPanelMargin}" />
|
||||
<Setter Target="CloseButton.(Grid.Column)" Value="0" />
|
||||
<Setter Target="CloseButton.(Grid.ColumnSpan)" Value="2" />
|
||||
<Setter Target="ActionButton.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="BothButtonsVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="CloseButton.Visibility" Value="Visible" />
|
||||
<Setter Target="CloseButton.Margin" Value="{ThemeResource TeachingTipRightButtonMargin}" />
|
||||
<Setter Target="ActionButton.Visibility" Value="Visible" />
|
||||
<Setter Target="ActionButton.(Grid.Column)" Value="0" />
|
||||
<Setter Target="ActionButton.Margin" Value="{ThemeResource TeachingTipLeftButtonMargin}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="ContentStates">
|
||||
<VisualState x:Name="Content">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainContentPresenter.Margin" Value="{StaticResource TeachingTipMainContentPresentMargin}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NoContent">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainContentPresenter.Margin" Value="{StaticResource TeachingTipMainContentAbsentMargin}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="CloseButtonLocations">
|
||||
<VisualState x:Name="HeaderCloseButton">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TitlesStackPanel.Margin" Value="{StaticResource TeachingTipTitleStackPanelMarginWithHeaderCloseButton}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="FooterCloseButton">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TitlesStackPanel.Margin" Value="{StaticResource TeachingTipTitleStackPanelMarginWithFooterCloseButton}" />
|
||||
<Setter Target="AlternateCloseButton.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="IconStates">
|
||||
<VisualState x:Name="Icon">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="IconPresenter.Margin" Value="{StaticResource TeachingTipIconPresenterMarginWithIcon}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NoIcon">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="IconPresenter.Margin" Value="{StaticResource TeachingTipIconPresenterMarginWithoutIcon}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="HeroContentPlacementStates">
|
||||
<VisualState x:Name="HeroContentTop">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="HeroContentBorder.(Grid.Row)" Value="0" />
|
||||
<Setter Target="HeroContentBorder.CornerRadius" Value="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TopCornerRadiusFilterConverter}, FallbackValue=0}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="HeroContentBottom">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="HeroContentBorder.(Grid.Row)" Value="2" />
|
||||
<Setter Target="HeroContentBorder.CornerRadius" Value="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BottomCornerRadiusFilterConverter}, FallbackValue=0}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="PlacementStates">
|
||||
<VisualState x:Name="Top">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,0 10,10, 20,0" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="4" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="2" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Center" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginTop}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Bottom">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,10 10,0 20,10" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="0" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="2" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Center" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Top" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginBottom}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Left">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,0 10,10 0,20" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="2" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="4" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Right" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginLeft}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Right">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="10,0 0,10 10,20" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="2" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="0" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Left" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginRight}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="TopRight">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,0 10,10 20,0" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="4" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="2" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Left" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginTop}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="TopLeft">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,0 10,10 20,0" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="4" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="2" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Right" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginTop}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="BottomRight">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,10 10,0 20,10" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="0" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="2" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Left" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Top" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginBottom}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="BottomLeft">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,10 10,0 20,10" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="0" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="2" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Right" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Top" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginBottom}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="LeftTop">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,0 10,10 0,20" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="2" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="4" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Right" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginLeft}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="LeftBottom">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,0 10,10 0,20" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="2" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="4" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Right" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Top" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginLeft}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="RightTop">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="10,0 0,10 10,20" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="2" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="0" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Left" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginRight}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="RightBottom">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="10,0 0,10 10,20" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="2" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="0" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Left" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Top" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginRight}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Center">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Visible" />
|
||||
<Setter Target="TailPolygon.Points" Value="0,0 10,10, 20,0" />
|
||||
<Setter Target="TailPolygon.(Grid.Row)" Value="4" />
|
||||
<Setter Target="TailPolygon.(Grid.Column)" Value="2" />
|
||||
<Setter Target="TailPolygon.HorizontalAlignment" Value="Center" />
|
||||
<Setter Target="TailPolygon.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="TailPolygon.Margin" Value="{StaticResource TeachingTipTailPolygonMarginTop}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Untargeted">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TailPolygon.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="TitleBlockStates">
|
||||
<VisualState x:Name="ShowTitleTextBlock">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TitleTextBlock.Visibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CollapseTitleTextBlock" />
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="SubtitleBlockStates">
|
||||
<VisualState x:Name="ShowSubtitleTextBlock">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SubtitleTextBlock.Visibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CollapseSubtitleTextBlock" />
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
Reference in New Issue
Block a user