use crate::css_parser as css;
use crate::css_parser::CssResult;
use crate::values::length::LengthPercentage;
use crate::values::percentage::{DimensionPercentage, Percentage};
use crate::values::protocol;
#[derive(Clone, PartialEq)]
pub struct Position {
pub x: HorizontalPosition,
pub y: VerticalPosition,
}
impl Position {
pub(crate) fn parse(input: &mut css::Parser) -> CssResult<Position> {
if let Ok(horizontal_pos) = input.try_parse(HorizontalPosition::parse) {
match horizontal_pos {
PositionComponent::Center => {
if let Ok(y) = input.try_parse(VerticalPosition::parse) {
return Ok(Position {
x: PositionComponent::Center,
y,
});
}
let x = input
.try_parse(HorizontalPosition::parse)
.unwrap_or(HorizontalPosition::Center);
let y = VerticalPosition::Center;
return Ok(Position { x, y });
}
PositionComponent::Length(x) => {
if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
let y = VerticalPosition::Side(PositionComponentSide {
side: y_keyword,
offset: None,
});
return Ok(Position {
x: PositionComponent::Length(x),
y,
});
}
if let Ok(y_lp) = input.try_parse(LengthPercentage::parse) {
let y = VerticalPosition::Length(y_lp);
return Ok(Position {
x: PositionComponent::Length(x),
y,
});
}
let y = VerticalPosition::Center;
let _ = input.try_parse(|i| i.expect_ident_matching(b"center"));
return Ok(Position {
x: PositionComponent::Length(x),
y,
});
}
PositionComponent::Side(side) => {
let x_keyword = side.side;
let lp = side.offset;
if input
.try_parse(|i| i.expect_ident_matching(b"center"))
.is_ok()
{
let x = HorizontalPosition::Side(PositionComponentSide {
side: x_keyword,
offset: lp,
});
let y = VerticalPosition::Center;
return Ok(Position { x, y });
}
if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
let y_lp = input.try_parse(LengthPercentage::parse).ok();
let x = HorizontalPosition::Side(PositionComponentSide {
side: x_keyword,
offset: lp,
});
let y = VerticalPosition::Side(PositionComponentSide {
side: y_keyword,
offset: y_lp,
});
return Ok(Position { x, y });
}
let x = HorizontalPosition::Side(PositionComponentSide {
side: x_keyword,
offset: None,
});
let y = if let Some(lp_val) = lp {
VerticalPosition::Length(lp_val)
} else {
VerticalPosition::Center
};
return Ok(Position { x, y });
}
}
}
let y_keyword = VerticalPositionKeyword::parse(input)?;
let lp_and_x_pos = input.try_parse(
|i: &mut css::Parser| -> CssResult<(Option<LengthPercentage>, HorizontalPosition)> {
let y_lp = i.try_parse(LengthPercentage::parse).ok();
if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
let x_lp = i.try_parse(LengthPercentage::parse).ok();
let x_pos = HorizontalPosition::Side(PositionComponentSide {
side: x_keyword,
offset: x_lp,
});
return Ok((y_lp, x_pos));
}
i.expect_ident_matching(b"center")?;
let x_pos = HorizontalPosition::Center;
Ok((y_lp, x_pos))
},
);
if let Ok(tuple) = lp_and_x_pos {
let y_lp = tuple.0;
let x = tuple.1;
let y = VerticalPosition::Side(PositionComponentSide {
side: y_keyword,
offset: y_lp,
});
return Ok(Position { x, y });
}
let x = HorizontalPosition::Center;
let y = VerticalPosition::Side(PositionComponentSide {
side: y_keyword,
offset: None,
});
Ok(Position { x, y })
}
pub(crate) fn to_css(&self, dest: &mut css::Printer) -> Result<(), css::PrintErr> {
if let (PositionComponent::Side(xs), PositionComponent::Length(yl)) = (&self.x, &self.y) {
if xs.side != HorizontalPositionKeyword::Left {
self.x.to_css(dest)?;
dest.write_str(" top ")?;
return yl.to_css(dest);
}
}
if let PositionComponent::Side(xs) = &self.x {
if xs.side != HorizontalPositionKeyword::Left && self.y.is_center() {
self.x.to_css(dest)?;
return dest.write_str(" center");
}
}
if let (PositionComponent::Length(xl), PositionComponent::Side(ys)) = (&self.x, &self.y) {
if ys.side != VerticalPositionKeyword::Top {
dest.write_str("left ")?;
xl.to_css(dest)?;
dest.write_str(" ")?;
return self.y.to_css(dest);
}
}
if self.x.is_center() && self.y.is_center() {
return self.x.to_css(dest);
}
if let PositionComponent::Length(xl) = &self.x {
if self.y.is_center() {
return xl.to_css(dest);
}
}
if let PositionComponent::Side(xs) = &self.x {
if xs.offset.is_none() && self.y.is_center() {
let p: LengthPercentage = xs.side.into_length_percentage();
return p.to_css(dest);
}
}
if let PositionComponent::Side(ys) = &self.y {
if ys.offset.is_none() && self.x.is_center() {
return self.y.to_css(dest);
}
}
if let (PositionComponent::Side(xs), PositionComponent::Side(ys)) = (&self.x, &self.y) {
if xs.offset.is_none() && ys.offset.is_none() {
let x: LengthPercentage = xs.side.into_length_percentage();
let y: LengthPercentage = ys.side.into_length_percentage();
x.to_css(dest)?;
dest.write_str(" ")?;
return y.to_css(dest);
}
}
let zero = LengthPercentage::zero();
let fifty = DimensionPercentage::Percentage(Percentage { v: 0.5 });
let x_len: Option<&LengthPercentage> = 'x_len: {
match &self.x {
PositionComponent::Side(side) => {
if side.side == HorizontalPositionKeyword::Left {
if let Some(offset) = &side.offset {
if offset.is_zero() {
break 'x_len Some(&zero);
} else {
break 'x_len Some(offset);
}
} else {
break 'x_len Some(&zero);
}
}
}
PositionComponent::Length(len) => {
if len.is_zero() {
break 'x_len Some(&zero);
}
}
PositionComponent::Center => break 'x_len Some(&fifty),
}
None
};
let y_len: Option<&LengthPercentage> = 'y_len: {
match &self.y {
PositionComponent::Side(side) => {
if side.side == VerticalPositionKeyword::Top {
if let Some(offset) = &side.offset {
if offset.is_zero() {
break 'y_len Some(&zero);
} else {
break 'y_len Some(offset);
}
} else {
break 'y_len Some(&zero);
}
}
}
PositionComponent::Length(len) => {
if len.is_zero() {
break 'y_len Some(&zero);
}
}
PositionComponent::Center => break 'y_len Some(&fifty),
}
None
};
if let (Some(xl), Some(yl)) = (x_len, y_len) {
xl.to_css(dest)?;
dest.write_str(" ")?;
yl.to_css(dest)
} else {
self.x.to_css(dest)?;
dest.write_str(" ")?;
self.y.to_css(dest)
}
}
pub(crate) fn is_center(&self) -> bool {
self.x.is_center() && self.y.is_center()
}
pub(crate) fn center() -> Position {
Position {
x: PositionComponent::Center,
y: PositionComponent::Center,
}
}
pub(crate) fn is_zero(&self) -> bool {
self.x.is_zero() && self.y.is_zero()
}
}
impl Default for Position {
fn default() -> Self {
Position {
x: HorizontalPosition::Length(DimensionPercentage::Percentage(Percentage { v: 0.0 })),
y: VerticalPosition::Length(DimensionPercentage::Percentage(Percentage { v: 0.0 })),
}
}
}
#[derive(Clone, PartialEq)]
pub struct PositionComponentSide<S> {
pub side: S,
pub offset: Option<LengthPercentage>,
}
#[derive(Clone, PartialEq)]
pub enum PositionComponent<S> {
Center,
Length(LengthPercentage),
Side(PositionComponentSide<S>),
}
impl<S: protocol::Parse + protocol::ToCss + Clone + PartialEq> PositionComponent<S> {
pub(crate) fn is_zero(&self) -> bool {
if let PositionComponent::Length(l) = self {
if l.is_zero() {
return true;
}
}
false
}
pub(crate) fn parse(input: &mut css::Parser) -> CssResult<Self> {
if input
.try_parse(|i: &mut css::Parser| i.expect_ident_matching(b"center"))
.is_ok()
{
return Ok(PositionComponent::Center);
}
if let Ok(lp) = input.try_parse(LengthPercentage::parse) {
return Ok(PositionComponent::Length(lp));
}
let side = S::parse(input)?;
let offset = input.try_parse(LengthPercentage::parse).ok();
Ok(PositionComponent::Side(PositionComponentSide {
side,
offset,
}))
}
pub(crate) fn to_css(&self, dest: &mut css::Printer) -> Result<(), css::PrintErr> {
match self {
PositionComponent::Center => {
if dest.minify {
dest.write_str("50%")
} else {
dest.write_str("center")
}
}
PositionComponent::Length(lp) => lp.to_css(dest),
PositionComponent::Side(s) => {
s.side.to_css(dest)?;
if let Some(lp) = &s.offset {
dest.write_str(" ")?;
lp.to_css(dest)?;
}
Ok(())
}
}
}
pub(crate) fn is_center(&self) -> bool {
match self {
PositionComponent::Center => return true,
PositionComponent::Length(l) => {
if let DimensionPercentage::Percentage(p) = l {
return p.v == 0.5;
}
}
_ => {}
}
false
}
}
#[derive(Clone, Copy, PartialEq, Eq, css::DefineEnumProperty)]
pub enum HorizontalPositionKeyword {
Left,
Right,
}
impl HorizontalPositionKeyword {
pub(crate) fn into_length_percentage(self) -> LengthPercentage {
match self {
HorizontalPositionKeyword::Left => LengthPercentage::zero(),
HorizontalPositionKeyword::Right => {
DimensionPercentage::Percentage(Percentage { v: 1.0 })
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, css::DefineEnumProperty)]
pub enum VerticalPositionKeyword {
Top,
Bottom,
}
impl VerticalPositionKeyword {
pub(crate) fn into_length_percentage(self) -> LengthPercentage {
match self {
VerticalPositionKeyword::Top => LengthPercentage::zero(),
VerticalPositionKeyword::Bottom => {
DimensionPercentage::Percentage(Percentage { v: 1.0 })
}
}
}
}
pub(crate) type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;
pub(crate) type VerticalPosition = PositionComponent<VerticalPositionKeyword>;