use alloc::string::{String, ToString};
use core::hash::Hash;
use core::time::Duration;
use bevy_egui::egui;
use super::{DefaultStyle, Editable};
use crate::ConfigField;
use crate::impls::NumericMetadata;
pub trait NumericLike: ConfigField + PartialOrd + Copy + Sized {
fn parse_from_str(s: &str) -> Option<Self>;
fn to_string(&self) -> String;
#[must_use]
fn saturating_add_usize(self, i: usize) -> Self;
#[must_use]
fn saturating_sub_usize(self, i: usize) -> Self;
fn metadata_wants_slider(metadata: &Self::Metadata) -> bool;
fn metadata_min(metadata: &Self::Metadata) -> Option<Self>;
fn metadata_max(metadata: &Self::Metadata) -> Option<Self>;
fn metadata_precision(metadata: &Self::Metadata) -> Option<f64>;
fn as_float(&self) -> f64;
fn from_float(f: f64) -> Self;
}
macro_rules! impl_primitive {
(
$ty:ty,
saturating_add_usize: $self1:ident, $i1:ident => $saturating_add_usize:expr,
saturating_sub_usize: $self2:ident, $i2:ident => $saturating_sub_usize:expr,
$metadata:ident => $precision:expr,
$float:ident => $from_float:expr,
) => {
#[allow(clippy::cast_lossless, reason = "u128 to f64 is lossy")]
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss, clippy::cast_sign_loss)]
impl NumericLike for $ty {
fn parse_from_str(s: &str) -> Option<Self> {
s.parse::<Self>().ok()
}
fn to_string(&self) -> String {
ToString::to_string(self)
}
fn saturating_add_usize($self1, $i1: usize) -> Self {
$saturating_add_usize
}
fn saturating_sub_usize($self2, $i2: usize) -> Self {
$saturating_sub_usize
}
fn metadata_wants_slider(metadata: &Self::Metadata) -> bool {
metadata.slider
}
fn metadata_min(metadata: &Self::Metadata) -> Option<Self> {
Some(metadata.min)
}
fn metadata_max(metadata: &Self::Metadata) -> Option<Self> {
Some(metadata.max)
}
fn metadata_precision($metadata: &Self::Metadata) -> Option<f64> {
$precision
}
fn as_float(&self) -> f64 {
*self as f64
}
fn from_float($float: f64) -> Self {
$from_float
}
}
}
}
macro_rules! impl_number_signed {
($(($ty:ty, $unsigned:ty),)*) => {
$(
impl_primitive! {
$ty,
saturating_add_usize: self, i => {
self.saturating_add_unsigned(<$unsigned>::try_from(i).unwrap_or_else(|_|
<$unsigned>::MAX,
))
},
saturating_sub_usize: self, i => {
self.saturating_sub_unsigned(<$unsigned>::try_from(i).unwrap_or_else(|_| <$unsigned>::MAX))
},
metadata => { metadata.precision.map(|n| n as f64) },
float => { float.round() as $ty },
}
)*
};
}
impl_number_signed! {
(i8, u8),
(i16, u16),
(i32, u32),
(i64, u64),
(i128, u128),
(isize, usize),
}
macro_rules! impl_number_unsigned {
($($ty:ty),*) => {
$(
impl_primitive! {
$ty,
saturating_add_usize: self, i => {
self.saturating_add(Self::try_from(i).unwrap_or_else(|_| Self::MAX))
},
saturating_sub_usize: self, i => {
self.saturating_sub(Self::try_from(i).unwrap_or_else(|_| Self::MAX))
},
metadata => { metadata.precision.map(|n| n as f64) },
float => { float.round() as $ty },
}
)*
};
}
impl_number_unsigned!(u8, u16, u32, u64, u128, usize);
impl_primitive! {
f32,
saturating_add_usize: self, i => self + i as f32 ,
saturating_sub_usize: self, i => self - i as f32 ,
metadata => metadata.precision.map(f64::from) ,
float => float as f32 ,
}
impl_primitive! {
f64,
saturating_add_usize: self, i => self + i as f64 ,
saturating_sub_usize: self, i => self - i as f64 ,
metadata => metadata.precision ,
float => float,
}
pub trait FloatLikeWithSuffix: ConfigField + PartialOrd + Copy + Sized {
fn suffix() -> &'static str;
fn as_float(&self) -> f64;
fn from_float(f: f64) -> Self;
fn saturating_add_usize(&self, i: usize) -> Self;
fn saturating_sub_usize(&self, i: usize) -> Self;
fn numeric_metadata(metadata: &Self::Metadata) -> NumericMetadata<Self>;
}
impl<T: FloatLikeWithSuffix> NumericLike for T {
fn parse_from_str(s: &str) -> Option<Self> {
let s = s.trim_end();
let s = s.strip_suffix(T::suffix()).unwrap_or(s);
let s = s.trim_end();
s.parse::<f64>().ok().map(T::from_float)
}
fn to_string(&self) -> String { alloc::format!("{}{}", self.as_float(), T::suffix()) }
fn saturating_add_usize(self, i: usize) -> Self {
FloatLikeWithSuffix::saturating_add_usize(&self, i)
}
fn saturating_sub_usize(self, i: usize) -> Self {
FloatLikeWithSuffix::saturating_sub_usize(&self, i)
}
fn metadata_wants_slider(metadata: &Self::Metadata) -> bool {
T::numeric_metadata(metadata).slider
}
fn metadata_min(metadata: &Self::Metadata) -> Option<Self> {
Some(T::numeric_metadata(metadata).min)
}
fn metadata_max(metadata: &Self::Metadata) -> Option<Self> {
Some(T::numeric_metadata(metadata).max)
}
fn metadata_precision(metadata: &Self::Metadata) -> Option<f64> {
T::numeric_metadata(metadata).precision.map(|v| v.as_float())
}
fn as_float(&self) -> f64 { <T as FloatLikeWithSuffix>::as_float(self) }
fn from_float(float: f64) -> Self { <T as FloatLikeWithSuffix>::from_float(float) }
}
impl FloatLikeWithSuffix for Duration {
fn suffix() -> &'static str { "s" }
fn as_float(&self) -> f64 { self.as_secs_f64() }
fn from_float(f: f64) -> Self { Duration::from_secs_f64(f) }
fn saturating_add_usize(&self, i: usize) -> Self {
self.saturating_add(Duration::from_secs(i as u64))
}
fn saturating_sub_usize(&self, i: usize) -> Self {
self.saturating_sub(Duration::from_secs(i as u64))
}
fn numeric_metadata(metadata: &Self::Metadata) -> NumericMetadata<Self> { metadata.clone() }
}
impl<T> Editable<DefaultStyle> for T
where
T: NumericLike,
{
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 let (true, Some(min), Some(max)) = (
T::metadata_wants_slider(metadata),
T::metadata_min(metadata),
T::metadata_max(metadata),
) {
let mut value_float = value.as_float();
let min_float = min.as_float();
let max_float = max.as_float();
let resp = ui.add(
egui::Slider::new(&mut value_float, min_float..=max_float)
.step_by(T::metadata_precision(metadata).unwrap_or(0.0)),
);
if resp.changed() {
*value = T::from_float(value_float);
}
resp
} 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 = T::parse_from_str(&value_str);
*temp_data = Some(value_str);
if resp.changed()
&& let Some(mut parsed) = parsed
{
if let Some(min) = T::metadata_min(metadata)
&& parsed < min
{
parsed = min;
}
if let Some(max) = T::metadata_max(metadata)
&& parsed > max
{
parsed = max;
}
*value = parsed;
} 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
}
}
}