From 0122335c37f8b6c53a78d5a3c0a1f7c01658e6a6 Mon Sep 17 00:00:00 2001 From: Hendrik Polczynski Date: Mon, 1 Jul 2024 14:29:27 +0200 Subject: [PATCH 1/4] fix border theme switch --- Nodify/Themes/Controls.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nodify/Themes/Controls.xaml b/Nodify/Themes/Controls.xaml index 4bb46116..851203cb 100644 --- a/Nodify/Themes/Controls.xaml +++ b/Nodify/Themes/Controls.xaml @@ -35,9 +35,9 @@ + Value="{DynamicResource ItemContainer.BorderBrush}" /> + Value="{DynamicResource ItemContainer.SelectedBrush}" /> From 1c6260dfd09e1d6460fd238e85ff61f926ca3c20 Mon Sep 17 00:00:00 2001 From: Hendrik Polczynski Date: Fri, 5 Jul 2024 19:02:22 +0200 Subject: [PATCH 2/4] Revert "fix border theme switch" This reverts commit 0122335c37f8b6c53a78d5a3c0a1f7c01658e6a6. --- Nodify/Themes/Controls.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nodify/Themes/Controls.xaml b/Nodify/Themes/Controls.xaml index 851203cb..4bb46116 100644 --- a/Nodify/Themes/Controls.xaml +++ b/Nodify/Themes/Controls.xaml @@ -35,9 +35,9 @@ + Value="{StaticResource ItemContainer.BorderBrush}" /> + Value="{StaticResource ItemContainer.SelectedBrush}" /> From b9bc2349052cb89190f47921683445b694d01df9 Mon Sep 17 00:00:00 2001 From: Hendrik Polczynski Date: Fri, 5 Jul 2024 19:06:26 +0200 Subject: [PATCH 3/4] Update theme manager to correctly unload, mergeddictionaries also present in App.xaml, also allow for Preload of Themes from Application with different assembly --- Examples/Nodify.Shared/ThemeManager.cs | 125 ++++++++++++++++++------- 1 file changed, 93 insertions(+), 32 deletions(-) diff --git a/Examples/Nodify.Shared/ThemeManager.cs b/Examples/Nodify.Shared/ThemeManager.cs index fd1cbcc6..34ac5229 100644 --- a/Examples/Nodify.Shared/ThemeManager.cs +++ b/Examples/Nodify.Shared/ThemeManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Windows; using System.Windows.Input; @@ -8,12 +9,20 @@ namespace Nodify { public static class ThemeManager { - private static readonly string? _assemblyName = Assembly.GetEntryAssembly()?.GetName().Name; + private static string? _assemblyName = Assembly.GetEntryAssembly()?.GetName().Name; private static readonly Dictionary> _themesUris = new Dictionary>(); private static readonly Dictionary> _themesResources = new Dictionary>(); - public static string? ActiveTheme { get; private set; } + private const string DEFAULT_THEME = "Nodify"; // This needs to be the one listed in App.xaml as preview, for correct switching behaviour + private static readonly string[] BUILTIN_THEMES = new string[] + { + "Dark", + "Light", + DEFAULT_THEME, + }; + + public static string? ActiveTheme { get; private set; } = DEFAULT_THEME; private static readonly List _availableThemes = new List(); public static IReadOnlyCollection AvailableThemes => _availableThemes; @@ -22,13 +31,29 @@ public static class ThemeManager static ThemeManager() { - PreloadTheme("Dark"); - PreloadTheme("Light"); - PreloadTheme("Nodify"); + PreloadThemes(); SetNextThemeCommand = new DelegateCommand(SetNextTheme); } + public static bool PreloadThemes(string? assemblyName = null, string[]? themes = null) + { + bool success = true; + themes ??= BUILTIN_THEMES; + + if (assemblyName != null) + { + _assemblyName = assemblyName; + } + + foreach (var themeName in themes) + { + success &= PreloadTheme(themeName); + } + + return success; + } + private static List FindExistingResources(List uris) { var result = new List(); @@ -43,49 +68,74 @@ private static List FindExistingResources(List uris) return result; } - private static void PreloadTheme(string themeName) + private static bool PreloadTheme(string themeName) { + bool success = false; + + if (_themesUris.ContainsKey(themeName)) + { + // Cleanup if that one was already loaded, might be reloaded from external assembly + _themesUris.Remove(themeName); + _themesResources.Remove(themeName); + _availableThemes.Remove(themeName); + } + if (!_themesUris.TryGetValue(themeName, out var preload)) { - preload = new List(3) + preload = new List() + { + // These are in application merged resource list and need to be reloaded as default fallback + new Uri($"pack://application:,,,/Nodify;component/Themes/{DEFAULT_THEME}.xaml"), + new Uri($"pack://application:,,,/Nodify.Shared;component/Themes/Icons.xaml"), + new Uri($"pack://application:,,,/Nodify.Shared;component/Themes/{DEFAULT_THEME}.xaml"), + }; + + // Actual theme if part of Nodify library + if (themeName != DEFAULT_THEME) { - new Uri($"pack://application:,,,/Nodify;component/Themes/{themeName}.xaml"), - new Uri($"pack://application:,,,/Nodify.Shared;component/Themes/{themeName}.xaml") + preload.Add(new Uri($"pack://application:,,,/Nodify;component/Themes/{themeName}.xaml")); + preload.Add(new Uri($"pack://application:,,,/Nodify.Shared;component/Themes/{themeName}.xaml")); }; + // Actual theme if in application (external/other assembly) if (_assemblyName != null) { preload.Add(new Uri($"pack://application:,,,/{_assemblyName};component/Themes/{themeName}.xaml")); } - - _themesUris.Add(themeName, preload); } var resources = FindExistingResources(preload); - if (resources.Count == 0) + + for (int i = 0; i < preload.Count; i++) { - for (int i = 0; i < preload.Count; i++) + try { - try + resources.Add(new ResourceDictionary { - resources.Add(new ResourceDictionary - { - Source = preload[i] - }); - } - catch - { - - } + Source = preload[i] + }); + success = true; + } + catch + { + // Debug note: Exception is OK if main application does not contain that theme + // The correct assembly can be set later through a seperate external PreloadThemes call. } } - else if (ActiveTheme == null) + + if (success) { - ActiveTheme = themeName; + _themesUris.Add(themeName, preload); + _themesResources.Add(themeName, resources); + _availableThemes.Add(themeName); + + if (ActiveTheme == null) + { + ActiveTheme = themeName; + } } - _themesResources.Add(themeName, resources); - _availableThemes.Add(themeName); + return success; } public static void SetNextTheme() @@ -113,20 +163,31 @@ public static void SetTheme(string themeName) // Load new theme if it is valid if (_themesResources.TryGetValue(themeName, out var resources)) { - foreach (var res in resources) - { - Application.Current.Resources.MergedDictionaries.Add(res); - } - // Unload current theme if (ActiveTheme != null) { foreach (var res in _themesResources[ActiveTheme]) { Application.Current.Resources.MergedDictionaries.Remove(res); + + // Additionally search by Source: + // as Theme resources in samples placed explicitly in App.xaml for xaml preview purposes + // (however ThemeManager doesn't find those as already preloaded on startup, + // because MainWindow not initialized yet) + var duplicates = Application.Current.Resources.MergedDictionaries.Where(r => r.Source == res.Source).ToList(); + foreach (var resFallback in duplicates) + { + Application.Current.Resources.MergedDictionaries.Remove(resFallback); + } } } + // Load new theme + foreach (var res in resources) + { + Application.Current.Resources.MergedDictionaries.Add(res); + } + ActiveTheme = themeName; } } From d758192d60f87c54c3c2a16794ef82f452f462c3 Mon Sep 17 00:00:00 2001 From: Hendrik Polczynski Date: Fri, 5 Jul 2024 19:28:15 +0200 Subject: [PATCH 4/4] add basic help for custom themes --- docs/Getting-Started.md | 53 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 7d82ea52..e746fed0 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -536,4 +536,55 @@ Use the `ViewportTransform` dependency property to have the grid move with the v ``` -> Tip: Right-click and drag the screen around to move the view and use the mouse wheel to zoom in and out. \ No newline at end of file +> Tip: Right-click and drag the screen around to move the view and use the mouse wheel to zoom in and out. + +## Applying custom theme + +Firstly define the theme in your assembly, if you don't have custom `Brushes` or `Controls` in your assembly then ommit those from the `MergedDictionary`. + +Create the file in your project under `Themes/Custom.xaml`: + +```xml + + + + + + + + + + + White + + +``` + +Redefine all colors you dislike by copying and them to your theme from: + +- `Nodify/Themes/Nodify.xaml` +- `Nodify.Shared/Themes/Nodify.xaml` + +To load a custom theme inside your application (from the assembly where your theme is defined) call `ThemeManager.PreloadThemes`: + +```cs + // Preload now themes with correct assembly setup + ThemeManager.PreloadThemes(Assembly.GetExecutingAssembly()?.GetName().Name, + new string[] + { + "Dark", + "Light", + "Nodify", + "Custom", + } + ); // +``` + +This will also add your theme to the theme cycle of the manager. If you want to remove the default themes from the cycle, ommit them from the preload call. + +Finally apply your theme: + +```cs +ThemeManager.SetTheme("Custom"); +```