016038aca0
- Add arrow key support (Left/Right/Home/End) for cursor movement - Add Delete key to remove character at cursor position - Render cursor between password dots with visual indicator - Modularize render.rs into separate modules (indicator, media_bar, status_bar, feedback) - Add cubic ease-in-out for fade-in animation - Optimize media_rects to use &'static str instead of String allocations
120 lines
3.4 KiB
Rust
120 lines
3.4 KiB
Rust
use serde::{Deserialize, Deserializer, Serializer};
|
|
|
|
pub fn parse_hex_color(s: &str) -> Result<(f64, f64, f64, f64), String> {
|
|
let s = s.trim_start_matches('#');
|
|
let len = s.len();
|
|
|
|
if len != 6 && len != 8 {
|
|
return Err("Color must be 6 (RRGGBB) or 8 (RRGGBBAA)".to_string());
|
|
}
|
|
|
|
let r = u8::from_str_radix(&s[0..2], 16).map_err(|_| "Invalid red")? as f64 / 255.0;
|
|
let g = u8::from_str_radix(&s[2..4], 16).map_err(|_| "Invalid green")? as f64 / 255.0;
|
|
let b = u8::from_str_radix(&s[4..6], 16).map_err(|_| "Invalid blue")? as f64 / 255.0;
|
|
|
|
let a = if len == 8 {
|
|
u8::from_str_radix(&s[6..8], 16).map_err(|_| "Invalid alpha")? as f64 / 255.0
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
Ok((r, g, b, a))
|
|
}
|
|
|
|
pub fn deserialize_hex_color<'de, D>(deserializer: D) -> Result<(f64, f64, f64, f64), D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s = String::deserialize(deserializer)?;
|
|
parse_hex_color(&s).map_err(serde::de::Error::custom)
|
|
}
|
|
|
|
pub fn serialize_hex_color<S>(
|
|
color: &(f64, f64, f64, f64),
|
|
serializer: S,
|
|
) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
let (r, g, b, a) = color;
|
|
let r = (r * 255.0) as u8;
|
|
let g = (g * 255.0) as u8;
|
|
let b = (b * 255.0) as u8;
|
|
let a = (a * 255.0) as u8;
|
|
|
|
if a == 255 {
|
|
serializer.serialize_str(&format!("{:02x}{:02x}{:02x}", r, g, b))
|
|
} else {
|
|
serializer.serialize_str(&format!("{:02x}{:02x}{:02x}{:02x}", r, g, b, a))
|
|
}
|
|
}
|
|
|
|
pub fn parse_blur_effect(s: &str) -> Result<(u32, u32), String> {
|
|
let parts: Vec<&str> = s.split('x').collect();
|
|
if parts.len() != 2 {
|
|
return Err("Blur must be radiusxtimes".to_string());
|
|
}
|
|
let radius = parts[0].parse().map_err(|_| "Invalid radius")?;
|
|
let times = parts[1].parse().map_err(|_| "Invalid times")?;
|
|
Ok((radius, times))
|
|
}
|
|
|
|
pub fn deserialize_blur_effect<'de, D>(deserializer: D) -> Result<Option<(u32, u32)>, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s = Option::<String>::deserialize(deserializer)?;
|
|
match s {
|
|
Some(s) => parse_blur_effect(&s)
|
|
.map(Some)
|
|
.map_err(serde::de::Error::custom),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
pub fn serialize_blur_effect<S>(val: &Option<(u32, u32)>, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
match val {
|
|
Some((radius, times)) => serializer.serialize_str(&format!("{}x{}", radius, times)),
|
|
None => serializer.serialize_none(),
|
|
}
|
|
}
|
|
|
|
pub fn parse_vignette_effect(s: &str) -> Result<(f32, f32), String> {
|
|
let parts: Vec<&str> = s.split(':').collect();
|
|
if parts.len() != 2 {
|
|
return Err("Vignette must be base:factor".to_string());
|
|
}
|
|
let base = parts[0].parse().map_err(|_| "Invalid base")?;
|
|
let factor = parts[1].parse().map_err(|_| "Invalid factor")?;
|
|
Ok((base, factor))
|
|
}
|
|
|
|
pub fn deserialize_vignette_effect<'de, D>(deserializer: D) -> Result<Option<(f32, f32)>, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s = Option::<String>::deserialize(deserializer)?;
|
|
match s {
|
|
Some(s) => parse_vignette_effect(&s)
|
|
.map(Some)
|
|
.map_err(serde::de::Error::custom),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
pub fn serialize_vignette_effect<S>(
|
|
val: &Option<(f32, f32)>,
|
|
serializer: S,
|
|
) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
match val {
|
|
Some((base, factor)) => serializer.serialize_str(&format!("{}:{}", base, factor)),
|
|
None => serializer.serialize_none(),
|
|
}
|
|
}
|