Setup support for live themes (#1162)

Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
This commit is contained in:
2025-12-17 17:24:03 +01:00
parent 7b2320e158
commit 69096795f7
25 changed files with 130 additions and 50 deletions
@@ -1,4 +1,4 @@
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.Themes;
using Daybreak.Shared.Services.Themes;
using Newtonsoft.Json;
+1 -1
View File
@@ -1,5 +1,5 @@
using Daybreak.Shared.Models.ColorPalette;
using static Daybreak.Shared.Models.Theme;
using static Daybreak.Shared.Models.Themes.Theme;
namespace Daybreak.Shared.Models;
@@ -0,0 +1,7 @@
namespace Daybreak.Shared.Models.Themes;
public sealed class EmbedBackground(string embedCode)
: IAppBackground
{
public string EmbedCode { get; } = embedCode;
}
@@ -0,0 +1,9 @@
namespace Daybreak.Shared.Models.Themes;
/// <summary>
/// Tells the app how to display the background.
/// Implemented in <see cref="StaticBackground"/> and <see cref="EmbedBackground"/>
/// </summary>
public interface IAppBackground
{
}
@@ -0,0 +1,7 @@
namespace Daybreak.Shared.Models.Themes;
public sealed class StaticBackground(string backdropImage)
: IAppBackground
{
public string BackdropImage { get; } = backdropImage;
}
@@ -2,9 +2,14 @@
using Daybreak.Shared.Models.ColorPalette;
using Newtonsoft.Json;
namespace Daybreak.Shared.Models;
namespace Daybreak.Shared.Models.Themes;
[JsonConverter(typeof(ThemeJsonConverter))]
public class Theme(string name, AccentColor accentColor, string backdrop, Theme.LightDarkMode mode, string filter)
public class Theme(
string name,
AccentColor accentColor,
IAppBackground background,
Theme.LightDarkMode mode,
string filter)
{
public enum LightDarkMode
{
@@ -15,7 +20,7 @@ public class Theme(string name, AccentColor accentColor, string backdrop, Theme.
public string Name { get; init; } = name;
public AccentColor AccentColor { get; internal set; } = accentColor;
public string Backdrop { get; internal set; } = backdrop;
public IAppBackground Background { get; internal set; } = background;
public LightDarkMode Mode { get; internal set; } = mode;
public string Filter { get; internal set; } = filter;
@@ -1,5 +1,5 @@
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Models.Themes;
using Daybreak.Shared.Services.Screenshots;
using System.Windows.Extensions.Services;
@@ -12,7 +12,7 @@ namespace Daybreak.Shared.Services.Themes;
public sealed class GameScreenshotsTheme(
IThemeManager themeManager,
IScreenshotService screenshotService)
: Theme(ThemeName, AccentColor.Orange, string.Empty, LightDarkMode.SystemSynchronized, string.Empty), IApplicationLifetimeService
: Theme(ThemeName, AccentColor.Orange, new StaticBackground(string.Empty), LightDarkMode.SystemSynchronized, string.Empty), IApplicationLifetimeService
{
public const string ThemeName = "Dynamic Screenshots";
@@ -37,7 +37,7 @@ public sealed class GameScreenshotsTheme(
}
this.AccentColor = entry.AccentColor;
this.Backdrop = entry.FilePath;
this.Background = new StaticBackground(entry.FilePath);
this.Mode = entry.LightDarkMode;
this.Filter = entry.LightDarkMode is LightDarkMode.Light
? "blur(3px) brightness(1.3) sepia(0.2) saturate(1.2)"
@@ -1,5 +1,5 @@
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Models.Themes;
namespace Daybreak.Shared.Services.Themes;
public interface IThemeManager
@@ -12,7 +12,8 @@ public interface IThemeManager
string AccentBaseColorHex { get; }
string NeutralBaseColorHex { get; }
float BaseLayerLuminance { get; }
string BackdropImage { get; }
string? BackdropImage { get; }
string? BackdropEmbed { get; }
double UIScale { get; }
double XXSmallFontSize { get; }
double XSmallFontSize { get; }
@@ -1,4 +1,4 @@
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.Themes;
namespace Daybreak.Shared.Services.Themes;
public interface IThemeProducer
@@ -1,5 +1,5 @@
using Daybreak.Shared.Attributes;
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.Themes;
namespace Daybreak.Attributes;
public sealed class DaybreakThemeOptionsAttribute : OptionValuesFactoryAttribute
@@ -1,6 +1,6 @@
using Daybreak.Attributes;
using Daybreak.Shared.Attributes;
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.Themes;
using Daybreak.Themes;
namespace Daybreak.Configuration.Options;
@@ -447,12 +447,10 @@ public class ProjectConfiguration : PluginConfigurationBase
public override void RegisterThemes(IThemeProducer themeProducer)
{
themeProducer.ThrowIfNull();
foreach (var theme in CoreThemes.Themes)
{
themeProducer.RegisterTheme(theme);
}
foreach (var theme in HeroThemes.Themes)
var themes = CoreThemes.Themes
.Concat(HeroThemes.Themes)
.Concat(LiveThemes.Themes);
foreach (var theme in themes)
{
themeProducer.RegisterTheme(theme);
}
+1
View File
@@ -1,6 +1,7 @@
using Daybreak.Configuration.Options;
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Models.Themes;
using Daybreak.Themes;
using Microsoft.Extensions.Options;
using System.ComponentModel;
@@ -4,7 +4,7 @@ using System.IO;
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Services.Screenshots;
using static Daybreak.Shared.Models.Theme;
using static Daybreak.Shared.Models.Themes.Theme;
namespace Daybreak.Services.Screenshots;
@@ -1,6 +1,6 @@
using Daybreak.Configuration.Options;
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Models.Themes;
using Daybreak.Shared.Services.Options;
using Daybreak.Shared.Services.Themes;
using Daybreak.Themes;
@@ -39,7 +39,8 @@ public class BlazorThemeInteropService(
public BackgroundColor NeutralBaseColor { get; private set; } = BackgroundColor.Gray40;
public string AccentBaseColorHex { get; private set; } = string.Empty;
public string NeutralBaseColorHex { get; private set; } = string.Empty;
public string BackdropImage { get; private set; } = string.Empty;
public string? BackdropImage { get; private set; }
public string? BackdropEmbed { get; private set; }
public float BaseLayerLuminance { get; private set; } = 0.0f;
public double UIScale { get; private set; } = 1.0;
public double XXSmallFontSize { get; private set; } = XXSmallFontSizeValue;
@@ -83,7 +84,8 @@ public class BlazorThemeInteropService(
var backgroundColor = lightMode ? BackgroundColor.Gray40 : BackgroundColor.Gray210;
var baseLayerLuminance = lightMode ? 0.98f : 0.23f;
this.CurrentTheme = theme;
this.BackdropImage = theme.Backdrop;
this.BackdropImage = theme.Background is StaticBackground staticBackground ? staticBackground.BackdropImage : default;
this.BackdropEmbed = theme.Background is EmbedBackground embeddedBackground ? embeddedBackground.EmbedCode : default;
this.IsLightMode = lightMode;
this.AccentBaseColor = accentColor;
this.AccentBaseColorHex = accentColor.Hex;
+9 -10
View File
@@ -1,22 +1,21 @@
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.ColorPalette;
using static Daybreak.Shared.Models.Theme;
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Models.Themes;
using static Daybreak.Shared.Models.Themes.Theme;
namespace Daybreak.Themes;
public static class CoreThemes
{
public static readonly Theme Daybreak = new("Daybreak", AccentColor.Yellow, "img/backdrops/daybreak.png", LightDarkMode.Light, string.Empty);
public static readonly Theme Nightfall = new("Nightfall", AccentColor.DarkGreen, "img/backdrops/nightfall.png", LightDarkMode.Dark, string.Empty);
public static readonly Theme WhiteMantle = new("White Mantle", AccentColor.Red, "img/backdrops/whitemantle.png", LightDarkMode.Dark, string.Empty);
public static readonly Theme ShiningBlade = new("Shining Blade", AccentColor.DarkGreen, "img/backdrops/shiningblade.png", LightDarkMode.Dark, string.Empty);
public static readonly Theme Jade = new("Jade", AccentColor.LightTeal, "img/backdrops/jade.png", LightDarkMode.Light, string.Empty);
public static readonly Theme HarbingerOfTheDeceiver = new("Harbinger of the Deceiver", AccentColor.DarkPurple, "img/backdrops/harbinger.png", LightDarkMode.Dark, string.Empty);
public static readonly Theme Daybreak = new("Daybreak", AccentColor.Yellow, new StaticBackground("img/backdrops/daybreak.png"), LightDarkMode.Light, string.Empty);
public static readonly Theme Nightfall = new("Nightfall", AccentColor.DarkGreen, new StaticBackground("img/backdrops/nightfall.png"), LightDarkMode.Dark, string.Empty);
public static readonly Theme WhiteMantle = new("White Mantle", AccentColor.Red, new StaticBackground("img/backdrops/whitemantle.png"), LightDarkMode.Dark, string.Empty);
public static readonly Theme ShiningBlade = new("Shining Blade", AccentColor.DarkGreen, new StaticBackground("img/backdrops/shiningblade.png"), LightDarkMode.Dark, string.Empty);
public static readonly Theme Jade = new("Jade", AccentColor.LightTeal, new StaticBackground("img/backdrops/jade.png"), LightDarkMode.Light, string.Empty);
public static readonly IReadOnlyList<Theme> Themes = [
Daybreak,
Nightfall,
WhiteMantle,
ShiningBlade,
Jade
Jade,
];
}
+6
View File
@@ -0,0 +1,6 @@
namespace Daybreak.Themes;
public static class EmbeddedBackgrounds
{
public const string EyeOfTheNorth = @"<video style=""object-fit: cover; width: 100%; height: 100%;"" autoplay muted loop playsinline><source src=""video/login_eotn.mp4"" type=""video/mp4"" /></video>";
}
+8 -8
View File
@@ -1,15 +1,15 @@
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.ColorPalette;
using static Daybreak.Shared.Models.Theme;
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Models.Themes;
using static Daybreak.Shared.Models.Themes.Theme;
namespace Daybreak.Themes;
public static class HeroThemes
{
public static readonly Theme Abaddon = new("Abaddon", AccentColor.OrangeLight, "img/backdrops/abaddon.png", LightDarkMode.Dark, string.Empty);
public static readonly Theme Gwen = new("Gwen", AccentColor.Purple, "img/backdrops/gwen2.png", LightDarkMode.Dark, string.Empty);
public static readonly Theme ZeiRi = new("Zei Ri", AccentColor.DarkTeal, "img/backdrops/zei-ri.png", LightDarkMode.Light, string.Empty);
public static readonly Theme Livia = new("Livia", AccentColor.DarkRed, "img/backdrops/livia.png", LightDarkMode.Light, string.Empty);
public static readonly Theme Razah = new("Razah", AccentColor.LightBlue, "img/backdrops/razah.png", LightDarkMode.Light, string.Empty);
public static readonly Theme Abaddon = new("Abaddon", AccentColor.OrangeLight, new StaticBackground("img/backdrops/abaddon.png"), LightDarkMode.Dark, string.Empty);
public static readonly Theme Gwen = new("Gwen", AccentColor.Purple, new StaticBackground("img/backdrops/gwen.png"), LightDarkMode.Dark, string.Empty);
public static readonly Theme ZeiRi = new("Zei Ri", AccentColor.DarkTeal, new StaticBackground("img/backdrops/zei-ri.png"), LightDarkMode.Light, string.Empty);
public static readonly Theme Livia = new("Livia", AccentColor.DarkRed, new StaticBackground("img/backdrops/livia.png"), LightDarkMode.Light, string.Empty);
public static readonly Theme Razah = new("Razah", AccentColor.LightBlue, new StaticBackground("img/backdrops/razah.png"), LightDarkMode.Light, string.Empty);
public static readonly IReadOnlyList<Theme> Themes = [
Abaddon,
+14
View File
@@ -0,0 +1,14 @@
using Daybreak.Shared.Models.ColorPalette;
using Daybreak.Shared.Models.Themes;
using static Daybreak.Shared.Models.Themes.Theme;
namespace Daybreak.Themes;
public static class LiveThemes
{
public static readonly Theme EotN = new("Eye of the North", AccentColor.LightBlue, new EmbedBackground(EmbeddedBackgrounds.EyeOfTheNorth), LightDarkMode.Light, string.Empty);
public static readonly IReadOnlyList<Theme> Themes = [
EotN,
];
}
+7 -2
View File
@@ -5,8 +5,7 @@
BaseLayerLuminance="@this.ViewModel.BaseLayerLuminace">
<ErrorLoggingBoundary>
<div class="app-container @(this.ViewModel.WindowState == WindowState.Maximized ? "maximized" : "")"
style="--backdrop-image: @this.ViewModel.BackdropImage;
--backdrop-image-filter: @this.ViewModel.BackdropImageFilter;
style="@(string.IsNullOrWhiteSpace(this.ViewModel.BackdropImage) is false ? $"--backdrop-image: {this.ViewModel.BackdropImage}; --backdrop-image-filter: {this.ViewModel.BackdropImageFilter};" : string.Empty)
--ui-scale: @(this.ViewModel.UIScale);
--font-size-xx-small: @(this.ViewModel.XXSmallFontSize)rem;
--font-size-x-small: @(this.ViewModel.XSmallFontSize)rem;
@@ -16,6 +15,12 @@
--font-size-x-large: @(this.ViewModel.XLargeFontSize)rem;
--font-size-xx-large: @(this.ViewModel.XXLargeFontSize)rem;
@(string.Join(";\n", AccentColor.Accents.Select(c => $"--accent-{c.Name.ToString().ToLower()}: {c.Hex}")))">
@if (!string.IsNullOrWhiteSpace(this.ViewModel.BackdropEmbed))
{
<div id="video-background">
@((MarkupString)this.ViewModel.BackdropEmbed)
</div>
}
<WindowBorders OnResize="@this.ViewModel.StartResize" />
<WindowTitlebar WindowState="@this.ViewModel.WindowState"
+6 -4
View File
@@ -59,8 +59,9 @@ public sealed class AppViewModel
public string AccentBaseColor { get; set; } = string.Empty;
public string NeutralBaseColor { get; set; } = string.Empty;
public float BaseLayerLuminace { get; set; } = 0.0f;
public string BackdropImage { get; set; } = string.Empty;
public string BackdropImageFilter { get; set; } = string.Empty;
public string? BackdropImage { get; set; } = string.Empty;
public string? BackdropImageFilter { get; set; } = string.Empty;
public string? BackdropEmbed { get; set; } = string.Empty;
public double UIScale { get; set; }
public double XXSmallFontSize { get; set; }
@@ -280,7 +281,9 @@ public sealed class AppViewModel
private void OnThemeChange()
{
this.BackdropImage = this.GetBackdropImageUrl(this.themeManager.BackdropImage);
this.BackdropImage = !string.IsNullOrWhiteSpace(this.themeManager.BackdropImage) ? this.GetBackdropImageUrl(this.themeManager.BackdropImage) : default;
this.BackdropEmbed = !string.IsNullOrWhiteSpace(this.themeManager.BackdropEmbed) ? this.themeManager.BackdropEmbed : default;
this.BackdropImageFilter = this.themeManager.CurrentTheme?.Filter ?? string.Empty;
this.BaseLayerLuminace = this.themeManager.BaseLayerLuminance;
this.AccentBaseColor = this.themeManager.AccentBaseColorHex;
this.NeutralBaseColor = this.themeManager.NeutralBaseColorHex;
@@ -292,7 +295,6 @@ public sealed class AppViewModel
this.XLargeFontSize = this.themeManager.XLargeFontSize;
this.XXLargeFontSize = this.themeManager.XXLargeFontSize;
this.UIScale = this.themeManager.UIScale;
this.BackdropImageFilter = this.themeManager.CurrentTheme?.Filter ?? string.Empty;
this.RedrawRequested?.Invoke(this, EventArgs.Empty);
}
+25 -1
View File
@@ -25,4 +25,28 @@
overflow: hidden;
max-height: 100%;
width: 100%;
}
}
/* Video Background */
#video-background {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: -2;
overflow: hidden;
pointer-events: none;
}
#video-background video {
position: absolute;
top: 50%;
left: 50%;
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
transform: translate(-50%, -50%);
object-fit: cover;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.