diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index fee0314208..50a4b55e5b 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -315,6 +315,7 @@ xef xes PACKAGEVERSIONNUMBER APPXMANIFESTVERSION +PROGMAN # MRU lists CACHEWRITE @@ -325,6 +326,14 @@ REGSTR # Misc Win32 APIs and PInvokes INVOKEIDLIST MEMORYSTATUSEX +ABE +HTCAPTION +POSCHANGED +QUERYPOS +SETAUTOHIDEBAR +WINDOWPOS +WINEVENTPROC +WORKERW # PowerRename metadata pattern abbreviations (used in tests and regex patterns) DDDD @@ -349,3 +358,6 @@ nostdin # Performance counter keys engtype Nonpaged + +# XAML +Untargeted diff --git a/src/modules/cmdpal/.wt.json b/src/modules/cmdpal/.wt.json index 230329e876..4488d6b025 100644 --- a/src/modules/cmdpal/.wt.json +++ b/src/modules/cmdpal/.wt.json @@ -26,6 +26,11 @@ "input": "pushd .\\ExtensionTemplate\\ ; git archive -o ..\\Microsoft.CmdPal.UI.ViewModels\\Assets\\template.zip HEAD -- .\\TemplateCmdPalExtension\\ ; popd", "name": "Update template project", "description": "zips up the ExtensionTemplate into our assets. Run this in the cmdpal/ directory." + }, + { + "input": " .\\extensionsdk\\nuget\\BuildSDKHelper.ps1 -VersionOfSDK 0.0.1", + "name": "Build SDK", + "description": "Builds the SDK nuget package with the specified version." } ] } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/CoreLogger.cs b/src/modules/cmdpal/Microsoft.CmdPal.Common/CoreLogger.cs index 4540544d05..650cebec32 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.Common/CoreLogger.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.Common/CoreLogger.cs @@ -2,8 +2,6 @@ // 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; - namespace Microsoft.CmdPal.Common; public static class CoreLogger @@ -15,6 +13,8 @@ public static class CoreLogger private static ILogger? _logger; + public static ILogger? Instance => _logger; + public static void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0) { _logger?.LogError(message, ex, memberName, sourceFilePath, sourceLineNumber); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/PinnedDockItem.cs b/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/PinnedDockItem.cs new file mode 100644 index 0000000000..295ea06232 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.Common/Helpers/PinnedDockItem.cs @@ -0,0 +1,24 @@ +// 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.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Common.Helpers; + +public partial class PinnedDockItem : WrappedDockItem +{ + public override string Title => $"{base.Title} ({Properties.Resources.PinnedItemSuffix})"; + + public PinnedDockItem(ICommand command) + : base(command, command.Name) + { + } + + public PinnedDockItem(IListItem item, string id) + : base([item], id, item.Title) + { + Icon = item.Icon; + } +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.Designer.cs b/src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.Designer.cs index 2837c1a1bd..f79dbd8817 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.Designer.cs @@ -72,5 +72,14 @@ namespace Microsoft.CmdPal.Common.Properties { return ResourceManager.GetString("ErrorReport_Global_Preamble", resourceCulture); } } + + /// + /// Looks up a localized string similar to Pinned. + /// + internal static string PinnedItemSuffix { + get { + return ResourceManager.GetString("PinnedItemSuffix", resourceCulture); + } + } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.resx b/src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.resx index e2aa867ad2..0939fc052a 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.resx +++ b/src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.resx @@ -1,4 +1,4 @@ - + - + @@ -21,6 +22,7 @@ + @@ -29,6 +31,12 @@ 240 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ScrollContainer.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ScrollContainer.xaml.cs new file mode 100644 index 0000000000..730ddf3f35 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ScrollContainer.xaml.cs @@ -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; + } + } +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockControl.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockControl.xaml new file mode 100644 index 0000000000..1987bb0675 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/DockControl.xaml @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +