use super::ir::Bbox;
use super::values::as_pair;
use crate::error::Error;
use crate::resolve::{AttrMap, ResolvedValue};
use crate::span::Span;
#[derive(Clone, Copy)]
pub enum Side {
Top,
Bottom,
Left,
Right,
}
#[derive(Clone, Copy)]
pub enum Align {
Start,
Center,
End,
}
#[derive(Clone, Copy, PartialEq)]
pub enum Place {
None,
In,
Out,
On,
}
#[derive(Clone, Copy)]
pub enum Pos {
Coord(f64, f64),
Edge {
side: Side,
align: Align,
place: Place,
},
}
impl Side {
pub fn parse(s: &str) -> Option<Self> {
Some(match s {
"top" => Self::Top,
"bottom" => Self::Bottom,
"left" => Self::Left,
"right" => Self::Right,
_ => return None,
})
}
}
impl Align {
pub fn parse(s: &str) -> Option<Self> {
Some(match s {
"start" | "left" => Self::Start,
"center" => Self::Center,
"end" | "right" => Self::End,
_ => return None,
})
}
}
impl Place {
fn parse(s: &str) -> Option<Self> {
Some(match s {
"none" => Self::None,
"in" => Self::In,
"out" => Self::Out,
"on" => Self::On,
_ => return None,
})
}
pub fn reserves(self) -> bool {
matches!(self, Self::In | Self::Out)
}
}
pub fn read_pos(attrs: &AttrMap, span: Span) -> Result<Option<Pos>, Error> {
if let Some(v) = attrs.get("at") {
let (x, y) = as_pair(v, span)?;
return Ok(Some(Pos::Coord(x, y)));
}
let place = match attrs.get("mount") {
Some(v) => ident(v)
.and_then(Place::parse)
.ok_or_else(|| Error::at(span, "'mount' expects none, in, out, or on"))?,
None => {
if attrs.get("side").is_some() {
return Err(Error::at(
span,
"a bare 'side:' needs a 'mount:' — in, out, or on",
));
}
Place::None
}
};
if place == Place::None {
return Ok(None);
}
let side = match attrs.get("side") {
Some(v) => ident(v)
.and_then(Side::parse)
.ok_or_else(|| Error::at(span, "'side' expects top, bottom, left, or right"))?,
None => Side::Top,
};
let align = match attrs.get("align") {
Some(v) => ident(v)
.and_then(Align::parse)
.ok_or_else(|| Error::at(span, "'align' expects start, center, or end"))?,
None => Align::Center,
};
Ok(Some(Pos::Edge { side, align, place }))
}
pub fn parse_offset(value: &ResolvedValue, span: Span) -> Result<(f64, f64), Error> {
as_pair(value, span)
}
#[derive(Clone, Copy, PartialEq)]
pub enum Role {
Flow,
Reserve,
Absolute,
}
pub fn is_out_band(attrs: &AttrMap) -> bool {
matches!(attrs.get("mount").and_then(ident), Some("out"))
}
pub fn child_role(attrs: &AttrMap, span: Span) -> Result<Role, Error> {
Ok(match read_pos(attrs, span)? {
None => Role::Flow,
Some(Pos::Coord(..)) => Role::Absolute,
Some(Pos::Edge { place, .. }) if place.reserves() => Role::Reserve,
Some(Pos::Edge { .. }) => Role::Absolute,
})
}
pub fn resolve(pos: Pos, parent: Bbox, child: Bbox) -> (f64, f64) {
let (cw, ch) = (child.w(), child.h());
match pos {
Pos::Coord(x, y) => (x, y),
Pos::Edge { side, align, place } => {
let along = |min: f64, max: f64, size: f64, align: Align| match align {
Align::Start => min + size / 2.0,
Align::Center => (min + max) / 2.0,
Align::End => max - size / 2.0,
};
let across = |edge: f64, half: f64, place: Place, outward: f64| match place {
Place::In => edge - outward * half,
Place::Out => edge + outward * half,
Place::On | Place::None => edge,
};
match side {
Side::Top => (
along(parent.min_x, parent.max_x, cw, align),
across(parent.min_y, ch / 2.0, place, -1.0),
),
Side::Bottom => (
along(parent.min_x, parent.max_x, cw, align),
across(parent.max_y, ch / 2.0, place, 1.0),
),
Side::Left => (
across(parent.min_x, cw / 2.0, place, -1.0),
along(parent.min_y, parent.max_y, ch, align),
),
Side::Right => (
across(parent.max_x, cw / 2.0, place, 1.0),
along(parent.min_y, parent.max_y, ch, align),
),
}
}
}
}
fn ident(v: &ResolvedValue) -> Option<&str> {
match v {
ResolvedValue::Ident(s) => Some(s.as_str()),
_ => None,
}
}