use std::ops::Add;
use nalgebra_glm::Vec2;
pub trait UiValueType:
Copy + Default + Add<Output = Self> + PartialEq + std::fmt::Debug + 'static
{
fn mul_scalar(self, scalar: f32) -> Self;
}
impl UiValueType for f32 {
fn mul_scalar(self, scalar: f32) -> Self {
self * scalar
}
}
impl UiValueType for Vec2 {
fn mul_scalar(self, scalar: f32) -> Self {
self * scalar
}
}
macro_rules! define_unit {
($name:ident) => {
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct $name<T>(pub T);
};
}
define_unit!(Ab);
define_unit!(Rl);
define_unit!(Rw);
define_unit!(Rh);
define_unit!(Em);
define_unit!(Vp);
define_unit!(Vw);
define_unit!(Vh);
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct UiValue<T: UiValueType> {
pub absolute: Option<T>,
pub relative: Option<T>,
pub relative_width: Option<T>,
pub relative_height: Option<T>,
pub em: Option<T>,
pub viewport: Option<T>,
pub viewport_width: Option<T>,
pub viewport_height: Option<T>,
}
macro_rules! impl_unit_from {
($unit:ident, $field:ident) => {
impl<T: UiValueType> From<$unit<T>> for UiValue<T> {
fn from(value: $unit<T>) -> Self {
let mut result = Self::default();
result.$field = Some(value.0);
result
}
}
};
}
impl_unit_from!(Ab, absolute);
impl_unit_from!(Rl, relative);
impl_unit_from!(Rw, relative_width);
impl_unit_from!(Rh, relative_height);
impl_unit_from!(Em, em);
impl_unit_from!(Vp, viewport);
impl_unit_from!(Vw, viewport_width);
impl_unit_from!(Vh, viewport_height);
impl<T: UiValueType> Add for UiValue<T> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
fn merge<T: UiValueType>(a: Option<T>, b: Option<T>) -> Option<T> {
match (a, b) {
(Some(a), Some(b)) => Some(a + b),
(a @ Some(_), None) => a,
(None, b @ Some(_)) => b,
(None, None) => None,
}
}
Self {
absolute: merge(self.absolute, rhs.absolute),
relative: merge(self.relative, rhs.relative),
relative_width: merge(self.relative_width, rhs.relative_width),
relative_height: merge(self.relative_height, rhs.relative_height),
em: merge(self.em, rhs.em),
viewport: merge(self.viewport, rhs.viewport),
viewport_width: merge(self.viewport_width, rhs.viewport_width),
viewport_height: merge(self.viewport_height, rhs.viewport_height),
}
}
}
macro_rules! impl_uivalue_add_unit {
($unit:ident, $field:ident) => {
impl<T: UiValueType> Add<$unit<T>> for UiValue<T> {
type Output = UiValue<T>;
fn add(self, rhs: $unit<T>) -> UiValue<T> {
let mut result = self;
result.$field = match result.$field {
Some(existing) => Some(existing + rhs.0),
None => Some(rhs.0),
};
result
}
}
};
}
impl_uivalue_add_unit!(Ab, absolute);
impl_uivalue_add_unit!(Rl, relative);
impl_uivalue_add_unit!(Rw, relative_width);
impl_uivalue_add_unit!(Rh, relative_height);
impl_uivalue_add_unit!(Em, em);
impl_uivalue_add_unit!(Vp, viewport);
impl_uivalue_add_unit!(Vw, viewport_width);
impl_uivalue_add_unit!(Vh, viewport_height);
macro_rules! impl_cross_adds {
($left:ident, $left_field:ident => $($right:ident, $right_field:ident);+) => {
$(
impl<T: UiValueType> Add<$right<T>> for $left<T> {
type Output = UiValue<T>;
fn add(self, rhs: $right<T>) -> UiValue<T> {
let mut result = UiValue::default();
result.$left_field = Some(self.0);
result.$right_field = Some(rhs.0);
result
}
}
)+
};
}
impl_cross_adds!(Ab, absolute => Rl, relative; Rw, relative_width; Rh, relative_height; Em, em; Vp, viewport; Vw, viewport_width; Vh, viewport_height);
impl_cross_adds!(Rl, relative => Ab, absolute; Rw, relative_width; Rh, relative_height; Em, em; Vp, viewport; Vw, viewport_width; Vh, viewport_height);
impl_cross_adds!(Rw, relative_width => Ab, absolute; Rl, relative; Rh, relative_height; Em, em; Vp, viewport; Vw, viewport_width; Vh, viewport_height);
impl_cross_adds!(Rh, relative_height => Ab, absolute; Rl, relative; Rw, relative_width; Em, em; Vp, viewport; Vw, viewport_width; Vh, viewport_height);
impl_cross_adds!(Em, em => Ab, absolute; Rl, relative; Rw, relative_width; Rh, relative_height; Vp, viewport; Vw, viewport_width; Vh, viewport_height);
impl_cross_adds!(Vp, viewport => Ab, absolute; Rl, relative; Rw, relative_width; Rh, relative_height; Em, em; Vw, viewport_width; Vh, viewport_height);
impl_cross_adds!(Vw, viewport_width => Ab, absolute; Rl, relative; Rw, relative_width; Rh, relative_height; Em, em; Vp, viewport; Vh, viewport_height);
impl_cross_adds!(Vh, viewport_height => Ab, absolute; Rl, relative; Rw, relative_width; Rh, relative_height; Em, em; Vp, viewport; Vw, viewport_width);
macro_rules! impl_same_unit_add {
($unit:ident, $field:ident) => {
impl<T: UiValueType> Add for $unit<T> {
type Output = UiValue<T>;
fn add(self, rhs: Self) -> UiValue<T> {
let mut result = UiValue::default();
result.$field = Some(self.0 + rhs.0);
result
}
}
};
}
impl_same_unit_add!(Ab, absolute);
impl_same_unit_add!(Rl, relative);
impl_same_unit_add!(Rw, relative_width);
impl_same_unit_add!(Rh, relative_height);
impl_same_unit_add!(Em, em);
impl_same_unit_add!(Vp, viewport);
impl_same_unit_add!(Vw, viewport_width);
impl_same_unit_add!(Vh, viewport_height);
pub struct UiEvalContext {
pub parent_width: f32,
pub parent_height: f32,
pub viewport_width: f32,
pub viewport_height: f32,
pub font_size: f32,
pub absolute_scale: f32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UiAxis {
Horizontal,
Vertical,
}
impl UiValue<f32> {
pub fn evaluate(&self, context: &UiEvalContext, axis: UiAxis) -> f32 {
let mut result = 0.0;
if let Some(value) = self.absolute {
result += value * context.absolute_scale;
}
if let Some(value) = self.relative {
result += (value / 100.0)
* match axis {
UiAxis::Horizontal => context.parent_width,
UiAxis::Vertical => context.parent_height,
};
}
if let Some(value) = self.relative_width {
result += (value / 100.0) * context.parent_width;
}
if let Some(value) = self.relative_height {
result += (value / 100.0) * context.parent_height;
}
if let Some(value) = self.em {
result += value * context.font_size;
}
if let Some(value) = self.viewport {
result += (value / 100.0)
* match axis {
UiAxis::Horizontal => context.viewport_width,
UiAxis::Vertical => context.viewport_height,
};
}
if let Some(value) = self.viewport_width {
result += (value / 100.0) * context.viewport_width;
}
if let Some(value) = self.viewport_height {
result += (value / 100.0) * context.viewport_height;
}
result
}
}
impl UiValue<Vec2> {
pub fn evaluate(&self, context: &UiEvalContext) -> Vec2 {
let mut result = Vec2::new(0.0, 0.0);
if let Some(value) = self.absolute {
result += value * context.absolute_scale;
}
if let Some(value) = self.relative {
result.x += (value.x / 100.0) * context.parent_width;
result.y += (value.y / 100.0) * context.parent_height;
}
if let Some(value) = self.relative_width {
result.x += (value.x / 100.0) * context.parent_width;
result.y += (value.y / 100.0) * context.parent_width;
}
if let Some(value) = self.relative_height {
result.x += (value.x / 100.0) * context.parent_height;
result.y += (value.y / 100.0) * context.parent_height;
}
if let Some(value) = self.em {
result += value * context.font_size;
}
if let Some(value) = self.viewport {
result.x += (value.x / 100.0) * context.viewport_width;
result.y += (value.y / 100.0) * context.viewport_height;
}
if let Some(value) = self.viewport_width {
result.x += (value.x / 100.0) * context.viewport_width;
result.y += (value.y / 100.0) * context.viewport_width;
}
if let Some(value) = self.viewport_height {
result.x += (value.x / 100.0) * context.viewport_height;
result.y += (value.y / 100.0) * context.viewport_height;
}
result
}
}