use std::{
ops::Deref,
slice::Iter,
};
pub use euclid::Rect;
use crate::{
geometry::Length,
measure::Phase,
scaled::Scaled,
};
#[derive(PartialEq, Clone, Debug)]
pub enum Size {
Inner,
Fill,
FillMinimum,
Percentage(Length),
Pixels(Length),
RootPercentage(Length),
InnerPercentage(Length),
DynamicCalculations(Box<Vec<DynamicCalculation>>),
Flex(Length),
}
impl Default for Size {
fn default() -> Self {
Self::Inner
}
}
impl Size {
pub fn flex_grow(&self) -> Option<Length> {
match self {
Self::Flex(f) => Some(*f),
_ => None,
}
}
pub fn is_flex(&self) -> bool {
matches!(self, Self::Flex(_))
}
pub fn inner_sized(&self) -> bool {
matches!(
self,
Self::Inner | Self::FillMinimum | Self::InnerPercentage(_)
)
}
pub fn inner_percentage_sized(&self) -> bool {
matches!(self, Self::InnerPercentage(_))
}
pub fn pretty(&self) -> String {
match self {
Size::Inner => "auto".to_string(),
Size::Pixels(s) => format!("{}", s.get()),
Size::DynamicCalculations(calcs) => format!(
"calc({})",
calcs
.iter()
.map(|c| c.to_string())
.collect::<Vec<String>>()
.join(" ")
),
Size::Percentage(p) => format!("{}%", p.get()),
Size::Fill => "fill".to_string(),
Size::FillMinimum => "fill-min".to_string(),
Size::RootPercentage(p) => format!("{}% of root", p.get()),
Size::InnerPercentage(p) => format!("{}% of auto", p.get()),
Size::Flex(f) => format!("flex({}", f.get()),
}
}
pub fn eval(
&self,
parent_value: f32,
available_parent_value: f32,
parent_margin: f32,
root_value: f32,
phase: Phase,
) -> Option<f32> {
match self {
Size::Pixels(px) => Some(px.get() + parent_margin),
Size::Percentage(per) => Some(parent_value / 100.0 * per.get()),
Size::DynamicCalculations(calculations) => Some(
run_calculations(calculations.deref(), parent_value, root_value).unwrap_or(0.0),
),
Size::Fill => Some(available_parent_value),
Size::FillMinimum if phase == Phase::Final => Some(available_parent_value),
Size::RootPercentage(per) => Some(root_value / 100.0 * per.get()),
Size::Flex(_) if phase == Phase::Final => Some(available_parent_value),
_ => None,
}
}
#[allow(clippy::too_many_arguments)]
pub fn min_max(
&self,
value: f32,
parent_value: f32,
available_parent_value: f32,
single_margin: f32,
margin: f32,
minimum: &Self,
maximum: &Self,
root_value: f32,
phase: Phase,
) -> f32 {
let value = self
.eval(
parent_value,
available_parent_value,
margin,
root_value,
phase,
)
.unwrap_or(value + margin);
let minimum_value = minimum
.eval(
parent_value,
available_parent_value,
margin,
root_value,
phase,
)
.map(|v| v + single_margin);
let maximum_value = maximum.eval(
parent_value,
available_parent_value,
margin,
root_value,
phase,
);
let mut final_value = value;
if let Some(minimum_value) = minimum_value {
if minimum_value > final_value {
final_value = minimum_value;
}
}
if let Some(maximum_value) = maximum_value {
if final_value > maximum_value {
final_value = maximum_value
}
}
final_value
}
pub fn most_fitting_size<'a>(&self, size: &'a f32, available_size: &'a f32) -> &'a f32 {
match self {
Self::Inner | Self::InnerPercentage(_) => available_size,
_ => size,
}
}
}
impl Scaled for Size {
fn scale(&mut self, scale_factor: f32) {
match self {
Size::Pixels(s) => *s *= scale_factor,
Size::DynamicCalculations(calcs) => {
calcs.iter_mut().for_each(|calc| calc.scale(scale_factor));
}
_ => (),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum DynamicCalculation {
Sub,
Mul,
Div,
Add,
OpenParenthesis,
ClosedParenthesis,
Percentage(f32),
RootPercentage(f32),
Pixels(f32),
}
impl Scaled for DynamicCalculation {
fn scale(&mut self, scale_factor: f32) {
if let DynamicCalculation::Pixels(s) = self {
*s *= scale_factor;
}
}
}
impl std::fmt::Display for DynamicCalculation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DynamicCalculation::Sub => f.write_str("-"),
DynamicCalculation::Mul => f.write_str("*"),
DynamicCalculation::Div => f.write_str("/"),
DynamicCalculation::Add => f.write_str("+"),
DynamicCalculation::OpenParenthesis => f.write_str("("),
DynamicCalculation::ClosedParenthesis => f.write_str(")"),
DynamicCalculation::Percentage(p) => f.write_fmt(format_args!("{p}%")),
DynamicCalculation::RootPercentage(p) => f.write_fmt(format_args!("{p}v")),
DynamicCalculation::Pixels(s) => f.write_fmt(format_args!("{s}")),
}
}
}
struct DynamicCalculationEvaluator<'a> {
calcs: Iter<'a, DynamicCalculation>,
parent_value: f32,
root_value: f32,
current: Option<&'a DynamicCalculation>,
}
impl<'a> DynamicCalculationEvaluator<'a> {
pub fn new(calcs: Iter<'a, DynamicCalculation>, parent_value: f32, root_value: f32) -> Self {
Self {
calcs,
parent_value,
root_value,
current: None,
}
}
pub fn evaluate(&mut self) -> Option<f32> {
let value = self.parse_expression(0);
match self.current {
Some(_) => None,
None => value,
}
}
fn parse_expression(&mut self, min_precedence: usize) -> Option<f32> {
self.current = self.calcs.next();
let mut lhs = self.parse_term()?;
while let Some(operator_precedence) = self.operator_precedence() {
if operator_precedence < min_precedence {
return Some(lhs);
}
let operator = self.current?;
let rhs = self.parse_expression(operator_precedence + 1)?;
match operator {
DynamicCalculation::Add => lhs += rhs,
DynamicCalculation::Sub => lhs -= rhs,
DynamicCalculation::Mul => lhs *= rhs,
DynamicCalculation::Div => lhs /= rhs,
_ => unreachable!(),
}
}
Some(lhs)
}
fn parse_term(&mut self) -> Option<f32> {
let prefix = self.parse_prefix()?;
let mut lhs = None;
let mut last_is_separator = true;
while let Some((rhs, seperator)) = self.parse_value() {
if last_is_separator || seperator {
lhs = Some(lhs.unwrap_or(1.0) * rhs);
} else {
return None;
}
last_is_separator = seperator;
}
if let Some(prefix) = prefix {
match prefix {
DynamicCalculation::Add => lhs,
DynamicCalculation::Sub => lhs.map(|v| v * -1.0),
_ => unreachable!("make sure to add the prefix here"),
}
} else {
lhs
}
}
fn parse_value(&mut self) -> Option<(f32, bool)> {
match self.current? {
DynamicCalculation::Percentage(value) => {
self.current = self.calcs.next();
Some(((self.parent_value / 100.0 * value).round(), false))
}
DynamicCalculation::RootPercentage(value) => {
self.current = self.calcs.next();
Some(((self.root_value / 100.0 * value).round(), false))
}
DynamicCalculation::Pixels(value) => {
self.current = self.calcs.next();
Some((*value, false))
}
DynamicCalculation::OpenParenthesis => {
let val = self.parse_expression(0);
if self.current != Some(&DynamicCalculation::ClosedParenthesis) {
return None;
}
self.current = self.calcs.next();
Some((val?, true))
}
_ => None,
}
}
fn parse_prefix(&mut self) -> Option<Option<DynamicCalculation>> {
match self.current? {
DynamicCalculation::Add => {
self.current = self.calcs.next();
Some(Some(DynamicCalculation::Add))
}
DynamicCalculation::Sub => {
self.current = self.calcs.next();
Some(Some(DynamicCalculation::Sub))
}
_ => Some(None),
}
}
fn operator_precedence(&self) -> Option<usize> {
match self.current? {
DynamicCalculation::Add | DynamicCalculation::Sub => Some(1),
DynamicCalculation::Mul | DynamicCalculation::Div => Some(2),
DynamicCalculation::OpenParenthesis => Some(0),
_ => None,
}
}
}
pub fn run_calculations(
calcs: &[DynamicCalculation],
parent_value: f32,
root_value: f32,
) -> Option<f32> {
DynamicCalculationEvaluator::new(calcs.iter(), parent_value, root_value).evaluate()
}