mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-08 05:17:03 +01:00
Compare commits
2 Commits
dev/snickl
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
062dce8002 | ||
|
|
bef6f6c49a |
@@ -4,9 +4,8 @@
|
||||
<Import Project=".\Common.Dotnet.PrepareGeneratedFolder.targets" />
|
||||
|
||||
<PropertyGroup>
|
||||
<CoreTargetFramework>net9.0</CoreTargetFramework>
|
||||
<WindowsSdkPackageVersion>10.0.26100.68-preview</WindowsSdkPackageVersion>
|
||||
<TargetFramework>$(CoreTargetFramework)-windows10.0.26100.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
|
||||
@@ -19,26 +19,6 @@
|
||||
|
||||
class FileLocksmithModule : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
// Update registration based on enabled state
|
||||
void UpdateRegistration(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
FileLocksmithRuntimeRegistration::EnsureRegistered();
|
||||
Logger::info(L"File Locksmith context menu registered");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
FileLocksmithRuntimeRegistration::Unregister();
|
||||
Logger::info(L"File Locksmith context menu unregistered");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
FileLocksmithModule()
|
||||
{
|
||||
@@ -108,16 +88,21 @@ public:
|
||||
package::RegisterSparsePackage(path, packageUri);
|
||||
}
|
||||
}
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
FileLocksmithRuntimeRegistration::EnsureRegistered();
|
||||
#endif
|
||||
|
||||
m_enabled = true;
|
||||
UpdateRegistration(m_enabled);
|
||||
}
|
||||
|
||||
virtual void disable() override
|
||||
{
|
||||
Logger::info(L"File Locksmith disabled");
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
FileLocksmithRuntimeRegistration::Unregister();
|
||||
Logger::info(L"File Locksmith context menu unregistered (Win10)");
|
||||
#endif
|
||||
m_enabled = false;
|
||||
UpdateRegistration(m_enabled);
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
@@ -150,7 +135,6 @@ private:
|
||||
{
|
||||
m_enabled = FileLocksmithSettingsInstance().GetEnabled();
|
||||
m_extended_only = FileLocksmithSettingsInstance().GetShowInExtendedContextMenu();
|
||||
UpdateRegistration(m_enabled);
|
||||
Trace::EnableFileLocksmith(m_enabled);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,26 +21,6 @@
|
||||
// Note: Settings are managed via Settings and UI Settings
|
||||
class NewModule : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
// Update registration based on enabled state
|
||||
void UpdateRegistration(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
NewPlusRuntimeRegistration::EnsureRegisteredWin10();
|
||||
Logger::info(L"New+ context menu registered");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
NewPlusRuntimeRegistration::Unregister();
|
||||
Logger::info(L"New+ context menu unregistered");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
NewModule()
|
||||
{
|
||||
@@ -118,9 +98,14 @@ public:
|
||||
{
|
||||
newplus::utilities::register_msix_package();
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
NewPlusRuntimeRegistration::EnsureRegisteredWin10();
|
||||
#endif
|
||||
}
|
||||
|
||||
powertoy_new_enabled = true;
|
||||
UpdateRegistration(powertoy_new_enabled);
|
||||
}
|
||||
|
||||
virtual void disable() override
|
||||
@@ -165,14 +150,19 @@ private:
|
||||
{
|
||||
Trace::EventToggleOnOff(false);
|
||||
}
|
||||
if (!package::IsWin11OrGreater())
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
NewPlusRuntimeRegistration::Unregister();
|
||||
Logger::info(L"New+ context menu unregistered (Win10)");
|
||||
#endif
|
||||
}
|
||||
powertoy_new_enabled = false;
|
||||
UpdateRegistration(powertoy_new_enabled);
|
||||
}
|
||||
|
||||
void init_settings()
|
||||
{
|
||||
powertoy_new_enabled = NewSettingsInstance().GetEnabled();
|
||||
UpdateRegistration(powertoy_new_enabled);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -43,32 +43,11 @@ private:
|
||||
//contains the non localized key of the powertoy
|
||||
std::wstring app_key;
|
||||
|
||||
// Update registration based on enabled state
|
||||
void UpdateRegistration(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
ImageResizerRuntimeRegistration::EnsureRegistered();
|
||||
Logger::info(L"ImageResizer context menu registered");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
ImageResizerRuntimeRegistration::Unregister();
|
||||
Logger::info(L"ImageResizer context menu unregistered");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
ImageResizerModule()
|
||||
{
|
||||
m_enabled = CSettingsInstance().GetEnabled();
|
||||
UpdateRegistration(m_enabled);
|
||||
app_name = GET_RESOURCE_STRING(IDS_IMAGERESIZER);
|
||||
app_key = ImageResizerConstants::ModuleKey;
|
||||
LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::imageResizerLoggerName);
|
||||
@@ -133,7 +112,10 @@ public:
|
||||
package::RegisterSparsePackage(path, packageUri);
|
||||
}
|
||||
}
|
||||
UpdateRegistration(m_enabled);
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
ImageResizerRuntimeRegistration::EnsureRegistered();
|
||||
#endif
|
||||
|
||||
Trace::EnableImageResizer(m_enabled);
|
||||
}
|
||||
|
||||
@@ -141,8 +123,11 @@ public:
|
||||
virtual void disable()
|
||||
{
|
||||
m_enabled = false;
|
||||
UpdateRegistration(m_enabled);
|
||||
Trace::EnableImageResizer(m_enabled);
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
ImageResizerRuntimeRegistration::Unregister();
|
||||
Logger::info(L"ImageResizer context menu unregistered (Win10)");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns if the powertoys is enabled
|
||||
|
||||
@@ -168,25 +168,6 @@ private:
|
||||
//contains the non localized key of the powertoy
|
||||
std::wstring app_key;
|
||||
|
||||
// Update registration based on enabled state
|
||||
void UpdateRegistration(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
PowerRenameRuntimeRegistration::EnsureRegistered();
|
||||
Logger::info(L"PowerRename context menu registered");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
PowerRenameRuntimeRegistration::Unregister();
|
||||
Logger::info(L"PowerRename context menu unregistered");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// Return the localized display name of the powertoy
|
||||
virtual PCWSTR get_name() override
|
||||
@@ -221,7 +202,9 @@ public:
|
||||
package::RegisterSparsePackage(path, packageUri);
|
||||
}
|
||||
}
|
||||
UpdateRegistration(m_enabled);
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
PowerRenameRuntimeRegistration::EnsureRegistered();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Disable the powertoy
|
||||
@@ -229,7 +212,10 @@ public:
|
||||
{
|
||||
m_enabled = false;
|
||||
Logger::info(L"PowerRename disabled");
|
||||
UpdateRegistration(m_enabled);
|
||||
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
|
||||
PowerRenameRuntimeRegistration::Unregister();
|
||||
Logger::info(L"PowerRename context menu unregistered (Win10)");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns if the powertoy is enabled
|
||||
@@ -329,7 +315,6 @@ public:
|
||||
void init_settings()
|
||||
{
|
||||
m_enabled = CSettingsInstance().GetEnabled();
|
||||
UpdateRegistration(m_enabled);
|
||||
Trace::EnablePowerRename(m_enabled);
|
||||
}
|
||||
|
||||
|
||||
@@ -316,6 +316,7 @@ void start_enabled_powertoys()
|
||||
should_powertoy_be_enabled = false;
|
||||
}
|
||||
|
||||
bool module_currently_enabled = powertoy->is_enabled();
|
||||
if (should_powertoy_be_enabled)
|
||||
{
|
||||
Logger::info(L"start_enabled_powertoys: Enabling powertoy {}", name);
|
||||
@@ -324,5 +325,13 @@ void start_enabled_powertoys()
|
||||
hkmng.EnableHotkeyByModule(name);
|
||||
powertoy.UpdateHotkeyEx();
|
||||
}
|
||||
else if (module_currently_enabled)
|
||||
{
|
||||
Logger::info(L"start_enabled_powertoys: Disabling powertoy {}", name);
|
||||
powertoy->disable();
|
||||
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
|
||||
hkmng.DisableHotkeyByModule(name);
|
||||
powertoy.UpdateHotkeyEx();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,6 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
"ShellPage.xaml",
|
||||
};
|
||||
|
||||
// Hardcoded panel-to-page mapping (temporary until generic panel host mapping is needed)
|
||||
// Key: panel file base name (without .xaml), Value: owning page base name
|
||||
private static readonly Dictionary<string, string> PanelPageMapping = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "MouseJumpPanel", "MouseUtilsPage" },
|
||||
};
|
||||
|
||||
private static JsonSerializerOptions serializeOption = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
@@ -40,117 +33,32 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
string xamlRootDirectory = args[0];
|
||||
string xamlDirectory = args[0];
|
||||
string outputFile = args[1];
|
||||
|
||||
if (!Directory.Exists(xamlRootDirectory))
|
||||
if (!Directory.Exists(xamlDirectory))
|
||||
{
|
||||
Debug.WriteLine($"Error: Directory '{xamlRootDirectory}' does not exist.");
|
||||
Debug.WriteLine($"Error: Directory '{xamlDirectory}' does not exist.");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var searchableElements = new List<SettingEntry>();
|
||||
var processedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var xamlFiles = Directory.GetFiles(xamlDirectory, "*.xaml", SearchOption.AllDirectories);
|
||||
|
||||
void ScanDirectory(string root)
|
||||
foreach (var xamlFile in xamlFiles)
|
||||
{
|
||||
if (!Directory.Exists(root))
|
||||
var fileName = Path.GetFileName(xamlFile);
|
||||
if (ExcludedXamlFiles.Contains(fileName))
|
||||
{
|
||||
return;
|
||||
// Skip ShellPage.xaml as it contains many elements not relevant for search
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[XamlIndexBuilder] Scanning root: {root}");
|
||||
var xamlFilesLocal = Directory.GetFiles(root, "*.xaml", SearchOption.AllDirectories);
|
||||
foreach (var xamlFile in xamlFilesLocal)
|
||||
{
|
||||
var fullPath = Path.GetFullPath(xamlFile);
|
||||
if (processedFiles.Contains(fullPath))
|
||||
{
|
||||
continue; // already handled (can happen if overlapping directories)
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileName(xamlFile);
|
||||
if (ExcludedXamlFiles.Contains(fileName))
|
||||
{
|
||||
continue; // explicitly excluded
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Processing: {fileName}");
|
||||
var elements = ExtractSearchableElements(xamlFile);
|
||||
|
||||
// Apply hardcoded panel mapping override
|
||||
var baseName = Path.GetFileNameWithoutExtension(xamlFile);
|
||||
if (PanelPageMapping.TryGetValue(baseName, out var hostPage))
|
||||
{
|
||||
for (int i = 0; i < elements.Count; i++)
|
||||
{
|
||||
var entry = elements[i];
|
||||
entry.PageTypeName = hostPage;
|
||||
elements[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
searchableElements.AddRange(elements);
|
||||
processedFiles.Add(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Scan well-known subdirectories under the provided root
|
||||
var subDirs = new[] { "Views", "Panels" };
|
||||
foreach (var sub in subDirs)
|
||||
{
|
||||
ScanDirectory(Path.Combine(xamlRootDirectory, sub));
|
||||
}
|
||||
|
||||
// Fallback: also scan root directly (in case some XAML lives at root level)
|
||||
ScanDirectory(xamlRootDirectory);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Explicit include section: add specific XAML files that we always want indexed
|
||||
// even if future logic excludes them or they live outside typical scan patterns.
|
||||
// Add future files to the ExplicitExtraXamlFiles array below.
|
||||
// -----------------------------------------------------------------------------
|
||||
string[] explicitExtraXamlFiles = new[]
|
||||
{
|
||||
"MouseJumpPanel.xaml", // Mouse Jump settings panel
|
||||
};
|
||||
|
||||
foreach (var extraFileName in explicitExtraXamlFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var matches = Directory.GetFiles(xamlRootDirectory, extraFileName, SearchOption.AllDirectories);
|
||||
foreach (var match in matches)
|
||||
{
|
||||
var full = Path.GetFullPath(match);
|
||||
if (processedFiles.Contains(full))
|
||||
{
|
||||
continue; // already processed in general scan
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Processing (explicit include): {extraFileName}");
|
||||
var elements = ExtractSearchableElements(full);
|
||||
var baseName = Path.GetFileNameWithoutExtension(full);
|
||||
if (PanelPageMapping.TryGetValue(baseName, out var hostPage))
|
||||
{
|
||||
for (int i = 0; i < elements.Count; i++)
|
||||
{
|
||||
var entry = elements[i];
|
||||
entry.PageTypeName = hostPage;
|
||||
elements[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
searchableElements.AddRange(elements);
|
||||
processedFiles.Add(full);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Explicit include failed for {extraFileName}: {ex.Message}");
|
||||
}
|
||||
Debug.WriteLine($"Processing: {fileName}");
|
||||
var elements = ExtractSearchableElements(xamlFile);
|
||||
searchableElements.AddRange(elements);
|
||||
}
|
||||
|
||||
searchableElements = searchableElements.OrderBy(e => e.PageTypeName).ThenBy(e => e.ElementName).ToList();
|
||||
@@ -189,15 +97,15 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
.Where(e => e.Name.LocalName == "SettingsPageControl")
|
||||
.Where(e => e.Attribute(x + "Uid") != null);
|
||||
|
||||
// Extract SettingsCard elements (support both Name and x:Name)
|
||||
// Extract SettingsCard elements
|
||||
var settingsElements = doc.Descendants()
|
||||
.Where(e => e.Name.LocalName == "SettingsCard")
|
||||
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Name") != null || e.Attribute(x + "Uid") != null);
|
||||
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Uid") != null);
|
||||
|
||||
// Extract SettingsExpander elements (support both Name and x:Name)
|
||||
// Extract SettingsExpander elements
|
||||
var settingsExpanderElements = doc.Descendants()
|
||||
.Where(e => e.Name.LocalName == "SettingsExpander")
|
||||
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Name") != null || e.Attribute(x + "Uid") != null);
|
||||
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Uid") != null);
|
||||
|
||||
// Process SettingsPageControl elements
|
||||
foreach (var element in settingsPageElements)
|
||||
@@ -277,12 +185,8 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
|
||||
public static string GetElementName(XElement element, XNamespace x)
|
||||
{
|
||||
// Get Name attribute (we call it ElementName in our indexing system)
|
||||
var name = element.Attribute("Name")?.Value;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
name = element.Attribute(x + "Name")?.Value;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -307,11 +211,6 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
if (expanderParent?.Name.LocalName == "SettingsExpander")
|
||||
{
|
||||
var expanderName = expanderParent.Attribute("Name")?.Value;
|
||||
if (string.IsNullOrEmpty(expanderName))
|
||||
{
|
||||
expanderName = expanderParent.Attribute(x + "Name")?.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(expanderName))
|
||||
{
|
||||
return expanderName;
|
||||
@@ -322,11 +221,6 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
|
||||
{
|
||||
// Direct child of SettingsExpander
|
||||
var expanderName = current.Attribute("Name")?.Value;
|
||||
if (string.IsNullOrEmpty(expanderName))
|
||||
{
|
||||
expanderName = current.Attribute(x + "Name")?.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(expanderName))
|
||||
{
|
||||
return expanderName;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(CoreTargetFramework)</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Microsoft.PowerToys.Tools.XamlIndexBuilder</RootNamespace>
|
||||
<AssemblyName>XamlIndexBuilder</AssemblyName>
|
||||
@@ -29,15 +29,16 @@
|
||||
<!-- Remove UI library reference to avoid pulling WindowsDesktop runtime (WindowsBase) -->
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Fallback to dotnet if not provided by the environment -->
|
||||
<DotNetExe Condition="'$(DotNetExe)' == ''">dotnet</DotNetExe>
|
||||
<XamlRootDir Condition="'$(XamlRootDir)' == ''">$(MSBuildProjectDirectory)\..\Settings.UI\SettingsXAML</XamlRootDir>
|
||||
<XamlRootDir Condition="'$(XamlViewsDir)' != ''">$([System.IO.Path]::GetDirectoryName('$(XamlViewsDir)'))</XamlRootDir>
|
||||
<XamlViewsDir Condition="'$(XamlViewsDir)' == ''">$(MSBuildProjectDirectory)\..\Settings.UI\SettingsXAML\Views</XamlViewsDir>
|
||||
<GeneratedJsonFile Condition="'$(GeneratedJsonFile)' == ''">$(MSBuildProjectDirectory)\..\Settings.UI\Assets\Settings\search.index.json</GeneratedJsonFile>
|
||||
</PropertyGroup>
|
||||
<Target Name="GenerateSearchIndexSelf" AfterTargets="Build">
|
||||
<RemoveDir Directories="$(MSBuildProjectDirectory)\obj\ARM64;$(MSBuildProjectDirectory)\obj\x64;$(MSBuildProjectDirectory)\bin" />
|
||||
<MakeDir Directories="$([System.IO.Path]::GetDirectoryName('$(GeneratedJsonFile)'))" />
|
||||
<Message Importance="high" Text="[XamlIndexBuilder] Generating search index. Root='$(XamlRootDir)'; Out='$(GeneratedJsonFile)'; Tool='$(TargetPath)'; DotNet='$(DotNetExe)'." />
|
||||
<Exec Command=""$(DotNetExe)" "$(TargetPath)" "$(XamlRootDir)" "$(GeneratedJsonFile)"" />
|
||||
<Message Importance="high" Text="[XamlIndexBuilder] Generating search index. Views='$(XamlViewsDir)'; Out='$(GeneratedJsonFile)'; Tool='$(TargetPath)'; DotNet='$(DotNetExe)'." />
|
||||
<!-- Execute via dotnet so host architecture doesn't need to match -->
|
||||
<Exec Command=""$(DotNetExe)" "$(TargetPath)" "$(XamlViewsDir)" "$(GeneratedJsonFile)"" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
public abstract partial class NavigablePage : Page
|
||||
{
|
||||
private const int ExpandWaitDuration = 500;
|
||||
private const int AnimationDuration = 1850;
|
||||
private const int AnimationDuration = 2000;
|
||||
|
||||
private NavigationParams _pendingNavigationParams;
|
||||
|
||||
@@ -80,9 +80,6 @@ public abstract partial class NavigablePage : Page
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to set keyboard focus so that screen readers announce the element and keyboard users land directly on it.
|
||||
TrySetFocus(target);
|
||||
|
||||
// Get the visual and compositor
|
||||
var visual = ElementCompositionPreview.GetElementVisual(target);
|
||||
var compositor = visual.Compositor;
|
||||
@@ -95,9 +92,9 @@ public abstract partial class NavigablePage : Page
|
||||
dropShadow.Offset = new Vector3(0, 0, 0);
|
||||
|
||||
var spriteVisual = compositor.CreateSpriteVisual();
|
||||
spriteVisual.Size = new Vector2((float)target.ActualWidth + 8, (float)target.ActualHeight + 8);
|
||||
spriteVisual.Size = new Vector2((float)target.ActualWidth, (float)target.ActualHeight);
|
||||
spriteVisual.Shadow = dropShadow;
|
||||
spriteVisual.Offset = new Vector3(-4, -4, 0);
|
||||
spriteVisual.Offset = new Vector3(0, 0, 0);
|
||||
|
||||
// Insert the shadow visual behind the target element
|
||||
ElementCompositionPreview.SetElementChildVisual(target, spriteVisual);
|
||||
@@ -116,129 +113,9 @@ public abstract partial class NavigablePage : Page
|
||||
ElementCompositionPreview.SetElementChildVisual(target, null);
|
||||
}
|
||||
|
||||
private static void TrySetFocus(FrameworkElement target)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Prefer Control.Focus when available.
|
||||
if (target is Control ctrl)
|
||||
{
|
||||
// Ensure it can receive focus.
|
||||
if (!ctrl.IsTabStop)
|
||||
{
|
||||
ctrl.IsTabStop = true;
|
||||
}
|
||||
|
||||
ctrl.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
// Target is not a Control. Find first focusable descendant Control.
|
||||
var focusCandidate = FindFirstFocusableDescendant(target);
|
||||
if (focusCandidate != null)
|
||||
{
|
||||
if (!focusCandidate.IsTabStop)
|
||||
{
|
||||
focusCandidate.IsTabStop = true;
|
||||
}
|
||||
|
||||
focusCandidate.Focus(FocusState.Programmatic);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: attempt to focus parent control if no descendant found.
|
||||
if (target.Parent is Control parent)
|
||||
{
|
||||
if (!parent.IsTabStop)
|
||||
{
|
||||
parent.IsTabStop = true;
|
||||
}
|
||||
|
||||
parent.Focus(FocusState.Programmatic);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow focus exceptions; not critical. Could log if logging enabled.
|
||||
// Leave the default focus as it is.
|
||||
}
|
||||
}
|
||||
|
||||
private static Control FindFirstFocusableDescendant(FrameworkElement root)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var queue = new System.Collections.Generic.Queue<DependencyObject>();
|
||||
queue.Enqueue(root);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var current = queue.Dequeue();
|
||||
if (current is Control c && c.IsEnabled && c.Visibility == Visibility.Visible)
|
||||
{
|
||||
return c;
|
||||
}
|
||||
|
||||
int count = VisualTreeHelper.GetChildrenCount(current);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
queue.Enqueue(VisualTreeHelper.GetChild(current, i));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected FrameworkElement FindElementByName(string name)
|
||||
{
|
||||
var element = this.FindName(name) as FrameworkElement;
|
||||
if (element != null)
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
if (this.Content is DependencyObject root)
|
||||
{
|
||||
var found = FindInDescendants(root, name);
|
||||
if (found != null)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FrameworkElement FindInDescendants(DependencyObject root, string name)
|
||||
{
|
||||
if (root == null || string.IsNullOrEmpty(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var queue = new System.Collections.Generic.Queue<DependencyObject>();
|
||||
queue.Enqueue(root);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var current = queue.Dequeue();
|
||||
if (current is FrameworkElement fe)
|
||||
{
|
||||
var local = fe.FindName(name) as FrameworkElement;
|
||||
if (local != null)
|
||||
{
|
||||
return local;
|
||||
}
|
||||
}
|
||||
|
||||
int count = VisualTreeHelper.GetChildrenCount(current);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(current, i);
|
||||
queue.Enqueue(child);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,8 +181,11 @@
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Removed nested publish/exec and copy targets. -->
|
||||
|
||||
<!-- Build XamlIndexBuilder before compiling Settings to ensure the search index exists without taking a project reference. -->
|
||||
<Target Name="BuildXamlIndexBeforeSettings" BeforeTargets="CoreCompile">
|
||||
<Message Importance="high" Text="[Settings] Building XamlIndexBuilder prior to compile. Views='$(MSBuildProjectDirectory)\SettingsXAML\Views' Out='$(GeneratedJsonFile)'" />
|
||||
<MSBuild Projects="..\Settings.UI.XamlIndexBuilder\Settings.UI.XamlIndexBuilder.csproj" Targets="Build" Properties="Configuration=$(Configuration);Platform=Any CPU;TargetFramework=$(CoreTargetFramework);XamlViewsDir=$(MSBuildProjectDirectory)\SettingsXAML\Views;GeneratedJsonFile=$(GeneratedJsonFile)" />
|
||||
<MSBuild Projects="..\Settings.UI.XamlIndexBuilder\Settings.UI.XamlIndexBuilder.csproj" Targets="Build" Properties="Configuration=$(Configuration);Platform=Any CPU;TargetFramework=net9.0;XamlViewsDir=$(MSBuildProjectDirectory)\SettingsXAML\Views;GeneratedJsonFile=$(GeneratedJsonFile)" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -24,7 +24,6 @@
|
||||
<controls:SettingsGroup x:Uid="MouseUtils_MouseJump">
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
x:Name="MouseUtilsEnableMouseJump"
|
||||
x:Uid="MouseUtils_Enable_MouseJump"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseJump.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
@@ -43,7 +42,6 @@
|
||||
</InfoBar>
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
x:Name="MouseUtilsMouseJumpActivationShortcut"
|
||||
x:Uid="MouseUtils_MouseJump_ActivationShortcut"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=OneWay}">
|
||||
@@ -128,7 +126,6 @@
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Name="MouseUtilsMouseJumpAppearance"
|
||||
x:Uid="MouseUtils_MouseJump_Appearance"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=OneWay}"
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
using Microsoft.PowerToys.Settings.UI.ViewModels;
|
||||
@@ -33,7 +32,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
if (e.Parameter is SearchResultsNavigationParams searchParams)
|
||||
{
|
||||
ViewModel.SetSearchResults(searchParams.Query, searchParams.Results);
|
||||
PageControl.ModuleDescription = $"{ResourceLoaderInstance.ResourceLoader.GetString("Search_ResultsFor")} '{searchParams.Query}'";
|
||||
PageControl.ModuleDescription = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +43,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void ModuleButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is SettingsCard card && card.DataContext is SettingEntry tagEntry)
|
||||
if (sender is CommunityToolkit.WinUI.Controls.SettingsCard card && card.DataContext is SettingEntry tagEntry)
|
||||
{
|
||||
NavigateToModule(tagEntry);
|
||||
}
|
||||
@@ -52,7 +51,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void SettingButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is SettingsCard card && card.DataContext is SettingEntry tagEntry)
|
||||
if (sender is CommunityToolkit.WinUI.Controls.SettingsCard card && card.DataContext is SettingEntry tagEntry)
|
||||
{
|
||||
NavigateToSetting(tagEntry);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="NoResultSearchResultTemplate" x:DataType="models:SuggestionItem">
|
||||
<Grid>
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,-4,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
<TextBlock
|
||||
Margin="8"
|
||||
HorizontalAlignment="Center"
|
||||
@@ -65,10 +71,10 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="ShowAllSearchResultTemplate" x:DataType="models:SuggestionItem">
|
||||
<Grid>
|
||||
<Grid Padding="16,8">
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0,-12,0,0"
|
||||
Margin="0,-4,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
@@ -86,6 +92,7 @@
|
||||
<ic:InvokeCommandAction Command="{x:Bind ViewModel.LoadedCommand}" />
|
||||
</ic:EventTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -141,7 +142,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
private const int SearchDebounceMs = 500;
|
||||
private bool _disposed;
|
||||
|
||||
// Removed trace id counter per cleanup
|
||||
// Tracing id for correlating logs of a single search interaction
|
||||
private static long _searchTraceIdCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShellPage"/> class.
|
||||
@@ -441,11 +443,25 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void ShellPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Logger.LogDebug("[Search][Index] Scheduling BuildIndex...");
|
||||
var swIndex = Stopwatch.StartNew();
|
||||
Task.Run(() =>
|
||||
{
|
||||
Logger.LogDebug("[Search][Index] BuildIndex started");
|
||||
SearchIndexService.BuildIndex();
|
||||
})
|
||||
.ContinueWith(_ => { });
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
swIndex.Stop();
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
Logger.LogDebug($"[Search][Index] BuildIndex FAILED after {swIndex.ElapsedMilliseconds} ms: {t.Exception?.Flatten().InnerException?.Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug($"[Search][Index] BuildIndex completed in {swIndex.ElapsedMilliseconds} ms.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
|
||||
@@ -496,6 +512,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
var query = sender.Text?.Trim() ?? string.Empty;
|
||||
|
||||
var traceId = Interlocked.Increment(ref _searchTraceIdCounter);
|
||||
var swOverall = Stopwatch.StartNew();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] start. query='{query}'");
|
||||
|
||||
// Debounce: cancel previous pending search
|
||||
_searchDebounceCts?.Cancel();
|
||||
_searchDebounceCts?.Dispose();
|
||||
@@ -508,6 +528,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
sender.IsSuggestionListOpen = false;
|
||||
_lastSearchResults.Clear();
|
||||
_lastQueryText = string.Empty;
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] empty query. end");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -517,11 +538,14 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
return; // debounce canceled
|
||||
// A newer keystroke arrived; abandon this run
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] debounce canceled at +{swOverall.ElapsedMilliseconds} ms");
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] token canceled post-debounce at +{swOverall.ElapsedMilliseconds} ms");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -530,25 +554,106 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
try
|
||||
{
|
||||
// If the token is already canceled before scheduling, the task won't start.
|
||||
var swSearch = Stopwatch.StartNew();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] dispatch search...");
|
||||
results = await Task.Run(() => SearchIndexService.Search(query, token), token);
|
||||
swSearch.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] search done in {swSearch.ElapsedMilliseconds} ms. results={results?.Count ?? 0}");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] search canceled at +{swOverall.ElapsedMilliseconds} ms");
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] token canceled after search at +{swOverall.ElapsedMilliseconds} ms");
|
||||
return;
|
||||
}
|
||||
|
||||
_lastSearchResults = results;
|
||||
_lastQueryText = query;
|
||||
|
||||
var top = BuildSuggestionItems(query, results);
|
||||
List<SuggestionItem> top;
|
||||
if (results.Count == 0)
|
||||
{
|
||||
// Explicit no-results row
|
||||
var rl = ResourceLoaderInstance.ResourceLoader;
|
||||
var noResultsPrefix = rl.GetString("Shell_Search_NoResults");
|
||||
if (string.IsNullOrEmpty(noResultsPrefix))
|
||||
{
|
||||
noResultsPrefix = "No results for";
|
||||
}
|
||||
|
||||
var headerText = $"{noResultsPrefix} '{query}'";
|
||||
top =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Header = headerText,
|
||||
IsNoResults = true,
|
||||
},
|
||||
];
|
||||
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] no results -> added placeholder item (count={top.Count})");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Project top 5 suggestions
|
||||
var swProject = Stopwatch.StartNew();
|
||||
top = [.. results.Take(5)
|
||||
.Select(e =>
|
||||
{
|
||||
string subtitle = string.Empty;
|
||||
if (e.Type != EntryType.SettingsPage)
|
||||
{
|
||||
var swSubtitle = Stopwatch.StartNew();
|
||||
subtitle = SearchIndexService.GetLocalizedPageName(e.PageTypeName);
|
||||
if (string.IsNullOrEmpty(subtitle))
|
||||
{
|
||||
// Fallback: look up the module title from the in-memory index
|
||||
var swFallback = Stopwatch.StartNew();
|
||||
subtitle = SearchIndexService.Index
|
||||
.Where(x => x.Type == EntryType.SettingsPage && x.PageTypeName == e.PageTypeName)
|
||||
.Select(x => x.Header)
|
||||
.FirstOrDefault() ?? string.Empty;
|
||||
swFallback.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] fallback subtitle for '{e.PageTypeName}' took {swFallback.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
swSubtitle.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] subtitle for '{e.PageTypeName}' took {swSubtitle.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
return new SuggestionItem
|
||||
{
|
||||
Header = e.Header,
|
||||
Icon = e.Icon,
|
||||
PageTypeName = e.PageTypeName,
|
||||
ElementName = e.ElementName,
|
||||
ParentElementName = e.ParentElementName,
|
||||
Subtitle = subtitle,
|
||||
IsShowAll = false,
|
||||
};
|
||||
})];
|
||||
swProject.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] project suggestions took {swProject.ElapsedMilliseconds} ms. topCount={top.Count}");
|
||||
|
||||
if (results.Count > 5)
|
||||
{
|
||||
// Add a tail item to show all results if there are more than 5
|
||||
top.Add(new SuggestionItem { IsShowAll = true });
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] added 'Show all results' item");
|
||||
}
|
||||
}
|
||||
|
||||
var swUi = Stopwatch.StartNew();
|
||||
sender.ItemsSource = top;
|
||||
sender.IsSuggestionListOpen = top.Count > 0;
|
||||
swUi.Stop();
|
||||
swOverall.Stop();
|
||||
Logger.LogDebug($"[Search][TextChanged][{traceId}] UI update took {swUi.ElapsedMilliseconds} ms. total={swOverall.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
private void SearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
|
||||
@@ -609,93 +714,19 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void SearchBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var box = sender as AutoSuggestBox;
|
||||
var current = box?.Text?.Trim() ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(current))
|
||||
{
|
||||
return; // nothing to restore
|
||||
}
|
||||
|
||||
// If current text matches last query and we have results, reconstruct the suggestion list.
|
||||
if (string.Equals(current, _lastQueryText, StringComparison.Ordinal) && _lastSearchResults?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var top = BuildSuggestionItems(current, _lastSearchResults);
|
||||
box.ItemsSource = top;
|
||||
box.IsSuggestionListOpen = top.Count > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error restoring suggestion list {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Centralized suggestion projection logic used by TextChanged & GotFocus restore.
|
||||
private List<SuggestionItem> BuildSuggestionItems(string query, List<SettingEntry> results)
|
||||
{
|
||||
results ??= new();
|
||||
if (results.Count == 0)
|
||||
{
|
||||
var rl = ResourceLoaderInstance.ResourceLoader;
|
||||
var noResultsPrefix = rl.GetString("Shell_Search_NoResults");
|
||||
if (string.IsNullOrEmpty(noResultsPrefix))
|
||||
{
|
||||
noResultsPrefix = "No results for";
|
||||
}
|
||||
|
||||
var headerText = $"{noResultsPrefix} '{query}'";
|
||||
return new List<SuggestionItem>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Header = headerText,
|
||||
IsNoResults = true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
var list = results.Take(5).Select(e =>
|
||||
{
|
||||
string subtitle = string.Empty;
|
||||
if (e.Type != EntryType.SettingsPage)
|
||||
{
|
||||
subtitle = SearchIndexService.GetLocalizedPageName(e.PageTypeName);
|
||||
if (string.IsNullOrEmpty(subtitle))
|
||||
{
|
||||
subtitle = SearchIndexService.Index
|
||||
.Where(x => x.Type == EntryType.SettingsPage && x.PageTypeName == e.PageTypeName)
|
||||
.Select(x => x.Header)
|
||||
.FirstOrDefault() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
return new SuggestionItem
|
||||
{
|
||||
Header = e.Header,
|
||||
Icon = e.Icon,
|
||||
PageTypeName = e.PageTypeName,
|
||||
ElementName = e.ElementName,
|
||||
ParentElementName = e.ParentElementName,
|
||||
Subtitle = subtitle,
|
||||
IsShowAll = false,
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
if (results.Count > 5)
|
||||
{
|
||||
list.Add(new SuggestionItem { IsShowAll = true });
|
||||
}
|
||||
|
||||
return list;
|
||||
// do not prompt unless search for text.
|
||||
return;
|
||||
}
|
||||
|
||||
private async void SearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
var swSubmit = Stopwatch.StartNew();
|
||||
Logger.LogDebug("[Search][Submit] start");
|
||||
|
||||
// If a suggestion is selected, navigate directly
|
||||
if (args.ChosenSuggestion is SuggestionItem chosen)
|
||||
{
|
||||
Logger.LogDebug($"[Search][Submit] chosen suggestion -> navigate to {chosen.PageTypeName} element={chosen.ElementName ?? "<page>"}");
|
||||
NavigateFromSuggestion(chosen);
|
||||
return;
|
||||
}
|
||||
@@ -703,6 +734,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
var queryText = (args.QueryText ?? _lastQueryText)?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(queryText))
|
||||
{
|
||||
Logger.LogDebug("[Search][Submit] empty query -> navigate Dashboard");
|
||||
NavigationService.Navigate<DashboardPage>();
|
||||
return;
|
||||
}
|
||||
@@ -710,10 +742,21 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
// Prefer cached results (from live search); if empty, perform a fresh search
|
||||
var matched = _lastSearchResults?.Count > 0 && string.Equals(_lastQueryText, queryText, StringComparison.Ordinal)
|
||||
? _lastSearchResults
|
||||
: await Task.Run(() => SearchIndexService.Search(queryText));
|
||||
: await Task.Run(() =>
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
Logger.LogDebug($"[Search][Submit] background search for '{queryText}'...");
|
||||
var r = SearchIndexService.Search(queryText);
|
||||
sw.Stop();
|
||||
Logger.LogDebug($"[Search][Submit] background search done in {sw.ElapsedMilliseconds} ms. results={r?.Count ?? 0}");
|
||||
return r;
|
||||
});
|
||||
|
||||
var searchParams = new SearchResultsNavigationParams(queryText, matched);
|
||||
Logger.LogDebug($"[Search][Submit] navigate to SearchResultsPage (results={matched?.Count ?? 0})");
|
||||
NavigationService.Navigate<SearchResultsPage>(searchParams);
|
||||
swSubmit.Stop();
|
||||
Logger.LogDebug($"[Search][Submit] total {swSubmit.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -2902,19 +2902,20 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="MouseUtils_GlidingCursor.Description" xml:space="preserve">
|
||||
<value>An accessibility feature that lets you control the mouse with a single button using guided horizontal and vertical lines</value>
|
||||
</data>
|
||||
<data name="MouseUtils_GlidingCursor_InitialSpeed.Header" xml:space="preserve">
|
||||
<data name="MouseUtils_GlidingCursor_InitialSpeed.Header" xml:space="preserve">
|
||||
<value>Initial line speed</value>
|
||||
</data>
|
||||
<data name="MouseUtils_GlidingCursor_InitialSpeed.Description" xml:space="preserve">
|
||||
<data name="MouseUtils_GlidingCursor_InitialSpeed.Description" xml:space="preserve">
|
||||
<value>Speed of the horizontal or vertical line when it begins moving</value>
|
||||
</data>
|
||||
<data name="MouseUtils_GlidingCursor_DelaySpeed.Header" xml:space="preserve">
|
||||
<data name="MouseUtils_GlidingCursor_DelaySpeed.Header" xml:space="preserve">
|
||||
<value>Reduced line speed</value>
|
||||
</data>
|
||||
<data name="MouseUtils_GlidingCursor_DelaySpeed.Description" xml:space="preserve">
|
||||
<data name="MouseUtils_GlidingCursor_DelaySpeed.Description" xml:space="preserve">
|
||||
<value>Speed after slowing down the line with a second shortcut press</value>
|
||||
</data>
|
||||
<data name="FancyZones_Radio_Custom_Colors.Content" xml:space="preserve">
|
||||
|
||||
<data name="FancyZones_Radio_Custom_Colors.Content" xml:space="preserve">
|
||||
<value>Custom colors</value>
|
||||
</data>
|
||||
<data name="FancyZones_Radio_Default_Theme.Content" xml:space="preserve">
|
||||
@@ -5271,7 +5272,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<value>All shortcuts function correctly</value>
|
||||
</data>
|
||||
<data name="ResolveConflicts_Button.Content" xml:space="preserve">
|
||||
<value>Resolve conflicts</value>
|
||||
<value>Resolve conflicts</value>
|
||||
</data>
|
||||
<data name="ShortcutConflictControl_Title.Text" xml:space="preserve">
|
||||
<value>Shortcut conflicts</value>
|
||||
@@ -5292,8 +5293,4 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<data name="Hosts_NoLeadingSpaces.Description" xml:space="preserve">
|
||||
<value>Do not prepend spaces to active lines when saving the hosts file</value>
|
||||
</data>
|
||||
<data name="Search_ResultsFor" xml:space="preserve">
|
||||
<value>Results for</value>
|
||||
<comment>Prefix for search string. E.g. "Results for 'shortcut'"</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -126,21 +126,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var assemblyName = new AssemblyName(assembly.FullName ?? throw new InvalidOperationException());
|
||||
|
||||
// Build the fully-qualified manifest resource name. Historically, subtle casing differences
|
||||
// (e.g. folder names or the assembly name) caused exact (case-sensitive) lookup failures on
|
||||
// some developer machines when the embedded resource's actual name differed only by case.
|
||||
// Manifest resource name comparison here does not need to be case-sensitive, so we resolve
|
||||
// the actual name using an OrdinalIgnoreCase match, then use the real casing for the stream.
|
||||
var resourceName = $"Microsoft.{assemblyName.Name}.{filename.Replace("/", ".")}";
|
||||
var resourceNames = assembly.GetManifestResourceNames();
|
||||
var actualResourceName = resourceNames.FirstOrDefault(n => string.Equals(n, resourceName, StringComparison.OrdinalIgnoreCase));
|
||||
if (actualResourceName is null)
|
||||
if (!resourceNames.Contains(resourceName))
|
||||
{
|
||||
throw new InvalidOperationException($"Embedded resource '{resourceName}' (case-insensitive) does not exist.");
|
||||
throw new InvalidOperationException($"Embedded resource '{resourceName}' does not exist.");
|
||||
}
|
||||
|
||||
var stream = assembly.GetManifestResourceStream(actualResourceName)
|
||||
var stream = assembly.GetManifestResourceStream(resourceName)
|
||||
?? throw new InvalidOperationException();
|
||||
var image = (Bitmap)Image.FromStream(stream);
|
||||
return image;
|
||||
|
||||
Reference in New Issue
Block a user