use std::sync::Arc;
#[derive(Clone, Debug, PartialEq)]
pub enum EditorKind {
NumberDrag(NumberAttrs),
Slider(NumberAttrs),
Toggle,
ColorPicker,
Matrix,
Display,
StringSingleLine,
StringMultiLine,
StringReadOnly,
EnumDropdown { variants: Vec<Arc<str>> },
EnumButtons { variants: Vec<Arc<str>> },
EnumTabs { variants: Vec<Arc<str>> },
Image,
}
impl EditorKind {
pub fn drag_range(min: f64, max: f64) -> Self {
EditorKind::NumberDrag(NumberAttrs {
min: Some(min),
max: Some(max),
..Default::default()
})
}
pub fn slider_range(min: f64, max: f64) -> Self {
EditorKind::Slider(NumberAttrs {
min: Some(min),
max: Some(max),
..Default::default()
})
}
pub fn numeric_range(&self) -> (Option<f64>, Option<f64>) {
match self {
EditorKind::NumberDrag(a) | EditorKind::Slider(a) => (a.min, a.max),
_ => (None, None),
}
}
pub fn number_attrs(&self) -> Option<&NumberAttrs> {
match self {
EditorKind::NumberDrag(a) | EditorKind::Slider(a) => Some(a),
_ => None,
}
}
}
impl Default for EditorKind {
fn default() -> Self {
EditorKind::Display
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct NumberAttrs {
pub min: Option<f64>,
pub max: Option<f64>,
pub step: Option<f64>,
pub integer: bool,
pub ease_in: Option<f64>,
pub snap_grid: bool,
pub max_decimal_places: Option<u8>,
}
impl NumberAttrs {
pub fn with_range(min: f64, max: f64) -> Self {
Self {
min: Some(min),
max: Some(max),
..Default::default()
}
}
pub fn integer(mut self) -> Self {
self.integer = true;
self
}
pub fn with_step(mut self, step: f64) -> Self {
self.step = Some(step);
self
}
pub fn with_ease_in(mut self, e: f64) -> Self {
self.ease_in = Some(e);
self
}
pub fn with_snap_grid(mut self) -> Self {
self.snap_grid = true;
self
}
pub fn with_decimal_places(mut self, n: u8) -> Self {
self.max_decimal_places = Some(n);
self
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum VisibleWhen {
#[default]
Always,
AdvancedOn,
AdvancedOff,
Never,
}
#[derive(Clone, Debug, Default)]
pub struct NodeFieldAttrs {
pub label: Option<Arc<str>>,
pub editor: EditorKind,
pub bound_input: Option<Arc<str>>,
pub description: Option<Arc<str>>,
pub visible_when: VisibleWhen,
}
impl NodeFieldAttrs {
pub fn new() -> Self {
Self::default()
}
pub fn with_label(mut self, label: impl Into<Arc<str>>) -> Self {
self.label = Some(label.into());
self
}
pub fn with_editor(mut self, editor: EditorKind) -> Self {
self.editor = editor;
self
}
pub fn bound_to(mut self, socket: impl Into<Arc<str>>) -> Self {
self.bound_input = Some(socket.into());
self
}
pub fn with_description(mut self, text: impl Into<Arc<str>>) -> Self {
self.description = Some(text.into());
self
}
pub fn visible_when(mut self, when: VisibleWhen) -> Self {
self.visible_when = when;
self
}
pub fn advanced(mut self) -> Self {
self.visible_when = VisibleWhen::AdvancedOn;
self
}
pub fn easy_only(mut self) -> Self {
self.visible_when = VisibleWhen::AdvancedOff;
self
}
pub fn hidden(mut self) -> Self {
self.visible_when = VisibleWhen::Never;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slider_range_helper_sets_min_max() {
let k = EditorKind::slider_range(1.0, 400.0);
match k {
EditorKind::Slider(a) => {
assert_eq!(a.min, Some(1.0));
assert_eq!(a.max, Some(400.0));
}
other => panic!("expected Slider, got {:?}", other),
}
}
#[test]
fn default_editor_is_display() {
assert!(matches!(EditorKind::default(), EditorKind::Display));
}
#[test]
fn number_attrs_builder_chains() {
let a = NumberAttrs::with_range(0.0, 360.0)
.integer()
.with_step(1.0)
.with_decimal_places(0)
.with_ease_in(2.0)
.with_snap_grid();
assert_eq!(a.min, Some(0.0));
assert_eq!(a.max, Some(360.0));
assert!(a.integer);
assert_eq!(a.step, Some(1.0));
assert_eq!(a.max_decimal_places, Some(0));
assert_eq!(a.ease_in, Some(2.0));
assert!(a.snap_grid);
}
#[test]
fn node_field_attrs_builder_chains() {
let a = NodeFieldAttrs::new()
.with_label("Diameter")
.with_editor(EditorKind::slider_range(1.0, 400.0))
.with_description("Width across.")
.advanced();
assert_eq!(a.label.as_deref().map(|x| x.as_ref()), Some("Diameter"));
assert!(matches!(a.editor, EditorKind::Slider(_)));
assert!(a
.description
.as_deref()
.map(|x| x.contains("Width"))
.unwrap_or(false));
assert_eq!(a.visible_when, VisibleWhen::AdvancedOn);
}
#[test]
fn visible_when_shorthands_set_expected_variant() {
assert_eq!(NodeFieldAttrs::new().visible_when, VisibleWhen::Always);
assert_eq!(
NodeFieldAttrs::new().advanced().visible_when,
VisibleWhen::AdvancedOn
);
assert_eq!(
NodeFieldAttrs::new().easy_only().visible_when,
VisibleWhen::AdvancedOff
);
assert_eq!(
NodeFieldAttrs::new().hidden().visible_when,
VisibleWhen::Never
);
}
#[test]
fn numeric_range_is_none_for_non_numeric() {
assert_eq!(EditorKind::Toggle.numeric_range(), (None, None));
assert_eq!(EditorKind::ColorPicker.numeric_range(), (None, None));
assert_eq!(EditorKind::StringSingleLine.numeric_range(), (None, None));
}
#[test]
fn enum_variants_round_trip() {
let k = EditorKind::EnumTabs {
variants: vec!["Easy".into(), "Advanced".into()],
};
if let EditorKind::EnumTabs { variants } = k {
assert_eq!(variants.len(), 2);
assert_eq!(variants[0].as_ref(), "Easy");
} else {
panic!("expected EnumTabs");
}
}
}