示例#1
0
        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);
        }
示例#2
0
        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
            });
        }
示例#4
0
        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);
        }
示例#5
0
        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);
        }
示例#6
0
        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);
        }