mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Continuing part deux
This commit is contained in:
@@ -4,17 +4,16 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class BeginInvokeEvent : TelemetryEventBase, IEvent
|
||||
public class BeginInvokeEvent : EventBase, IEvent
|
||||
{
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
public BeginInvokeEvent()
|
||||
{
|
||||
@@ -4,17 +4,16 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class ColdLaunchEvent : TelemetryEventBase, IEvent
|
||||
public class ColdLaunchEvent : EventBase, IEvent
|
||||
{
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
public ColdLaunchEvent()
|
||||
{
|
||||
@@ -4,15 +4,14 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class DismissedOnEscEvent : TelemetryEventBase, IEvent
|
||||
public class DismissedOnEscEvent : EventBase, IEvent
|
||||
{
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -4,15 +4,14 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class DismissedOnLostFocusEvent : TelemetryEventBase, IEvent
|
||||
public class DismissedOnLostFocusEvent : EventBase, IEvent
|
||||
{
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -4,15 +4,14 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class HotkeySummonedEvent : TelemetryEventBase, IEvent
|
||||
public class HotkeySummonedEvent : EventBase, IEvent
|
||||
{
|
||||
public bool Global { get; set; }
|
||||
|
||||
@@ -21,5 +20,5 @@ public class HotkeySummonedEvent : TelemetryEventBase, IEvent
|
||||
Global = global;
|
||||
}
|
||||
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -4,15 +4,14 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class OpenPageEvent : TelemetryEventBase, IEvent
|
||||
public class OpenPageEvent : EventBase, IEvent
|
||||
{
|
||||
public int PageDepth { get; set; }
|
||||
|
||||
@@ -26,5 +25,5 @@ public class OpenPageEvent : TelemetryEventBase, IEvent
|
||||
EventName = "CmdPal_OpenPage";
|
||||
}
|
||||
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -4,17 +4,16 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class OpenUriEvent : TelemetryEventBase, IEvent
|
||||
public class OpenUriEvent : EventBase, IEvent
|
||||
{
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
public string Uri { get; set; }
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class ProcessStartedEvent : TelemetryEventBase, IEvent
|
||||
public class ProcessStartedEvent : EventBase, IEvent
|
||||
{
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -4,17 +4,16 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class ReactivateInstanceEvent : TelemetryEventBase, IEvent
|
||||
public class ReactivateInstanceEvent : EventBase, IEvent
|
||||
{
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
public ReactivateInstanceEvent()
|
||||
{
|
||||
@@ -4,17 +4,16 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class RunCommandEvent : TelemetryEventBase, IEvent
|
||||
public class RunCommandEvent : EventBase, IEvent
|
||||
{
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
public string Command { get; set; }
|
||||
|
||||
@@ -4,17 +4,16 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class RunQueryEvent : TelemetryEventBase, IEvent
|
||||
public class RunQueryEvent : EventBase, IEvent
|
||||
{
|
||||
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
public string Query { get; set; }
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers;
|
||||
namespace Microsoft.CommandPalette.UI.Models.Helpers;
|
||||
|
||||
internal static class ResourceLoaderInstance
|
||||
public static class ResourceLoaderInstance
|
||||
{
|
||||
internal static ResourceLoader ResourceLoader { get; private set; }
|
||||
public static ResourceLoader ResourceLoader { get; private set; }
|
||||
|
||||
static ResourceLoaderInstance()
|
||||
{
|
||||
ResourceLoader = new ResourceLoader("resources.pri");
|
||||
}
|
||||
|
||||
internal static string GetString(string resourceId) => ResourceLoader.GetString(resourceId);
|
||||
public static string GetString(string resourceId) => ResourceLoader.GetString(resourceId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record ClearSearchMessage()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record FocusSearchBoxMessage()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record HandleCommandResultMessage() // ExtensionObject<ICommandResult> Result)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record HideDetailsMessage()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record LaunchUriMessage(Uri Uri)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record NavigateBackMessage(bool FromBackspace = false)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record NavigateToPageMessage(); // PageViewModel Page, bool WithAnimation, CancellationToken CancellationToken);
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Used to do a command - navigate to a page or invoke it
|
||||
/// </summary>
|
||||
public record PerformCommandMessage
|
||||
{
|
||||
// public ExtensionObject<ICommand> Command { get; }
|
||||
public object? Context { get; }
|
||||
|
||||
public bool WithAnimation { get; set; } = true;
|
||||
|
||||
public PerformCommandMessage() // ExtensionObject<ICommand> command)
|
||||
{
|
||||
// Command = command;
|
||||
Context = null;
|
||||
}
|
||||
|
||||
// public PerformCommandMessage(ExtensionObject<ICommand> command, ExtensionObject<IListItem> context)
|
||||
// {
|
||||
// Command = command;
|
||||
// Context = context.Unsafe;
|
||||
// }
|
||||
|
||||
// public PerformCommandMessage(ExtensionObject<ICommand> command, ExtensionObject<ICommandItem> context)
|
||||
// {
|
||||
// Command = command;
|
||||
// Context = context.Unsafe;
|
||||
// }
|
||||
|
||||
// public PerformCommandMessage(ExtensionObject<ICommand> command, ExtensionObject<ICommandContextItem> context)
|
||||
// {
|
||||
// Command = command;
|
||||
// Context = context.Unsafe;
|
||||
// }
|
||||
|
||||
// public PerformCommandMessage(CommandContextItemViewModel contextCommand)
|
||||
// {
|
||||
// Command = contextCommand.Command.Model;
|
||||
// Context = contextCommand.Model.Unsafe;
|
||||
// }
|
||||
|
||||
// public PerformCommandMessage(ConfirmResultViewModel vm)
|
||||
// {
|
||||
// Command = vm.PrimaryCommand.Model;
|
||||
// Context = null;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record SettingsWindowClosedMessage
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record ShowConfirmationMessage(IConfirmationArgs? Args)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record ShowDetailsMessage() // DetailsViewModel Details)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record ShowToastMessage(string Message)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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 Windows.System;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Models.Messages;
|
||||
|
||||
public record TryCommandKeybindingMessage(bool Ctrl, bool Alt, bool Shift, bool Win, VirtualKey Key)
|
||||
{
|
||||
public bool Handled { get; set; }
|
||||
}
|
||||
@@ -39,8 +39,4 @@
|
||||
<ProjectReference Include="..\..\SDK\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Events\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CommandPalette</OutputPath>
|
||||
<!-- For MVVM Toolkit Partial Properties/AOT support -->
|
||||
<LangVersion>preview</LangVersion>
|
||||
<!-- Disable SA1313 for Primary Constructor fields conflict https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors -->
|
||||
<NoWarn>SA1313;</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Common" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\SDK\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,66 @@
|
||||
GetPhysicallyInstalledSystemMemory
|
||||
GlobalMemoryStatusEx
|
||||
GetSystemInfo
|
||||
GetForegroundWindow
|
||||
SetForegroundWindow
|
||||
GetWindowRect
|
||||
GetCursorPos
|
||||
SetWindowPos
|
||||
HWND_TOPMOST
|
||||
HWND_BOTTOM
|
||||
IsIconic
|
||||
RegisterHotKey
|
||||
UnregisterHotKey
|
||||
SetWindowLongPtr
|
||||
CallWindowProc
|
||||
ShowWindow
|
||||
SetForegroundWindow
|
||||
EnableWindow
|
||||
IsWindowEnabled
|
||||
SetFocus
|
||||
SetActiveWindow
|
||||
MonitorFromWindow
|
||||
GetMonitorInfo
|
||||
GetDpiForMonitor
|
||||
WM_HOTKEY
|
||||
WM_NCLBUTTONDBLCLK
|
||||
|
||||
Shell_NotifyIcon
|
||||
LoadIcon
|
||||
WM_USER
|
||||
WM_WINDOWPOSCHANGING
|
||||
RegisterWindowMessageW
|
||||
ExtractIconEx
|
||||
TRACK_POPUP_MENU_FLAGS
|
||||
WM_COMMAND
|
||||
WM_RBUTTONUP
|
||||
WM_LBUTTONUP
|
||||
WM_LBUTTONDBLCLK
|
||||
CreatePopupMenu
|
||||
TrackPopupMenuEx
|
||||
InsertMenu
|
||||
|
||||
MessageBox
|
||||
DwmGetWindowAttribute
|
||||
DwmSetWindowAttribute
|
||||
DWM_CLOAKED_APP
|
||||
DWM_WINDOW_CORNER_PREFERENCE
|
||||
|
||||
CoWaitForMultipleObjects
|
||||
INFINITE
|
||||
CWMO_FLAGS
|
||||
|
||||
GetCurrentThreadId
|
||||
SetWindowsHookEx
|
||||
UnhookWindowsHookEx
|
||||
CallNextHookEx
|
||||
GetModuleHandle
|
||||
|
||||
GetWindowLong
|
||||
SetWindowLong
|
||||
WINDOW_EX_STYLE
|
||||
CreateWindowEx
|
||||
WNDCLASSEXW
|
||||
RegisterClassEx
|
||||
GetStockObject
|
||||
GetModuleHandle
|
||||
@@ -8,14 +8,10 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CommandPalette.UI.Models;
|
||||
using Microsoft.CommandPalette.UI.Models.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.Shell;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using WinRT.Interop;
|
||||
using RS_ = Microsoft.CommandPalette.UI.Helpers.ResourceLoaderInstance;
|
||||
using RS_ = Microsoft.CommandPalette.UI.Models.Helpers.ResourceLoaderInstance;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers;
|
||||
namespace Microsoft.CommandPalette.UI.Services;
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")]
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates a navigation request within Command Palette view models.
|
||||
/// </summary>
|
||||
/// <param name="TargetViewModel">A view model that should be navigated to.</param>
|
||||
/// <param name="NavigationToken"> A <see cref="CancellationToken"/> that can be used to cancel the pending navigation.</param>
|
||||
public record AsyncNavigationRequest(object? TargetViewModel, CancellationToken NavigationToken);
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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 CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.ViewModels;
|
||||
|
||||
public partial class PageViewModel : ObservableObject
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -2,7 +2,11 @@
|
||||
// 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.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CommandPalette.UI.Models.Messages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.CommandPalette.ViewModels;
|
||||
@@ -12,28 +16,381 @@ public partial class ShellViewModel : ObservableObject
|
||||
private readonly ILogger logger;
|
||||
private readonly TaskScheduler _scheduler;
|
||||
|
||||
// private IPage? _rootPage;
|
||||
[ObservableProperty]
|
||||
public partial bool IsLoaded { get; set; } = false;
|
||||
|
||||
private bool _isNested;
|
||||
// private bool _isNested;
|
||||
// public bool IsNested => _isNested;
|
||||
// Cancellation token source for page loading/navigation operations
|
||||
// private CancellationTokenSource? _navigationCts;
|
||||
// [ObservableProperty]
|
||||
// public partial DetailsViewModel? Details { get; set; }
|
||||
[ObservableProperty]
|
||||
public partial bool IsDetailsVisible { get; set; }
|
||||
|
||||
public bool IsNested => _isNested;
|
||||
[ObservableProperty]
|
||||
public partial bool IsSearchBoxVisible { get; set; } = true;
|
||||
|
||||
// private PageViewModel _currentPage;
|
||||
// public PageViewModel CurrentPage
|
||||
// {
|
||||
// get => _currentPage;
|
||||
// set
|
||||
// {
|
||||
// var oldValue = _currentPage;
|
||||
// if (SetProperty(ref _currentPage, value))
|
||||
// {
|
||||
// oldValue.PropertyChanged -= CurrentPage_PropertyChanged;
|
||||
// value.PropertyChanged += CurrentPage_PropertyChanged;
|
||||
// if (oldValue is IDisposable disposable)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// disposable.Dispose();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// CoreLogger.LogError(ex.ToString());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
private void CurrentPage_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
// if (e.PropertyName == nameof(PageViewModel.HasSearchBox))
|
||||
// {
|
||||
// IsSearchBoxVisible = CurrentPage.HasSearchBox;
|
||||
// }
|
||||
}
|
||||
|
||||
// public PageViewModel NullPage { get; private set; }
|
||||
public ShellViewModel(
|
||||
TaskScheduler scheduler,
|
||||
ILogger logger)
|
||||
|
||||
// IRootPageService rootPageService,
|
||||
// IPageViewModelFactoryService pageViewModelFactory,
|
||||
// IAppHostService appHostService)
|
||||
{
|
||||
this.logger = logger;
|
||||
_scheduler = scheduler;
|
||||
_isNested = false;
|
||||
this.logger = logger;
|
||||
|
||||
// _pageViewModelFactory = pageViewModelFactory;
|
||||
// _rootPageService = rootPageService;
|
||||
// _appHostService = appHostService;
|
||||
// NullPage = new NullPageViewModel(_scheduler, appHostService.GetDefaultHost());
|
||||
// _currentPage = new LoadingPageViewModel(null, _scheduler, appHostService.GetDefaultHost());
|
||||
|
||||
// Register to receive messages
|
||||
// WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
|
||||
// WeakReferenceMessenger.Default.Register<HandleCommandResultMessage>(this);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task<bool> LoadAsync()
|
||||
{
|
||||
// First, do any loading that the root page service needs to do before we can
|
||||
// display the root page. For example, this might include loading
|
||||
// the built-in commands, or loading the settings.
|
||||
// await _rootPageService.PreLoadAsync();
|
||||
IsLoaded = true;
|
||||
|
||||
// Now that the basics are set up, we can load the root page.
|
||||
// _rootPage = _rootPageService.GetRootPage();
|
||||
// This sends a message to us to load the root page view model.
|
||||
// WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(new ExtensionObject<ICommand>(_rootPage)));
|
||||
// Now that the root page is loaded, do any post-load work that the root page service needs to do.
|
||||
// This runs asynchronously, on a background thread.
|
||||
// This might include starting extensions, for example.
|
||||
// Note: We don't await this, so that we can return immediately.
|
||||
// This is important because we don't want to block the UI thread.
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
// await _rootPageService.PostLoadRootPageAsync();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// private async Task LoadPageViewModelAsync(PageViewModel viewModel, CancellationToken cancellationToken = default)
|
||||
// {
|
||||
// // Note: We removed the general loading state, extensions sometimes use their `IsLoading`, but it's inconsistently implemented it seems.
|
||||
// // IsInitialized is our main indicator of the general overall state of loading props/items from a page we use for the progress bar
|
||||
// // This triggers that load generally with the InitializeCommand asynchronously when we navigate to a page.
|
||||
// // We could re-track the page loading status, if we need it more granularly below again, but between the initialized and error message, we can infer some status.
|
||||
// // We could also maybe move this thread offloading we do for loading into PageViewModel and better communicate between the two... a few different options.
|
||||
// ////LoadedState = ViewModelLoadedState.Loading;
|
||||
// if (!viewModel.IsInitialized
|
||||
// && viewModel.InitializeCommand is not null)
|
||||
// {
|
||||
// var outer = Task.Run(
|
||||
// async () =>
|
||||
// {
|
||||
// // You know, this creates the situation where we wait for
|
||||
// // both loading page properties, AND the items, before we
|
||||
// // display anything.
|
||||
// //
|
||||
// // We almost need to do an async await on initialize, then
|
||||
// // just a fire-and-forget on FetchItems.
|
||||
// // RE: We do set the CurrentPage in ShellPage.xaml.cs as well, so, we kind of are doing two different things here.
|
||||
// // Definitely some more clean-up to do, but at least its centralized to one spot now.
|
||||
// viewModel.InitializeCommand.Execute(null);
|
||||
// await viewModel.InitializeCommand.ExecutionTask!;
|
||||
// if (viewModel.InitializeCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
||||
// {
|
||||
// if (viewModel.InitializeCommand.ExecutionTask.Exception is AggregateException ex)
|
||||
// {
|
||||
// CoreLogger.LogError(ex.ToString());
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var t = Task.Factory.StartNew(
|
||||
// () =>
|
||||
// {
|
||||
// if (cancellationToken.IsCancellationRequested)
|
||||
// {
|
||||
// if (viewModel is IDisposable disposable)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// disposable.Dispose();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// CoreLogger.LogError(ex.ToString());
|
||||
// }
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// CurrentPage = viewModel;
|
||||
// },
|
||||
// cancellationToken,
|
||||
// TaskCreationOptions.None,
|
||||
// _scheduler);
|
||||
// await t;
|
||||
// }
|
||||
// },
|
||||
// cancellationToken);
|
||||
// await outer;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (cancellationToken.IsCancellationRequested)
|
||||
// {
|
||||
// if (viewModel is IDisposable disposable)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// disposable.Dispose();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// CoreLogger.LogError(ex.ToString());
|
||||
// }
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// CurrentPage = viewModel;
|
||||
// }
|
||||
// }
|
||||
public void Receive(PerformCommandMessage message)
|
||||
{
|
||||
// PerformCommand(message);
|
||||
}
|
||||
|
||||
// private void PerformCommand(PerformCommandMessage message)
|
||||
// {
|
||||
// // Create/replace the navigation cancellation token.
|
||||
// // If one already exists, cancel and dispose it first.
|
||||
// var newCts = new CancellationTokenSource();
|
||||
// var oldCts = Interlocked.Exchange(ref _navigationCts, newCts);
|
||||
// if (oldCts is not null)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// oldCts.Cancel();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// CoreLogger.LogError(ex.ToString());
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// oldCts.Dispose();
|
||||
// }
|
||||
// }
|
||||
// var navigationToken = newCts.Token;
|
||||
// var command = message.Command.Unsafe;
|
||||
// if (command is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// var host = _appHostService.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
|
||||
// _rootPageService.OnPerformCommand(message.Context, !CurrentPage.IsNested, host);
|
||||
// try
|
||||
// {
|
||||
// if (command is IPage page)
|
||||
// {
|
||||
// CoreLogger.LogDebug($"Navigating to page");
|
||||
// var isMainPage = command == _rootPage;
|
||||
// _isNested = !isMainPage;
|
||||
// // Construct our ViewModel of the appropriate type and pass it the UI Thread context.
|
||||
// var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
|
||||
// if (pageViewModel is null)
|
||||
// {
|
||||
// CoreLogger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
|
||||
// throw new NotSupportedException();
|
||||
// }
|
||||
// // Clear command bar, ViewModel initialization can already set new commands if it wants to
|
||||
// OnUIThread(() => WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null)));
|
||||
// // Kick off async loading of our ViewModel
|
||||
// LoadPageViewModelAsync(pageViewModel, navigationToken)
|
||||
// .ContinueWith(
|
||||
// (Task t) =>
|
||||
// {
|
||||
// // clean up the navigation token if it's still ours
|
||||
// if (Interlocked.CompareExchange(ref _navigationCts, null, newCts) == newCts)
|
||||
// {
|
||||
// newCts.Dispose();
|
||||
// }
|
||||
// },
|
||||
// navigationToken,
|
||||
// TaskContinuationOptions.None,
|
||||
// _scheduler);
|
||||
// // While we're loading in the background, immediately move to the next page.
|
||||
// WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(pageViewModel, message.WithAnimation, navigationToken));
|
||||
// // Note: Originally we set our page back in the ViewModel here, but that now happens in response to the Frame navigating triggered from the above
|
||||
// // See RootFrame_Navigated event handler.
|
||||
// }
|
||||
// else if (command is IInvokableCommand invokable)
|
||||
// {
|
||||
// CoreLogger.LogDebug($"Invoking command");
|
||||
// WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
|
||||
// StartInvoke(message, invokable, host);
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// // TODO: It would be better to do this as a page exception, rather
|
||||
// // than a silent log message.
|
||||
// host?.Log(ex.Message);
|
||||
// }
|
||||
// }
|
||||
// private void StartInvoke(PerformCommandMessage message, IInvokableCommand invokable, AppExtensionHost? host)
|
||||
// {
|
||||
// // TODO GH #525 This needs more better locking.
|
||||
// lock (_invokeLock)
|
||||
// {
|
||||
// if (_handleInvokeTask is not null)
|
||||
// {
|
||||
// // do nothing - a command is already doing a thing
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _handleInvokeTask = Task.Run(() =>
|
||||
// {
|
||||
// SafeHandleInvokeCommandSynchronous(message, invokable, host);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// private void SafeHandleInvokeCommandSynchronous(PerformCommandMessage message, IInvokableCommand invokable, AppExtensionHost? host)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// // Call out to extension process.
|
||||
// // * May fail!
|
||||
// // * May never return!
|
||||
// var result = invokable.Invoke(message.Context);
|
||||
// // But if it did succeed, we need to handle the result.
|
||||
// UnsafeHandleCommandResult(result);
|
||||
// _handleInvokeTask = null;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// _handleInvokeTask = null;
|
||||
// // TODO: It would be better to do this as a page exception, rather
|
||||
// // than a silent log message.
|
||||
// host?.Log(ex.Message);
|
||||
// }
|
||||
// }
|
||||
// private void UnsafeHandleCommandResult(ICommandResult? result)
|
||||
// {
|
||||
// if (result is null)
|
||||
// {
|
||||
// // No result, nothing to do.
|
||||
// return;
|
||||
// }
|
||||
// var kind = result.Kind;
|
||||
// CoreLogger.LogDebug($"handling {kind.ToString()}");
|
||||
// WeakReferenceMessenger.Default.Send<CmdPalInvokeResultMessage>(new(kind));
|
||||
// switch (kind)
|
||||
// {
|
||||
// case CommandResultKind.Dismiss:
|
||||
// {
|
||||
// // Reset the palette to the main page and dismiss
|
||||
// GoHome(withAnimation: false, focusSearch: false);
|
||||
// WeakReferenceMessenger.Default.Send<DismissMessage>();
|
||||
// break;
|
||||
// }
|
||||
// case CommandResultKind.GoHome:
|
||||
// {
|
||||
// // Go back to the main page, but keep it open
|
||||
// GoHome();
|
||||
// break;
|
||||
// }
|
||||
// case CommandResultKind.GoBack:
|
||||
// {
|
||||
// GoBack();
|
||||
// break;
|
||||
// }
|
||||
// case CommandResultKind.Hide:
|
||||
// {
|
||||
// // Keep this page open, but hide the palette.
|
||||
// WeakReferenceMessenger.Default.Send<DismissMessage>();
|
||||
// break;
|
||||
// }
|
||||
// case CommandResultKind.KeepOpen:
|
||||
// {
|
||||
// // Do nothing.
|
||||
// break;
|
||||
// }
|
||||
// case CommandResultKind.Confirm:
|
||||
// {
|
||||
// if (result.Args is IConfirmationArgs a)
|
||||
// {
|
||||
// WeakReferenceMessenger.Default.Send<ShowConfirmationMessage>(new(a));
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// case CommandResultKind.ShowToast:
|
||||
// {
|
||||
// if (result.Args is IToastArgs a)
|
||||
// {
|
||||
// WeakReferenceMessenger.Default.Send<ShowToastMessage>(new(a.Message));
|
||||
// UnsafeHandleCommandResult(a.Result);
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
public void GoHome(bool withAnimation = true, bool focusSearch = true)
|
||||
{
|
||||
// _rootPageService.GoHome();
|
||||
WeakReferenceMessenger.Default.Send<GoHomeMessage>(new(withAnimation, focusSearch));
|
||||
}
|
||||
|
||||
public void GoBack(bool withAnimation = true, bool focusSearch = true)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<GoBackMessage>(new(withAnimation, focusSearch));
|
||||
}
|
||||
|
||||
public void Receive(HandleCommandResultMessage message)
|
||||
{
|
||||
// UnsafeHandleCommandResult(message.Result.Unsafe);
|
||||
}
|
||||
|
||||
private void OnUIThread(Action action)
|
||||
@@ -44,4 +401,9 @@ public partial class ShellViewModel : ObservableObject
|
||||
TaskCreationOptions.None,
|
||||
_scheduler);
|
||||
}
|
||||
|
||||
public void CancelNavigation()
|
||||
{
|
||||
// _navigationCts?.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +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 CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.ViewModels;
|
||||
|
||||
public partial class ToastViewModel : ObservableObject
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CommandPalette.UI.Controls.CommandBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringNotEmptyToVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
NotEmptyValue="Visible" />
|
||||
<converters:BoolToVisibilityConverter
|
||||
x:Key="BoolToInvertedVisibilityConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
|
||||
<StackLayout
|
||||
x:Name="VerticalStackLayout"
|
||||
Orientation="Vertical"
|
||||
Spacing="4" />
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid
|
||||
MinHeight="40"
|
||||
Padding="4"
|
||||
ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid
|
||||
x:Name="IconRoot"
|
||||
Margin="3,0,-5,0">
|
||||
<Button
|
||||
x:Name="StatusMessagesButton"
|
||||
x:Uid="StatusMessagesButton"
|
||||
Padding="4"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
</Button>
|
||||
</Grid>
|
||||
<Button
|
||||
x:Name="SettingsIconButton"
|
||||
x:Uid="SettingsButton"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Glyph="" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="Settings" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="Title here"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
Padding="0,0,4,0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="4">
|
||||
<Button
|
||||
x:Name="PrimaryButton"
|
||||
Padding="6,4,4,4"
|
||||
AutomationProperties.AutomationId="PrimaryCommandButton"
|
||||
AutomationProperties.Name="Title here"
|
||||
Background="Transparent"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock
|
||||
MaxWidth="160"
|
||||
VerticalAlignment="Center"
|
||||
MaxLines="1"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<Border Style="{StaticResource HotkeyStyle}">
|
||||
<FontIcon Glyph="" Style="{StaticResource HotkeyFontIconStyle}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="SecondaryButton"
|
||||
Padding="6,4,4,4"
|
||||
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
|
||||
AutomationProperties.AutomationId="SecondaryCommandButton"
|
||||
AutomationProperties.Name="Secondary"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock
|
||||
MaxWidth="160"
|
||||
VerticalAlignment="Center"
|
||||
MaxLines="1"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="Secondary"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Border Padding="4,2,4,2" Style="{StaticResource HotkeyStyle}">
|
||||
<TextBlock Style="{StaticResource HotkeyTextBlockStyle}" Text="Ctrl" />
|
||||
</Border>
|
||||
<Border Style="{StaticResource HotkeyStyle}">
|
||||
<FontIcon Glyph="" Style="{StaticResource HotkeyFontIconStyle}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="MoreCommandsButton"
|
||||
x:Uid="MoreCommandsButton"
|
||||
Padding="6,4,4,4"
|
||||
AutomationProperties.AutomationId="MoreContextMenuButton"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
ToolTipService.ToolTip="Ctrl+K">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="More"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Border Padding="4,2,4,2" Style="{StaticResource HotkeyStyle}">
|
||||
<TextBlock Style="{StaticResource HotkeyTextBlockStyle}" Text="Ctrl" />
|
||||
</Border>
|
||||
<Border Padding="4,2,4,2" Style="{StaticResource HotkeyStyle}">
|
||||
<TextBlock Style="{StaticResource HotkeyTextBlockStyle}" Text="K" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class CommandBar : UserControl
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CommandPalette.UI.Controls.SearchBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="using:Microsoft.CommandPalette.UI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<!-- Search box -->
|
||||
<TextBox
|
||||
x:Name="SearchBox"
|
||||
MinHeight="32"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
AutomationProperties.AutomationId="MainSearchBox"
|
||||
Style="{StaticResource SearchTextBoxStyle}" />
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class SearchBar : UserControl
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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.UI.Xaml.Data;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Converters;
|
||||
|
||||
public partial class KeyChordToStringConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is KeyChord shortcut && (VirtualKey)shortcut.Vkey != VirtualKey.None)
|
||||
{
|
||||
var result = string.Empty;
|
||||
|
||||
if (shortcut.Modifiers.HasFlag(VirtualKeyModifiers.Control))
|
||||
{
|
||||
result += "Ctrl+";
|
||||
}
|
||||
|
||||
if (shortcut.Modifiers.HasFlag(VirtualKeyModifiers.Shift))
|
||||
{
|
||||
result += "Shift+";
|
||||
}
|
||||
|
||||
if (shortcut.Modifiers.HasFlag(VirtualKeyModifiers.Menu))
|
||||
{
|
||||
result += "Alt+";
|
||||
}
|
||||
|
||||
result += (VirtualKey)shortcut.Vkey;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Converters;
|
||||
|
||||
public partial class MessageStateToSeverityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is MessageState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case MessageState.Info:
|
||||
return InfoBarSeverity.Informational;
|
||||
case MessageState.Success:
|
||||
return InfoBarSeverity.Success;
|
||||
case MessageState.Warning:
|
||||
return InfoBarSeverity.Warning;
|
||||
case MessageState.Error:
|
||||
return InfoBarSeverity.Error;
|
||||
}
|
||||
}
|
||||
|
||||
return InfoBarSeverity.Informational;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using RS_ = Microsoft.CommandPalette.UI.Helpers.ResourceLoaderInstance;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Converters;
|
||||
|
||||
public partial class PlaceholderTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
return value is string placeholder && !string.IsNullOrEmpty(placeholder)
|
||||
? placeholder
|
||||
: (object)RS_.GetString("DefaultSearchPlaceholderText");
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders;
|
||||
|
||||
internal sealed partial class CompositeImageSourceProvider : IImageSourceProvider
|
||||
{
|
||||
private readonly IImageSourceProvider[] _imageProviders =
|
||||
[
|
||||
new HttpImageSourceProvider(),
|
||||
new LocalImageSourceProvider(),
|
||||
new DataImageSourceProvider()
|
||||
];
|
||||
|
||||
public Task<ImageSourceInfo> GetImageSource(string url)
|
||||
{
|
||||
var provider = _imageProviders.FirstOrDefault(p => p.ShouldUseThisProvider(url));
|
||||
if (provider == null)
|
||||
{
|
||||
throw new NotSupportedException($"No image provider found for URL: {url}");
|
||||
}
|
||||
|
||||
return provider.GetImageSource(url);
|
||||
}
|
||||
|
||||
public bool ShouldUseThisProvider(string url)
|
||||
{
|
||||
return _imageProviders.Any(provider => provider.ShouldUseThisProvider(url));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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.Text;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders;
|
||||
|
||||
internal sealed partial class DataImageSourceProvider : IImageSourceProvider
|
||||
{
|
||||
private readonly ImageSourceFactory.ImageDecodeOptions _decodeOptions;
|
||||
|
||||
public DataImageSourceProvider(ImageSourceFactory.ImageDecodeOptions? decodeOptions = null)
|
||||
=> _decodeOptions = decodeOptions ?? new ImageSourceFactory.ImageDecodeOptions();
|
||||
|
||||
public bool ShouldUseThisProvider(string url)
|
||||
=> url.StartsWith("data:", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public async Task<ImageSourceInfo> GetImageSource(string url)
|
||||
{
|
||||
if (!ShouldUseThisProvider(url))
|
||||
{
|
||||
throw new ArgumentException("URL is not a data: URI.", nameof(url));
|
||||
}
|
||||
|
||||
// data:[<media type>][;base64],<data>
|
||||
var comma = url.IndexOf(',');
|
||||
if (comma < 0)
|
||||
{
|
||||
throw new FormatException("Invalid data URI: missing comma separator.");
|
||||
}
|
||||
|
||||
var header = url[5..comma]; // after "data:"
|
||||
var payload = url[(comma + 1)..]; // after comma
|
||||
|
||||
// Parse header
|
||||
string? contentType = null;
|
||||
var isBase64 = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(header))
|
||||
{
|
||||
var parts = header.Split(';');
|
||||
|
||||
// first token may be media type
|
||||
if (!string.IsNullOrWhiteSpace(parts[0]) && parts[0].Contains('/'))
|
||||
{
|
||||
contentType = parts[0];
|
||||
}
|
||||
|
||||
isBase64 = parts.Any(static p => p.Equals("base64", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
var bytes = isBase64
|
||||
? Convert.FromBase64String(payload)
|
||||
: Encoding.UTF8.GetBytes(Uri.UnescapeDataString(payload));
|
||||
|
||||
var mem = new InMemoryRandomAccessStream();
|
||||
using (var writer = new DataWriter(mem.GetOutputStreamAt(0)))
|
||||
{
|
||||
writer.WriteBytes(bytes);
|
||||
await writer.StoreAsync()!;
|
||||
}
|
||||
|
||||
mem.Seek(0);
|
||||
|
||||
var imagePayload = new ImageSourceFactory.ImagePayload(mem, contentType, null);
|
||||
var imageSource = await ImageSourceFactory.CreateAsync(imagePayload, _decodeOptions);
|
||||
return new ImageSourceInfo(imageSource, new ImageHints
|
||||
{
|
||||
DownscaleOnly = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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.WindowsRuntime;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of IImageProvider to handle http/https images, but adds
|
||||
/// a new functionality to handle image scaling.
|
||||
/// </summary>
|
||||
internal sealed partial class HttpImageSourceProvider : IImageSourceProvider
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
|
||||
public HttpImageSourceProvider(HttpClient? httpClient = null)
|
||||
=> _http = httpClient ?? SharedHttpClient.Instance;
|
||||
|
||||
public bool ShouldUseThisProvider(string url)
|
||||
=> Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
|
||||
public async Task<ImageSourceInfo> GetImageSource(string url)
|
||||
{
|
||||
if (!ShouldUseThisProvider(url))
|
||||
{
|
||||
throw new ArgumentException("URL must be absolute http/https.", nameof(url));
|
||||
}
|
||||
|
||||
using var req = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
req.Headers.TryAddWithoutValidation("Accept", "image/*,text/xml;q=0.9,application/xml;q=0.9,*/*;q=0.8");
|
||||
|
||||
using var resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
|
||||
var contentType = resp.Content.Headers.ContentType?.MediaType;
|
||||
|
||||
using var mem = new InMemoryRandomAccessStream();
|
||||
await CopyToRandomAccessStreamAsync(resp, mem);
|
||||
|
||||
var hints = ImageHints.ParseHintsFromUri(new Uri(url));
|
||||
var imageSource = await ImageSourceFactory.CreateAsync(
|
||||
new ImageSourceFactory.ImagePayload(mem, contentType, new Uri(url)),
|
||||
new ImageSourceFactory.ImageDecodeOptions { SniffContent = true });
|
||||
|
||||
return new ImageSourceInfo(imageSource, hints);
|
||||
}
|
||||
|
||||
private static async Task CopyToRandomAccessStreamAsync(HttpResponseMessage resp, InMemoryRandomAccessStream mem)
|
||||
{
|
||||
var data = await resp.Content.ReadAsByteArrayAsync();
|
||||
await mem.WriteAsync(data.AsBuffer());
|
||||
mem.Seek(0);
|
||||
}
|
||||
|
||||
private static class SharedHttpClient
|
||||
{
|
||||
public static readonly HttpClient Instance = Create();
|
||||
|
||||
private static HttpClient Create()
|
||||
{
|
||||
var c = new HttpClient
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(30),
|
||||
};
|
||||
c.DefaultRequestHeaders.UserAgent.ParseAdd("CommandPalette/1.0");
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders;
|
||||
|
||||
internal interface IImageSourceProvider
|
||||
{
|
||||
Task<ImageSourceInfo> GetImageSource(string url);
|
||||
|
||||
bool ShouldUseThisProvider(string url);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders;
|
||||
|
||||
internal sealed class ImageHints
|
||||
{
|
||||
public static ImageHints Empty { get; } = new();
|
||||
|
||||
public double? DesiredPixelWidth { get; init; }
|
||||
|
||||
public double? DesiredPixelHeight { get; init; }
|
||||
|
||||
public double? MaxPixelWidth { get; init; }
|
||||
|
||||
public double? MaxPixelHeight { get; init; }
|
||||
|
||||
public bool? DownscaleOnly { get; init; }
|
||||
|
||||
public string? FitMode { get; init; } // fit=fit
|
||||
|
||||
public static ImageHints ParseHintsFromUri(Uri? uri)
|
||||
{
|
||||
if (uri is null || string.IsNullOrEmpty(uri.Query))
|
||||
{
|
||||
return Empty;
|
||||
}
|
||||
|
||||
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var p in uri.Query.TrimStart('?').Split('&', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var kv = p.Split('=', 2);
|
||||
var k = Uri.UnescapeDataString(kv[0]);
|
||||
var v = kv.Length > 1 ? Uri.UnescapeDataString(kv[1]) : string.Empty;
|
||||
dict[k] = v;
|
||||
}
|
||||
|
||||
return new ImageHints
|
||||
{
|
||||
DesiredPixelWidth = GetInt("--x-cmdpal-width"),
|
||||
DesiredPixelHeight = GetInt("--x-cmdpal-height"),
|
||||
MaxPixelWidth = GetInt("--x-cmdpal-maxwidth"),
|
||||
MaxPixelHeight = GetInt("--x-cmdpal-maxheight"),
|
||||
DownscaleOnly = GetBool("--x-cmdpal-downscaleOnly") ?? (GetBool("--x-cmdpal-upscale") is bool u ? !u : (bool?)null),
|
||||
FitMode = dict.TryGetValue("--x-cmdpal-fit", out var f) ? f : null,
|
||||
};
|
||||
|
||||
int? GetInt(params string[] keys)
|
||||
{
|
||||
foreach (var k in keys)
|
||||
{
|
||||
if (dict.TryGetValue(k, out var v) && int.TryParse(v, out var n))
|
||||
{
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
bool? GetBool(params string[] keys)
|
||||
{
|
||||
foreach (var k in keys)
|
||||
{
|
||||
if (dict.TryGetValue(k, out var v) && (v.Equals("true", StringComparison.OrdinalIgnoreCase) || v == "1"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (dict.TryGetValue(k, out var v2) && (v2.Equals("false", StringComparison.OrdinalIgnoreCase) || v2 == "0"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// 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.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Foundation;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new image source.
|
||||
/// </summary>
|
||||
internal static class ImageSourceFactory
|
||||
{
|
||||
private static DispatcherQueue? _dispatcherQueue;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
}
|
||||
|
||||
internal sealed record ImagePayload(
|
||||
IRandomAccessStream Stream,
|
||||
string? ContentType,
|
||||
Uri? SourceUri);
|
||||
|
||||
internal sealed class ImageDecodeOptions
|
||||
{
|
||||
public bool SniffContent { get; init; } = true;
|
||||
|
||||
public int? DecodePixelWidth { get; init; }
|
||||
|
||||
public int? DecodePixelHeight { get; init; }
|
||||
}
|
||||
|
||||
public static async Task<ImageSource> CreateAsync(ImagePayload payload, ImageDecodeOptions? options = null)
|
||||
{
|
||||
options ??= new ImageDecodeOptions();
|
||||
|
||||
var isSvg =
|
||||
IsSvgByHeaderOrUrl(payload.ContentType, payload.SourceUri) ||
|
||||
(options.SniffContent && SniffSvg(payload.Stream));
|
||||
|
||||
payload.Stream.Seek(0);
|
||||
|
||||
return await _dispatcherQueue!.EnqueueAsync(async () =>
|
||||
{
|
||||
if (isSvg)
|
||||
{
|
||||
var size = GetSvgSize(payload.Stream.AsStreamForRead());
|
||||
payload.Stream.Seek(0);
|
||||
|
||||
var svg = new SvgImageSource();
|
||||
await svg.SetSourceAsync(payload.Stream);
|
||||
svg.RasterizePixelWidth = size.Width;
|
||||
svg.RasterizePixelHeight = size.Height;
|
||||
return svg;
|
||||
}
|
||||
else
|
||||
{
|
||||
var bmp = new BitmapImage();
|
||||
if (options.DecodePixelWidth is int w and > 0)
|
||||
{
|
||||
bmp.DecodePixelWidth = w;
|
||||
}
|
||||
|
||||
if (options.DecodePixelHeight is int h and > 0)
|
||||
{
|
||||
bmp.DecodePixelHeight = h;
|
||||
}
|
||||
|
||||
await bmp.SetSourceAsync(payload.Stream);
|
||||
return (ImageSource)bmp;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Size GetSvgSize(Stream stream)
|
||||
{
|
||||
// Parse the SVG string as an XML document
|
||||
var svgDocument = XDocument.Load(stream);
|
||||
|
||||
// Get the root element of the document
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
var svgElement = svgDocument.Root;
|
||||
|
||||
// Get the height and width attributes of the root element
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
var heightAttribute = svgElement.Attribute("height");
|
||||
#pragma warning restore CS8602 // Dereference of a possibly null reference.
|
||||
var widthAttribute = svgElement.Attribute("width");
|
||||
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
|
||||
// Convert the attribute values to double
|
||||
double.TryParse(heightAttribute?.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var height);
|
||||
double.TryParse(widthAttribute?.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var width);
|
||||
|
||||
// Return the height and width as a tuple
|
||||
return new(width, height);
|
||||
}
|
||||
|
||||
private static bool IsSvgByHeaderOrUrl(string? contentType, Uri? uri)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(contentType) &&
|
||||
contentType.StartsWith("image/svg+xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var s = uri?.ToString();
|
||||
return !string.IsNullOrEmpty(s) && s.Contains(".svg", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool SniffSvg(IRandomAccessStream ras)
|
||||
{
|
||||
try
|
||||
{
|
||||
const int maxProbe = 1024;
|
||||
ras.Seek(0);
|
||||
var s = ras.AsStreamForRead();
|
||||
var toRead = (int)Math.Min(ras.Size, maxProbe);
|
||||
var buf = new byte[toRead];
|
||||
var read = s.Read(buf, 0, toRead);
|
||||
if (read <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var head = System.Text.Encoding.UTF8.GetString(buf, 0, read);
|
||||
ras.Seek(0);
|
||||
return head.Contains("<svg", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ras.Seek(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders;
|
||||
|
||||
internal sealed record ImageSourceInfo(ImageSource ImageSource, ImageHints Hints);
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders;
|
||||
|
||||
internal sealed partial class LocalImageSourceProvider : IImageSourceProvider
|
||||
{
|
||||
private readonly ImageSourceFactory.ImageDecodeOptions _decodeOptions;
|
||||
|
||||
public LocalImageSourceProvider(ImageSourceFactory.ImageDecodeOptions? decodeOptions = null)
|
||||
=> _decodeOptions = decodeOptions ?? new ImageSourceFactory.ImageDecodeOptions();
|
||||
|
||||
public bool ShouldUseThisProvider(string url)
|
||||
{
|
||||
if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri) && uri.IsAbsoluteUri)
|
||||
{
|
||||
var scheme = uri.Scheme.ToLowerInvariant();
|
||||
return scheme is "file" or "ms-appx" or "ms-appdata";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<ImageSourceInfo> GetImageSource(string url)
|
||||
{
|
||||
if (!ShouldUseThisProvider(url))
|
||||
{
|
||||
throw new ArgumentException("Not a local URL/path (file, ms-appx, ms-appdata).", nameof(url));
|
||||
}
|
||||
|
||||
// Absolute URI?
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out var uri) && uri.IsAbsoluteUri)
|
||||
{
|
||||
var scheme = uri.Scheme.ToLowerInvariant();
|
||||
|
||||
var hints = ImageHints.ParseHintsFromUri(uri);
|
||||
|
||||
if (scheme is "ms-appx" or "ms-appdata")
|
||||
{
|
||||
// Load directly from the package/appdata URI
|
||||
var rasRef = RandomAccessStreamReference.CreateFromUri(uri);
|
||||
using var ras = await rasRef.OpenReadAsync();
|
||||
var payload = new ImageSourceFactory.ImagePayload(ras, ImageContentTypeHelper.GuessFromPathOrUri(uri.AbsoluteUri), uri);
|
||||
return new ImageSourceInfo(await ImageSourceFactory.CreateAsync(payload, _decodeOptions), hints);
|
||||
}
|
||||
|
||||
if (scheme is "file")
|
||||
{
|
||||
var path = uri.LocalPath;
|
||||
return new ImageSourceInfo(await FromFilePathAsync(path, uri, _decodeOptions), hints);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Unsupported local URL/path.");
|
||||
}
|
||||
|
||||
private static async Task<ImageSource> FromFilePathAsync(string path, Uri sourceUri, ImageSourceFactory.ImageDecodeOptions decodeOptions)
|
||||
{
|
||||
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 64 * 1024, useAsync: true);
|
||||
using var mem = new InMemoryRandomAccessStream();
|
||||
using var outStream = mem.AsStreamForWrite();
|
||||
await fs.CopyToAsync(outStream).ConfigureAwait(true);
|
||||
await outStream.FlushAsync().ConfigureAwait(true);
|
||||
|
||||
mem.Seek(0);
|
||||
|
||||
var payload = new ImageSourceFactory.ImagePayload(mem, ImageContentTypeHelper.GuessFromPathOrUri(path), sourceUri);
|
||||
return await ImageSourceFactory.CreateAsync(payload, decodeOptions).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private static class ImageContentTypeHelper
|
||||
{
|
||||
public static string? GuessFromPathOrUri(string? pathOrUri)
|
||||
{
|
||||
if (string.IsNullOrEmpty(pathOrUri))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to get extension from path/uri
|
||||
var ext = Path.GetExtension(pathOrUri);
|
||||
if (string.IsNullOrEmpty(ext))
|
||||
{
|
||||
// try query-less URI path portion
|
||||
if (Uri.TryCreate(pathOrUri, UriKind.RelativeOrAbsolute, out var u))
|
||||
{
|
||||
ext = Path.GetExtension(u.IsAbsoluteUri ? u.AbsolutePath : u.OriginalString);
|
||||
}
|
||||
}
|
||||
|
||||
ext = ext?.Trim().ToLowerInvariant();
|
||||
return ext switch
|
||||
{
|
||||
".svg" => "image/svg+xml",
|
||||
".png" => "image/png",
|
||||
".jpg" => "image/jpeg",
|
||||
".jpeg" => "image/jpeg",
|
||||
".gif" => "image/gif",
|
||||
".webp" => "image/webp",
|
||||
".bmp" => "image/bmp",
|
||||
".ico" => "image/x-icon",
|
||||
".tif" => "image/tiff",
|
||||
".tiff" => "image/tiff",
|
||||
".avif" => "image/avif",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new image configured to behave well as an inline image in a RichTextBlock.
|
||||
/// </summary>
|
||||
internal static class RtbInlineImageFactory
|
||||
{
|
||||
public sealed class InlineImageOptions
|
||||
{
|
||||
public double? WidthDip { get; init; }
|
||||
|
||||
public double? HeightDip { get; init; }
|
||||
|
||||
public double? MaxWidthDip { get; init; }
|
||||
|
||||
public double? MaxHeightDip { get; init; }
|
||||
|
||||
public bool FitColumnWidth { get; init; } = true;
|
||||
|
||||
public bool DownscaleOnly { get; init; } = true;
|
||||
|
||||
public Stretch Stretch { get; init; } = Stretch.None;
|
||||
}
|
||||
|
||||
internal static Image Create(ImageSource source, InlineImageOptions? options = null)
|
||||
{
|
||||
options ??= new InlineImageOptions();
|
||||
|
||||
var img = new Image
|
||||
{
|
||||
Source = source,
|
||||
Stretch = options.Stretch,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
};
|
||||
|
||||
// Track host RTB and subscribe once
|
||||
RichTextBlock? rtb = null;
|
||||
long paddingToken = 0;
|
||||
var paddingSubscribed = false;
|
||||
|
||||
SizeChangedEventHandler? rtbSizeChangedHandler = null;
|
||||
TypedEventHandler<XamlRoot, XamlRootChangedEventArgs>? xamlRootChangedHandler = null;
|
||||
|
||||
// If Source is replaced later, recompute
|
||||
var sourceToken = img.RegisterPropertyChangedCallback(Image.SourceProperty!, (_, __) => Update());
|
||||
|
||||
img.Loaded += OnLoaded;
|
||||
img.Unloaded += OnUnloaded;
|
||||
img.ImageOpened += (_, __) => Update();
|
||||
|
||||
return img;
|
||||
|
||||
void OnLoaded(object? s, RoutedEventArgs e)
|
||||
{
|
||||
// store image initial Width and Height if they are force upon the image by
|
||||
// MarkdownControl itself as result of parsing <img width="123" height="123" />
|
||||
// If user sets Width/Height in options, that takes precedence
|
||||
img.Tag ??= (img.Width, img.Height);
|
||||
|
||||
rtb ??= FindAncestor<RichTextBlock>(img);
|
||||
if (rtb != null && !paddingSubscribed)
|
||||
{
|
||||
rtbSizeChangedHandler ??= (_, __) => Update();
|
||||
rtb.SizeChanged += rtbSizeChangedHandler;
|
||||
|
||||
paddingToken = rtb.RegisterPropertyChangedCallback(Control.PaddingProperty!, (_, __) => Update());
|
||||
paddingSubscribed = true;
|
||||
}
|
||||
|
||||
if (img.XamlRoot != null)
|
||||
{
|
||||
xamlRootChangedHandler ??= (_, __) => Update();
|
||||
img.XamlRoot.Changed += xamlRootChangedHandler;
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void OnUnloaded(object? s, RoutedEventArgs e)
|
||||
{
|
||||
if (rtb != null && rtbSizeChangedHandler is not null)
|
||||
{
|
||||
rtb.SizeChanged -= rtbSizeChangedHandler;
|
||||
}
|
||||
|
||||
if (rtb != null && paddingSubscribed)
|
||||
{
|
||||
rtb.UnregisterPropertyChangedCallback(Control.PaddingProperty!, paddingToken);
|
||||
paddingSubscribed = false;
|
||||
}
|
||||
|
||||
if (img.XamlRoot != null && xamlRootChangedHandler is not null)
|
||||
{
|
||||
img.XamlRoot.Changed -= xamlRootChangedHandler;
|
||||
}
|
||||
|
||||
img.UnregisterPropertyChangedCallback(Image.SourceProperty!, sourceToken);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (rtb is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double? externalWidth = null;
|
||||
double? externalHeight = null;
|
||||
if (img.Tag != null)
|
||||
{
|
||||
(externalWidth, externalHeight) = ((double, double))img.Tag;
|
||||
}
|
||||
|
||||
var pad = rtb.Padding;
|
||||
var columnDip = Math.Max(0.0, rtb.ActualWidth - pad.Left - pad.Right);
|
||||
var scale = img.XamlRoot?.RasterizationScale is double s1 and > 0 ? s1 : 1.0;
|
||||
|
||||
var isSvg = img.Source is SvgImageSource;
|
||||
var naturalPxW = GetNaturalPixelWidth(img.Source);
|
||||
var naturalDipW = naturalPxW > 0 && naturalPxW != int.MaxValue ? naturalPxW / scale : double.PositiveInfinity; // SVG => ∞
|
||||
|
||||
double? desiredWidth = null;
|
||||
if (externalWidth.HasValue && !double.IsNaN(externalWidth.Value))
|
||||
{
|
||||
img.Width = externalWidth.Value;
|
||||
desiredWidth = externalWidth.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (options.WidthDip is double forcedW)
|
||||
{
|
||||
desiredWidth = options.DownscaleOnly && naturalPxW != int.MaxValue
|
||||
? Math.Min(forcedW, naturalPxW)
|
||||
: forcedW;
|
||||
}
|
||||
else if (options.FitColumnWidth)
|
||||
{
|
||||
desiredWidth = options.DownscaleOnly && naturalPxW != int.MaxValue
|
||||
? Math.Min(columnDip, naturalPxW)
|
||||
: columnDip;
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredWidth = naturalPxW;
|
||||
}
|
||||
|
||||
// Apply MaxWidth (never exceed column width by default)
|
||||
double maxW;
|
||||
var maxConstraint = options.FitColumnWidth ? columnDip : (isSvg ? 256 : double.PositiveInfinity);
|
||||
if (options.MaxWidthDip.HasValue)
|
||||
{
|
||||
maxW = Math.Min(options.MaxWidthDip.Value, maxConstraint);
|
||||
}
|
||||
else if (options.DownscaleOnly)
|
||||
{
|
||||
maxW = Math.Min(naturalPxW, maxConstraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
maxW = maxConstraint;
|
||||
}
|
||||
|
||||
// Commit sizes
|
||||
if (desiredWidth.HasValue)
|
||||
{
|
||||
img.Width = Math.Max(0, desiredWidth.Value);
|
||||
}
|
||||
|
||||
img.MaxWidth = maxW > 0 ? maxW : maxConstraint;
|
||||
}
|
||||
|
||||
if (externalHeight.HasValue && !double.IsNaN(externalHeight.Value))
|
||||
{
|
||||
img.Height = externalHeight.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ---- Height & MaxHeight ----
|
||||
var desiredHeight = options.HeightDip;
|
||||
var maxH = options.MaxHeightDip;
|
||||
|
||||
if (desiredHeight is double h)
|
||||
{
|
||||
img.Height = Math.Max(0, h);
|
||||
}
|
||||
|
||||
if (maxH is double mh && mh > 0)
|
||||
{
|
||||
img.MaxHeight = mh;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.FitColumnWidth
|
||||
|| options.WidthDip is not null
|
||||
|| options.HeightDip is not null
|
||||
|| options.MaxWidthDip is not null
|
||||
|| options.MaxHeightDip is not null
|
||||
|| externalWidth.HasValue
|
||||
|| externalHeight.HasValue)
|
||||
{
|
||||
img.Stretch = Stretch.Uniform;
|
||||
}
|
||||
else
|
||||
{
|
||||
img.Stretch = Stretch.None;
|
||||
}
|
||||
|
||||
// Decode/rasterization hints
|
||||
if (isSvg && img.Source is SvgImageSource svg)
|
||||
{
|
||||
var targetW = desiredWidth ?? Math.Min(img.MaxWidth, columnDip);
|
||||
var pxW = Math.Max(1, (int)Math.Round(targetW * scale));
|
||||
if ((int)svg.RasterizePixelWidth != pxW)
|
||||
{
|
||||
svg.RasterizePixelWidth = pxW;
|
||||
}
|
||||
|
||||
if (options.HeightDip is double forcedH)
|
||||
{
|
||||
var pxH = Math.Max(1, (int)Math.Round(forcedH * scale));
|
||||
if ((int)svg.RasterizePixelHeight != pxH)
|
||||
{
|
||||
svg.RasterizePixelHeight = pxH;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (img.Source is BitmapImage bi && naturalPxW > 0)
|
||||
{
|
||||
var widthToUse = desiredWidth ?? Math.Min(img.MaxWidth, columnDip);
|
||||
if (widthToUse > 0)
|
||||
{
|
||||
var desiredPx = (int)Math.Round(Math.Min(naturalPxW, widthToUse * scale));
|
||||
if (desiredPx > 0 && bi.DecodePixelWidth != desiredPx)
|
||||
{
|
||||
bi.DecodePixelWidth = desiredPx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetNaturalPixelWidth(ImageSource? src) => src switch
|
||||
{
|
||||
BitmapSource bs when bs.PixelWidth > 0 => bs.PixelWidth, // raster
|
||||
SvgImageSource sis => sis.RasterizePixelWidth > 0 ? (int)sis.RasterizePixelWidth : int.MaxValue, // vector => infinite
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
private static T? FindAncestor<T>(DependencyObject start)
|
||||
where T : DependencyObject
|
||||
{
|
||||
var cur = (DependencyObject?)start;
|
||||
while (cur != null)
|
||||
{
|
||||
cur = VisualTreeHelper.GetParent(cur);
|
||||
if (cur is T hit)
|
||||
{
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,10 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.CommandPalette.UI.Controls;
|
||||
using Microsoft.CommandPalette.UI.Helpers;
|
||||
using Microsoft.CommandPalette.UI.Models;
|
||||
using Microsoft.CommandPalette.UI.Models.Events;
|
||||
using Microsoft.CommandPalette.UI.Models.Messages;
|
||||
using Microsoft.CommandPalette.UI.Pages;
|
||||
using Microsoft.CommandPalette.UI.ViewModels.Helpers;
|
||||
@@ -230,7 +229,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
return;
|
||||
}
|
||||
|
||||
var newRect = EnsureWindowIsVisible(savedPosition.ToPhysicalWindowRectangle(), new SizeInt32(savedPosition.ScreenWidth, savedPosition.ScreenHeight), savedPosition.Dpi);
|
||||
var newRect = EnsureWindowIsVisible(savedPosition.ToPhysicalWindowRectangle(), new SizeInt32(savedPosition.ScreenWidth, savedPosition.ScreenHeight), savedPosition.Dpi, logger);
|
||||
AppWindow.MoveAndResize(newRect);
|
||||
}
|
||||
|
||||
@@ -343,14 +342,13 @@ public sealed partial class MainWindow : WindowEx,
|
||||
if (PInvoke.IsIconic(hwnd))
|
||||
{
|
||||
// Make sure our HWND is cloaked before any possible window manipulations
|
||||
Cloak();
|
||||
|
||||
// Cloak();
|
||||
PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_RESTORE);
|
||||
}
|
||||
|
||||
if (target == MonitorBehavior.ToLast)
|
||||
{
|
||||
var newRect = EnsureWindowIsVisible(_currentWindowPosition.ToPhysicalWindowRectangle(), new SizeInt32(_currentWindowPosition.ScreenWidth, _currentWindowPosition.ScreenHeight), _currentWindowPosition.Dpi);
|
||||
var newRect = EnsureWindowIsVisible(_currentWindowPosition.ToPhysicalWindowRectangle(), new SizeInt32(_currentWindowPosition.ScreenWidth, _currentWindowPosition.ScreenHeight), _currentWindowPosition.Dpi, logger);
|
||||
AppWindow.MoveAndResize(newRect);
|
||||
}
|
||||
else
|
||||
@@ -390,7 +388,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
/// A window rectangle in physical pixels, moved to the nearest display and resized
|
||||
/// if the DPI has changed.
|
||||
/// </returns>
|
||||
private static RectInt32 EnsureWindowIsVisible(RectInt32 windowRect, SizeInt32 originalScreen, int originalDpi)
|
||||
private static RectInt32 EnsureWindowIsVisible(RectInt32 windowRect, SizeInt32 originalScreen, int originalDpi, ILogger logger)
|
||||
{
|
||||
var displayArea = DisplayArea.GetFromRect(windowRect, DisplayAreaFallback.Nearest);
|
||||
if (displayArea is null)
|
||||
@@ -405,7 +403,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
return windowRect;
|
||||
}
|
||||
|
||||
var effectiveDpi = GetEffectiveDpiFromDisplayId(displayArea);
|
||||
var effectiveDpi = GetEffectiveDpiFromDisplayId(displayArea, logger);
|
||||
if (originalDpi <= 0)
|
||||
{
|
||||
originalDpi = effectiveDpi; // use current DPI as baseline (no scaling adjustment needed)
|
||||
@@ -462,7 +460,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
return new RectInt32(targetX, targetY, targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
private static int GetEffectiveDpiFromDisplayId(DisplayArea displayArea)
|
||||
private static int GetEffectiveDpiFromDisplayId(DisplayArea displayArea, ILogger logger)
|
||||
{
|
||||
var effectiveDpi = 96;
|
||||
|
||||
@@ -476,7 +474,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning($"GetDpiForMonitor failed with HRESULT: 0x{hr.Value:X8} on display {displayArea.DisplayId}");
|
||||
Log_GetDpiForMonitorFailed(logger, hr, displayArea);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,7 +933,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
{
|
||||
// ... then manually hide our window. When debugged, we won't get the cool cloaking,
|
||||
// but that's the price to pay for having the HWND not light-dismiss while we're debugging.
|
||||
Cloak();
|
||||
// Cloak();
|
||||
this.Hide();
|
||||
|
||||
return;
|
||||
@@ -993,7 +991,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
partial void Log_ExternalReloadDisabled();
|
||||
|
||||
[LoggerMessage(level: LogLevel.Warning, Message = "GetDpiForMonitor failed with HRESULT: 0x{hr.Value:X8} on display {DisplayArea.DisplayId}")]
|
||||
partial void LogWarning_GetDpiForMonitorFailed(HRESULT hr, DisplayArea displayArea);
|
||||
static partial void Log_GetDpiForMonitorFailed(ILogger logger, HRESULT hr, DisplayArea displayArea);
|
||||
|
||||
[LoggerMessage(level: LogLevel.Warning, Message = "DWM cloaking of the main window failed. HRESULT: {hr.Value}.")]
|
||||
partial void Log_DwmCloakingFailed(HRESULT hr);
|
||||
|
||||
@@ -164,14 +164,12 @@
|
||||
<ProjectReference Include="..\Microsoft.CommandPalette.UI.Models\Microsoft.CommandPalette.UI.Models.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CommandPalette.UI.ViewModels\Microsoft.CommandPalette.UI.ViewModels.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="ToastWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\ShellPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Selectors\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Microsoft.CommandPalette.UI.Pages.LoadingPage"
|
||||
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<ProgressRing
|
||||
Width="36"
|
||||
Height="36"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsIndeterminate="True" />
|
||||
<TextBlock Text="Here we go"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.UI.ViewModels;
|
||||
using Microsoft.CommandPalette.ViewModels;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// We use this page to do initialization of our extensions and cache loading to hydrate our ViewModels.
|
||||
/// </summary>
|
||||
public sealed partial class LoadingPage : Page
|
||||
{
|
||||
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
public LoadingPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
if (e.Parameter is not AsyncNavigationRequest request)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid navigation parameter: {nameof(e.Parameter)} must be {nameof(AsyncNavigationRequest)}");
|
||||
}
|
||||
|
||||
if (request.TargetViewModel is not ShellViewModel shellVM)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid navigation target: AsyncNavigationRequest.{nameof(AsyncNavigationRequest.TargetViewModel)} must be {nameof(ShellViewModel)}");
|
||||
}
|
||||
|
||||
// This will load the built-in commands, then navigate to the main page.
|
||||
// Once the mainpage loads, we'll start loading extensions.
|
||||
shellVM.LoadCommand.Execute(null);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await shellVM.LoadCommand.ExecutionTask!;
|
||||
|
||||
if (shellVM.LoadCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
// TODO: Handle failure case
|
||||
}
|
||||
});
|
||||
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Microsoft.CommandPalette.UI.Pages.ShellPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.CommandPalette.UI.Pages"
|
||||
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
|
||||
xmlns:cmdpalUI="using:Microsoft.CommandPalette.UI"
|
||||
xmlns:cmdpalConverters="using:Microsoft.CommandPalette.UI.Converters"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:cpcontrols="using:Microsoft.CommandPalette.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:help="using:Microsoft.CommandPalette.UI.Helpers"
|
||||
xmlns:markdownImageProviders="using:Microsoft.CommandPalette.UI.Helpers.MarkdownImageProviders"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d">
|
||||
<!--xmlns:labToolkit="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"-->
|
||||
|
||||
<Page.Resources>
|
||||
<ResourceDictionary>
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringNotEmptyToVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
NotEmptyValue="Visible" />
|
||||
|
||||
<cmdpalConverters:MessageStateToSeverityConverter x:Key="MessageStateToSeverityConverter" />
|
||||
|
||||
<!--<selectors:DetailsDataTemplateSelector
|
||||
x:Key="DetailsDataTemplateSelector"
|
||||
CommandTemplate="{StaticResource DetailsCommandsTemplate}"
|
||||
LinkTemplate="{StaticResource DetailsLinkTemplate}"
|
||||
SeparatorTemplate="{StaticResource DetailsSeparatorTemplate}"
|
||||
TagTemplate="{StaticResource DetailsTagsTemplate}" />-->
|
||||
|
||||
<converters:BoolToVisibilityConverter
|
||||
x:Key="BoolToInvertedVisibilityConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
|
||||
<Style
|
||||
x:Key="DetailKeyTextBlockStyle"
|
||||
BasedOn="{StaticResource CaptionTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="WrapWholeWords" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="SeparatorKeyTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="WrapWholeWords" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="DetailValueTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="WrapWholeWords" />
|
||||
</Style>
|
||||
|
||||
<tkcontrols:MarkdownThemes
|
||||
x:Key="DefaultMarkdownThemeConfig"
|
||||
H3FontSize="12"
|
||||
H3FontWeight="Normal" />
|
||||
<!--<markdownImageProviders:ImageProvider x:Key="ImageProvider" />-->
|
||||
<tkcontrols:MarkdownConfig
|
||||
x:Key="DefaultMarkdownConfig"
|
||||
ImageProvider="{StaticResource ImageProvider}"
|
||||
Themes="{StaticResource DefaultMarkdownThemeConfig}" />
|
||||
</ResourceDictionary>
|
||||
</Page.Resources>
|
||||
|
||||
@@ -35,6 +90,340 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Back button and search box -->
|
||||
<Grid
|
||||
x:Name="TopBarGrid"
|
||||
Padding="0,12,0,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Back button -->
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image
|
||||
Width="20"
|
||||
Margin="20,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}}"-->
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:OpacityAnimation
|
||||
EasingMode="EaseIn"
|
||||
EasingType="Cubic"
|
||||
From="0"
|
||||
To="1.0"
|
||||
Duration="0:0:0.187" />
|
||||
<animations:ScaleAnimation
|
||||
EasingMode="EaseIn"
|
||||
EasingType="Cubic"
|
||||
From="0.5"
|
||||
To="1"
|
||||
Duration="0:0:0.187" />
|
||||
</animations:Implicit.ShowAnimations>
|
||||
<animations:Implicit.HideAnimations>
|
||||
<animations:OpacityAnimation
|
||||
EasingMode="EaseOut"
|
||||
EasingType="Cubic"
|
||||
From="1.0"
|
||||
To="0"
|
||||
Duration="0:0:0.187" />
|
||||
<animations:ScaleAnimation
|
||||
EasingMode="EaseOut"
|
||||
EasingType="Cubic"
|
||||
From="1"
|
||||
To="0.5"
|
||||
Duration="0:0:0.187" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</Image>
|
||||
<!--<Button
|
||||
x:Name="BackButton"
|
||||
x:Uid="BackButton"
|
||||
Margin="4,0,4,0"
|
||||
Padding="4"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
Click="BackButton_Clicked"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=14}"
|
||||
FontSize="16"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="{x:Bind viewModel.CurrentPage.IsNested, Mode=OneWay}">
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:OpacityAnimation
|
||||
EasingMode="EaseIn"
|
||||
EasingType="Cubic"
|
||||
From="0"
|
||||
To="1.0"
|
||||
Duration="0:0:0.333" />
|
||||
<animations:ScaleAnimation
|
||||
From="0.5"
|
||||
To="1"
|
||||
Duration="0:0:0.333" />
|
||||
<animations:TranslationAnimation
|
||||
From="16,0,0"
|
||||
To="0,0,0"
|
||||
Duration="0:0:0.333" />
|
||||
</animations:Implicit.ShowAnimations>
|
||||
<animations:Implicit.HideAnimations>
|
||||
<animations:OpacityAnimation
|
||||
EasingMode="EaseOut"
|
||||
EasingType="Cubic"
|
||||
From="1.0"
|
||||
To="0"
|
||||
Duration="0:0:0.333" />
|
||||
<animations:ScaleAnimation
|
||||
EasingMode="EaseOut"
|
||||
EasingType="Cubic"
|
||||
From="1"
|
||||
To="0.5"
|
||||
Duration="0:0:0.333" />
|
||||
<animations:TranslationAnimation
|
||||
EasingMode="EaseOut"
|
||||
EasingType="Cubic"
|
||||
From="0,0,0"
|
||||
To="16,0,0"
|
||||
Duration="0:0:0.187" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</Button>-->
|
||||
<!--<cpcontrols:IconBox
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Margin="4,0,4,0"
|
||||
VerticalAlignment="Center"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
SourceKey="{x:Bind viewModel.CurrentPage.Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}"
|
||||
Visibility="{x:Bind viewModel.CurrentPage.IsNested, Mode=OneWay}">
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:OpacityAnimation
|
||||
From="0"
|
||||
To="1.0"
|
||||
Duration="0:0:0.333" />
|
||||
<animations:ScaleAnimation
|
||||
From="0.8"
|
||||
To="1"
|
||||
Duration="0:0:0.333" />
|
||||
<animations:TranslationAnimation
|
||||
From="8,0,0"
|
||||
To="0,0,0"
|
||||
Duration="0:0:0.187" />
|
||||
</animations:Implicit.ShowAnimations>
|
||||
<animations:Implicit.HideAnimations>
|
||||
<animations:OpacityAnimation
|
||||
From="1.0"
|
||||
To="0"
|
||||
Duration="0:0:0.333" />
|
||||
<animations:ScaleAnimation
|
||||
From="1"
|
||||
To="0.8"
|
||||
Duration="0:0:0.333" />
|
||||
<animations:TranslationAnimation
|
||||
From="0,0,0"
|
||||
To="8,0,0"
|
||||
Duration="0:0:0.187" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</cpcontrols:IconBox>-->
|
||||
</StackPanel>
|
||||
<!-- Search box: wrapped in a grid to enable RepositionThemeTransitions -->
|
||||
<Grid Grid.Column="1" HorizontalAlignment="Stretch">
|
||||
<cpcontrols:SearchBar
|
||||
x:Name="SearchBox"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<!--CurrentPageviewModel="{x:Bind viewModel.CurrentPage, Mode=OneWay}" />-->
|
||||
<Grid.Transitions>
|
||||
<TransitionCollection>
|
||||
<RepositionThemeTransition />
|
||||
</TransitionCollection>
|
||||
</Grid.Transitions>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid x:Name="ContentGrid" Grid.Row="1">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="3*" />
|
||||
<ColumnDefinition x:Name="DetailsColumn" Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Frame
|
||||
Name="RootFrame"
|
||||
AutomationProperties.AutomationControlType="Pane"
|
||||
AutomationProperties.LandmarkType="Navigation"
|
||||
AutomationProperties.LiveSetting="Assertive"
|
||||
IsNavigationStackEnabled="True" />
|
||||
|
||||
<ScrollViewer
|
||||
x:Name="DetailsContent"
|
||||
Grid.Column="1"
|
||||
Margin="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource ControlCornerRadius}"
|
||||
Visibility="Collapsed">
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:OpacityAnimation
|
||||
From="0"
|
||||
To="1.0"
|
||||
Duration="0:0:0.187" />
|
||||
<animations:TranslationAnimation
|
||||
From="24,0,0"
|
||||
To="0,0,0"
|
||||
Duration="0:0:0.187" />
|
||||
</animations:Implicit.ShowAnimations>
|
||||
<animations:Implicit.HideAnimations>
|
||||
<animations:OpacityAnimation
|
||||
From="1.0"
|
||||
To="0"
|
||||
Duration="0:0:0.187" />
|
||||
<animations:TranslationAnimation
|
||||
From="0,0,0"
|
||||
To="24,0,0"
|
||||
Duration="0:0:0.187" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!--<cpcontrols:IconBox
|
||||
x:Name="HeroImageBorder"
|
||||
Width="64"
|
||||
Margin="0,0,16,16"
|
||||
HorizontalAlignment="Left"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
SourceKey="{x:Bind viewModel.Details.HeroImage, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}"
|
||||
Visibility="{x:Bind HasHeroImage, Mode=OneWay}" />-->
|
||||
|
||||
<!--<TextBlock
|
||||
Grid.Row="1"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="{x:Bind viewModel.Details.Title, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="{x:Bind viewModel.Details.Title, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}" />
|
||||
|
||||
<tkcontrols:MarkdownTextBlock
|
||||
Grid.Row="2"
|
||||
Margin="0,4,0,24"
|
||||
Background="Transparent"
|
||||
Config="{StaticResource DefaultMarkdownConfig}"
|
||||
Text="{x:Bind viewModel.Details.Body, Mode=OneWay}"
|
||||
UseEmphasisExtras="True"
|
||||
UsePipeTables="True" />
|
||||
|
||||
<ItemsRepeater
|
||||
Grid.Row="3"
|
||||
ItemTemplate="{StaticResource DetailsDataTemplateSelector}"
|
||||
ItemsSource="{x:Bind viewModel.Details.Metadata, Mode=OneWay}">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout Spacing="12" />
|
||||
</ItemsRepeater.Layout>
|
||||
</ItemsRepeater>-->
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
<!-- /DetailsContent -->
|
||||
</Grid>
|
||||
<!--<ScrollView
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="2"
|
||||
MaxHeight="120"
|
||||
Background="{ThemeResource SystemControlErrorBackgroundColor}"
|
||||
BorderBrush="{ThemeResource SystemControlErrorTextForegroundBrush}"
|
||||
BorderThickness="0,1,0,1"
|
||||
CornerRadius="0"
|
||||
Visibility="{x:Bind viewModel.CurrentPage.ErrorMessage, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}">
|
||||
<TextBlock Margin="16,8,16,16" IsTextSelectionEnabled="True">
|
||||
<Run
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemErrorTextColor}"
|
||||
Text="Error(s) on page:" />
|
||||
<LineBreak /><Run Text="{x:Bind viewModel.CurrentPage.ErrorMessage, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</ScrollView>-->
|
||||
</Grid>
|
||||
|
||||
<!--
|
||||
Horrifying: You may ask yourself - why is there a Background on this InfoBar?
|
||||
|
||||
Well, as it turns out, the Informational InfoBar has a transparent
|
||||
background. It just cannot be bothered. So, we need to manually give
|
||||
it one to actually obscure the text beneath it. And you can't just give
|
||||
the InfoBar itself a Background, because then the other Severity's
|
||||
won't get colorized.
|
||||
|
||||
See https://github.com/microsoft/microsoft-ui-xaml/issues/5741
|
||||
-->
|
||||
<!--<StackPanel
|
||||
Grid.Row="0"
|
||||
Margin="16,8,16,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<InfoBar
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
IsOpen="{x:Bind viewModel.CurrentPage.HasStatusMessage, Mode=OneWay}"
|
||||
Message="{x:Bind viewModel.CurrentPage.MostRecentStatusMessage.Message, Mode=OneWay}"
|
||||
Severity="{x:Bind viewModel.CurrentPage.MostRecentStatusMessage.State, Mode=OneWay, Converter={StaticResource MessageStateToSeverityConverter}}">
|
||||
|
||||
<InfoBar.Content>
|
||||
<ProgressBar
|
||||
Margin="0,-20,0,0"
|
||||
IsIndeterminate="{x:Bind viewModel.CurrentPage.MostRecentStatusMessage.Progress.IsIndeterminate, Mode=OneWay}"
|
||||
Visibility="{x:Bind viewModel.CurrentPage.MostRecentStatusMessage.HasProgress, Mode=OneWay}"
|
||||
Value="{x:Bind viewModel.CurrentPage.MostRecentStatusMessage.Progress.ProgressPercent, Mode=OneWay}" />
|
||||
--><!-- Margin="0,0,0,6" MaxWidth="200"/> --><!--
|
||||
</InfoBar.Content>
|
||||
</InfoBar>
|
||||
</StackPanel>-->
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource LayerOnAcrylicSecondaryBackgroundBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<cpcontrols:CommandBar />
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
<!--
|
||||
Why disable it and make it transparent?
|
||||
To prevent the container from collapsing, ensuring the layout metrics remain unchanged.
|
||||
-->
|
||||
<VisualStateGroup.Transitions>
|
||||
<VisualTransition GeneratedDuration="0:0:0.187" To="SearchBarCollapsed" />
|
||||
</VisualStateGroup.Transitions>
|
||||
<VisualState x:Name="SearchBarVisible" />
|
||||
<!--<VisualState x:Name="SearchBarCollapsed">
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind h:BindTransformers.Negate(viewModel.IsSearchBoxVisible), Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SearchBox.IsEnabled" Value="False" />
|
||||
<Setter Target="SearchBox.Opacity" Value="0" />
|
||||
<Setter Target="TopBarGrid.BorderBrush" Value="Transparent" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>-->
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -2,38 +2,129 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Text;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CommandPalette.UI.Helpers;
|
||||
using Microsoft.CommandPalette.UI.Models;
|
||||
using Microsoft.CommandPalette.UI.Models.Messages;
|
||||
using Microsoft.CommandPalette.UI.ViewModels;
|
||||
using Microsoft.CommandPalette.ViewModels;
|
||||
using Windows.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Pages;
|
||||
|
||||
public sealed partial class ShellPage : Page
|
||||
public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page
|
||||
{
|
||||
private readonly ShellViewModel viewModel;
|
||||
private readonly SettingsModel _settingsModel;
|
||||
private readonly ILogger logger;
|
||||
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
|
||||
private readonly Microsoft.UI.Dispatching.DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
|
||||
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
|
||||
private readonly SlideNavigationTransitionInfo _slideRightTransition = new() { Effect = SlideNavigationTransitionEffect.FromRight };
|
||||
private readonly SuppressNavigationTransitionInfo _noAnimation = new();
|
||||
|
||||
private readonly ToastWindow _toast = new();
|
||||
|
||||
private readonly CompositeFormat _pageNavigatedAnnouncement;
|
||||
|
||||
|
||||
private CancellationTokenSource? _focusAfterLoadedCts;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private WeakReference<Page>? _lastNavigatedPageRef;
|
||||
private SettingsWindow? _settingsWindow;
|
||||
|
||||
public ShellPage(ShellViewModel viewModel)
|
||||
public ShellPage(ShellViewModel viewModel, SettingsModel settingsModel, ILogger logger)
|
||||
{
|
||||
InitializeComponent();
|
||||
this.viewModel = viewModel;
|
||||
_settingsModel = settingsModel;
|
||||
this.logger = logger;
|
||||
|
||||
// AddHandler(PreviewKeyDownEvent, new KeyEventHandler(ShellPage_OnPreviewKeyDown), true);
|
||||
// AddHandler(KeyDownEvent, new KeyEventHandler(ShellPage_OnKeyDown), false);
|
||||
// AddHandler(PointerPressedEvent, new PointerEventHandler(ShellPage_OnPointerPressed), true);
|
||||
RootFrame.Navigate(typeof(LoadingPage), new AsyncNavigationRequest(viewModel, CancellationToken.None));
|
||||
|
||||
var pageAnnouncementFormat = ResourceLoaderInstance.GetString("ScreenReader_Announcement_NavigatedToPage0");
|
||||
_pageNavigatedAnnouncement = CompositeFormat.Parse(pageAnnouncementFormat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default page animation, depending on the settings
|
||||
/// </summary>
|
||||
private NavigationTransitionInfo DefaultPageAnimation
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settingsModel.DisableAnimations ? _noAnimation : _slideRightTransition;
|
||||
}
|
||||
}
|
||||
|
||||
private void SummonOnUiThread(HotkeySummonMessage message)
|
||||
{
|
||||
var commandId = message.CommandId;
|
||||
var isRoot = string.IsNullOrEmpty(commandId);
|
||||
if (isRoot)
|
||||
{
|
||||
// If this is the hotkey for the root level, then always show us
|
||||
WeakReferenceMessenger.Default.Send<ShowWindowMessage>(new(message.Hwnd));
|
||||
|
||||
// Depending on the settings, either
|
||||
// * Go home, or
|
||||
// * Select the search text (if we should remain open on this page)
|
||||
if (_settingsModel.AutoGoHomeInterval == TimeSpan.Zero)
|
||||
{
|
||||
// GoHome(false);
|
||||
}
|
||||
else if (_settingsModel.HighlightSearchOnActivate)
|
||||
{
|
||||
// SearchBox.SelectSearch();
|
||||
}
|
||||
}
|
||||
|
||||
// else
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// // For a hotkey bound to a command, first lookup the
|
||||
// // command from our list of toplevel commands.
|
||||
// var tlcManager = App.Current.Services.GetService<TopLevelCommandManager>()!;
|
||||
// var topLevelCommand = tlcManager.LookupCommand(commandId);
|
||||
// if (topLevelCommand is not null)
|
||||
// {
|
||||
// var command = topLevelCommand.CommandViewModel.Model.Unsafe;
|
||||
// var isPage = command is not IInvokableCommand;
|
||||
// // If the bound command is an invokable command, then
|
||||
// // we don't want to open the window at all - we want to
|
||||
// // just do it.
|
||||
// if (isPage)
|
||||
// {
|
||||
// // If we're here, then the bound command was a page
|
||||
// // of some kind. Let's pop the stack, show the window, and navigate to it.
|
||||
// GoHome(false);
|
||||
// WeakReferenceMessenger.Default.Send<ShowWindowMessage>(new(message.Hwnd));
|
||||
// }
|
||||
// var msg = topLevelCommand.GetPerformCommandMessage();
|
||||
// msg.WithAnimation = false;
|
||||
// WeakReferenceMessenger.Default.Send<PerformCommandMessage>(msg);
|
||||
// // we can't necessarily SelectSearch() here, because when the page is loaded,
|
||||
// // we'll fetch the SearchText from the page itself, and that'll stomp the
|
||||
// // selection we start now.
|
||||
// // That's probably okay though.
|
||||
// }
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// }
|
||||
// }
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
|
||||
private void BackButton_Clicked(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) => WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Error)]
|
||||
partial void Log_ShowConfirmationMessageError(Exception ex);
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Warning, Message = "Invalid navigation target: AsyncNavigationRequest.{targetViewModel} must be {pageViewModel}")]
|
||||
partial void Log_InvalidNavigationTarget(string targetViewModel, string pageViewModel);
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Warning, Message = "Unrecognized target for shell navigation: {parameter}")]
|
||||
partial void Log_UnrecognizedNavigationTarget(NavigationEventArgs parameter);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// 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.Events;
|
||||
using Microsoft.CommandPalette.UI.Models.Events;
|
||||
using Microsoft.CommandPalette.UI.Services;
|
||||
using Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Windows.Win32;
|
||||
@@ -38,7 +38,7 @@ internal sealed partial class Program
|
||||
}
|
||||
|
||||
Log_AppStart(logger, DateTime.UtcNow);
|
||||
TelemetryService.WriteEvent(new ProcessStartedEvent());
|
||||
PowerToysTelemetry.Log.WriteEvent(new ProcessStartedEvent());
|
||||
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
var isRedirect = DecideRedirection();
|
||||
@@ -63,13 +63,13 @@ internal sealed partial class Program
|
||||
|
||||
if (keyInstance.IsCurrent)
|
||||
{
|
||||
TelemetryService.WriteEvent(new ColdLaunchEvent());
|
||||
PowerToysTelemetry.Log.WriteEvent(new ColdLaunchEvent());
|
||||
keyInstance.Activated += OnActivated;
|
||||
}
|
||||
else
|
||||
{
|
||||
isRedirect = true;
|
||||
TelemetryService.WriteEvent(new ReactivateInstanceEvent());
|
||||
PowerToysTelemetry.Log.WriteEvent(new ReactivateInstanceEvent());
|
||||
RedirectActivationTo(args, keyInstance);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// 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.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
|
||||
public abstract class TelemetryEventBase : EventBase, IEvent
|
||||
{
|
||||
// Overridden in derived classes
|
||||
public abstract PartA_PrivTags PartA_PrivTags { get; }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// 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.PowerToys.Telemetry;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI.Services.Telemetry;
|
||||
|
||||
internal static class TelemetryService
|
||||
{
|
||||
public static void WriteEvent(TelemetryEventBase telemetryEvent) => PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<winuiex:WindowEx
|
||||
x:Class="Microsoft.CommandPalette.UI.ToastWindow"
|
||||
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.CommandPalette.UI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Title="Command Palette Toast"
|
||||
mc:Ignorable="d">
|
||||
<winuiex:WindowEx.SystemBackdrop>
|
||||
<DesktopAcrylicBackdrop />
|
||||
</winuiex:WindowEx.SystemBackdrop>
|
||||
<Grid x:Name="ToastGrid">
|
||||
<!-- This padding is used to calculate the dimensions of the ToastWindow -->
|
||||
<TextBlock
|
||||
x:Name="ToastText"
|
||||
Padding="12,12,24,20"
|
||||
Text="{x:Bind ViewModel.ToastMessage, Mode=OneWay}"
|
||||
TextAlignment="Center" />
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
@@ -1,97 +0,0 @@
|
||||
// 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 ManagedCommon;
|
||||
using Microsoft.CommandPalette.UI.Models.Messages;
|
||||
using Windows.System;
|
||||
using RS_ = Microsoft.CommandPalette.UI.Helpers.ResourceLoaderInstance;
|
||||
|
||||
namespace Microsoft.CommandPalette.UI;
|
||||
|
||||
public sealed partial class ToastWindow : WindowEx,
|
||||
IRecipient<QuitMessage>
|
||||
{
|
||||
private readonly HWND _hwnd;
|
||||
private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
|
||||
private readonly HiddenOwnerWindowBehavior _hiddenOwnerWindowBehavior = new();
|
||||
|
||||
public ToastViewModel ViewModel { get; } = new();
|
||||
|
||||
public ToastWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
AppWindow.Hide();
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
AppWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay);
|
||||
this.SetIcon();
|
||||
AppWindow.Title = RS_.GetString("ToastWindowTitle");
|
||||
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
_hiddenOwnerWindowBehavior.ShowInTaskbar(this, false);
|
||||
|
||||
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
|
||||
PInvoke.EnableWindow(_hwnd, false);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||
}
|
||||
|
||||
private static double GetScaleFactor(HWND hwnd)
|
||||
{
|
||||
try
|
||||
{
|
||||
var monitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
|
||||
_ = PInvoke.GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out _);
|
||||
return dpiX / 96.0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to get scale factor, error: {ex.Message}");
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
private void PositionCentered()
|
||||
{
|
||||
this.SetWindowSize(ToastText.ActualWidth, ToastText.ActualHeight);
|
||||
|
||||
var displayArea = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest);
|
||||
if (displayArea is not null)
|
||||
{
|
||||
var centeredPosition = AppWindow.Position;
|
||||
centeredPosition.X = (displayArea.WorkArea.Width - AppWindow.Size.Width) / 2;
|
||||
|
||||
var monitorHeight = displayArea.WorkArea.Height;
|
||||
var windowHeight = AppWindow.Size.Height;
|
||||
centeredPosition.Y = monitorHeight - (windowHeight + 8); // Align with other shell toasts, like the volume indicator.
|
||||
AppWindow.Move(centeredPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowToast(string message)
|
||||
{
|
||||
ViewModel.ToastMessage = message;
|
||||
DispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.Low,
|
||||
() =>
|
||||
{
|
||||
PositionCentered();
|
||||
|
||||
// SW_SHOWNA prevents us from getting activated (and stealing FG)
|
||||
PInvoke.ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_SHOWNA);
|
||||
|
||||
_debounceTimer.Debounce(
|
||||
() =>
|
||||
{
|
||||
AppWindow.Hide();
|
||||
},
|
||||
interval: TimeSpan.FromMilliseconds(2500),
|
||||
immediate: false);
|
||||
});
|
||||
}
|
||||
|
||||
public void Receive(QuitMessage message)
|
||||
{
|
||||
// This might come in on a background thread
|
||||
DispatcherQueue.TryEnqueue(() => Close());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user