use std::collections::HashMap;
use palette::color_math::{adjust_lightness_hex, adjust_saturation_hex};
use web_sys::HtmlElement;
use crate::{error::AnimationError, state_machine::{RenderOutput, StateMachineBuilder}};
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BackgroundAnimationState {
Rotating {
angle: f64,
},
}
#[derive(Clone, Debug)]
pub struct BackgroundRenderContext {
pub center_x: f64,
pub center_y: f64,
pub color1: String,
pub color2: String,
}
#[derive(Clone, Debug)]
pub enum BackgroundEvent {
}
pub fn background_transition(
prev_state: BackgroundAnimationState,
timestamp: f64,
_events: Vec<BackgroundEvent>,
) -> Result<(BackgroundAnimationState, BackgroundRenderContext), AnimationError> {
match prev_state {
BackgroundAnimationState::Rotating { angle } => {
let period_ms = 60000.0;
let new_angle = (timestamp / period_ms) * 2.0 * std::f64::consts::PI;
let radius_percent = 20.0;
let center_x = 50.0 + radius_percent * new_angle.cos();
let center_y = 50.0 + radius_percent * new_angle.sin();
let (color1, color2) = get_theme_colors();
let breathing_progress = (timestamp / 4000.0) % 2.0;
let actual_progress = if breathing_progress > 1.0 {
2.0 - breathing_progress
} else {
breathing_progress
};
let sin_val = (actual_progress * std::f64::consts::PI).sin();
let saturation_factor = 1.0 + (sin_val * 0.05);
let lightness_factor = 1.0 + (sin_val * 0.05);
let breathing_color1 = adjust_saturation_hex(&color1, saturation_factor);
let breathing_color1 = adjust_lightness_hex(&breathing_color1, lightness_factor);
let breathing_color2 = adjust_saturation_hex(&color2, saturation_factor);
let breathing_color2 = adjust_lightness_hex(&breathing_color2, lightness_factor);
let ctx = BackgroundRenderContext {
center_x,
center_y,
color1: breathing_color1,
color2: breathing_color2,
};
Ok((BackgroundAnimationState::Rotating { angle: new_angle }, ctx))
}
}
}
pub fn background_render(
_state: BackgroundAnimationState,
ctx: BackgroundRenderContext,
elements: &HashMap<String, HtmlElement>,
) -> Result<RenderOutput, AnimationError> {
let mut output = RenderOutput::default();
if let Some(_bg_el) = elements.get("background") {
let mut styles = HashMap::new();
styles.insert("--bg-center-x".to_string(), format!("{:.1}%", ctx.center_x));
styles.insert("--bg-center-y".to_string(), format!("{:.1}%", ctx.center_y));
styles.insert("--bg-color-1".to_string(), ctx.color1);
styles.insert("--bg-color-2".to_string(), ctx.color2);
output.styles.insert("background".to_string(), styles);
} else {
return Err(AnimationError::ElementNotFound("background".to_string()));
}
Ok(output)
}
#[derive(Clone)]
struct ThemeCache {
last_theme: String,
colors: (String, String),
}
fn get_theme_colors() -> (String, String) {
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
{
use palette::Color;
use std::cell::RefCell;
thread_local! {
static THEME_CACHE: RefCell<Option<ThemeCache>> = RefCell::new(None);
}
let yuebai = Color::from_rgb_hex(0xd6, 0xec, 0xf0); let fenhong = Color::from_rgb_hex(0xff, 0xb3, 0xa7); let mose = Color::from_rgb_hex(0x50, 0x61, 0x6d); let dianlan = Color::from_rgb_hex(0x06, 0x52, 0x79);
let document = match web_sys::window().and_then(|w| w.document()) {
Some(doc) => doc,
None => return (yuebai.hex(), fenhong.hex()),
};
let current_theme = match document
.query_selector(".hi-theme-provider[data-theme]")
.ok()
.flatten()
.and_then(|el| el.get_attribute("data-theme"))
{
Some(t) => t,
None => "hikari".to_string(),
};
let colors = THEME_CACHE.with_borrow(|cache| {
if let Some(cached) = cache.as_ref() {
if cached.last_theme == current_theme {
return cached.colors.clone();
}
}
let new_colors = match current_theme.as_str() {
"tairitsu" => (mose.hex(), dianlan.hex()),
_ => (yuebai.hex(), fenhong.hex()),
};
new_colors
});
THEME_CACHE.with_borrow_mut(|cache| {
let needs_update = cache.as_ref()
.map_or(true, |c| c.last_theme != current_theme);
if needs_update {
*cache = Some(ThemeCache {
last_theme: current_theme,
colors: colors.clone(),
});
}
});
colors
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
{
use palette::Color;
let yuebai = Color::from_rgb_hex(0xd6, 0xec, 0xf0); let fenhong = Color::from_rgb_hex(0xff, 0xb3, 0xa7); (yuebai.hex(), fenhong.hex())
}
}
pub fn create_background_state_machine(
) -> StateMachineBuilder<BackgroundAnimationState, BackgroundEvent, BackgroundRenderContext> {
StateMachineBuilder::new()
.initial_state(BackgroundAnimationState::Rotating { angle: 0.0 })
.transition_fn(background_transition)
.render_fn(background_render)
}