cloudiful-bevy-settings 0.1.0

Reusable Bevy settings runtime for app-defined actions, field keys, and localization context.
Documentation
#[derive(Debug, Clone)]
pub struct SettingFieldSpec<AppAction, FieldKey, TextKey> {
    pub label: String,
    pub label_key: Option<TextKey>,
    pub control: SettingControlSpec<AppAction, FieldKey>,
}

#[derive(Debug, Clone)]
pub struct SettingSelectOption<Action> {
    pub text: String,
    pub action: Action,
    pub disabled: bool,
}

#[derive(Debug, Clone)]
pub struct SettingSliderSpec<Action> {
    pub value: f32,
    pub min: f32,
    pub max: f32,
    pub step: f32,
    pub precision: i32,
    pub suffix: String,
    pub action: fn(f32) -> Action,
}

impl<Action> SettingSliderSpec<Action> {
    pub fn new(
        value: f32,
        min: f32,
        max: f32,
        step: f32,
        precision: i32,
        suffix: impl Into<String>,
        action: fn(f32) -> Action,
    ) -> Self {
        Self {
            value,
            min,
            max,
            step,
            precision,
            suffix: suffix.into(),
            action,
        }
    }
}

#[derive(Debug, Clone)]
pub enum SettingControlSpec<Action, FieldKey> {
    Toggle {
        text: String,
        action: Action,
    },
    Stepper {
        key: FieldKey,
        value: String,
        decrease_action: Option<Action>,
        increase_action: Option<Action>,
        slider: Option<SettingSliderSpec<Action>>,
    },
    Select {
        key: FieldKey,
        value: String,
        options: Vec<SettingSelectOption<Action>>,
    },
}

impl<Action, FieldKey, TextKey> SettingFieldSpec<Action, FieldKey, TextKey> {
    pub fn toggle(label: impl Into<String>, text: String, action: Action) -> Self {
        Self {
            label: label.into(),
            label_key: None,
            control: SettingControlSpec::Toggle { text, action },
        }
    }

    pub fn stepper(
        label: impl Into<String>,
        key: FieldKey,
        value: String,
        decrease_action: Option<Action>,
        increase_action: Option<Action>,
    ) -> Self {
        Self {
            label: label.into(),
            label_key: None,
            control: SettingControlSpec::Stepper {
                key,
                value,
                decrease_action,
                increase_action,
                slider: None,
            },
        }
    }

    pub fn select(
        label: impl Into<String>,
        key: FieldKey,
        value: String,
        options: Vec<SettingSelectOption<Action>>,
    ) -> Self {
        Self {
            label: label.into(),
            label_key: None,
            control: SettingControlSpec::Select {
                key,
                value,
                options,
            },
        }
    }

    pub fn with_label_key(mut self, label_key: TextKey) -> Self {
        self.label_key = Some(label_key);
        self
    }

    pub fn with_slider(mut self, slider: SettingSliderSpec<Action>) -> Self {
        if let SettingControlSpec::Stepper {
            slider: slider_slot,
            ..
        } = &mut self.control
        {
            *slider_slot = Some(slider);
        }
        self
    }
}

pub trait SettingFieldSource<AppAction, Context, FieldKey, TextKey> {
    fn field_specs(&self, context: &Context)
    -> Vec<SettingFieldSpec<AppAction, FieldKey, TextKey>>;
}

#[cfg(test)]
mod tests {
    use super::SettingSliderSpec;

    fn slider_action(value: f32) -> u32 {
        value.round() as u32
    }

    #[test]
    fn slider_spec_new_preserves_fields() {
        let spec = SettingSliderSpec::new(42.0, 0.0, 100.0, 5.0, 1, "%", slider_action);

        assert_eq!(spec.value, 42.0);
        assert_eq!(spec.min, 0.0);
        assert_eq!(spec.max, 100.0);
        assert_eq!(spec.step, 5.0);
        assert_eq!(spec.precision, 1);
        assert_eq!(spec.suffix, "%");
        assert_eq!((spec.action)(12.4), 12);
    }
}