mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
add sort feature
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
This commit is contained in:
@@ -5,7 +5,11 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewModels="using:Microsoft.PowerToys.QuickAccess.ViewModels"
|
||||
xmlns:local="using:Microsoft.PowerToys.QuickAccess.Flyout"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<local:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
|
||||
</Page.Resources>
|
||||
<Grid Background="{ThemeResource LayerOnAcrylicFillColorDefaultBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -16,25 +20,49 @@
|
||||
x:Uid="AllAppsTxt"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<Button
|
||||
x:Uid="BackBtn"
|
||||
Padding="8,4,8,4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Click="BackButton_Click">
|
||||
<Button.Content>
|
||||
<StackPanel
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<FontIcon
|
||||
Margin="0,2,0,0"
|
||||
FontSize="12"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="BackLabel" Style="{StaticResource CaptionTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
|
||||
<Button
|
||||
x:Uid="Dashboard_SortBy"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="Dashboard_SortBy_ToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Uid="Dashboard_SortAlphabetical"
|
||||
Click="SortAlphabetical_Click"
|
||||
IsChecked="{x:Bind ViewModel.DashboardSortOrder, Mode=OneWay, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Alphabetical}" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Uid="Dashboard_SortByStatus"
|
||||
Click="SortByStatus_Click"
|
||||
IsChecked="{x:Bind ViewModel.DashboardSortOrder, Mode=OneWay, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=ByStatus}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Button
|
||||
x:Uid="BackBtn"
|
||||
Padding="8,4,8,4"
|
||||
VerticalAlignment="Center"
|
||||
Click="BackButton_Click">
|
||||
<Button.Content>
|
||||
<StackPanel
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<FontIcon
|
||||
Margin="0,2,0,0"
|
||||
FontSize="12"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="BackLabel" Style="{StaticResource CaptionTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// 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.PowerToys.QuickAccess.ViewModels;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
@@ -42,4 +44,20 @@ public sealed partial class AppsListPage : Page
|
||||
|
||||
Frame.Navigate(typeof(LaunchPage), _context, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromLeft });
|
||||
}
|
||||
|
||||
private void SortAlphabetical_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.DashboardSortOrder = DashboardSortOrder.Alphabetical;
|
||||
}
|
||||
}
|
||||
|
||||
private void SortByStatus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.DashboardSortOrder = DashboardSortOrder.ByStatus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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.UI.Xaml.Data;
|
||||
|
||||
namespace Microsoft.PowerToys.QuickAccess.Flyout;
|
||||
|
||||
public partial class EnumToBooleanConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value == null || parameter == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var enumString = value.ToString();
|
||||
var parameterString = parameter.ToString();
|
||||
|
||||
return string.Equals(enumString, parameterString, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using global::PowerToys.GPOWrapper;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.QuickAccess.Helpers;
|
||||
@@ -20,18 +22,34 @@ public sealed class AllAppsViewModel : Observable
|
||||
{
|
||||
private readonly IQuickAccessCoordinator _coordinator;
|
||||
private readonly ISettingsRepository<GeneralSettings> _settingsRepository;
|
||||
private readonly ISettingsUtils _settingsUtils;
|
||||
private readonly ResourceLoader _resourceLoader;
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
private GeneralSettings _generalSettings;
|
||||
|
||||
public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; }
|
||||
|
||||
public DashboardSortOrder DashboardSortOrder
|
||||
{
|
||||
get => _generalSettings.DashboardSortOrder;
|
||||
set
|
||||
{
|
||||
if (_generalSettings.DashboardSortOrder != value)
|
||||
{
|
||||
_generalSettings.DashboardSortOrder = value;
|
||||
_settingsUtils.SaveSettings(_generalSettings.ToJsonString(), _generalSettings.GetModuleName());
|
||||
OnPropertyChanged();
|
||||
RefreshFlyoutMenuItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AllAppsViewModel(IQuickAccessCoordinator coordinator)
|
||||
{
|
||||
_coordinator = coordinator;
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
var settingsUtils = new SettingsUtils();
|
||||
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils);
|
||||
_settingsUtils = new SettingsUtils();
|
||||
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
|
||||
_generalSettings = _settingsRepository.SettingsConfig;
|
||||
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
|
||||
_settingsRepository.SettingsChanged += OnSettingsChanged;
|
||||
@@ -39,35 +57,90 @@ public sealed class AllAppsViewModel : Observable
|
||||
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
FlyoutMenuItems = new ObservableCollection<FlyoutMenuItem>();
|
||||
|
||||
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
|
||||
{
|
||||
AddFlyoutMenuItem(moduleType);
|
||||
}
|
||||
RefreshFlyoutMenuItems();
|
||||
}
|
||||
|
||||
private void OnSettingsChanged(GeneralSettings newSettings)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
ModuleEnabledChangedOnSettingsPage();
|
||||
_generalSettings = newSettings;
|
||||
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
|
||||
OnPropertyChanged(nameof(DashboardSortOrder));
|
||||
RefreshFlyoutMenuItems();
|
||||
});
|
||||
}
|
||||
|
||||
private void AddFlyoutMenuItem(ModuleType moduleType)
|
||||
private void RefreshFlyoutMenuItems()
|
||||
{
|
||||
var gpo = ModuleHelper.GetModuleGpoConfiguration(moduleType);
|
||||
var isLocked = gpo is GpoRuleConfigured.Enabled or GpoRuleConfigured.Disabled;
|
||||
var isEnabled = gpo == GpoRuleConfigured.Enabled || (!isLocked && ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType));
|
||||
var desiredItems = new List<FlyoutMenuItem>();
|
||||
|
||||
FlyoutMenuItems.Add(new FlyoutMenuItem
|
||||
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
|
||||
{
|
||||
Label = _resourceLoader.GetString(ModuleHelper.GetModuleLabelResourceName(moduleType)),
|
||||
IsEnabled = isEnabled,
|
||||
IsLocked = isLocked,
|
||||
Tag = moduleType,
|
||||
Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
|
||||
EnabledChangedCallback = EnabledChangedOnUI,
|
||||
});
|
||||
var gpo = ModuleHelper.GetModuleGpoConfiguration(moduleType);
|
||||
var isLocked = gpo is GpoRuleConfigured.Enabled or GpoRuleConfigured.Disabled;
|
||||
var isEnabled = gpo == GpoRuleConfigured.Enabled || (!isLocked && ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType));
|
||||
|
||||
var existingItem = FlyoutMenuItems.FirstOrDefault(x => x.Tag == moduleType);
|
||||
|
||||
if (existingItem != null)
|
||||
{
|
||||
existingItem.Label = _resourceLoader.GetString(ModuleHelper.GetModuleLabelResourceName(moduleType));
|
||||
existingItem.IsLocked = isLocked;
|
||||
existingItem.Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType);
|
||||
|
||||
if (existingItem.IsEnabled != isEnabled)
|
||||
{
|
||||
var callback = existingItem.EnabledChangedCallback;
|
||||
existingItem.EnabledChangedCallback = null;
|
||||
existingItem.IsEnabled = isEnabled;
|
||||
existingItem.EnabledChangedCallback = callback;
|
||||
}
|
||||
|
||||
desiredItems.Add(existingItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredItems.Add(new FlyoutMenuItem
|
||||
{
|
||||
Label = _resourceLoader.GetString(ModuleHelper.GetModuleLabelResourceName(moduleType)),
|
||||
IsEnabled = isEnabled,
|
||||
IsLocked = isLocked,
|
||||
Tag = moduleType,
|
||||
Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
|
||||
EnabledChangedCallback = EnabledChangedOnUI,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var sortedItems = DashboardSortOrder switch
|
||||
{
|
||||
DashboardSortOrder.ByStatus => desiredItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
|
||||
_ => desiredItems.OrderBy(x => x.Label).ToList(),
|
||||
};
|
||||
|
||||
for (int i = FlyoutMenuItems.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!sortedItems.Contains(FlyoutMenuItems[i]))
|
||||
{
|
||||
FlyoutMenuItems.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < sortedItems.Count; i++)
|
||||
{
|
||||
var item = sortedItems[i];
|
||||
var oldIndex = FlyoutMenuItems.IndexOf(item);
|
||||
|
||||
if (oldIndex < 0)
|
||||
{
|
||||
FlyoutMenuItems.Insert(i, item);
|
||||
}
|
||||
else if (oldIndex != i)
|
||||
{
|
||||
FlyoutMenuItems.Move(oldIndex, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnabledChangedOnUI(FlyoutMenuItem item)
|
||||
@@ -75,20 +148,36 @@ public sealed class AllAppsViewModel : Observable
|
||||
if (_coordinator.UpdateModuleEnabled(item.Tag, item.IsEnabled))
|
||||
{
|
||||
_coordinator.NotifyUserSettingsInteraction();
|
||||
|
||||
// If sorting by status, we might want to re-sort, but that could be jarring.
|
||||
// DashboardViewModel calls RequestConflictData but doesn't seem to re-sort immediately on toggle?
|
||||
// Actually DashboardViewModel calls RefreshModuleList() in ModuleEnabledChangedOnSettingsPage, but that's from settings change.
|
||||
// EnabledChangedOnUI in DashboardViewModel calls UpdateGeneralSettingsCallback.
|
||||
// If we want to re-sort on toggle, we should call RefreshFlyoutMenuItems().
|
||||
// But usually users don't like items jumping around when they toggle them.
|
||||
// So let's leave it for now.
|
||||
}
|
||||
}
|
||||
|
||||
private void ModuleEnabledChangedOnSettingsPage()
|
||||
{
|
||||
_generalSettings = _settingsRepository.SettingsConfig;
|
||||
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
|
||||
// This is called when settings change (via OnSettingsChanged -> this).
|
||||
// But OnSettingsChanged already calls RefreshFlyoutMenuItems.
|
||||
// However, ModuleEnabledChangedOnSettingsPage is also passed as a callback to GeneralSettings.
|
||||
// Wait, GeneralSettings.AddEnabledModuleChangeNotification adds it to a list.
|
||||
// But GeneralSettings is just a data object. Who calls the notification?
|
||||
// It seems GeneralSettings doesn't have logic to call it itself unless something calls it.
|
||||
// In DashboardViewModel, it's called in OnSettingsChanged.
|
||||
|
||||
foreach (var item in FlyoutMenuItems)
|
||||
{
|
||||
if (!item.IsLocked)
|
||||
{
|
||||
item.IsEnabled = ModuleHelper.GetIsModuleEnabled(_generalSettings, item.Tag);
|
||||
}
|
||||
}
|
||||
// In my implementation of OnSettingsChanged, I call RefreshFlyoutMenuItems directly.
|
||||
// So I might not need ModuleEnabledChangedOnSettingsPage to do much, or I can remove it if I don't use the callback mechanism inside GeneralSettings (which seems to be for internal notification within the object, but GeneralSettings is just a POCO-like object with some logic).
|
||||
|
||||
// Actually, let's look at DashboardViewModel again.
|
||||
// It adds ModuleEnabledChangedOnSettingsPage to generalSettingsConfig.AddEnabledModuleChangeNotification.
|
||||
// And OnSettingsChanged calls ModuleEnabledChangedOnSettingsPage.
|
||||
|
||||
// I'll stick to calling RefreshFlyoutMenuItems in OnSettingsChanged.
|
||||
// And I'll keep ModuleEnabledChangedOnSettingsPage for compatibility if needed, but maybe just redirect it.
|
||||
RefreshFlyoutMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user