bevy_mod_config 0.2.1

A Bevy plugin for configuration management
Documentation
use alloc::string::{String, ToString};
use core::fmt;
use core::hash::Hash;
use core::str::FromStr;

use bevy_egui::egui;

use super::{DefaultStyle, Editable};
use crate::ConfigField;
use crate::impls::NumericMetadata;

trait Number:
    num_traits::Bounded
    + num_traits::Num
    + Copy
    + PartialOrd
    + ConfigField<Metadata = NumericMetadata<Self>>
    + fmt::Display
    + FromStr
{
    fn saturating_add_usize(self, i: usize) -> Self;

    fn saturating_sub_usize(self, i: usize) -> Self;
}

macro_rules! impl_number_signed {
    ($(($ty:ty, $unsigned:ty),)*) => {
        $(
            impl Number for $ty {
                fn saturating_add_usize(self, i: usize) -> Self {
                    self.saturating_add_unsigned(<$unsigned>::try_from(i).unwrap_or_else(|_| <$unsigned>::max_value()))
                }

                fn saturating_sub_usize(self, i: usize) -> Self {
                    self.saturating_sub_unsigned(<$unsigned>::try_from(i).unwrap_or_else(|_| <$unsigned>::max_value()))
                }
            }
        )*
    };
}

impl_number_signed! {
    (i8, u8),
    (i16, u16),
    (i32, u32),
    (i64, u64),
    (i128, u128),
    (isize, usize),
}

macro_rules! impl_number_unsigned {
    ($($ty:ty),*) => {
        $(
            impl Number for $ty {
                fn saturating_add_usize(self, i: usize) -> Self {
                    self.saturating_add(Self::try_from(i).unwrap_or_else(|_| Self::max_value()))
                }

                fn saturating_sub_usize(self, i: usize) -> Self {
                    self.saturating_sub(Self::try_from(i).unwrap_or_else(|_| Self::max_value()))
                }
            }
        )*
    };
}

impl_number_unsigned!(u8, u16, u32, u64, u128, usize);

impl Number for f32 {
    fn saturating_add_usize(self, i: usize) -> Self { self + i as f32 }

    fn saturating_sub_usize(self, i: usize) -> Self { self - i as f32 }
}

impl Number for f64 {
    fn saturating_add_usize(self, i: usize) -> Self { self + i as f64 }

    fn saturating_sub_usize(self, i: usize) -> Self { self - i as f64 }
}

impl<T> Editable<DefaultStyle> for T
where
    T: Number + egui::emath::Numeric + TryInto<f64>,
{
    type TempData = String;

    fn show(
        ui: &mut egui::Ui,
        value: &mut Self,
        metadata: &Self::Metadata,
        temp_data: &mut Option<Self::TempData>,
        id_salt: impl Hash,
        _: &DefaultStyle,
    ) -> egui::Response {
        if metadata.slider {
            ui.add(egui::Slider::new(value, metadata.min..=metadata.max).step_by(metadata.precision.and_then(|n| n.try_into().ok()).unwrap_or(0.0)))
        } else {
            let mut value_str = temp_data.take().unwrap_or_else(|| value.to_string());
            let edit = egui::TextEdit::singleline(&mut value_str).id_salt(id_salt);
            let mut resp = ui.add(edit);
            let parsed = value_str.parse::<Self>().ok();
            *temp_data = Some(value_str);
            if resp.changed()
                && let Some(parsed) = parsed
            {
                let min = metadata.min;
                let max = metadata.max;
                *value = num_traits::clamp(parsed, min, max);
            } else if resp.has_focus() {
                ui.input_mut(|input| {
                    if let presses @ 1.. =
                        input.count_and_consume_key(egui::Modifiers::NONE, egui::Key::ArrowUp)
                    {
                        *value = value.saturating_add_usize(presses);
                        *temp_data = Some(value.to_string());
                        resp.mark_changed();
                    }
                    if let presses @ 1.. =
                        input.count_and_consume_key(egui::Modifiers::NONE, egui::Key::ArrowDown)
                    {
                        *value = value.saturating_sub_usize(presses);
                        *temp_data = Some(value.to_string());
                        resp.mark_changed();
                    }
                });
            }
            if resp.lost_focus() {
                *temp_data = None;
            }
        resp
        }
    }
}