Refactor PowerDisplay profile management

Simplified profile management by removing the concept of "Custom profiles" and "current profile" tracking. Profiles are now treated as templates for quick application of monitor settings, rather than persistent states.

Key changes include:
- Replaced `ObservableCollection<string>` with `ObservableCollection<PowerDisplayProfile>` to manage profile objects directly.
- Removed redundant properties and methods related to "selected" and "current" profiles.
- Refactored methods for creating, updating, and deleting profiles to operate on `PowerDisplayProfile` objects.
- Updated `PowerDisplayViewModel` and `ProfileManager` to streamline profile loading, saving, and application logic.
- Updated the UI to replace the profile dropdown with buttons for quick application, along with context menu options for managing profiles.
- Improved logging and error handling for profile operations.
- Updated resource strings and removed references to "Custom profiles" and "current profile."

These changes simplify the codebase, improve maintainability, and align the application with the new design philosophy of treating profiles as templates.
This commit is contained in:
Yu Leng
2025-11-20 04:40:36 +08:00
parent b8abff02ac
commit d64bb78727
10 changed files with 5957 additions and 505 deletions

View File

@@ -475,15 +475,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// Profile-related fields
private ObservableCollection<string> _profiles = new ObservableCollection<string>();
private string _selectedProfile = PowerDisplayProfiles.CustomProfileName;
private string _currentProfile = PowerDisplayProfiles.CustomProfileName;
private ObservableCollection<PowerDisplayProfile> _profiles = new ObservableCollection<PowerDisplayProfile>();
private string _profilesFilePath = string.Empty;
/// <summary>
/// Collection of available profile names (including Custom)
/// Collection of available profiles (for button display)
/// </summary>
public ObservableCollection<string> Profiles
public ObservableCollection<PowerDisplayProfile> Profiles
{
get => _profiles;
set
@@ -496,61 +494,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
/// <summary>
/// Currently selected profile in the ComboBox
/// </summary>
public string SelectedProfile
{
get => _selectedProfile;
set
{
if (_selectedProfile != value && !string.IsNullOrEmpty(value))
{
_selectedProfile = value;
OnPropertyChanged();
// Apply the selected profile
ApplyProfile(value);
}
}
}
/// <summary>
/// Currently active profile (read from settings, may differ from selected during transition)
/// </summary>
public string CurrentProfile
{
get => _currentProfile;
set
{
if (_currentProfile != value)
{
_currentProfile = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsCustomProfile));
}
}
}
/// <summary>
/// True if current profile is Custom
/// </summary>
public bool IsCustomProfile => _currentProfile?.Equals(PowerDisplayProfiles.CustomProfileName, StringComparison.OrdinalIgnoreCase) ?? true;
/// <summary>
/// True if a non-Custom profile is selected (enables delete/rename)
/// </summary>
public bool CanModifySelectedProfile => !string.IsNullOrEmpty(_selectedProfile) &&
!_selectedProfile.Equals(PowerDisplayProfiles.CustomProfileName, StringComparison.OrdinalIgnoreCase);
public ButtonClickCommand AddProfileCommand => new ButtonClickCommand(AddProfile);
public ButtonClickCommand DeleteProfileCommand => new ButtonClickCommand(DeleteProfile);
public ButtonClickCommand RenameProfileCommand => new ButtonClickCommand(RenameProfile);
public ButtonClickCommand SaveAsProfileCommand => new ButtonClickCommand(SaveAsProfile);
public void RefreshEnabledState()
{
InitializeEnabledValue();
@@ -583,24 +526,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
var profilesData = LoadProfilesFromDisk();
// Build profile names list
var profileNames = new List<string> { PowerDisplayProfiles.CustomProfileName };
profileNames.AddRange(profilesData.Profiles.Select(p => p.Name));
// Load profile objects (no Custom - it's not a profile anymore)
Profiles.Clear();
foreach (var profile in profilesData.Profiles)
{
Profiles.Add(profile);
}
Profiles = new ObservableCollection<string>(profileNames);
// Set current profile from settings
CurrentProfile = _settings.Properties.CurrentProfile ?? PowerDisplayProfiles.CustomProfileName;
_selectedProfile = CurrentProfile;
OnPropertyChanged(nameof(SelectedProfile));
Logger.LogInfo($"Loaded {profilesData.Profiles.Count} profiles, current: {CurrentProfile}");
Logger.LogInfo($"Loaded {Profiles.Count} profiles");
}
catch (Exception ex)
{
Logger.LogError($"Failed to load profiles: {ex.Message}");
Profiles = new ObservableCollection<string> { PowerDisplayProfiles.CustomProfileName };
CurrentProfile = PowerDisplayProfiles.CustomProfileName;
Profiles.Clear();
}
}
@@ -638,40 +576,33 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
/// <summary>
/// Apply a profile
/// Apply a profile to monitors
/// </summary>
private void ApplyProfile(string profileName)
public void ApplyProfile(PowerDisplayProfile profile)
{
try
{
Logger.LogInfo($"Applying profile: {profileName}");
var profilesData = LoadProfilesFromDisk();
var profile = profilesData.GetProfile(profileName);
if (profile == null || !profile.IsValid())
{
Logger.LogWarning($"Profile '{profileName}' not found or invalid");
Logger.LogWarning("Invalid profile");
return;
}
Logger.LogInfo($"Applying profile: {profile.Name}");
// Create pending operation
var operation = new ProfileOperation(profileName, profile.MonitorSettings);
var operation = new ProfileOperation(profile.Name, profile.MonitorSettings);
_settings.Properties.PendingProfileOperation = operation;
_settings.Properties.CurrentProfile = profileName;
// Save settings
NotifySettingsChanged();
// Update current profile
CurrentProfile = profileName;
// Send custom action to trigger profile application
SendConfigMSG(
string.Format(
CultureInfo.InvariantCulture,
"{{ \"action\": {{ \"PowerDisplay\": {{ \"action_name\": \"ApplyProfile\", \"value\": \"{0}\" }} }} }}",
profileName));
profile.Name));
// Signal PowerDisplay to apply profile
using (var eventHandle = new System.Threading.EventWaitHandle(
@@ -682,7 +613,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
eventHandle.Set();
}
Logger.LogInfo($"Profile '{profileName}' applied successfully");
Logger.LogInfo($"Profile '{profile.Name}' applied successfully");
}
catch (Exception ex)
{
@@ -691,74 +622,90 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
/// <summary>
/// Add a new profile
/// Create a new profile
/// </summary>
private async void AddProfile()
public void CreateProfile(PowerDisplayProfile profile)
{
try
{
Logger.LogInfo("Adding new profile");
if (Monitors == null || Monitors.Count == 0)
if (profile == null || !profile.IsValid())
{
Logger.LogWarning("No monitors available to create profile");
Logger.LogWarning("Invalid profile");
return;
}
var profilesData = LoadProfilesFromDisk();
var defaultName = profilesData.GenerateProfileName();
// Show profile editor dialog
var dialog = new Views.ProfileEditorDialog(Monitors, defaultName);
var result = await dialog.ShowAsync();
if (result == Microsoft.UI.Xaml.Controls.ContentDialogResult.Primary && dialog.ResultProfile != null)
{
var newProfile = dialog.ResultProfile;
// Validate profile name
if (string.IsNullOrWhiteSpace(newProfile.Name))
{
newProfile = new PowerDisplayProfile(defaultName, newProfile.MonitorSettings);
}
profilesData.SetProfile(newProfile);
SaveProfilesToDisk(profilesData);
// Reload profile list
LoadProfiles();
Logger.LogInfo($"Profile '{newProfile.Name}' created successfully");
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to add profile: {ex.Message}");
}
}
/// <summary>
/// Delete the selected profile
/// </summary>
private void DeleteProfile()
{
try
{
if (!CanModifySelectedProfile)
{
return;
}
Logger.LogInfo($"Deleting profile: {SelectedProfile}");
Logger.LogInfo($"Creating profile: {profile.Name}");
var profilesData = LoadProfilesFromDisk();
profilesData.RemoveProfile(SelectedProfile);
profilesData.SetProfile(profile);
SaveProfilesToDisk(profilesData);
// Reload profile list
LoadProfiles();
Logger.LogInfo($"Profile '{SelectedProfile}' deleted successfully");
Logger.LogInfo($"Profile '{profile.Name}' created successfully");
}
catch (Exception ex)
{
Logger.LogError($"Failed to create profile: {ex.Message}");
}
}
/// <summary>
/// Update an existing profile
/// </summary>
public void UpdateProfile(string oldName, PowerDisplayProfile newProfile)
{
try
{
if (newProfile == null || !newProfile.IsValid())
{
Logger.LogWarning("Invalid profile");
return;
}
Logger.LogInfo($"Updating profile: {oldName} -> {newProfile.Name}");
var profilesData = LoadProfilesFromDisk();
// Remove old profile and add updated one
profilesData.RemoveProfile(oldName);
profilesData.SetProfile(newProfile);
SaveProfilesToDisk(profilesData);
// Reload profile list
LoadProfiles();
Logger.LogInfo($"Profile updated to '{newProfile.Name}' successfully");
}
catch (Exception ex)
{
Logger.LogError($"Failed to update profile: {ex.Message}");
}
}
/// <summary>
/// Delete a profile
/// </summary>
public void DeleteProfile(string profileName)
{
try
{
if (string.IsNullOrEmpty(profileName))
{
return;
}
Logger.LogInfo($"Deleting profile: {profileName}");
var profilesData = LoadProfilesFromDisk();
profilesData.RemoveProfile(profileName);
SaveProfilesToDisk(profilesData);
// Reload profile list
LoadProfiles();
Logger.LogInfo($"Profile '{profileName}' deleted successfully");
}
catch (Exception ex)
{
@@ -766,127 +713,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
/// <summary>
/// Rename the selected profile
/// </summary>
private async void RenameProfile()
{
try
{
if (!CanModifySelectedProfile)
{
return;
}
Logger.LogInfo($"Renaming profile: {SelectedProfile}");
// Load the existing profile
var profilesData = LoadProfilesFromDisk();
var existingProfile = profilesData.GetProfile(SelectedProfile);
if (existingProfile == null)
{
Logger.LogWarning($"Profile '{SelectedProfile}' not found");
return;
}
// Show profile editor dialog with existing profile data
var dialog = new Views.ProfileEditorDialog(Monitors, existingProfile.Name);
// Pre-fill monitor settings from existing profile
foreach (var monitorSetting in existingProfile.MonitorSettings)
{
var monitorItem = dialog.ViewModel.Monitors.FirstOrDefault(m => m.Monitor.HardwareId == monitorSetting.HardwareId);
if (monitorItem != null)
{
monitorItem.IsSelected = true;
monitorItem.Brightness = monitorSetting.Brightness;
monitorItem.ColorTemperature = monitorSetting.ColorTemperature;
if (monitorSetting.Contrast.HasValue)
{
monitorItem.Contrast = monitorSetting.Contrast.Value;
}
if (monitorSetting.Volume.HasValue)
{
monitorItem.Volume = monitorSetting.Volume.Value;
}
}
}
var result = await dialog.ShowAsync();
if (result == Microsoft.UI.Xaml.Controls.ContentDialogResult.Primary && dialog.ResultProfile != null)
{
var updatedProfile = dialog.ResultProfile;
// Remove old profile and add updated one
profilesData.RemoveProfile(SelectedProfile);
profilesData.SetProfile(updatedProfile);
SaveProfilesToDisk(profilesData);
// Reload profile list
LoadProfiles();
// Select the renamed profile
SelectedProfile = updatedProfile.Name;
Logger.LogInfo($"Profile renamed to '{updatedProfile.Name}' successfully");
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to rename profile: {ex.Message}");
}
}
/// <summary>
/// Save current settings as a new profile
/// </summary>
private void SaveAsProfile()
{
try
{
Logger.LogInfo("Saving current settings as new profile");
var profilesData = LoadProfilesFromDisk();
var newProfileName = profilesData.GenerateProfileName();
// Collect current monitor settings
var monitorSettings = new List<ProfileMonitorSetting>();
foreach (var monitor in Monitors)
{
var setting = new ProfileMonitorSetting(
monitor.HardwareId,
monitor.CurrentBrightness,
monitor.ColorTemperature,
monitor.EnableContrast ? (int?)50 : null,
monitor.EnableVolume ? (int?)50 : null);
monitorSettings.Add(setting);
}
if (monitorSettings.Count == 0)
{
Logger.LogWarning("No monitors available to save profile");
return;
}
var newProfile = new PowerDisplayProfile(newProfileName, monitorSettings);
profilesData.SetProfile(newProfile);
SaveProfilesToDisk(profilesData);
// Reload profile list and select the new profile
LoadProfiles();
SelectedProfile = newProfileName;
Logger.LogInfo($"Saved as profile '{newProfileName}' successfully");
}
catch (Exception ex)
{
Logger.LogError($"Failed to save as profile: {ex.Message}");
}
}
private void NotifySettingsChanged()
{
// Persist locally first so settings survive even if the module DLL isn't loaded yet.