use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
static REGISTRY: Mutex<BTreeMap<&'static str, &'static str>> = Mutex::new(BTreeMap::new());
pub struct Keyframes {
name: &'static str,
body: &'static str,
injected: AtomicBool,
}
impl Keyframes {
pub const fn new(name: &'static str, body: &'static str) -> Self {
Self {
name,
body,
injected: AtomicBool::new(false),
}
}
pub fn name(&self) -> &'static str {
self.ensure();
self.name
}
pub const fn name_unregistered(&self) -> &'static str {
self.name
}
pub const fn body(&self) -> &'static str {
self.body
}
pub fn ensure(&self) {
if self.injected.load(Ordering::Relaxed) {
return;
}
register(self.name, self.body);
self.injected.store(true, Ordering::Relaxed);
}
pub fn css(&self) -> String {
format!("@keyframes {} {{ {} }}", self.name, self.body)
}
}
impl std::fmt::Display for Keyframes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
pub struct AnimationDecl {
keyframes: &'static Keyframes,
css: &'static str,
}
impl AnimationDecl {
pub const fn new(keyframes: &'static Keyframes, css: &'static str) -> Self {
Self { keyframes, css }
}
}
impl std::ops::Deref for AnimationDecl {
type Target = str;
fn deref(&self) -> &str {
self.keyframes.ensure();
self.css
}
}
impl std::fmt::Display for AnimationDecl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self)
}
}
pub fn register(name: &'static str, body: &'static str) {
let mut registry = match REGISTRY.lock() {
Ok(registry) => registry,
Err(poisoned) => poisoned.into_inner(),
};
if let Some(existing) = registry.get(name) {
debug_assert!(
*existing == body,
"@keyframes `{name}` was registered twice with different bodies. \
Give one of them a distinct name — dwkeyframes! namespaces by crate \
unless you override it with #[name = \"...\"]."
);
return;
}
registry.insert(name, body);
dominator::stylesheet_raw(format!("@keyframes {name} {{ {body} }}"));
}
pub fn is_registered(name: &str) -> bool {
match REGISTRY.lock() {
Ok(registry) => registry.contains_key(name),
Err(poisoned) => poisoned.into_inner().contains_key(name),
}
}
pub fn registered_css() -> String {
let registry = match REGISTRY.lock() {
Ok(registry) => registry,
Err(poisoned) => poisoned.into_inner(),
};
registry
.iter()
.map(|(name, body)| format!("@keyframes {name} {{ {body} }}"))
.collect::<Vec<_>>()
.join("\n")
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn css_renders_a_complete_rule() {
let kf = Keyframes::new("test-fade", "from{opacity:0;}to{opacity:1;}");
assert_eq!(
kf.css(),
"@keyframes test-fade { from{opacity:0;}to{opacity:1;} }"
);
}
#[test]
fn name_unregistered_does_not_register() {
let kf = Keyframes::new("test-untouched", "from{opacity:0;}");
assert_eq!(kf.name_unregistered(), "test-untouched");
assert!(!is_registered("test-untouched"));
}
}