新主题

This commit is contained in:
2026-08-06 00:41:10 -07:00
parent 7eb9d00a50
commit ac80cd1db4
35 changed files with 1693 additions and 24 deletions
+28
View File
@@ -12,6 +12,8 @@ use serde_json::Value;
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::typography::{FontFamilyId, default_font_brightness, sanitize_font_brightness};
/// Stable identifier for a renderer theme.
///
/// This enum is deliberately shared by defaults, validation and serialization:
@@ -23,6 +25,7 @@ use uuid::Uuid;
pub enum OverlayThemeId {
#[default]
JadeScroll,
MoonlitWater,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -31,6 +34,10 @@ pub struct OverlaySettings {
/// Visual theme selected for this tenant-owned component instance.
#[serde(default)]
pub theme_id: OverlayThemeId,
#[serde(default)]
pub font_family: FontFamilyId,
#[serde(default = "default_font_brightness")]
pub font_brightness: u16,
#[serde(default = "default_font_scale")]
pub font_scale: u16,
pub show_danmaku: bool,
@@ -59,6 +66,8 @@ impl Default for OverlaySettings {
fn default() -> Self {
Self {
theme_id: OverlayThemeId::default(),
font_family: FontFamilyId::default(),
font_brightness: default_font_brightness(),
font_scale: default_font_scale(),
show_danmaku: true,
show_enter: true,
@@ -83,6 +92,7 @@ impl Default for OverlaySettings {
impl OverlaySettings {
pub fn sanitize(mut self) -> Self {
self.max_visible = self.max_visible.clamp(1, 12);
self.font_brightness = sanitize_font_brightness(self.font_brightness);
self.font_scale = self.font_scale.clamp(50, 300);
self.collapse_after_seconds = self.collapse_after_seconds.clamp(2, 120);
self.unfold_duration_ms = self.unfold_duration_ms.clamp(200, 5_000);
@@ -546,6 +556,7 @@ mod tests {
fn settings_are_bounded_before_persistence() {
let settings = OverlaySettings {
font_scale: 999,
font_brightness: 1,
max_visible: 99,
collapse_after_seconds: 1,
unfold_duration_ms: 9_000,
@@ -558,6 +569,7 @@ mod tests {
}
.sanitize();
assert_eq!(settings.font_scale, 300);
assert_eq!(settings.font_brightness, 70);
assert_eq!(settings.max_visible, 12);
assert_eq!(settings.collapse_after_seconds, 2);
assert_eq!(settings.unfold_duration_ms, 5_000);
@@ -580,12 +592,16 @@ mod tests {
let object = value.as_object_mut().expect("settings object");
object.insert("title".into(), json!("旧版弹幕栏标题"));
object.remove("fontScale");
object.remove("fontFamily");
object.remove("fontBrightness");
object.remove("unfoldDurationMs");
object.remove("particleCount");
object.remove("particleSpeed");
object.remove("themeId");
let settings: OverlaySettings = serde_json::from_value(value).expect("legacy settings");
assert_eq!(settings.theme_id, OverlayThemeId::JadeScroll);
assert_eq!(settings.font_family, FontFamilyId::FangSong);
assert_eq!(settings.font_brightness, 130);
assert_eq!(settings.font_scale, 140);
assert_eq!(settings.unfold_duration_ms, 1_000);
assert_eq!(settings.particle_count, 8);
@@ -599,4 +615,16 @@ mod tests {
let error = serde_json::from_value::<OverlaySettings>(value).expect_err("unknown theme");
assert!(error.to_string().contains("unknown variant"));
}
#[test]
fn moonlit_water_theme_is_a_valid_persisted_identifier() {
let mut value = json!(OverlaySettings::default());
value["themeId"] = json!("moonlit-water");
let settings: OverlaySettings = serde_json::from_value(value).expect("moonlit theme");
assert_eq!(settings.theme_id, OverlayThemeId::MoonlitWater);
assert_eq!(
serde_json::to_value(settings).unwrap()["themeId"],
"moonlit-water"
);
}
}