public static string GenerateNewTheme(ApplicationMode mode, string themeName) { var themeDirName = Common.Paths.GetSafeFilename(themeName).Replace(" ", string.Empty); var defaultThemeDir = Path.Combine(Paths.GetThemesPath(mode), "Default"); var outDir = Path.Combine(PlaynitePaths.ThemesProgramPath, mode.GetDescription(), themeDirName); if (Directory.Exists(outDir)) { throw new Exception($"Theme directory \"{outDir}\" already exists."); } FileSystem.CreateDirectory(outDir); var defaultThemeXamlFiles = GenerateCommonThemeFiles(mode, outDir); CopyThemeDirectory(defaultThemeDir, outDir, defaultThemeXamlFiles.Select(a => Path.Combine(defaultThemeDir, a)).ToList()); var themeDesc = new ThemeDescription() { Author = "Your Name Here", Name = themeName, Version = "1.0", Mode = mode, ThemeApiVersion = ThemeManager.GetApiVersion(mode).ToString() }; File.WriteAllText(Path.Combine(outDir, PlaynitePaths.ThemeManifestFileName), Serialization.ToYaml(themeDesc)); Explorer.NavigateToFileSystemEntry(Path.Combine(outDir, Themes.ThemeSlnName)); return(outDir); }
public static bool ApplyTheme(Application app, ThemeDescription theme, ApplicationMode mode) { if ((new System.Version(theme.ThemeApiVersion).Major != ThemeApiVersion.Major)) { logger.Error($"Failed to apply {theme.Name} theme, unsupported API version {theme.ThemeApiVersion}."); return(false); } var allLoaded = true; var loadedXamls = new List <ResourceDictionary>(); var acceptableXamls = new List <string>(); var defaultRoot = $"Themes/{mode.GetDescription()}/{DefaultTheme.DirectoryName}/"; foreach (var dict in app.Resources.MergedDictionaries) { if (dict.Source.OriginalString.StartsWith("Themes") && dict.Source.OriginalString.EndsWith("xaml")) { acceptableXamls.Add(dict.Source.OriginalString.Replace(defaultRoot, "").Replace('/', '\\')); } } foreach (var accXaml in acceptableXamls) { var xamlPath = Path.Combine(theme.DirectoryPath, accXaml); if (!File.Exists(xamlPath)) { continue; } try { var xaml = Xaml.FromFile(xamlPath); if (xaml is ResourceDictionary xamlDir) { xamlDir.Source = new Uri(xamlPath, UriKind.Absolute); loadedXamls.Add(xamlDir as ResourceDictionary); } else { logger.Error($"Skipping theme file {xamlPath}, it's not resource dictionary."); } } catch (Exception e) when(!PlayniteEnvironment.ThrowAllErrors) { logger.Error(e, $"Failed to load xaml {xamlPath}"); allLoaded = false; break; } } if (allLoaded) { loadedXamls.ForEach(a => app.Resources.MergedDictionaries.Add(a)); return(true); } return(false); }
public static ThemeManifest GetDesignTimeDefaultTheme(ApplicationMode mode) { if (lastUserThemeFound == null) { if (Process.GetCurrentProcess().TryGetParentId(out var parentId)) { var proc = Process.GetProcessById(parentId); var cmdline = proc.GetCommandLine(); var regEx = Regex.Match(cmdline, @"([^""]+\.sln?)"""); if (regEx.Success) { var spath = regEx.Groups[1].Value; if (spath.Contains($"Themes\\{mode.GetDescription()}")) { lastUserTheme = new FileInfo(spath); lastUserThemeFound = true; } } } } if (lastUserThemeFound == true) { return(new ThemeManifest() { DirectoryName = lastUserTheme.DirectoryName, DirectoryPath = lastUserTheme.Directory.FullName, Name = "Default" }); } lastUserThemeFound = false; var defaultTheme = "Default"; var projectName = mode == ApplicationMode.Fullscreen ? "Playnite.FullscreenApp" : "Playnite.DesktopApp"; var slnPath = Path.Combine(Environment.GetEnvironmentVariable("PLAYNITE_SLN", EnvironmentVariableTarget.User), projectName); var themePath = Path.Combine(slnPath, "Themes", ThemeManager.GetThemeRootDir(mode), defaultTheme); return(new ThemeManifest() { DirectoryName = defaultTheme, DirectoryPath = themePath, Name = defaultTheme }); }
public static List <string> GenerateCommonThemeFiles(ApplicationMode mode, string outDir) { var defaultThemeXamlFiles = new List <string>(); // Modify paths in App.xaml var appXaml = XDocument.Load(Paths.GetThemeTemplateFilePath(mode, Themes.AppXamlName)); foreach (var resDir in appXaml.Descendants().Where(a => a.Name.LocalName == "ResourceDictionary" && a.Attribute("Source")?.Value.StartsWith("Themes") == true)) { var val = resDir.Attribute("Source").Value.Replace($"Themes/{mode.GetDescription()}/Default/", ""); resDir.Attribute("Source").Value = val; defaultThemeXamlFiles.Add(val.Replace('/', '\\')); } // Change localization file reference var langElem = appXaml.Descendants().First(a => a.Attribute("Source")?.Value.EndsWith(PlaynitePaths.EngLocSourceFileName) == true); langElem.Attribute("Source").Value = PlaynitePaths.EngLocSourceFileName; // Update theme project file XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003"; var csproj = XDocument.Load(Paths.GetThemeTemplateFilePath(mode, Themes.ThemeProjName)); var groupRoot = new XElement(ns + "ItemGroup"); csproj.Root.Add(groupRoot); foreach (var resDir in appXaml.Descendants().Where(a => a.Name.LocalName == "ResourceDictionary" && a.Attribute("Source") != null)) { groupRoot.Add(new XElement(ns + "Content", new XAttribute("Include", resDir.Attribute("Source").Value.Replace('/', '\\')), new XElement(ns + "Generator", "MSBuild:Compile"), new XElement(ns + "SubType", "Designer"))); } appXaml.Save(Path.Combine(outDir, Themes.AppXamlName)); csproj.Save(Path.Combine(outDir, Themes.ThemeProjName)); FileSystem.CopyFile(Paths.GetThemeTemplatePath(PlaynitePaths.EngLocSourceFileName), Path.Combine(outDir, PlaynitePaths.EngLocSourceFileName)); FileSystem.CopyFile(Paths.GetThemeTemplateFilePath(mode, Themes.GlobalResourcesName), Path.Combine(outDir, Themes.GlobalResourcesName)); FileSystem.CopyFile(Paths.GetThemeTemplateFilePath(mode, Themes.ThemeSlnName), Path.Combine(outDir, Themes.ThemeSlnName)); var commonFontsDirs = Paths.GetThemeTemplatePath("Fonts"); if (Directory.Exists(commonFontsDirs)) { foreach (var fontFile in Directory.GetFiles(commonFontsDirs)) { var targetPath = Path.Combine(outDir, "Fonts", Path.GetFileName(fontFile)); FileSystem.CopyFile(fontFile, targetPath); } } var modeFontDir = Paths.GetThemeTemplateFilePath(mode, "Fonts"); if (Directory.Exists(modeFontDir)) { foreach (var fontFile in Directory.GetFiles(modeFontDir)) { var targetPath = Path.Combine(outDir, "Fonts", Path.GetFileName(fontFile)); FileSystem.CopyFile(fontFile, targetPath); } } return(defaultThemeXamlFiles); }
public static AddonLoadError ApplyTheme(Application app, ThemeManifest theme, ApplicationMode mode) { if (theme.Id.IsNullOrEmpty()) { logger.Error($"Theme {theme.Name}, doesn't have ID."); return(AddonLoadError.Uknown); } var apiVesion = mode == ApplicationMode.Desktop ? DesktopApiVersion : FullscreenApiVersion; if (!theme.ThemeApiVersion.IsNullOrEmpty()) { var themeVersion = new Version(theme.ThemeApiVersion); if (themeVersion.Major != apiVesion.Major || themeVersion > apiVesion) { logger.Error($"Failed to apply {theme.Name} theme, unsupported API version {theme.ThemeApiVersion}."); return(AddonLoadError.SDKVersion); } } var acceptableXamls = new List <string>(); var defaultRoot = $"Themes/{mode.GetDescription()}/{DefaultTheme.DirectoryName}/"; foreach (var dict in app.Resources.MergedDictionaries) { if (dict.Source.OriginalString.StartsWith(defaultRoot)) { acceptableXamls.Add(dict.Source.OriginalString.Replace(defaultRoot, "").Replace('/', '\\')); } } var allLoaded = true; foreach (var accXaml in acceptableXamls) { var xamlPath = Path.Combine(theme.DirectoryPath, accXaml); if (!File.Exists(xamlPath)) { continue; } try { var xaml = Xaml.FromFile(xamlPath); } catch (Exception e) when(!PlayniteEnvironment.ThrowAllErrors) { logger.Error(e, $"Failed to load xaml {xamlPath}"); allLoaded = false; break; } } if (!allLoaded) { return(AddonLoadError.Uknown); } try { var cursorFile = ThemeFile.GetFilePath("cursor.cur"); if (cursorFile.IsNullOrEmpty()) { cursorFile = ThemeFile.GetFilePath("cursor.ani"); } if (!cursorFile.IsNullOrEmpty()) { Mouse.OverrideCursor = new Cursor(cursorFile, true); } } catch (Exception e) when(!PlayniteEnvironment.ThrowAllErrors) { logger.Error(e, "Failed to set custom mouse cursor."); } var themeRoot = $"Themes\\{mode.GetDescription()}\\{theme.DirectoryName}\\"; // This is sad that we have to do this, but it fixes issues like #2328 // We need to remove all loaded theme resources and reload them in specific order: // default/1.xaml -> theme/1.xaml -> default/2.xaml -> theme/2.xaml etc. // // We can't just load custom theme files at the end or insert them in already loaded pool of resources // because styling with static references won't reload data from custom theme files. // That's why we also have to create new instances of default styles. foreach (var defaultRes in app.Resources.MergedDictionaries.ToList()) { if (defaultRes.Source.OriginalString.StartsWith(defaultRoot)) { app.Resources.MergedDictionaries.Remove(defaultRes); } } foreach (var themeXamlFile in acceptableXamls) { var defaultPath = Path.Combine(PlaynitePaths.ThemesProgramPath, mode.GetDescription(), "Default", themeXamlFile); var defaultXaml = Xaml.FromFile(defaultPath); if (defaultXaml is ResourceDictionary xamlDir) { xamlDir.Source = new Uri(defaultPath, UriKind.Absolute); app.Resources.MergedDictionaries.Add(xamlDir); } var xamlPath = Path.Combine(theme.DirectoryPath, themeXamlFile); if (!File.Exists(xamlPath)) { continue; } var xaml = Xaml.FromFile(xamlPath); if (xaml is ResourceDictionary themeDir) { themeDir.Source = new Uri(xamlPath, UriKind.Absolute); app.Resources.MergedDictionaries.Add(themeDir); } else { logger.Error($"Skipping theme file {xamlPath}, it's not resource dictionary."); } } return(AddonLoadError.None); }
public static string GenerateNewTheme(ApplicationMode mode, string themeName) { var themeDirName = Common.Paths.GetSafeFilename(themeName).Replace(" ", string.Empty); var defaultThemeDir = Path.Combine(Paths.GetThemesPath(mode), "Default"); var outDir = Path.Combine(PlaynitePaths.ThemesProgramPath, mode.GetDescription(), themeDirName); if (Directory.Exists(outDir)) { throw new Exception($"Theme directory \"{outDir}\" already exists."); } FileSystem.CreateDirectory(outDir); var defaultThemeXamlFiles = new List <string>(); // Modify paths in App.xaml var appXaml = XDocument.Load(Paths.GetThemeTemplateFilePath(mode, Themes.AppXamlName)); foreach (var resDir in appXaml.Descendants().Where(a => a.Name.LocalName == "ResourceDictionary" && a.Attribute("Source")?.Value.StartsWith("Themes") == true)) { var val = resDir.Attribute("Source").Value.Replace($"Themes/{mode.GetDescription()}/Default/", ""); resDir.Attribute("Source").Value = val; defaultThemeXamlFiles.Add(val.Replace('/', '\\')); } // Change localization file reference var langElem = appXaml.Descendants().First(a => a.Attribute("Source")?.Value.EndsWith(Themes.LocSourceName) == true); langElem.Attribute("Source").Value = Themes.LocSourceName; // Update theme project file XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003"; var csproj = XDocument.Load(Paths.GetThemeTemplateFilePath(mode, Themes.ThemeProjName)); var groupRoot = new XElement(ns + "ItemGroup"); csproj.Root.Add(groupRoot); foreach (var resDir in appXaml.Descendants().Where(a => a.Name.LocalName == "ResourceDictionary" && a.Attribute("Source") != null)) { groupRoot.Add(new XElement(ns + "Content", new XAttribute("Include", resDir.Attribute("Source").Value.Replace('/', '\\')), new XElement(ns + "Generator", "MSBuild:Compile"), new XElement(ns + "SubType", "Designer"))); } // Copy to output CopyThemeDirectory(defaultThemeDir, outDir, defaultThemeXamlFiles.Select(a => Path.Combine(defaultThemeDir, a)).ToList()); appXaml.Save(Path.Combine(outDir, Themes.AppXamlName)); csproj.Save(Path.Combine(outDir, Themes.ThemeProjName)); FileSystem.CopyFile(Paths.GetThemeTemplatePath(Themes.LocSourceName), Path.Combine(outDir, Themes.LocSourceName)); FileSystem.CopyFile(Paths.GetThemeTemplateFilePath(mode, Themes.GlobalResourcesName), Path.Combine(outDir, Themes.GlobalResourcesName)); FileSystem.CopyFile(Paths.GetThemeTemplateFilePath(mode, Themes.ThemeSlnName), Path.Combine(outDir, Themes.ThemeSlnName)); var commonFontsDirs = Paths.GetThemeTemplatePath("Fonts"); if (Directory.Exists(commonFontsDirs)) { foreach (var fontFile in Directory.GetFiles(commonFontsDirs)) { var targetPath = Path.Combine(outDir, "Fonts", Path.GetFileName(fontFile)); FileSystem.CopyFile(fontFile, targetPath); } } var modeFontDir = Paths.GetThemeTemplateFilePath(mode, "Fonts"); if (Directory.Exists(modeFontDir)) { foreach (var fontFile in Directory.GetFiles(modeFontDir)) { var targetPath = Path.Combine(outDir, "Fonts", Path.GetFileName(fontFile)); FileSystem.CopyFile(fontFile, targetPath); } } var themeDesc = new ThemeDescription() { Author = "Your Name Here", Name = themeName, Version = "1.0", Mode = mode }; File.WriteAllText(Path.Combine(outDir, ThemeManager.ThemeManifestFileName), Serialization.ToYaml(themeDesc)); Explorer.NavigateToFileSystemEntry(Path.Combine(outDir, Themes.ThemeSlnName)); return(outDir); }