mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-15 16:56:26 +01:00
A collection of prototype fixes (#158)
Probably the last real prototype commit. There was a collection of _little things_ that made the current state of the prototype a little wacky. This fixes a lot of them, so that the prototype is in a "demoable" state. * Updates the WinUI version - there was a bugfix in that minor version bump that's needed to help fix the `E_LAYOUTCYCLE`. * The list no longer flickers uncontrollably as it loads top-level commands * filtering the list is a lot more efficient (still not what it used to be though) * Forms us a `ListView` instead of an `ItemsRepeater`. `ItemsView` also had a layout cycle, go figure. * Fixes the namespace of the samples extension, so it doesn't conflict with the SSH one * Adds a bunch of icons, subtitles * When the list updates, we'll try to maintain the selected item (really useful for something like the mastodon extension) * When we update tthe filter text, we'll do _way_ better at actually updating our own `SelectedItem`, * so that the Details doesn't stay open on an app if you hit `esc` * so that the selection doesn't.... fuck off to space (closes #155) * Fixes the calculator command to show up originally in the list * Includes _some_ of the styling changes @niels9001 is working on (notably, the subtitle being on it's own line) * I think also fixes #113
This commit is contained in:
@@ -51,7 +51,7 @@
|
||||
-->
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.1.5" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.6.240923002" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.6.241106002" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
|
||||
|
||||
@@ -630,7 +630,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "src\common\Tele
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBorders.UnitTests", "src\modules\MouseWithoutBorders\MouseWithoutBorders.UnitTests\MouseWithoutBorders.UnitTests.csproj", "{66614C26-314C-4B91-9071-76133422CFEF}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CommandPallete", "CommandPallete", "{3846508C-77EB-4034-A702-F8BB263C4F79}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CommandPalette", "CommandPalette", "{3846508C-77EB-4034-A702-F8BB263C4F79}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.UI.Poc", "src\modules\cmdpal\WindowsCommandPalette\Microsoft.CmdPal.UI.Poc.csproj", "{A60CB091-3E95-49F3-8315-18EA3B4334B9}"
|
||||
EndProject
|
||||
@@ -694,6 +694,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.UI.ViewMod
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PokedexExtension", "src\modules\cmdpal\Exts\PokedexExtension\PokedexExtension.csproj", "{D8DD2E06-7956-4673-95E7-F395AB5A5485}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MenusExtension", "src\modules\cmdpal\Exts\Menus\MenusExtension.csproj", "{8ABE2195-7514-425E-9A89-685FA42CEFC3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -3232,6 +3234,24 @@ Global
|
||||
{D8DD2E06-7956-4673-95E7-F395AB5A5485}.Release|x86.ActiveCfg = Release|x64
|
||||
{D8DD2E06-7956-4673-95E7-F395AB5A5485}.Release|x86.Build.0 = Release|x64
|
||||
{D8DD2E06-7956-4673-95E7-F395AB5A5485}.Release|x86.Deploy.0 = Release|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Debug|x64.Build.0 = Debug|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Debug|x86.Build.0 = Debug|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Debug|x86.Deploy.0 = Debug|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Release|x64.ActiveCfg = Release|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Release|x64.Build.0 = Release|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Release|x64.Deploy.0 = Release|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Release|x86.ActiveCfg = Release|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Release|x86.Build.0 = Release|x64
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3}.Release|x86.Deploy.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3495,6 +3515,7 @@ Global
|
||||
{8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
|
||||
{C66020D1-CB10-4CF7-8715-84C97FD5E5E2} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
|
||||
{D8DD2E06-7956-4673-95E7-F395AB5A5485} = {071E18A4-A530-46B8-AB7D-B862EE55E24E}
|
||||
{8ABE2195-7514-425E-9A89-685FA42CEFC3} = {071E18A4-A530-46B8-AB7D-B862EE55E24E}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed partial class MastodonExtensionPage : ListPage
|
||||
internal static readonly HttpClient Client = new();
|
||||
internal static readonly JsonSerializerOptions Options = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
private readonly List<MastodonStatus> _posts = new();
|
||||
private readonly List<ListItem> _items = new();
|
||||
|
||||
public MastodonExtensionPage()
|
||||
{
|
||||
@@ -37,18 +37,11 @@ internal sealed partial class MastodonExtensionPage : ListPage
|
||||
AccentColor = Color.FromArgb(255, 99, 100, 255);
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
private void AddPosts(List<MastodonStatus> posts)
|
||||
{
|
||||
if (_posts.Count == 0)
|
||||
foreach (var p in posts)
|
||||
{
|
||||
var postsAsync = FetchExplorePage();
|
||||
postsAsync.ConfigureAwait(false);
|
||||
var posts = postsAsync.Result;
|
||||
this._posts.AddRange(posts);
|
||||
}
|
||||
|
||||
return _posts
|
||||
.Select(p => new ListItem(new MastodonPostPage(p))
|
||||
var postItem = new ListItem(new MastodonPostPage(p))
|
||||
{
|
||||
Title = p.Account.DisplayName, // p.ContentAsPlainText(),
|
||||
Subtitle = $"@{p.Account.Username}",
|
||||
@@ -76,18 +69,38 @@ internal sealed partial class MastodonExtensionPage : ListPage
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new OpenUrlCommand(p.Url) { Name = "Open on web" }),
|
||||
],
|
||||
})
|
||||
};
|
||||
this._items.Add(postItem);
|
||||
}
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
if (_items.Count == 0)
|
||||
{
|
||||
var postsAsync = FetchExplorePage();
|
||||
postsAsync.ConfigureAwait(false);
|
||||
var posts = postsAsync.Result;
|
||||
this.AddPosts(posts);
|
||||
}
|
||||
|
||||
return _items
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public override void LoadMore()
|
||||
{
|
||||
var postsAsync = FetchExplorePage(20, this._posts.Count);
|
||||
this.Loading = true;
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Loading 20 posts, starting with {_items.Count}..." });
|
||||
var postsAsync = FetchExplorePage(20, this._items.Count);
|
||||
postsAsync.ContinueWith((res) =>
|
||||
{
|
||||
var posts = postsAsync.Result;
|
||||
this._posts.AddRange(posts);
|
||||
this.RaiseItemsChanged(this._posts.Count);
|
||||
this.AddPosts(posts);
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"... got {posts.Count} new posts" });
|
||||
|
||||
this.Loading = false;
|
||||
this.RaiseItemsChanged(this._items.Count);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
BIN
src/modules/cmdpal/Exts/Menus/Assets/SplashScreen.scale-200.png
Normal file
BIN
src/modules/cmdpal/Exts/Menus/Assets/SplashScreen.scale-200.png
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/modules/cmdpal/Exts/Menus/Assets/StoreLogo.png
Normal file
BIN
src/modules/cmdpal/Exts/Menus/Assets/StoreLogo.png
Normal file
Binary file not shown.
Binary file not shown.
231
src/modules/cmdpal/Exts/Menus/MenusCommandsProvider.cs
Normal file
231
src/modules/cmdpal/Exts/Menus/MenusCommandsProvider.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Menus;
|
||||
|
||||
public partial class MenusActionsProvider : CommandProvider
|
||||
{
|
||||
public MenusActionsProvider()
|
||||
{
|
||||
DisplayName = $"Menus from the open windows";
|
||||
}
|
||||
|
||||
private readonly IListItem[] _commands = [
|
||||
new ListItem(new AllWindowsPage()) { Subtitle = "Search menus in open windows" },
|
||||
];
|
||||
|
||||
public override IListItem[] TopLevelCommands()
|
||||
{
|
||||
return _commands;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
internal sealed partial class MenuItemCommand : InvokableCommand
|
||||
{
|
||||
private readonly MenuData _menuData;
|
||||
private readonly HWND _hwnd;
|
||||
|
||||
public MenuItemCommand(MenuData data, HWND hwnd)
|
||||
{
|
||||
_menuData = data;
|
||||
_hwnd = hwnd;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
PInvoke.SetForegroundWindow(_hwnd);
|
||||
PInvoke.SetActiveWindow(_hwnd);
|
||||
PInvoke.PostMessage(_hwnd, 273/*WM_COMMAND*/, _menuData.WID, 0);
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
internal sealed class MenuData
|
||||
{
|
||||
public string ItemText { get; set; }
|
||||
|
||||
public string PathText { get; set; }
|
||||
|
||||
public uint WID { get; set; }
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
internal sealed class WindowData
|
||||
{
|
||||
private readonly HWND handle;
|
||||
|
||||
private readonly string title = string.Empty;
|
||||
|
||||
public string Title => title;
|
||||
|
||||
public HWND Handle => handle;
|
||||
|
||||
internal WindowData(HWND hWnd)
|
||||
{
|
||||
handle = hWnd;
|
||||
var textLen = PInvoke.GetWindowTextLength(handle);
|
||||
if (textLen == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bufferSize = textLen + 1;
|
||||
unsafe
|
||||
{
|
||||
fixed (char* windowNameChars = new char[bufferSize])
|
||||
{
|
||||
if (PInvoke.GetWindowText(handle, windowNameChars, bufferSize) == 0)
|
||||
{
|
||||
var errorCode = Marshal.GetLastWin32Error();
|
||||
if (errorCode != 0)
|
||||
{
|
||||
throw new Win32Exception(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
title = new string(windowNameChars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<MenuData> GetMenuItems()
|
||||
{
|
||||
var hMenu = PInvoke.GetMenu_SafeHandle(handle);
|
||||
return GetMenuItems(hMenu, string.Empty);
|
||||
}
|
||||
|
||||
public List<MenuData> GetMenuItems(DestroyMenuSafeHandle hMenu, string menuPath)
|
||||
{
|
||||
// var s = new SafeMenu();
|
||||
// s.SetHandle(hMenu);
|
||||
List<MenuData> results = new();
|
||||
var menuItemCount = PInvoke.GetMenuItemCount(hMenu);
|
||||
for (var i = 0; i < menuItemCount; i++)
|
||||
{
|
||||
var mii = default(MENUITEMINFOW);
|
||||
mii.cbSize = (uint)Marshal.SizeOf<MENUITEMINFOW>();
|
||||
mii.fMask = MENU_ITEM_MASK.MIIM_STRING | MENU_ITEM_MASK.MIIM_ID | MENU_ITEM_MASK.MIIM_SUBMENU;
|
||||
mii.cch = 256;
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (char* menuTextBuffer = new char[mii.cch])
|
||||
{
|
||||
mii.dwTypeData = new PWSTR(menuTextBuffer); // Allocate memory for string
|
||||
|
||||
if (PInvoke.GetMenuItemInfo(hMenu, (uint)i, true, ref mii))
|
||||
{
|
||||
var itemText = mii.dwTypeData.ToString();
|
||||
|
||||
// Sanitize it. If it's got a tab, grab the text before that:
|
||||
var withoutShortcut = itemText.Split("\t").First();
|
||||
|
||||
// Now remove a `&`
|
||||
var sanitized = withoutShortcut.Replace("&", string.Empty);
|
||||
|
||||
var itemPath = $"{menuPath}{sanitized}";
|
||||
|
||||
// Leaf item
|
||||
if (mii.hSubMenu == IntPtr.Zero)
|
||||
{
|
||||
// Console.WriteLine($"- Leaf Item: {itemText}");
|
||||
// TriggerMenuItem(hWnd, mii.wID);
|
||||
var data = new MenuData() { ItemText = sanitized, PathText = itemPath, WID = mii.wID };
|
||||
results.Add(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recursively list submenu items
|
||||
var subMenuTest = PInvoke.GetSubMenu(hMenu, i);
|
||||
var otherTest = mii.hSubMenu;
|
||||
_ = otherTest == subMenuTest.DangerousGetHandle();
|
||||
var newPath = $"{sanitized} > ";
|
||||
var subItems = GetMenuItems(subMenuTest, newPath);
|
||||
results.AddRange(subItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
internal sealed partial class WindowMenusPage : ListPage
|
||||
{
|
||||
private readonly WindowData _window;
|
||||
|
||||
public WindowMenusPage(WindowData window)
|
||||
{
|
||||
_window = window;
|
||||
Icon = new(string.Empty);
|
||||
Name = window.Title;
|
||||
ShowDetails = false;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return _window.GetMenuItems().Select(menuData => new ListItem(new MenuItemCommand(menuData, _window.Handle)) { Title = menuData.ItemText, Subtitle = menuData.PathText }).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
internal sealed partial class AllWindowsPage : ListPage
|
||||
{
|
||||
private readonly List<WindowData> windows = new();
|
||||
|
||||
public AllWindowsPage()
|
||||
{
|
||||
Icon = new("\uf0b5"); // ChecklistMirrored
|
||||
Name = "Open Windows";
|
||||
ShowDetails = false;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
PInvoke.EnumWindows(EnumWindowsCallback, IntPtr.Zero);
|
||||
|
||||
return windows
|
||||
.Where(w => !string.IsNullOrEmpty(w.Title))
|
||||
.Select(w => new ListItem(new WindowMenusPage(w))
|
||||
{
|
||||
Title = w.Title,
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private BOOL EnumWindowsCallback(HWND hWnd, LPARAM lParam)
|
||||
{
|
||||
// Only consider top-level visible windows with menus
|
||||
if (/*PInvoke.IsWindowVisible(hWnd) &&*/ PInvoke.GetMenu(hWnd) != IntPtr.Zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
windows.Add(new(hWnd));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true; // Continue enumeration
|
||||
}
|
||||
}
|
||||
57
src/modules/cmdpal/Exts/Menus/MenusExtension.csproj
Normal file
57
src/modules/cmdpal/Exts/Menus/MenusExtension.csproj
Normal file
@@ -0,0 +1,57 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>MenusExtension</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>false</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPalExtensions\$(RootNamespace)</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Assets\StoreLogo.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CmdPal.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||
the Windows App SDK Nuget package has not yet been restored.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
13
src/modules/cmdpal/Exts/Menus/NativeMethods.txt
Normal file
13
src/modules/cmdpal/Exts/Menus/NativeMethods.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
GetMenu
|
||||
GetMenuItemInfoW
|
||||
SendMessageW
|
||||
EnumWindows
|
||||
IsWindowVisible
|
||||
GetWindowTextW
|
||||
GetWindowTextLength
|
||||
GetMenuItemCount
|
||||
GetSubMenu
|
||||
PostMessage
|
||||
SetActiveWindow
|
||||
SetForegroundWindow
|
||||
78
src/modules/cmdpal/Exts/Menus/Package.appxmanifest
Normal file
78
src/modules/cmdpal/Exts/Menus/Package.appxmanifest
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap uap3 rescap">
|
||||
|
||||
<Identity
|
||||
Name="MenusExtension"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="0.0.1.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Menus from the open windows</DisplayName>
|
||||
<PublisherDisplayName>A Lone Developer</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Menus from the open windows"
|
||||
Description="Menus from the open windows"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer Executable="MenusExtension.exe" Arguments="-RegisterProcessAsComServer" DisplayName="ClementineExtensionApp">
|
||||
<com:Class Id="9db16200-5579-4a62-9050-d67469316bba" DisplayName="Menus from the open windows" />
|
||||
</com:ExeServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.commandpalette"
|
||||
Id="PG-SP-ID"
|
||||
PublicFolder="Public"
|
||||
DisplayName="Menus from the open windows"
|
||||
Description="Menus from the open windows">
|
||||
<uap3:Properties>
|
||||
<CmdPalProvider>
|
||||
<Activation>
|
||||
<CreateInstance ClassId="9db16200-5579-4a62-9050-d67469316bba" />
|
||||
</Activation>
|
||||
<SupportedInterfaces>
|
||||
<Commands/>
|
||||
</SupportedInterfaces>
|
||||
</CmdPalProvider>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
25
src/modules/cmdpal/Exts/Menus/Pages/MenusPage.cs
Normal file
25
src/modules/cmdpal/Exts/Menus/Pages/MenusPage.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Menus;
|
||||
|
||||
internal sealed partial class MenusPage : ListPage
|
||||
{
|
||||
public MenusPage()
|
||||
{
|
||||
Icon = new(string.Empty);
|
||||
Name = "Menus from the open windows";
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return [
|
||||
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" }
|
||||
];
|
||||
}
|
||||
}
|
||||
36
src/modules/cmdpal/Exts/Menus/Program.cs
Normal file
36
src/modules/cmdpal/Exts/Menus/Program.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
|
||||
namespace Menus;
|
||||
|
||||
public class Program
|
||||
{
|
||||
[MTAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
|
||||
{
|
||||
using ExtensionServer server = new();
|
||||
var extensionDisposedEvent = new ManualResetEvent(false);
|
||||
var extensionInstance = new SampleExtension(extensionDisposedEvent);
|
||||
|
||||
// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
|
||||
// This makes sure that only one instance of SampleExtension is alive, which is returned every time the host asks for the IExtension object.
|
||||
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
|
||||
server.RegisterExtension(() => extensionInstance);
|
||||
|
||||
// This will make the main thread wait until the event is signalled by the extension class.
|
||||
// Since we have single instance of the extension object, we exit as sooon as it is disposed.
|
||||
extensionDisposedEvent.WaitOne();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Not being launched as a Extension... exiting.");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/modules/cmdpal/Exts/Menus/Properties/launchSettings.json
Normal file
11
src/modules/cmdpal/Exts/Menus/Properties/launchSettings.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Menus (Package)": {
|
||||
"commandName": "MsixPackage",
|
||||
"doNotLaunchApp": true
|
||||
},
|
||||
"Menus (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/modules/cmdpal/Exts/Menus/SampleExtension.cs
Normal file
39
src/modules/cmdpal/Exts/Menus/SampleExtension.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
|
||||
namespace Menus;
|
||||
|
||||
[ComVisible(true)]
|
||||
[Guid("9db16200-5579-4a62-9050-d67469316bba")]
|
||||
[ComDefaultInterface(typeof(IExtension))]
|
||||
public sealed partial class SampleExtension : IExtension
|
||||
{
|
||||
private readonly ManualResetEvent _extensionDisposedEvent;
|
||||
|
||||
public SampleExtension(ManualResetEvent extensionDisposedEvent)
|
||||
{
|
||||
this._extensionDisposedEvent = extensionDisposedEvent;
|
||||
}
|
||||
|
||||
public object GetProvider(ProviderType providerType)
|
||||
{
|
||||
switch (providerType)
|
||||
{
|
||||
case ProviderType.Commands:
|
||||
return new MenusActionsProvider();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this._extensionDisposedEvent.Set();
|
||||
}
|
||||
}
|
||||
19
src/modules/cmdpal/Exts/Menus/app.manifest
Normal file
19
src/modules/cmdpal/Exts/Menus/app.manifest
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="HackerNewsExtension.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -90,14 +90,18 @@ public partial class BookmarksCommandProvider : CommandProvider
|
||||
var listItem = new ListItem(action);
|
||||
|
||||
// Add actions for folder types
|
||||
if (action is UrlAction urlAction && urlAction.Type == "folder")
|
||||
if (action is UrlAction urlAction)
|
||||
{
|
||||
listItem.MoreCommands = [
|
||||
new CommandContextItem(new OpenInTerminalAction(urlAction.Url))
|
||||
];
|
||||
if (urlAction.Type == "folder")
|
||||
{
|
||||
listItem.MoreCommands = [
|
||||
new CommandContextItem(new OpenInTerminalAction(urlAction.Url))
|
||||
];
|
||||
}
|
||||
|
||||
listItem.Subtitle = urlAction.Url;
|
||||
}
|
||||
|
||||
// listItem.Subtitle = "Bookmark";
|
||||
if (action is not AddBookmarkPage)
|
||||
{
|
||||
listItem.Tags = [
|
||||
|
||||
@@ -17,14 +17,14 @@ public partial class CalculatorTopLevelListItem : ListItem, IFallbackHandler
|
||||
// In the case of the calculator, the ListItem itself is the fallback
|
||||
// handler so that it can update its Title and Subtitle accordingly.
|
||||
FallbackHandler = this;
|
||||
Subtitle = "Type an equation";
|
||||
SetDefaultTitle();
|
||||
}
|
||||
|
||||
public void UpdateQuery(string query)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query) || query == "=")
|
||||
{
|
||||
Title = "=";
|
||||
SetDefaultTitle();
|
||||
}
|
||||
else if (query.StartsWith('='))
|
||||
{
|
||||
@@ -37,6 +37,12 @@ public partial class CalculatorTopLevelListItem : ListItem, IFallbackHandler
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDefaultTitle()
|
||||
{
|
||||
Title = "=";
|
||||
Subtitle = "Type an equation";
|
||||
}
|
||||
|
||||
private string ParseQuery(string query)
|
||||
{
|
||||
var equation = query.Substring(1);
|
||||
|
||||
@@ -443,36 +443,13 @@ internal sealed partial class PokedexExtensionPage : ListPage
|
||||
|
||||
public PokedexExtensionPage()
|
||||
{
|
||||
Icon = new(string.Empty);
|
||||
Icon = new("https://e7.pngegg.com/pngimages/311/5/png-clipart-pokedex-pokemon-go-hoenn-pokemon-x-and-y-hoenn-pokedex-pokemon-ash-thumbnail.png");
|
||||
Name = "Pokedex";
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return _kanto.AsEnumerable().Concat(_johto.AsEnumerable()).Concat(_hoenn.AsEnumerable()).Select(p => GetPokemonListItem(p)).ToArray();
|
||||
/*return [
|
||||
new ListSection()
|
||||
{
|
||||
Title = "Kanto",
|
||||
Items = _kanto
|
||||
.AsEnumerable()
|
||||
.Select(p => GetPokemonListItem(p)).ToArray(),
|
||||
},
|
||||
new ListSection()
|
||||
{
|
||||
Title = "Johto",
|
||||
Items = _johto
|
||||
.AsEnumerable()
|
||||
.Select(p => GetPokemonListItem(p)).ToArray(),
|
||||
},
|
||||
new ListSection()
|
||||
{
|
||||
Title = "Hoenn",
|
||||
Items = _hoenn
|
||||
.AsEnumerable()
|
||||
.Select(p => GetPokemonListItem(p)).ToArray(),
|
||||
},
|
||||
];*/
|
||||
}
|
||||
|
||||
private static ListItem GetPokemonListItem(Pokemon pokemon)
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
@@ -16,11 +11,11 @@ public partial class PokedexExtensionActionsProvider : CommandProvider
|
||||
{
|
||||
public PokedexExtensionActionsProvider()
|
||||
{
|
||||
DisplayName = "Pocket Monsters for the Command Palette Commands";
|
||||
DisplayName = "Pocket Monsters for the Command Palette";
|
||||
}
|
||||
|
||||
private readonly IListItem[] _commands = [
|
||||
new ListItem(new PokedexExtensionPage()),
|
||||
new ListItem(new PokedexExtensionPage()) { Subtitle = "Search your favorite pocket monsters" },
|
||||
];
|
||||
|
||||
public override IListItem[] TopLevelCommands()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>SSHKeychainExtension</RootNamespace>
|
||||
<RootNamespace>SamplePagesExtension</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>false</UseWinUI>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using WindowsCommandPalette.Models;
|
||||
@@ -12,7 +13,7 @@ using WindowsCommandPalette.Views;
|
||||
|
||||
namespace WindowsCommandPalette;
|
||||
|
||||
public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable
|
||||
public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable, IEquatable<ListItemViewModel>
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
|
||||
@@ -192,4 +193,45 @@ public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable
|
||||
/* log something */
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(ListItemViewModel? other)
|
||||
{
|
||||
return other == null ? false : other.ListItem.Unsafe == this.ListItem.Unsafe;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return Equals(obj as ListItemViewModel);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(ListItemViewModel? l, ListItemViewModel? r)
|
||||
{
|
||||
return l is null ? r is null : l.Equals(r);
|
||||
}
|
||||
|
||||
public static bool operator !=(ListItemViewModel? l, ListItemViewModel? r)
|
||||
{
|
||||
return !(l == r);
|
||||
}
|
||||
|
||||
private struct ScoredListItemViewModel
|
||||
{
|
||||
public int Score;
|
||||
public ListItemViewModel ViewModel;
|
||||
}
|
||||
|
||||
public static IEnumerable<ListItemViewModel> FilterList(IEnumerable<ListItemViewModel> items, string query)
|
||||
{
|
||||
var scores = items
|
||||
.Select(li => new ScoredListItemViewModel() { ViewModel = li, Score = ListHelpers.ScoreListItem(query, li.ListItem.Unsafe) })
|
||||
.Where(score => score.Score > 0)
|
||||
.OrderByDescending(score => score.Score);
|
||||
return scores
|
||||
.Select(score => score.ViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using WindowsCommandPalette.Models;
|
||||
using WindowsCommandPalette.Views;
|
||||
|
||||
@@ -19,8 +21,12 @@ public sealed partial class MainListPage : DynamicListPage
|
||||
private readonly FilteredListSection _filteredSection;
|
||||
private readonly ObservableCollection<MainListItem> topLevelItems = new();
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
|
||||
public MainListPage(MainViewModel viewModel)
|
||||
{
|
||||
this._dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
this._mainViewModel = viewModel;
|
||||
|
||||
// wacky: "All apps" is added to _mainViewModel.TopLevelCommands before
|
||||
@@ -57,9 +63,6 @@ public sealed partial class MainListPage : DynamicListPage
|
||||
|
||||
private void UpdateQuery()
|
||||
{
|
||||
// Let our filtering wrapper know the newly typed search text:
|
||||
_filteredSection.Query = SearchText;
|
||||
|
||||
// Update all the top-level commands which are fallback providers:
|
||||
var fallbacks = topLevelItems
|
||||
.Select(i => i?.FallbackHandler)
|
||||
@@ -68,26 +71,42 @@ public sealed partial class MainListPage : DynamicListPage
|
||||
|
||||
foreach (var fb in fallbacks)
|
||||
{
|
||||
fb.UpdateQuery(SearchText);
|
||||
try
|
||||
{
|
||||
fb.UpdateQuery(SearchText);
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
Debug.WriteLine("Failed to update fallback handler:");
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Let our filtering wrapper know the newly typed search text.
|
||||
// Do this _after_ updating our fallback handlers.
|
||||
_filteredSection.Query = SearchText;
|
||||
|
||||
var count = string.IsNullOrEmpty(SearchText) ? topLevelItems.Count : _filteredSection.Count;
|
||||
RaiseItemsChanged(count);
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SearchText))
|
||||
{
|
||||
return topLevelItems.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
return _filteredSection.Items;
|
||||
}
|
||||
return string.IsNullOrEmpty(SearchText) ? topLevelItems
|
||||
.Where(item => !string.IsNullOrEmpty(item.Title))
|
||||
.ToArray()
|
||||
: _filteredSection.Items;
|
||||
}
|
||||
|
||||
private void TopLevelCommands_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
this._dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () =>
|
||||
{
|
||||
this.Handle_TopLevelCommands_CollectionChanged(sender, e);
|
||||
});
|
||||
}
|
||||
|
||||
private void Handle_TopLevelCommands_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
Debug.WriteLine("TopLevelCommands_CollectionChanged");
|
||||
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
|
||||
|
||||
@@ -107,6 +107,7 @@ public sealed partial class MainWindow : Window
|
||||
|
||||
_mainViewModel.QuitRequested += (s, e) =>
|
||||
{
|
||||
Activated -= MainWindow_Activated;
|
||||
Close();
|
||||
|
||||
// Application.Current.Exit();
|
||||
@@ -121,12 +122,12 @@ public sealed partial class MainWindow : Window
|
||||
var success = Windows.Win32.PInvoke.RegisterHotKey(hwnd, 0, mod, vk);
|
||||
hotKeyPrc = HotKeyPrc;
|
||||
var hotKeyPrcPointer = Marshal.GetFunctionPointerForDelegate(hotKeyPrc);
|
||||
origPrc = Marshal.GetDelegateForFunctionPointer<WNDPROC>((IntPtr)Windows.Win32.PInvoke.SetWindowLongPtr(hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer));
|
||||
origPrc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(Windows.Win32.PInvoke.SetWindowLongPtr(hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer));
|
||||
}
|
||||
|
||||
private void PositionCentered()
|
||||
{
|
||||
_appWindow.Resize(new SizeInt32 { Width = 860, Height = 560 });
|
||||
_appWindow.Resize(new SizeInt32 { Width = 1000, Height = 680 });
|
||||
DisplayArea displayArea = DisplayArea.GetFromWindowId(_appWindow.Id, DisplayAreaFallback.Nearest);
|
||||
if (displayArea is not null)
|
||||
{
|
||||
@@ -150,7 +151,7 @@ public sealed partial class MainWindow : Window
|
||||
var onLeft = false;
|
||||
try
|
||||
{
|
||||
using RegistryKey? key = Registry.CurrentUser?.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced");
|
||||
using var key = Registry.CurrentUser?.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced");
|
||||
if (key != null)
|
||||
{
|
||||
var o = key.GetValue("TaskbarGlomLevel");
|
||||
@@ -174,7 +175,7 @@ public sealed partial class MainWindow : Window
|
||||
// react appropriately
|
||||
}
|
||||
|
||||
Microsoft.UI.Windowing.DisplayArea displayArea = Microsoft.UI.Windowing.DisplayArea.GetFromWindowId(_appWindow.Id, Microsoft.UI.Windowing.DisplayAreaFallback.Nearest);
|
||||
var displayArea = Microsoft.UI.Windowing.DisplayArea.GetFromWindowId(_appWindow.Id, Microsoft.UI.Windowing.DisplayAreaFallback.Nearest);
|
||||
if (displayArea is not null)
|
||||
{
|
||||
var centeredPosition = _appWindow.Position;
|
||||
@@ -233,14 +234,9 @@ public sealed partial class MainWindow : Window
|
||||
private static string KeybindingToString(VirtualKey key, VirtualKeyModifiers modifiers)
|
||||
{
|
||||
var keyString = key.ToString();
|
||||
if (keyString.Length == 1)
|
||||
{
|
||||
keyString = keyString.ToUpper(System.Globalization.CultureInfo.CurrentCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
keyString = Regex.Replace(keyString, "([a-z])([A-Z])", "$1+$2");
|
||||
}
|
||||
keyString = keyString.Length == 1
|
||||
? keyString.ToUpper(System.Globalization.CultureInfo.CurrentCulture)
|
||||
: Regex.Replace(keyString, "([a-z])([A-Z])", "$1+$2");
|
||||
|
||||
var modifierString = string.Empty;
|
||||
if (modifiers.HasFlag(VirtualKeyModifiers.Control))
|
||||
@@ -370,20 +366,16 @@ public sealed partial class MainWindow : Window
|
||||
|
||||
private DesktopAcrylicController GetAcrylicConfig()
|
||||
{
|
||||
if (((FrameworkElement)this.Content).ActualTheme == ElementTheme.Light)
|
||||
{
|
||||
return new DesktopAcrylicController()
|
||||
return ((FrameworkElement)this.Content).ActualTheme == ElementTheme.Light
|
||||
? new DesktopAcrylicController()
|
||||
{
|
||||
Kind = DesktopAcrylicKind.Thin,
|
||||
TintColor = Windows.UI.Color.FromArgb(255, 243, 243, 243),
|
||||
LuminosityOpacity = 0.90f,
|
||||
TintOpacity = 0.0f,
|
||||
FallbackColor = Windows.UI.Color.FromArgb(255, 238, 238, 238),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DesktopAcrylicController()
|
||||
}
|
||||
: new DesktopAcrylicController()
|
||||
{
|
||||
Kind = DesktopAcrylicKind.Thin,
|
||||
TintColor = Windows.UI.Color.FromArgb(255, 32, 32, 32),
|
||||
@@ -391,7 +383,6 @@ public sealed partial class MainWindow : Window
|
||||
TintOpacity = 0.5f,
|
||||
FallbackColor = Windows.UI.Color.FromArgb(255, 28, 28, 28),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_ActualThemeChanged(FrameworkElement sender, object args)
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
<PackageReference Include="AdaptiveCards.ObjectModel.WinUI3" GeneratePathProperty="true" />
|
||||
<PackageReference Include="AdaptiveCards.Rendering.WinUI3" GeneratePathProperty="True" />
|
||||
<PackageReference Include="AdaptiveCards.Templating" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
|
||||
|
||||
<!-- <PackageReference Include="CommunityToolkit.WinUI" /> -->
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Markdown" />
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<DataTemplate x:Key="CardTemplate" x:DataType="local:FormViewModel">
|
||||
<Border
|
||||
x:Name="DetailsContent"
|
||||
Margin="0,0,0,0"
|
||||
Margin="0, 4, 0, 4"
|
||||
Padding="8"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
@@ -33,7 +33,7 @@
|
||||
</ResourceDictionary>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid Padding="12" RowSpacing="16">
|
||||
<Grid Padding="4, 4, 0, 0" RowSpacing="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
@@ -57,16 +57,12 @@
|
||||
Text="{x:Bind ViewModel.Page.Name}" />
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer
|
||||
x:Name="ScrollViewer"
|
||||
Grid.Row="1">
|
||||
<Grid x:Name="FormContent">
|
||||
<ItemsRepeater
|
||||
x:Name="FormsRepeater"
|
||||
ItemTemplate="{StaticResource CardTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.Forms, Mode=OneWay}"
|
||||
Layout="{StaticResource VerticalStackLayout}" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
<ListView x:Name="FormItems"
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectionMode="None"
|
||||
ItemTemplate="{StaticResource CardTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.Forms, Mode=OneWay}"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed partial class FormPage : Page
|
||||
public FormPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
UISettings settings = new UISettings();
|
||||
var settings = new UISettings();
|
||||
|
||||
// yep this is the way to check if you're in light theme or dark.
|
||||
// yep it's this dumb
|
||||
@@ -50,7 +50,7 @@ public sealed partial class FormPage : Page
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
FormsRepeater.ItemsSource = ViewModel.Forms;
|
||||
FormItems.ItemsSource = ViewModel.Forms;
|
||||
|
||||
Debug.WriteLine($"Rendering {this.ViewModel.Forms.Count} forms");
|
||||
foreach (var form in this.ViewModel.Forms)
|
||||
@@ -58,14 +58,14 @@ public sealed partial class FormPage : Page
|
||||
AddCardElement(form);
|
||||
}
|
||||
|
||||
FormContent.Focus(FocusState.Programmatic);
|
||||
FormItems.Focus(FocusState.Programmatic);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void Page_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FormContent.Focus(FocusState.Programmatic);
|
||||
FormItems.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
private void BackButton_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
|
||||
@@ -3,22 +3,28 @@
|
||||
x:Class="WindowsCommandPalette.Views.ListPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ani="using:CommunityToolkit.WinUI.Animations"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:devpal="using:WindowsCommandPalette"
|
||||
xmlns:local="using:WindowsCommandPalette.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:rsdk="using:Microsoft.CmdPal.Extensions"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
Background="{x:Bind AccentColorBrush, Mode=OneWay}"
|
||||
Loaded="Page_Loaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
|
||||
<Page.Resources>
|
||||
<ResourceDictionary>
|
||||
<converters:StringVisibilityConverter x:Key="StringNotEmptyToVisibilityConverter" EmptyValue="Collapsed" NotEmptyValue="Visible" />
|
||||
<converters:BoolToVisibilityConverter x:Key="ReverseBoolToVisibilityConverter" TrueValue="Collapsed" FalseValue="Visible" />
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringNotEmptyToVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
NotEmptyValue="Visible" />
|
||||
<converters:BoolToVisibilityConverter
|
||||
x:Key="ReverseBoolToVisibilityConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
|
||||
<StackLayout
|
||||
x:Name="HorizontalStackLayout"
|
||||
@@ -42,7 +48,7 @@
|
||||
Visibility="{x:Bind HasIcon, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
FontSize="12"
|
||||
Foreground="{x:Bind TextBrush, Mode=OneWay}"
|
||||
Text="{x:Bind Text, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
@@ -52,13 +58,13 @@
|
||||
<!-- Template for items in the main list view -->
|
||||
<DataTemplate x:Key="ListItemTemplate" x:DataType="devpal:ListItemViewModel">
|
||||
<ListViewItem
|
||||
MinHeight="40"
|
||||
MinHeight="56"
|
||||
KeyDown="ListItem_KeyDown"
|
||||
Tapped="ListItem_Tapped"
|
||||
Visibility="{x:Bind Title, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="24" />
|
||||
<ColumnDefinition Width="28" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
@@ -67,30 +73,30 @@
|
||||
Width="20"
|
||||
Height="20"
|
||||
VerticalAlignment="Center">
|
||||
<ContentControl
|
||||
Width="24"
|
||||
Height="24"
|
||||
Content="{x:Bind IcoElement, Mode=OneWay}" />
|
||||
<ContentControl Content="{x:Bind IcoElement, Mode=OneWay}" />
|
||||
</Viewbox>
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
Orientation="Vertical"
|
||||
Spacing="0">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
FontWeight="Medium"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Title, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
Margin="4,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Subtitle, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Visibility="{x:Bind Subtitle, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}">
|
||||
<Run Text=" - " />
|
||||
<Run Text="{x:Bind Subtitle, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind Subtitle, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<ItemsRepeater ItemTemplate="{StaticResource TagTemplate}"
|
||||
ItemsSource="{x:Bind Tags}"
|
||||
@@ -146,8 +152,10 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="56" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" MinHeight="36" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Row 0: Back button and search box -->
|
||||
@@ -220,12 +228,45 @@
|
||||
PlaceholderText="Type here to search..."
|
||||
Style="{StaticResource SearchTextBoxStyle}"
|
||||
TextChanged="FilterBox_TextChanged" />
|
||||
|
||||
</Grid>
|
||||
<Grid
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Padding="24,12,0,0"
|
||||
Orientation="Horizontal"
|
||||
Visibility="Collapsed"
|
||||
Spacing="16">
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Text="All" />
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Apps" />
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Extensions" />
|
||||
<Button
|
||||
Height="24"
|
||||
Click="ToggleButton_Click"
|
||||
Content="Tralalal"
|
||||
Opacity="0" />
|
||||
</StackPanel>
|
||||
<Grid
|
||||
Grid.Row="3"
|
||||
Background="{ThemeResource LayerOnAcrylicFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,1,0,1">
|
||||
BorderThickness="0,1,0,1"
|
||||
CornerRadius="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="3*" />
|
||||
<ColumnDefinition x:Name="DetailsColumn" Width="2*" />
|
||||
@@ -234,29 +275,25 @@
|
||||
<ListView
|
||||
x:Name="ItemsList"
|
||||
Grid.Column="0"
|
||||
Margin="4,0,0,0"
|
||||
Margin="0,0,0,0"
|
||||
IsItemClickEnabled="True"
|
||||
ItemTemplate="{StaticResource ListItemTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
SelectionChanged="ItemsList_SelectionChanged"
|
||||
Style="{StaticResource NoAnimationsPlease}">
|
||||
|
||||
</ListView>
|
||||
Style="{StaticResource NoAnimationsPlease}" />
|
||||
|
||||
<Border
|
||||
x:Name="DetailsContent"
|
||||
Grid.Column="1"
|
||||
Margin="12"
|
||||
Padding="8"
|
||||
Background="{ThemeResource LayerFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource OverlayCornerRadius}"
|
||||
BorderThickness="1,0,0,0"
|
||||
Visibility="Collapsed" />
|
||||
</Grid>
|
||||
|
||||
<!-- Footer -->
|
||||
<Grid Grid.Row="3" Padding="8,0,8,0">
|
||||
<Grid Grid.Row="4" Padding="8,0,8,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
@@ -305,6 +342,46 @@
|
||||
</Flyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="InstallationDialog"
|
||||
Grid.Row="5"
|
||||
Visibility="Collapsed">
|
||||
<ani:Implicit.ShowAnimations>
|
||||
<ani:OpacityAnimation
|
||||
From="0"
|
||||
To="1"
|
||||
Duration="0:0:0.4" />
|
||||
<ani:TranslationAnimation
|
||||
From="0,20,0"
|
||||
To="0"
|
||||
Duration="0:0:0.4" />
|
||||
</ani:Implicit.ShowAnimations>
|
||||
<ani:Implicit.HideAnimations>
|
||||
<ani:OpacityAnimation
|
||||
From="1"
|
||||
To="0"
|
||||
Duration="0:0:0.2" />
|
||||
<ani:TranslationAnimation
|
||||
From="0"
|
||||
To="0,20,0"
|
||||
Duration="0:0:0.2" />
|
||||
</ani:Implicit.HideAnimations>
|
||||
<Grid Background="{ThemeResource AccentAcrylicBackgroundFillColorBaseBrush}" Opacity="0.2" />
|
||||
<StackPanel
|
||||
Margin="22,12,12,12"
|
||||
Orientation="Horizontal"
|
||||
Spacing="18">
|
||||
<ProgressRing
|
||||
Width="20"
|
||||
Height="20"
|
||||
IsActive="True" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="Installing SpongeBot extension.." />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
@@ -34,6 +33,7 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP
|
||||
get => _selectedItem;
|
||||
set
|
||||
{
|
||||
Debug.WriteLine($" Selected: {SelectedItem?.Title}");
|
||||
_selectedItem = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MoreCommandsAvailable)));
|
||||
@@ -128,6 +128,7 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP
|
||||
ViewModel.InitialRender().ContinueWith((t) =>
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() => { UpdateFilter(FilterBox.Text); });
|
||||
ViewModel.FilteredItems.CollectionChanged += FilteredItems_CollectionChanged;
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -138,6 +139,37 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP
|
||||
this.ItemsList.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void FilteredItems_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// *
|
||||
// Debug.WriteLine($"FilteredItems_CollectionChanged");
|
||||
// Try to maintain the selected item, if we can.
|
||||
if (ItemsList.SelectedItem != null &&
|
||||
ItemsList.SelectedItem is ListItemViewModel li)
|
||||
{
|
||||
var xamlListItem = ItemsList.ContainerFromItem(li);
|
||||
if (xamlListItem != null)
|
||||
{
|
||||
var index = ItemsList.IndexFromContainer(xamlListItem);
|
||||
if (index >= 0)
|
||||
{
|
||||
// Debug.WriteLine("Found original selected item");
|
||||
this.ItemsList.SelectedIndex = index;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Debug.WriteLine($"Didn't find {li.Title} in new list");
|
||||
}
|
||||
}
|
||||
|
||||
// */
|
||||
|
||||
// Debug.WriteLine($"Selecting index 0");
|
||||
this.ItemsList.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void DoAction(ActionViewModel actionViewModel)
|
||||
{
|
||||
ViewModel?.DoAction(actionViewModel);
|
||||
@@ -200,6 +232,7 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP
|
||||
|
||||
private void ItemsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
Debug.WriteLine($" ItemsList_SelectionChanged");
|
||||
if (sender is not ListView lv)
|
||||
{
|
||||
return;
|
||||
@@ -301,6 +334,7 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP
|
||||
{
|
||||
if (FilterBox.Text.Length > 0)
|
||||
{
|
||||
Debug.WriteLine("Clear seearch text");
|
||||
FilterBox.Text = string.Empty;
|
||||
}
|
||||
else
|
||||
@@ -340,31 +374,17 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.WriteLine($"UpdateFilter({text})");
|
||||
|
||||
// Go ask the ViewModel for the items to display. This might:
|
||||
// * do an async request to the extension (fixme after GH #77)
|
||||
// * just return already filtered items.
|
||||
// * return a subset of items matching the filter text
|
||||
var items = ViewModel.GetFilteredItems(text);
|
||||
|
||||
Debug.WriteLine($" UpdateFilter after GetFilteredItems({text}) --> {items.Count()} ; {ViewModel.FilteredItems.Count}");
|
||||
|
||||
// Here, actually populate ViewModel.FilteredItems
|
||||
// WARNING: if you do this off the UI thread, it sure won't work right.
|
||||
ListHelpers.InPlaceUpdateList(ViewModel.FilteredItems, new(items.ToList()));
|
||||
Debug.WriteLine($" UpdateFilter after InPlaceUpdateList --> {ViewModel.FilteredItems.Count}");
|
||||
|
||||
// set the selected index to the first item in the list
|
||||
if (ItemsList.Items.Count > 0)
|
||||
{
|
||||
ItemsList.SelectedIndex = 0;
|
||||
ItemsList.ScrollIntoView(ItemsList.SelectedItem);
|
||||
}
|
||||
// Debug.WriteLine($"UpdateFilter({text})");
|
||||
ViewModel.UpdateSearchText(text);
|
||||
}
|
||||
|
||||
private void BackButton_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.GoBack();
|
||||
}
|
||||
|
||||
private void ToggleButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InstallationDialog.Visibility = InstallationDialog.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,28 +91,27 @@ public sealed class ListPageViewModel : PageViewModel
|
||||
|
||||
// still on main thread
|
||||
|
||||
// TODO! For dynamic lists, we're clearing out the whole list of items
|
||||
// we already have, then rebuilding it. We shouldn't do that. We should
|
||||
// still use the results from GetItems and put them into the code in
|
||||
// UpdateFilter to intelligently add/remove as needed.
|
||||
// TODODO! are we still? ^^
|
||||
// This creates an entirely new list of ListItemViewModels, and we're
|
||||
// really hoping that the equality check in `InPlaceUpdateList`
|
||||
// properly uses ListItemViewModel.Equals to compare if the objects
|
||||
// are literally the same.
|
||||
Collection<ListItemViewModel> newItems = new(items.Select(i => new ListItemViewModel(i)).ToList());
|
||||
Debug.WriteLine($" Found {newItems.Count} items");
|
||||
|
||||
// Debug.WriteLine($" Found {newItems.Count} items");
|
||||
|
||||
// THIS populates FilteredItems. If you do this off the UI thread, guess what -
|
||||
// the list view won't update. So WATCH OUT
|
||||
ListHelpers.InPlaceUpdateList(FilteredItems, newItems);
|
||||
ListHelpers.InPlaceUpdateList(_items, newItems);
|
||||
|
||||
Debug.WriteLine($"Done with UpdateListItems, found {FilteredItems.Count} / {_items.Count}");
|
||||
// Debug.WriteLine($"Done with UpdateListItems, found {FilteredItems.Count} / {_items.Count}");
|
||||
}
|
||||
|
||||
internal IEnumerable<ListItemViewModel> GetFilteredItems(string query)
|
||||
public void UpdateSearchText(string query)
|
||||
{
|
||||
// This method does NOT change any lists. It doesn't modify _items or FilteredItems...
|
||||
if (query == _query)
|
||||
{
|
||||
return FilteredItems;
|
||||
return;
|
||||
}
|
||||
|
||||
_query = query;
|
||||
@@ -120,25 +119,13 @@ public sealed class ListPageViewModel : PageViewModel
|
||||
{
|
||||
// Tell the dynamic page the new search text. If they need to update, they will.
|
||||
IsDynamicPage.SearchText = _query;
|
||||
|
||||
return FilteredItems;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Static lists don't need to re-fetch the items
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
return _items;
|
||||
}
|
||||
|
||||
// TODO! Probably bad that this turns list view models into listitems back to NEW view models
|
||||
// TODO! make this safer
|
||||
// TODODO! ^ still relevant?
|
||||
var newFilter = ListHelpers
|
||||
.FilterList(_items.Select(vm => vm.ListItem.Unsafe), query)
|
||||
.Select(li => new ListItemViewModel(li));
|
||||
|
||||
return newFilter;
|
||||
var filtered = ListItemViewModel
|
||||
.FilterList(_items, query);
|
||||
Collection<ListItemViewModel> newItems = new(filtered.ToList());
|
||||
ListHelpers.InPlaceUpdateList(FilteredItems, newItems);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,13 @@ namespace Microsoft.CmdPal.Extensions.Helpers;
|
||||
public class ListHelpers
|
||||
{
|
||||
// Generate a score for a list item.
|
||||
// TODO! This has side effects! This calls UpdateQuery on fallback handlers and that's async
|
||||
public static int ScoreListItem(string query, IListItem listItem)
|
||||
{
|
||||
var isFallback = false;
|
||||
if (listItem.FallbackHandler != null)
|
||||
{
|
||||
isFallback = true;
|
||||
listItem.FallbackHandler.UpdateQuery(query);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(listItem.Title))
|
||||
{
|
||||
return 0;
|
||||
@@ -58,7 +57,10 @@ public class ListHelpers
|
||||
{
|
||||
for (var j = i; j < original.Count; j++)
|
||||
{
|
||||
if (original[j] == newContents[i])
|
||||
var og_2 = original[j];
|
||||
var newItem_2 = newContents[i];
|
||||
var areEqual_2 = og_2.Equals(newItem_2);
|
||||
if (areEqual_2)
|
||||
{
|
||||
for (var k = i; k < j; k++)
|
||||
{
|
||||
@@ -70,14 +72,14 @@ public class ListHelpers
|
||||
}
|
||||
}
|
||||
|
||||
var og = original[i];
|
||||
var newItem = newContents[i];
|
||||
var areEqual = og.Equals(newItem);
|
||||
|
||||
// Is this new item already in the list?
|
||||
if (original[i] == newContents[i])
|
||||
if (areEqual)
|
||||
{
|
||||
// It is already in the list
|
||||
if (original[i] is Collection<T> og && newContents[i] is Collection<T> newG)
|
||||
{
|
||||
InPlaceUpdateList(og, newG);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user