├── icon.png ├── .gitignore ├── packages.config ├── Views ├── SettingsView.xaml.cs ├── Common.cs ├── FullscreenSettingsView.cs ├── SettingsView.xaml └── FullscreenSettingsView.xaml ├── ThemeOptions.csproj.user ├── Controls ├── CommandControl.xaml ├── GamepadAltControl.xaml ├── out_of_order │ ├── CommandControl_TriggerActions.cs.txt │ ├── CommandControl_SendEvent.cs..txt │ └── CommandControl_UpdateOneTimeBind.cs.txt ├── CommandControl_BeginStoryboard.cs ├── CommandControl_Toggle.cs ├── CommandControl.xaml.cs ├── CommandControl_MainMenuCommands.cs ├── CommandControl_TouchTag.cs ├── CommandControl_ChangeProperty.cs ├── CommandControl_GameControllerHold.cs └── GamepadAltControl.xaml.cs ├── App.xaml ├── Localization ├── en_US.xaml └── ru_RU.xaml ├── extension.yaml ├── Models ├── MinimalVersion.cs ├── Extensions.cs ├── Preset.cs ├── DynamicProperties.cs ├── Options.cs ├── GamepadState.cs ├── Variable.cs ├── Theme.cs ├── Presets.cs ├── Variables.cs └── SettingsViewModel.cs ├── AssemblyInfo.cs ├── LICENSE ├── Tools ├── DisplayAspectRatio.cs ├── VersionComparer.cs ├── GetFakeModel.cs ├── DynamicEventHandler.cs └── Gamepad.cs ├── Tests ├── Tools │ └── VersionComparer.cs ├── Tests.csproj └── Models │ ├── VariablesToObjectsCast.test.cs │ └── VariableValuesSearization.test.cs ├── Converters ├── VisibilityToBoolean.cs ├── DurationToDouble.cs └── DurationToString.cs ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── ThemeOptions.sln ├── ThemeOptions.csproj ├── Localization.cs ├── ThemeOptions.yaml └── ThemeOptions.cs /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashpynov/ThemeOptions/HEAD/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs 2 | /bin 3 | /obj 4 | /packages 5 | Properties/launchSettings.json 6 | /.idea 7 | Tests/bin/ 8 | Tests/obj/ 9 | -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Views/SettingsView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using System.Windows.Markup; 3 | 4 | namespace ThemeOptions.Views 5 | { 6 | public partial class SettingsView : UserControl 7 | { 8 | public SettingsView() 9 | { 10 | ((IComponentConnector)this).InitializeComponent(); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /ThemeOptions.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ProjectDebugger 5 | 6 | 7 | Fullscreen 8 | 9 | -------------------------------------------------------------------------------- /Controls/CommandControl.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /App.xaml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 100 | 101 | 102 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 117 | 120 | 122 | 123 | 124 | 125 | 126 | 131 | 132 | 135 | 136 | 137 | 138 | 139 | 140 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 154 | 160 | 172 | 178 | 179 | 180 | 181 | 182 | 185 | 191 | 203 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /Views/FullscreenSettingsView.xaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 30 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 61 | 62 | 63 | 64 | 65 | 69 | 70 | 74 | 75 | 81 | 82 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 101 | 102 | 103 | 104 | 107 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 130 | 131 | 132 | 133 | 134 | 137 | 138 | 147 | 148 | 149 | 150 | 151 | 152 | 158 | 159 | 160 | 161 | 162 | 163 | 169 | 170 | 171 | 172 | 173 | 174 | 176 | 177 | 188 | 193 | 194 | 195 | 196 | 197 | 199 | 200 | 211 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /ThemeOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | using System.Windows.Controls; 6 | using System.Windows; 7 | using System.Windows.Markup; 8 | 9 | using Playnite.SDK; 10 | using Playnite.SDK.Plugins; 11 | using Playnite.SDK.Data; 12 | 13 | using ThemeOptions.Models; 14 | using ThemeOptions.Views; 15 | using ThemeOptions.Controls; 16 | using Playnite.SDK.Events; 17 | using ThemeOptions.Tools; 18 | 19 | 20 | namespace ThemeOptions 21 | { 22 | public class ThemeOptions : GenericPlugin 23 | { 24 | private static readonly ILogger logger = LogManager.GetLogger(); 25 | 26 | public static string PluginVersion; 27 | private static readonly string PluginFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 28 | public static IPlayniteAPI PlayniteAPI { get; private set; } 29 | 30 | public static SettingsViewModel Settings { get; set; } 31 | public Views.SettingsView SettingsView { get; private set; } 32 | public static Window CustomWindow { get; private set; } 33 | 34 | public string CurrentThemeId; 35 | 36 | public Extensions MissingExtensions { get; private set; } = new Extensions(); 37 | 38 | public override Guid Id { get; } = Guid.Parse("904cbf3b-573f-48f8-9642-0a09d05c64ef"); 39 | List themeResources = new List(); 40 | 41 | public ResourceDictionary LoadResource(string resourceFile, bool mandatory = true) 42 | { 43 | if (File.Exists(resourceFile)) 44 | { 45 | try 46 | { 47 | using (var stream = new StreamReader(resourceFile)) 48 | { 49 | ResourceDictionary resource = (ResourceDictionary)XamlReader.Load(stream.BaseStream); 50 | resource.Source = new Uri(resourceFile, UriKind.Absolute); 51 | Application.Current.Resources.MergedDictionaries.Add(resource); 52 | return resource; 53 | } 54 | } 55 | catch (Exception e) 56 | { 57 | logger.Error($"Error: '{e.Message}' during loading resource {resourceFile} "); 58 | } 59 | } 60 | else if (mandatory) 61 | { 62 | logger.Error($"File not found {resourceFile}"); 63 | } 64 | else 65 | { 66 | logger.Info($"Optional file not found {resourceFile}"); 67 | } 68 | return null; 69 | } 70 | public void LoadThemeOption() 71 | { 72 | foreach (ResourceDictionary resource in themeResources) 73 | { 74 | Application.Current.Resources.MergedDictionaries.Remove(resource); 75 | } 76 | themeResources.Clear(); 77 | 78 | Theme theme = Theme.FromId(CurrentThemeId); 79 | if (theme == null) return; 80 | 81 | if (theme.Options?.Presets?.Count > 0) 82 | { 83 | List selectedPresets = Settings.ThemePresets(theme.Id); 84 | if (selectedPresets?.Count > 0) 85 | { 86 | var presets = String.Join(", ", selectedPresets); 87 | logger.Info($"Loading presets {presets}"); 88 | foreach (var presetResource in theme.Options.Presets.GetResourceFiles(selectedPresets)) 89 | { 90 | logger.Info($"Loading resource {presetResource}"); 91 | if (LoadResource(Path.Combine(theme.Path, presetResource)) is ResourceDictionary resource) 92 | { 93 | themeResources.Add(resource); 94 | } 95 | } 96 | } 97 | } 98 | 99 | 100 | var missingRequired = new List(); 101 | var missingRecommended = new List(); 102 | 103 | var installedExtensions = PlayniteAPI.Addons.Addons; 104 | if (theme.Extensions?.Required?.Count > 0) 105 | { 106 | foreach (var ext in theme.Extensions.Required) 107 | { 108 | if (!installedExtensions.Exists(a => a.Equals(ext, StringComparison.OrdinalIgnoreCase))) 109 | { 110 | missingRequired.Add(ext); 111 | } 112 | } 113 | } 114 | 115 | if(theme.Extensions?.Recommended?.Count > 0) 116 | { 117 | foreach (var ext in theme.Extensions.Recommended) 118 | { 119 | if (!installedExtensions.Exists(a => a.Equals(ext, StringComparison.OrdinalIgnoreCase))) 120 | { 121 | missingRecommended.Add(ext); 122 | } 123 | } 124 | } 125 | 126 | MissingExtensions = new Extensions 127 | { 128 | Required = missingRequired, 129 | Recommended = missingRecommended 130 | }; 131 | 132 | if (PlayniteAPI.ApplicationSettings.Language != "en_US") 133 | { 134 | logger.Info($"Loading theme localization {PlayniteAPI.ApplicationSettings.Language}"); 135 | Localization.Load(theme.Path, PlayniteAPI.ApplicationSettings.Language); 136 | } 137 | 138 | if (theme.Options != null) 139 | { 140 | var themeSettings = Settings.ThemeSettings(theme.Id); 141 | if (themeSettings != null && themeSettings.Count > 0) 142 | { 143 | try 144 | { 145 | var settingsText = themeSettings.FormatResourceDictionary(); 146 | logger.Debug("FormatResourceDictionary:\n" + settingsText); 147 | ResourceDictionary resource = (ResourceDictionary)XamlReader.Parse(settingsText); 148 | Application.Current.Resources.MergedDictionaries.Add(resource); 149 | themeResources.Add(resource); 150 | } 151 | catch (Exception e) 152 | { 153 | logger.Error($"Error while parsing settings: {e.Message}"); 154 | } 155 | } 156 | } 157 | } 158 | 159 | public static void OpenWindow(string styleName) 160 | { 161 | var parent = PlayniteAPI.Dialogs.GetCurrentAppWindow(); 162 | CustomWindow = PlayniteAPI.Dialogs.CreateWindow(new WindowCreationOptions 163 | { 164 | ShowMinimizeButton = false 165 | }); 166 | 167 | CustomWindow.Height = parent.Height; 168 | CustomWindow.Width = parent.Width; 169 | CustomWindow.Title = "MoData"; 170 | 171 | string xamlString = $@" 172 | 176 | 177 | 180 | 181 | "; 182 | 183 | // Parse the XAML string 184 | var element = (FrameworkElement)XamlReader.Parse(xamlString); 185 | 186 | 187 | CustomWindow.Content = element; 188 | 189 | CustomWindow.DataContext = parent.DataContext; 190 | 191 | CustomWindow.Owner = parent; 192 | CustomWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner; 193 | 194 | CustomWindow.PreviewKeyDown += (s, e) => 195 | { 196 | if (e.Key == System.Windows.Input.Key.Escape) 197 | { 198 | CustomWindow.Close(); 199 | CustomWindow = null; 200 | e.Handled = true; 201 | } 202 | }; 203 | 204 | CustomWindow.ShowDialog(); 205 | } 206 | 207 | public ThemeOptions(IPlayniteAPI api) : base(api) 208 | { 209 | PlayniteAPI = api; 210 | CurrentThemeId = 211 | PlayniteAPI.ApplicationInfo.Mode == ApplicationMode.Fullscreen 212 | ? PlayniteAPI.ApplicationSettings.FullscreenTheme 213 | : PlayniteAPI.ApplicationSettings.DesktopTheme; 214 | 215 | Settings = new SettingsViewModel(this); 216 | Properties = new GenericPluginProperties 217 | { 218 | HasSettings = true 219 | }; 220 | Localization.Load(PluginFolder, PlayniteAPI.ApplicationSettings.Language); 221 | LoadThemeOption(); 222 | if (PlayniteAPI.ApplicationInfo.Mode == ApplicationMode.Fullscreen) 223 | { 224 | FullscreenSettingsView.Init(); 225 | } 226 | 227 | AddSettingsSupport(new AddSettingsSupportArgs 228 | { 229 | SourceName = "ThemeOptions", 230 | SettingsRoot = $"{nameof(Settings)}.{nameof(Settings.Settings)}" 231 | }); 232 | 233 | 234 | AddCustomElementSupport(new AddCustomElementSupportArgs 235 | { 236 | SourceName = "ThemeOptions", 237 | ElementList = new List 238 | { 239 | "Command", 240 | "GamepadAdd", 241 | "GamepadAlt", 242 | "GamepadAddGlobal", 243 | "GamepadAltGlobal" 244 | } 245 | }); 246 | } 247 | 248 | public Options FromFile(string optionsFile) 249 | { 250 | if (!File.Exists(optionsFile)) return null; 251 | 252 | try 253 | { 254 | return Serialization.FromYamlFile(optionsFile); 255 | } 256 | catch (Exception e) 257 | { 258 | logger.Error($"Error loading theme option file {optionsFile}: \n{e.Message}"); 259 | return null; 260 | } 261 | } 262 | 263 | public override ISettings GetSettings(bool firstRunSettings) 264 | { 265 | return Settings; 266 | } 267 | 268 | public override UserControl GetSettingsView(bool firstRunSettings) 269 | { 270 | if (SettingsView == null) 271 | SettingsView = new Views.SettingsView(); 272 | return SettingsView as UserControl; 273 | } 274 | 275 | public override Control GetGameViewControl(GetGameViewControlArgs args) 276 | { 277 | var strArgs = args.Name.Split('_'); 278 | 279 | var controlType = strArgs[0]; 280 | 281 | switch (controlType) 282 | { 283 | case "Command": 284 | return new CommandControl(); 285 | case "GamepadAdd": 286 | return new GamepadAltControl(suppressDefaults: false); 287 | case "GamepadAlt": 288 | return new GamepadAltControl(suppressDefaults: true); 289 | case "GamepadAddGlobal": 290 | return new GamepadAltControl(suppressDefaults: false, global: true); 291 | case "GamepadAltGlobal": 292 | return new GamepadAltControl(suppressDefaults: true, global: true); 293 | default: 294 | throw new ArgumentException($"Unrecognized controlType '{controlType}' for request '{args.Name}'"); 295 | } 296 | } 297 | 298 | public override void OnApplicationStarted(OnApplicationStartedEventArgs args) 299 | { 300 | if (MissingExtensions?.Required?.Count > 0) 301 | { 302 | var result = PlayniteAPI.Dialogs.ShowMessage( 303 | $"You are missing {MissingExtensions.Required.Count} required extensions for this theme. Would you like to open their addon pages?", 304 | "Missing Extensions", 305 | MessageBoxButton.YesNo 306 | ); 307 | if (result == MessageBoxResult.Yes) 308 | { 309 | foreach (var ext in MissingExtensions.Required) 310 | { 311 | System.Diagnostics.Process.Start($"https://playnite.link/addons.html#{ext}"); 312 | } 313 | } 314 | } 315 | if (MissingExtensions?.Recommended?.Count > 0) 316 | { 317 | PlayniteAPI.Notifications.Add( 318 | new NotificationMessage( 319 | "ThemeOptions-MissingExtensions", 320 | $"You are missing {MissingExtensions.Recommended.Count} recommended extensions for this theme.", 321 | NotificationType.Info, 322 | () => 323 | { 324 | var result = PlayniteAPI.Dialogs.ShowMessage( 325 | $"You are missing {MissingExtensions.Recommended.Count} recommended extensions for this theme. Would you like to open their addon pages?", 326 | "Missing Extensions", 327 | MessageBoxButton.YesNo 328 | ); 329 | if (result == MessageBoxResult.Yes) 330 | { 331 | foreach (var ext in MissingExtensions.Recommended) 332 | { 333 | System.Diagnostics.Process.Start($"https://playnite.link/addons.html#{ext}"); 334 | } 335 | } 336 | } 337 | ) 338 | ); 339 | } 340 | } 341 | 342 | public override void OnApplicationStopped(OnApplicationStoppedEventArgs args) 343 | { 344 | Settings.Settings.OptionsToTheme(CurrentThemeId); 345 | SavePluginSettings(Settings.Settings); 346 | } 347 | public override void OnControllerButtonStateChanged(OnControllerButtonStateChangedArgs args) 348 | { 349 | 350 | if (CustomWindow != null && CustomWindow.IsVisible && args.Button == ControllerInput.B && args.State == ControllerInputState.Pressed) 351 | { 352 | CustomWindow.Close(); 353 | CustomWindow = null; 354 | } 355 | else 356 | { 357 | Gamepad.SetState(args); 358 | } 359 | } 360 | } 361 | } -------------------------------------------------------------------------------- /Models/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Windows; 4 | using System.ComponentModel; 5 | 6 | using Playnite.SDK; 7 | using Playnite.SDK.Data; 8 | using ThemeOptions.Tools; 9 | using System.Windows.Input; 10 | 11 | 12 | namespace ThemeOptions.Models 13 | { 14 | using ThemesSelectedPresets = Dictionary>; 15 | using ThemesSettings = Dictionary; 16 | 17 | public static class DictionaryExtension 18 | { 19 | static public TValue Get(this Dictionarydict, TKey key, TValue def = default) 20 | { 21 | return dict != null && dict.ContainsKey(key) ? dict[key] : def; 22 | } 23 | } 24 | 25 | public class OpenWindowHelper 26 | { 27 | public ICommand this[string key] 28 | { 29 | get 30 | { 31 | return new RelayCommand(() => ThemeOptions.OpenWindow(key)); 32 | } 33 | } 34 | } 35 | 36 | public class Settings : ObservableObject 37 | { 38 | 39 | [DontSerialize] 40 | public OpenWindowHelper OpenWindow { get; } = new OpenWindowHelper(); 41 | 42 | private ThemesSelectedPresets selectedPresets = new ThemesSelectedPresets(); 43 | 44 | private ThemesSettings userSettings = new ThemesSettings(); 45 | 46 | public ThemesSelectedPresets SelectedPresets { get => selectedPresets; } 47 | 48 | public ThemesSettings UserSettings { get => userSettings; } 49 | 50 | [DontSerialize] 51 | public DynamicProperties Options { get; } = new DynamicProperties(); 52 | 53 | [DontSerialize] 54 | public GamepadState GamepadState { get; } = new GamepadState(); 55 | 56 | [DontSerialize] 57 | public MinimalVersion MinimalVersion { get; } = new MinimalVersion(); 58 | 59 | [DontSerialize] 60 | public string Version { get => MinimalVersion.PluginVersion; } 61 | 62 | [DontSerialize] 63 | public bool IsInstalled { get => true; } 64 | 65 | [DontSerialize] 66 | private string aspectRatio = "dsp169"; 67 | 68 | [DontSerialize] 69 | public string AspectRatio 70 | { 71 | get => aspectRatio; 72 | set 73 | { 74 | if (aspectRatio != value) 75 | { 76 | aspectRatio = value; 77 | OnPropertyChanged(); 78 | } 79 | } 80 | } 81 | 82 | public void ThemeToOptions(string themeId) 83 | { 84 | Options themeOptions = Theme.FromId(themeId)?.Options; 85 | 86 | Variables themeSettings = themeOptions?.Variables; 87 | VariablesValues presetSettings = themeOptions?.Presets?.GetConstants(SelectedPresets.Get(themeId)); 88 | VariablesValues userSettings = UserSettings.Get(themeId); 89 | 90 | VariablesValues variableValues = new VariablesValues(themeSettings).Merge(presetSettings).Merge(userSettings); 91 | Options.Update(variableValues); 92 | } 93 | 94 | public void OptionsToTheme(string themeId) 95 | { 96 | VariablesValues themeValues = UserSettings.Get(themeId, new VariablesValues()); 97 | VariablesValues userSettings = themeValues.FromDynamicProperties(Options); 98 | if (userSettings?.Count > 0) 99 | { 100 | UserSettings[themeId] = userSettings; 101 | } 102 | } 103 | } 104 | 105 | public class SettingsViewModel : ObservableObject, ISettings, INotifyPropertyChanged 106 | { 107 | private readonly ThemeOptions plugin; 108 | public Theme SelectedTheme { get; set; } 109 | 110 | public List CustomizableThemes { get; set; } 111 | 112 | private string _PreviewImage; 113 | public string PreviewImage 114 | { 115 | get { return _PreviewImage; } 116 | set 117 | { 118 | _PreviewImage = value; 119 | OnPropertyChanged("PreviewImage"); 120 | } 121 | } 122 | public void UpdatePreview(string path, bool show) 123 | { 124 | if (show) 125 | { 126 | PreviewImage = path ; 127 | } 128 | else if (PreviewImage == path) 129 | { 130 | PreviewImage = null; 131 | } 132 | } 133 | 134 | private Settings settings; 135 | public Settings Settings 136 | { 137 | get => settings; 138 | set 139 | { 140 | settings = value; 141 | OnPropertyChanged(); 142 | } 143 | } 144 | public void UpdateAspectRatio() 145 | { 146 | if (plugin.PlayniteApi.Dialogs.GetCurrentAppWindow() is Window window 147 | && !(window.Width is double.NaN) 148 | && !(window.Height is double.NaN) 149 | && window.Width != 0 150 | && window.Height != 0) 151 | { 152 | Settings.AspectRatio = DisplayAspectRatio.GetAspectRatio(window.Width, window.Height ); 153 | } 154 | } 155 | public List ThemePresets(string themeId) => settings.SelectedPresets.Get(themeId, new List()); 156 | public VariablesValues ThemeSettings(string themeId) 157 | { 158 | Theme theme = Theme.FromId(themeId); 159 | if (theme == null) return new VariablesValues(); 160 | Variables themeSettings = Theme.FromId(themeId)?.Options?.Variables ?? new Variables(); 161 | VariablesValues userSettings = settings.UserSettings.Get(themeId, new VariablesValues()); 162 | VariablesValues filtered = new VariablesValues(userSettings.Where( 163 | s => s.Value.Value != null 164 | && s.Value.Type == themeSettings.Get(s.Key)?.Type 165 | && s.Value.Value != themeSettings.Get(s.Key)?.Default) 166 | ); 167 | 168 | var result = theme.Options.Presets?.GetConstants(ThemePresets(themeId)) ?? new VariablesValues(); 169 | return result.Merge(filtered); 170 | } 171 | 172 | public SettingsViewModel(ThemeOptions plugin) 173 | { 174 | // Injecting your plugin instance is required for Save/Load method because Playnite saves data to a location based on what plugin requested the operation. 175 | this.plugin = plugin; 176 | 177 | // Load saved settings. 178 | var savedSettings = plugin.LoadPluginSettings(); 179 | 180 | // LoadPluginSettings returns null if no saved data is available. 181 | if (savedSettings != null) 182 | { 183 | Settings = savedSettings; 184 | } 185 | else 186 | { 187 | Settings = new Settings(); 188 | } 189 | 190 | Settings.ThemeToOptions(plugin.CurrentThemeId); 191 | Settings.Options.PropertyChanged += (o, e) => 192 | { 193 | Settings.OnPropertyChanged("Options"); 194 | }; 195 | } 196 | 197 | 198 | 199 | public Options SelectedThemeOptions = null; 200 | 201 | public Dictionary ThemesOptions = null; 202 | 203 | 204 | private void LoadCustomizableThemes() 205 | { 206 | CustomizableThemes = Theme.EnumThemes().ToList(); 207 | SelectedTheme = CustomizableThemes.FirstOrDefault(t => t.Id == ThemeOptions.PlayniteAPI.ApplicationSettings.FullscreenTheme) ?? 208 | CustomizableThemes.FirstOrDefault(t => t.Id == ThemeOptions.PlayniteAPI.ApplicationSettings.DesktopTheme) ?? 209 | CustomizableThemes.FirstOrDefault(); 210 | 211 | } 212 | 213 | private void LoadFromSettings(Settings settings, List themes ) 214 | { 215 | foreach (var theme in themes) 216 | { 217 | theme.TranslateOptions(); 218 | 219 | foreach (var variable in theme?.Options?.Variables ?? new Variables()) 220 | { 221 | if (variable.Value.NeedRestart) 222 | { 223 | variable.Value.Title += " *"; 224 | } 225 | } 226 | 227 | if (theme?.Options?.Presets?.Count > 0) 228 | { 229 | theme.Options.Presets.Enumerate().ForEach(p => { 230 | if (p.Value.NeedRestart) 231 | { 232 | p.Value.Name += " *"; 233 | } 234 | }); 235 | } 236 | 237 | foreach (var preset in theme.PresetList) 238 | { 239 | preset.Selected = preset.OptionsList 240 | .FirstOrDefault( 241 | option => settings.SelectedPresets.Get(theme.Id)?.Contains(option.Id) == true); 242 | } 243 | 244 | if (theme.Options.Variables != null) 245 | { 246 | var themeSettings = settings.UserSettings.Get(theme.Id); 247 | 248 | foreach (var variable in theme.Options.Variables) 249 | { 250 | variable.Value.Value = ( 251 | themeSettings != null && 252 | variable.Value.Type != null && 253 | themeSettings.Get(variable.Key)?.Type == variable.Value.Type 254 | ? themeSettings[variable.Key].Value : null) 255 | ?? variable.Value.Default; 256 | } 257 | } 258 | } 259 | } 260 | 261 | private bool SaveToSettings(List themes, Settings settings) 262 | { 263 | Settings original = Serialization.GetClone(settings); 264 | 265 | foreach (var theme in themes) 266 | { 267 | var selectedPresets = theme 268 | .PresetList 269 | .Where(p => p.Selected != null && !p.Selected.Id.ToLower().EndsWith("default")) 270 | .Select(p => p.Selected.Id).ToList(); 271 | 272 | if (theme.PresetList?.Count > 0) 273 | { 274 | settings.SelectedPresets[theme.Id] = selectedPresets; 275 | } 276 | 277 | if (theme.Options.Variables != null) 278 | { 279 | var variables = new VariablesValues( 280 | theme.Options.Variables 281 | .Where(v => v.Value.Value != v.Value.Default) 282 | ); 283 | 284 | if (variables?.Count > 0) 285 | { 286 | settings.UserSettings[theme.Id] = variables; 287 | } 288 | 289 | List defaultValues = original.UserSettings.Get(theme.Id, new VariablesValues()) 290 | .Where(v => !theme.Options.Variables.ContainsKey(v.Key) || v.Value.Value == theme.Options.Variables[v.Key]?.Default) 291 | .Select(v => v.Key) 292 | .ToList(); 293 | 294 | foreach( var key in defaultValues) 295 | { 296 | original.UserSettings[theme.Id].Remove(key); 297 | } 298 | } 299 | } 300 | 301 | string currentTheme = plugin.CurrentThemeId; 302 | 303 | Settings updated = Serialization.GetClone(settings); 304 | 305 | List needsRestart = Theme.FromId(currentTheme)?.Options?.Variables 306 | ?.Where(v=>v.Value.NeedRestart) 307 | ?.Select(v => v.Key) 308 | ?.ToList() 309 | ?? new List(); 310 | 311 | List needsPresetsRestart = Theme.FromId(currentTheme)?.Options?.Presets 312 | ?.SelectMany(p => p.Value.Presets?.Where(sp => sp.Value.NeedRestart).Select(sp => $"{p.Key}.{sp.Key}")) 313 | ?.ToList(); 314 | 315 | var originalNonDefaultPreset = original.SelectedPresets.Get(plugin.CurrentThemeId, new List()); 316 | 317 | var originalPreset = Theme.FromId(currentTheme) 318 | ?.PresetList 319 | ?.Select( 320 | p => p.OptionsList.FirstOrDefault(o => originalNonDefaultPreset.Contains(o.Id))?.Id 321 | ?? p.OptionsList.FirstOrDefault(o => o.Id.ToLower().EndsWith("default"))?.Id) 322 | ?.ToList() ?? new List(); 323 | 324 | var updatedNonDefaultPreset = updated.SelectedPresets.Get(plugin.CurrentThemeId, new List()); 325 | var updatedPreset = Theme.FromId(currentTheme) 326 | ?.PresetList 327 | ?.Select( 328 | p => p.OptionsList.FirstOrDefault(o => updatedNonDefaultPreset.Contains(o.Id))?.Id 329 | ?? p.OptionsList.FirstOrDefault(o => o.Id.ToLower().EndsWith("default"))?.Id) 330 | ?.ToList() ?? new List(); 331 | 332 | var presetsEqual = Serialization.AreObjectsEqual( 333 | originalPreset.Where(p => needsPresetsRestart.Contains(p)), 334 | updatedPreset.Where(p => needsPresetsRestart.Contains(p)) 335 | ); 336 | 337 | 338 | var variablesEqual = Serialization.AreObjectsEqual( 339 | original.UserSettings.Get(plugin.CurrentThemeId, new VariablesValues()).Where(v => needsRestart.Contains(v.Key)), 340 | updated.UserSettings.Get(plugin.CurrentThemeId, new VariablesValues()).Where(v => needsRestart.Contains(v.Key)) 341 | ); 342 | 343 | return !presetsEqual || !variablesEqual; 344 | } 345 | 346 | public void BeginEdit() 347 | { 348 | LoadCustomizableThemes(); 349 | Settings.OptionsToTheme(plugin.CurrentThemeId); 350 | LoadFromSettings(Settings, CustomizableThemes); 351 | } 352 | 353 | public void BeginFullscreenEdit(string themeId) 354 | { 355 | SelectedTheme = Theme.FromId(themeId); 356 | CustomizableThemes = new List { SelectedTheme }; 357 | 358 | Settings.OptionsToTheme(plugin.CurrentThemeId); 359 | LoadFromSettings(Settings, CustomizableThemes); 360 | } 361 | 362 | public void CancelEdit() 363 | { 364 | } 365 | 366 | public void EndEdit() 367 | { 368 | bool needReload = SaveToSettings(CustomizableThemes, Settings); 369 | plugin.SavePluginSettings(Settings); 370 | Settings.ThemeToOptions(plugin.CurrentThemeId); 371 | 372 | if (needReload) 373 | { 374 | 375 | dynamic ctx = Application.Current.MainWindow.DataContext; 376 | ctx.AppSettings.Fullscreen.OnPropertyChanged("Theme"); 377 | ctx.AppSettings.OnPropertyChanged("Theme"); 378 | } 379 | else 380 | { 381 | plugin.LoadThemeOption(); 382 | } 383 | } 384 | 385 | public bool VerifySettings(out List errors) 386 | { 387 | // Code execute when user decides to confirm changes made since BeginEdit was called. 388 | // Executed before EndEdit is called and EndEdit is not called if false is returned. 389 | // List of errors is presented to user if verification fails. 390 | errors = new List(); 391 | return true; 392 | } 393 | } 394 | } --------------------------------------------------------------------------------