use std::{fmt::Debug, fmt::Display, str::FromStr, sync::Arc};
use four_cc::FourCC;
use super::{formatters, Parameter, ParameterType, ParameterValueUpdate};
#[derive(Clone)]
pub struct EnumParameter {
id: FourCC,
name: &'static str,
values: &'static [&'static str],
default_index: usize,
value_to_string: Option<fn(&str) -> String>,
string_to_value: Option<fn(&str) -> Option<String>>,
}
impl Debug for EnumParameter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EnumParameter")
.field("id", &self.id)
.field("name", &self.name)
.field("values", &self.values)
.field("default_index", &self.default_index)
.field("value_to_string", &self.value_to_string.is_some())
.field("string_to_value", &self.string_to_value.is_some())
.finish()
}
}
impl EnumParameter {
pub const fn new(
id: FourCC,
name: &'static str,
values: &'static [&'static str],
default_index: usize,
) -> Self {
assert!(!values.is_empty(), "EnumParameter values cannot be empty");
assert!(
default_index < values.len(),
"Default index out of bounds for EnumParameter"
);
Self {
id,
name,
values,
default_index,
value_to_string: None,
string_to_value: None,
}
}
pub fn from_enum<E: strum::VariantNames + PartialEq + ToString>(
id: FourCC,
name: &'static str,
default_value: E,
) -> Self {
let values = E::VARIANTS;
let default_string = default_value.to_string();
let default_index = values
.iter()
.position(|v| v == &default_string)
.expect("Failed to resolve enum default value");
Self::new(id, name, values, default_index)
}
pub const fn with_formatter(mut self, formatter: formatters::EnumFormatter) -> Self {
self.value_to_string = Some(formatter.0);
self.string_to_value = Some(formatter.1);
self
}
#[must_use]
pub fn value_update<E: PartialEq + ToString + Send + Sync + 'static>(
&self,
value: E,
) -> (FourCC, ParameterValueUpdate) {
debug_assert!(
self.values.iter().any(|v| v == &value.to_string()),
"Enum value for parameter '{}' is not one of '{:?}', but is {}",
self.id,
self.values,
value.to_string()
);
(self.id, ParameterValueUpdate::Raw(Arc::new(value)))
}
#[must_use]
pub fn value_update_index(&self, index: usize) -> (FourCC, ParameterValueUpdate) {
debug_assert!(
index < self.values.len(),
"Enum value for parameter '{}' must be < {}, but is {}",
self.id,
self.values.len(),
index
);
(self.id, ParameterValueUpdate::Raw(Arc::new(index)))
}
pub const fn id(&self) -> FourCC {
self.id
}
pub const fn values(&self) -> &'static [&'static str] {
self.values
}
pub const fn default_value(&self) -> &str {
self.values[self.default_index]
}
pub fn clamp_value<'a>(&self, value: &'a str) -> &'a str {
if self.values.contains(&value) {
value
} else {
self.values[self.default_index]
}
}
pub fn normalize_value(&self, value: &str) -> f32 {
if let Some(index) = self.values.iter().position(|v| v == &value) {
return index as f32 / (self.values.len() - 1) as f32;
}
0.0
}
pub fn denormalize_value(&self, normalized: f32) -> &str {
assert!((0.0..=1.0).contains(&normalized));
let index = (normalized * (self.values.len() - 1) as f32).round() as usize;
self.values[index]
}
pub fn value_to_string(&self, value: &str) -> String {
match &self.value_to_string {
Some(f) => f(value),
None => value.to_owned(),
}
}
pub fn string_to_value(&self, string: &str) -> Option<String> {
match &self.string_to_value {
Some(f) => f(string.trim()).map(|v| self.clamp_value(&v).to_owned()),
None => Some(self.clamp_value(string.trim()).to_owned()),
}
}
}
impl Parameter for EnumParameter {
fn id(&self) -> FourCC {
self.id
}
fn name(&self) -> &'static str {
self.name
}
fn parameter_type(&self) -> ParameterType {
ParameterType::Enum {
values: self.values.iter().map(|v| v.to_string()).collect(),
}
}
fn default_value(&self) -> f32 {
self.default_index as f32 / (self.values.len() - 1) as f32
}
fn value_to_string(&self, normalized: f32, _include_unit: bool) -> String {
let value = self.denormalize_value(normalized.clamp(0.0, 1.0));
self.value_to_string(value)
}
fn string_to_value(&self, string: String) -> Option<f32> {
let value = self.string_to_value(&string)?;
Some(self.normalize_value(&value))
}
fn dyn_clone(&self) -> Box<dyn Parameter> {
Box::new(self.clone())
}
}
#[derive(Debug, Clone)]
pub struct EnumParameterValue<T: Clone> {
description: EnumParameter,
value: T,
}
impl<T: Clone> EnumParameterValue<T>
where
T: FromStr + 'static,
{
pub fn from_description(description: EnumParameter) -> Self
where
<T as FromStr>::Err: Debug,
{
let value = T::from_str(description.default_value())
.expect("Failed to convert default enum string value to the actual enum type");
Self { value, description }
}
pub fn with_value(mut self, value: T) -> Self {
self.value = value;
self
}
pub fn description(&self) -> &EnumParameter {
&self.description
}
#[inline(always)]
pub fn value(&self) -> T {
self.value.clone()
}
pub fn set_value(&mut self, value: T) {
self.value = value;
}
pub fn apply_update(&mut self, update: &ParameterValueUpdate) {
match update {
ParameterValueUpdate::Raw(raw) => {
if let Some(value) = raw.downcast_ref::<T>() {
self.value = value.clone();
} else if let Some(value_str) = raw.downcast_ref::<String>() {
if let Ok(value) = T::from_str(value_str) {
self.value = value;
} else {
log::warn!(
"Invalid string value for enum parameter '{}'",
self.description.id()
);
}
} else {
log::warn!(
"Invalid value type for enum parameter '{}'",
self.description.id()
);
}
}
ParameterValueUpdate::Normalized(normalized) => {
let value_str = self
.description
.denormalize_value(normalized.clamp(0.0, 1.0));
if let Ok(value) = T::from_str(value_str) {
self.set_value(value);
} else {
log::warn!(
"Invalid value string for enum parameter '{}'",
self.description.id()
);
}
}
}
}
}
impl<T: Clone> Display for EnumParameterValue<T>
where
T: ToString,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value_str = self.value.to_string();
f.write_str(&self.description.value_to_string(&value_str))
}
}