Guess I should put this under version control LOL

This commit is contained in:
Ryan McGrath 2019-05-23 22:11:07 -07:00
commit 2035318460
No known key found for this signature in database
GPG key ID: 811674B62B666830
73 changed files with 8836 additions and 0 deletions

23
styles/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "alchemy-styles"
description = "Style parsing and hoisting for Alchemy, the Rust cross-platform GUI framework."
version = "0.1.0"
edition = "2018"
authors = ["Ryan McGrath <ryan@rymc.io>"]
license = "MPL-2.0+"
repository = "https://github.com/ryanmcgrath/alchemy"
categories = ["gui", "rendering::engine", "multimedia"]
keywords = ["gui", "css", "styles", "layout", "ui"]
[badges]
maintenance = { status = "actively-developed" }
[features]
tokenize = ["proc-macro2", "quote"]
parser = ["cssparser"]
[dependencies]
cssparser = { version = "0.25.5", optional = true }
lazy_static = "1.3"
proc-macro2 = { version = "0.4.24", optional = true }
quote = { version = "0.6.10", optional = true }

5
styles/README.md Normal file
View file

@ -0,0 +1,5 @@
# Alchemy-Styles
This crate implements CSS parsing and Flexbox layout. CSS parsing relies on the [CSS Parser from Servo](https://github.com/servo/rust-cssparser). Flexbox is implemented with [Stretch](https://github.com/vislyhq/stretch), albeit currently [a fork by msilgreith](https://github.com/msiglreith/stretch/tree/index), cloned into here to serve a few small changes (a change for more thread safety, and to push appearance based styles that Flexbox doesn't concern itself with). Down the road, I could see this not including Stretch inline.
## Questions, Comments?
Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).

667
styles/src/color.rs Normal file
View file

@ -0,0 +1,667 @@
//! Implements `Color`. Heavily based on the `Color` module in Servo's CSS parser, but tweaked
//! for (what I believe) is a friendlier API, and to separate out the parsing into a separate
//! module.
#[cfg(feature="parser")]
use std::{fmt, f32::consts::PI};
#[cfg(feature="parser")]
use cssparser::{BasicParseError, ParseError, Parser, ToCss, Token};
/// A color with red, green, blue, and alpha components, in a byte each.
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Color {
/// The red component.
pub red: u8,
/// The green component.
pub green: u8,
/// The blue component.
pub blue: u8,
/// The alpha component.
pub alpha: u8,
}
impl Default for Color {
fn default() -> Color {
Color { red: 0, green: 0, blue: 0, alpha: 0 }
}
}
impl Color {
/// Constructs a new Color value from float components. It expects the red,
/// green, blue and alpha channels in that order, and all values will be
/// clamped to the 0.0 ... 1.0 range.
#[inline]
pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
Self::new(
clamp_unit_f32(red),
clamp_unit_f32(green),
clamp_unit_f32(blue),
clamp_unit_f32(alpha),
)
}
/// Returns a transparent color.
#[inline]
pub fn transparent() -> Self {
Self::new(0, 0, 0, 0)
}
/// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
#[inline]
pub fn new(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
Color {
red: red,
green: green,
blue: blue,
alpha: alpha,
}
}
/// Returns the red channel in a floating point number form, from 0 to 1.
#[inline]
pub fn red_f32(&self) -> f32 {
self.red as f32 / 255.0
}
/// Returns the green channel in a floating point number form, from 0 to 1.
#[inline]
pub fn green_f32(&self) -> f32 {
self.green as f32 / 255.0
}
/// Returns the blue channel in a floating point number form, from 0 to 1.
#[inline]
pub fn blue_f32(&self) -> f32 {
self.blue as f32 / 255.0
}
/// Returns the alpha channel in a floating point number form, from 0 to 1.
#[inline]
pub fn alpha_f32(&self) -> f32 {
self.alpha as f32 / 255.0
}
/// Parse a <color> value, per CSS Color Module Level 3.
///
/// FIXME(#2) Deprecated CSS2 System Colors are not supported yet.
#[cfg(feature="parser")]
pub fn parse_with<'i, 't, ComponentParser>(
component_parser: &ComponentParser,
input: &mut Parser<'i, 't>,
) -> Result<Color, ParseError<'i, ComponentParser::Error>>
where
ComponentParser: ColorComponentParser<'i>,
{
// FIXME: remove clone() when lifetimes are non-lexical
let location = input.current_source_location();
let token = input.next()?.clone();
match token {
Token::Hash(ref value) | Token::IDHash(ref value) => {
Color::parse_hash(value.as_bytes())
}
Token::Ident(ref value) => parse_color_keyword(&*value),
Token::Function(ref name) => {
return input.parse_nested_block(|arguments| {
parse_color_function(component_parser, &*name, arguments)
})
}
_ => Err(()),
}
.map_err(|()| location.new_unexpected_token_error(token))
}
/// Parse a <color> value, per CSS Color Module Level 3.
#[cfg(feature="parser")]
pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Color, BasicParseError<'i>> {
let component_parser = DefaultComponentParser;
Self::parse_with(&component_parser, input).map_err(ParseError::basic)
}
/// Parse a color hash, without the leading '#' character.
#[cfg(feature="parser")]
#[inline]
pub fn parse_hash(value: &[u8]) -> Result<Self, ()> {
match value.len() {
8 => Ok(rgba(
from_hex(value[0])? * 16 + from_hex(value[1])?,
from_hex(value[2])? * 16 + from_hex(value[3])?,
from_hex(value[4])? * 16 + from_hex(value[5])?,
from_hex(value[6])? * 16 + from_hex(value[7])?,
)),
6 => Ok(rgb(
from_hex(value[0])? * 16 + from_hex(value[1])?,
from_hex(value[2])? * 16 + from_hex(value[3])?,
from_hex(value[4])? * 16 + from_hex(value[5])?,
)),
4 => Ok(rgba(
from_hex(value[0])? * 17,
from_hex(value[1])? * 17,
from_hex(value[2])? * 17,
from_hex(value[3])? * 17,
)),
3 => Ok(rgb(
from_hex(value[0])? * 17,
from_hex(value[1])? * 17,
from_hex(value[2])? * 17,
)),
_ => Err(()),
}
}
}
#[cfg(feature="parser")]
impl ToCss for Color {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
let serialize_alpha = self.alpha != 255;
dest.write_str(if serialize_alpha { "rgba(" } else { "rgb(" })?;
self.red.to_css(dest)?;
dest.write_str(", ")?;
self.green.to_css(dest)?;
dest.write_str(", ")?;
self.blue.to_css(dest)?;
if serialize_alpha {
dest.write_str(", ")?;
// Try first with two decimal places, then with three.
let mut rounded_alpha = (self.alpha_f32() * 100.).round() / 100.;
if clamp_unit_f32(rounded_alpha) != self.alpha {
rounded_alpha = (self.alpha_f32() * 1000.).round() / 1000.;
}
rounded_alpha.to_css(dest)?;
}
dest.write_char(')')
}
}
/// Either a number or a percentage.
#[cfg(feature="parser")]
pub enum NumberOrPercentage {
/// `<number>`.
Number {
/// The numeric value parsed, as a float.
value: f32,
},
/// `<percentage>`
Percentage {
/// The value as a float, divided by 100 so that the nominal range is
/// 0.0 to 1.0.
unit_value: f32,
},
}
#[cfg(feature="parser")]
impl NumberOrPercentage {
fn unit_value(&self) -> f32 {
match *self {
NumberOrPercentage::Number { value } => value,
NumberOrPercentage::Percentage { unit_value } => unit_value,
}
}
}
/// Either an angle or a number.
#[cfg(feature="parser")]
pub enum AngleOrNumber {
/// `<number>`.
Number {
/// The numeric value parsed, as a float.
value: f32,
},
/// `<angle>`
Angle {
/// The value as a number of degrees.
degrees: f32,
},
}
#[cfg(feature="parser")]
impl AngleOrNumber {
fn degrees(&self) -> f32 {
match *self {
AngleOrNumber::Number { value } => value,
AngleOrNumber::Angle { degrees } => degrees,
}
}
}
/// A trait that can be used to hook into how `cssparser` parses color
/// components, with the intention of implementing more complicated behavior.
///
/// For example, this is used by Servo to support calc() in color.
#[cfg(feature="parser")]
pub trait ColorComponentParser<'i> {
/// A custom error type that can be returned from the parsing functions.
type Error: 'i;
/// Parse an `<angle>` or `<number>`.
///
/// Returns the result in degrees.
fn parse_angle_or_number<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
let location = input.current_source_location();
Ok(match *input.next()? {
Token::Number { value, .. } => AngleOrNumber::Number { value },
Token::Dimension {
value: v, ref unit, ..
} => {
let degrees = match_ignore_ascii_case! { &*unit,
"deg" => v,
"grad" => v * 360. / 400.,
"rad" => v * 360. / (2. * PI),
"turn" => v * 360.,
_ => return Err(location.new_unexpected_token_error(Token::Ident(unit.clone()))),
};
AngleOrNumber::Angle { degrees }
}
ref t => return Err(location.new_unexpected_token_error(t.clone())),
})
}
/// Parse a `<percentage>` value.
///
/// Returns the result in a number from 0.0 to 1.0.
fn parse_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<f32, ParseError<'i, Self::Error>> {
input.expect_percentage().map_err(From::from)
}
/// Parse a `<number>` value.
fn parse_number<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<f32, ParseError<'i, Self::Error>> {
input.expect_number().map_err(From::from)
}
/// Parse a `<number>` value or a `<percentage>` value.
fn parse_number_or_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
let location = input.current_source_location();
Ok(match *input.next()? {
Token::Number { value, .. } => NumberOrPercentage::Number { value },
Token::Percentage { unit_value, .. } => NumberOrPercentage::Percentage { unit_value },
ref t => return Err(location.new_unexpected_token_error(t.clone())),
})
}
}
#[cfg(feature="parser")]
struct DefaultComponentParser;
#[cfg(feature="parser")]
impl<'i> ColorComponentParser<'i> for DefaultComponentParser {
type Error = ();
}
#[cfg(feature="parser")]
#[inline]
fn rgb(red: u8, green: u8, blue: u8) -> Color {
rgba(red, green, blue, 255)
}
#[cfg(feature="parser")]
#[inline]
fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
Color::new(red, green, blue, alpha)
}
/// Return the named color with the given name.
///
/// Matching is case-insensitive in the ASCII range.
/// CSS escaping (if relevant) should be resolved before calling this function.
/// (For example, the value of an `Ident` token is fine.)
#[cfg(feature="parser")]
#[inline]
pub fn parse_color_keyword(ident: &str) -> Result<Color, ()> {
macro_rules! rgb {
($red: expr, $green: expr, $blue: expr) => {
Color {
red: $red,
green: $green,
blue: $blue,
alpha: 255,
}
};
}
ascii_case_insensitive_phf_map! {
keyword -> Color = {
"black" => rgb!(0, 0, 0),
"silver" => rgb!(192, 192, 192),
"gray" => rgb!(128, 128, 128),
"white" => rgb!(255, 255, 255),
"maroon" => rgb!(128, 0, 0),
"red" => rgb!(255, 0, 0),
"purple" => rgb!(128, 0, 128),
"fuchsia" => rgb!(255, 0, 255),
"green" => rgb!(0, 128, 0),
"lime" => rgb!(0, 255, 0),
"olive" => rgb!(128, 128, 0),
"yellow" => rgb!(255, 255, 0),
"navy" => rgb!(0, 0, 128),
"blue" => rgb!(0, 0, 255),
"teal" => rgb!(0, 128, 128),
"aqua" => rgb!(0, 255, 255),
"aliceblue" => rgb!(240, 248, 255),
"antiquewhite" => rgb!(250, 235, 215),
"aquamarine" => rgb!(127, 255, 212),
"azure" => rgb!(240, 255, 255),
"beige" => rgb!(245, 245, 220),
"bisque" => rgb!(255, 228, 196),
"blanchedalmond" => rgb!(255, 235, 205),
"blueviolet" => rgb!(138, 43, 226),
"brown" => rgb!(165, 42, 42),
"burlywood" => rgb!(222, 184, 135),
"cadetblue" => rgb!(95, 158, 160),
"chartreuse" => rgb!(127, 255, 0),
"chocolate" => rgb!(210, 105, 30),
"coral" => rgb!(255, 127, 80),
"cornflowerblue" => rgb!(100, 149, 237),
"cornsilk" => rgb!(255, 248, 220),
"crimson" => rgb!(220, 20, 60),
"cyan" => rgb!(0, 255, 255),
"darkblue" => rgb!(0, 0, 139),
"darkcyan" => rgb!(0, 139, 139),
"darkgoldenrod" => rgb!(184, 134, 11),
"darkgray" => rgb!(169, 169, 169),
"darkgreen" => rgb!(0, 100, 0),
"darkgrey" => rgb!(169, 169, 169),
"darkkhaki" => rgb!(189, 183, 107),
"darkmagenta" => rgb!(139, 0, 139),
"darkolivegreen" => rgb!(85, 107, 47),
"darkorange" => rgb!(255, 140, 0),
"darkorchid" => rgb!(153, 50, 204),
"darkred" => rgb!(139, 0, 0),
"darksalmon" => rgb!(233, 150, 122),
"darkseagreen" => rgb!(143, 188, 143),
"darkslateblue" => rgb!(72, 61, 139),
"darkslategray" => rgb!(47, 79, 79),
"darkslategrey" => rgb!(47, 79, 79),
"darkturquoise" => rgb!(0, 206, 209),
"darkviolet" => rgb!(148, 0, 211),
"deeppink" => rgb!(255, 20, 147),
"deepskyblue" => rgb!(0, 191, 255),
"dimgray" => rgb!(105, 105, 105),
"dimgrey" => rgb!(105, 105, 105),
"dodgerblue" => rgb!(30, 144, 255),
"firebrick" => rgb!(178, 34, 34),
"floralwhite" => rgb!(255, 250, 240),
"forestgreen" => rgb!(34, 139, 34),
"gainsboro" => rgb!(220, 220, 220),
"ghostwhite" => rgb!(248, 248, 255),
"gold" => rgb!(255, 215, 0),
"goldenrod" => rgb!(218, 165, 32),
"greenyellow" => rgb!(173, 255, 47),
"grey" => rgb!(128, 128, 128),
"honeydew" => rgb!(240, 255, 240),
"hotpink" => rgb!(255, 105, 180),
"indianred" => rgb!(205, 92, 92),
"indigo" => rgb!(75, 0, 130),
"ivory" => rgb!(255, 255, 240),
"khaki" => rgb!(240, 230, 140),
"lavender" => rgb!(230, 230, 250),
"lavenderblush" => rgb!(255, 240, 245),
"lawngreen" => rgb!(124, 252, 0),
"lemonchiffon" => rgb!(255, 250, 205),
"lightblue" => rgb!(173, 216, 230),
"lightcoral" => rgb!(240, 128, 128),
"lightcyan" => rgb!(224, 255, 255),
"lightgoldenrodyellow" => rgb!(250, 250, 210),
"lightgray" => rgb!(211, 211, 211),
"lightgreen" => rgb!(144, 238, 144),
"lightgrey" => rgb!(211, 211, 211),
"lightpink" => rgb!(255, 182, 193),
"lightsalmon" => rgb!(255, 160, 122),
"lightseagreen" => rgb!(32, 178, 170),
"lightskyblue" => rgb!(135, 206, 250),
"lightslategray" => rgb!(119, 136, 153),
"lightslategrey" => rgb!(119, 136, 153),
"lightsteelblue" => rgb!(176, 196, 222),
"lightyellow" => rgb!(255, 255, 224),
"limegreen" => rgb!(50, 205, 50),
"linen" => rgb!(250, 240, 230),
"magenta" => rgb!(255, 0, 255),
"mediumaquamarine" => rgb!(102, 205, 170),
"mediumblue" => rgb!(0, 0, 205),
"mediumorchid" => rgb!(186, 85, 211),
"mediumpurple" => rgb!(147, 112, 219),
"mediumseagreen" => rgb!(60, 179, 113),
"mediumslateblue" => rgb!(123, 104, 238),
"mediumspringgreen" => rgb!(0, 250, 154),
"mediumturquoise" => rgb!(72, 209, 204),
"mediumvioletred" => rgb!(199, 21, 133),
"midnightblue" => rgb!(25, 25, 112),
"mintcream" => rgb!(245, 255, 250),
"mistyrose" => rgb!(255, 228, 225),
"moccasin" => rgb!(255, 228, 181),
"navajowhite" => rgb!(255, 222, 173),
"oldlace" => rgb!(253, 245, 230),
"olivedrab" => rgb!(107, 142, 35),
"orange" => rgb!(255, 165, 0),
"orangered" => rgb!(255, 69, 0),
"orchid" => rgb!(218, 112, 214),
"palegoldenrod" => rgb!(238, 232, 170),
"palegreen" => rgb!(152, 251, 152),
"paleturquoise" => rgb!(175, 238, 238),
"palevioletred" => rgb!(219, 112, 147),
"papayawhip" => rgb!(255, 239, 213),
"peachpuff" => rgb!(255, 218, 185),
"peru" => rgb!(205, 133, 63),
"pink" => rgb!(255, 192, 203),
"plum" => rgb!(221, 160, 221),
"powderblue" => rgb!(176, 224, 230),
"rebeccapurple" => rgb!(102, 51, 153),
"rosybrown" => rgb!(188, 143, 143),
"royalblue" => rgb!(65, 105, 225),
"saddlebrown" => rgb!(139, 69, 19),
"salmon" => rgb!(250, 128, 114),
"sandybrown" => rgb!(244, 164, 96),
"seagreen" => rgb!(46, 139, 87),
"seashell" => rgb!(255, 245, 238),
"sienna" => rgb!(160, 82, 45),
"skyblue" => rgb!(135, 206, 235),
"slateblue" => rgb!(106, 90, 205),
"slategray" => rgb!(112, 128, 144),
"slategrey" => rgb!(112, 128, 144),
"snow" => rgb!(255, 250, 250),
"springgreen" => rgb!(0, 255, 127),
"steelblue" => rgb!(70, 130, 180),
"tan" => rgb!(210, 180, 140),
"thistle" => rgb!(216, 191, 216),
"tomato" => rgb!(255, 99, 71),
"turquoise" => rgb!(64, 224, 208),
"violet" => rgb!(238, 130, 238),
"wheat" => rgb!(245, 222, 179),
"whitesmoke" => rgb!(245, 245, 245),
"yellowgreen" => rgb!(154, 205, 50),
"transparent" => Color { red: 0, green: 0, blue: 0, alpha: 0 }
}
}
keyword(ident).cloned().ok_or(())
}
#[cfg(feature="parser")]
#[inline]
fn from_hex(c: u8) -> Result<u8, ()> {
match c {
b'0'...b'9' => Ok(c - b'0'),
b'a'...b'f' => Ok(c - b'a' + 10),
b'A'...b'F' => Ok(c - b'A' + 10),
_ => Err(()),
}
}
fn clamp_unit_f32(val: f32) -> u8 {
// Whilst scaling by 256 and flooring would provide
// an equal distribution of integers to percentage inputs,
// this is not what Gecko does so we instead multiply by 255
// and round (adding 0.5 and flooring is equivalent to rounding)
//
// Chrome does something similar for the alpha value, but not
// the rgb values.
//
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1340484
//
// Clamping to 256 and rounding after would let 1.0 map to 256, and
// `256.0_f32 as u8` is undefined behavior:
//
// https://github.com/rust-lang/rust/issues/10184
clamp_floor_256_f32(val * 255.)
}
fn clamp_floor_256_f32(val: f32) -> u8 {
val.round().max(0.).min(255.) as u8
}
#[cfg(feature="parser")]
#[inline]
fn parse_color_function<'i, 't, ComponentParser>(
component_parser: &ComponentParser,
name: &str,
arguments: &mut Parser<'i, 't>,
) -> Result<Color, ParseError<'i, ComponentParser::Error>>
where
ComponentParser: ColorComponentParser<'i>,
{
let (red, green, blue, uses_commas) = match_ignore_ascii_case! { name,
"rgb" | "rgba" => parse_rgb_components_rgb(component_parser, arguments)?,
"hsl" | "hsla" => parse_rgb_components_hsl(component_parser, arguments)?,
_ => return Err(arguments.new_unexpected_token_error(Token::Ident(name.to_owned().into()))),
};
let alpha = if !arguments.is_exhausted() {
if uses_commas {
arguments.expect_comma()?;
} else {
arguments.expect_delim('/')?;
};
clamp_unit_f32(
component_parser
.parse_number_or_percentage(arguments)?
.unit_value(),
)
} else {
255
};
arguments.expect_exhausted()?;
Ok(rgba(red, green, blue, alpha))
}
#[cfg(feature="parser")]
#[inline]
fn parse_rgb_components_rgb<'i, 't, ComponentParser>(
component_parser: &ComponentParser,
arguments: &mut Parser<'i, 't>,
) -> Result<(u8, u8, u8, bool), ParseError<'i, ComponentParser::Error>>
where
ComponentParser: ColorComponentParser<'i>,
{
// Either integers or percentages, but all the same type.
// https://drafts.csswg.org/css-color/#rgb-functions
let (red, is_number) = match component_parser.parse_number_or_percentage(arguments)? {
NumberOrPercentage::Number { value } => (clamp_floor_256_f32(value), true),
NumberOrPercentage::Percentage { unit_value } => (clamp_unit_f32(unit_value), false),
};
let uses_commas = arguments.try_parse(|i| i.expect_comma()).is_ok();
let green;
let blue;
if is_number {
green = clamp_floor_256_f32(component_parser.parse_number(arguments)?);
if uses_commas {
arguments.expect_comma()?;
}
blue = clamp_floor_256_f32(component_parser.parse_number(arguments)?);
} else {
green = clamp_unit_f32(component_parser.parse_percentage(arguments)?);
if uses_commas {
arguments.expect_comma()?;
}
blue = clamp_unit_f32(component_parser.parse_percentage(arguments)?);
}
Ok((red, green, blue, uses_commas))
}
#[cfg(feature="parser")]
#[inline]
fn parse_rgb_components_hsl<'i, 't, ComponentParser>(
component_parser: &ComponentParser,
arguments: &mut Parser<'i, 't>,
) -> Result<(u8, u8, u8, bool), ParseError<'i, ComponentParser::Error>>
where
ComponentParser: ColorComponentParser<'i>,
{
// Hue given as an angle
// https://drafts.csswg.org/css-values/#angles
let hue_degrees = component_parser.parse_angle_or_number(arguments)?.degrees();
// Subtract an integer before rounding, to avoid some rounding errors:
let hue_normalized_degrees = hue_degrees - 360. * (hue_degrees / 360.).floor();
let hue = hue_normalized_degrees / 360.;
// Saturation and lightness are clamped to 0% ... 100%
// https://drafts.csswg.org/css-color/#the-hsl-notation
let uses_commas = arguments.try_parse(|i| i.expect_comma()).is_ok();
let saturation = component_parser.parse_percentage(arguments)?;
let saturation = saturation.max(0.).min(1.);
if uses_commas {
arguments.expect_comma()?;
}
let lightness = component_parser.parse_percentage(arguments)?;
let lightness = lightness.max(0.).min(1.);
// https://drafts.csswg.org/css-color/#hsl-color
// except with h pre-multiplied by 3, to avoid some rounding errors.
fn hue_to_rgb(m1: f32, m2: f32, mut h3: f32) -> f32 {
if h3 < 0. {
h3 += 3.
}
if h3 > 3. {
h3 -= 3.
}
if h3 * 2. < 1. {
m1 + (m2 - m1) * h3 * 2.
} else if h3 * 2. < 3. {
m2
} else if h3 < 2. {
m1 + (m2 - m1) * (2. - h3) * 2.
} else {
m1
}
}
let m2 = if lightness <= 0.5 {
lightness * (saturation + 1.)
} else {
lightness + saturation - lightness * saturation
};
let m1 = lightness * 2. - m2;
let hue_times_3 = hue * 3.;
let red = clamp_unit_f32(hue_to_rgb(m1, m2, hue_times_3 + 1.));
let green = clamp_unit_f32(hue_to_rgb(m1, m2, hue_times_3));
let blue = clamp_unit_f32(hue_to_rgb(m1, m2, hue_times_3 - 1.));
return Ok((red, green, blue, uses_commas));
}

15
styles/src/lib.rs Normal file
View file

@ -0,0 +1,15 @@
//! This crate hoists various styles and layout parameters for implementing
//! Flexbox in Alchemy. For all intents and purposes, you can essentially consider
//! this to be the root crate for Alchemy, as just about everything ends up using it.
#[cfg(feature="parser")]
#[macro_use] pub extern crate cssparser;
mod stretch;
pub use stretch::{geometry, node, number, result, Stretch, Error};
pub mod color;
pub mod styles;
#[cfg(feature="parser")]
pub mod styles_parser;

1363
styles/src/stretch/algo.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,136 @@
use core::ops::Add;
use crate::number::Number;
use crate::styles as style;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Rect<T> {
pub start: T,
pub end: T,
pub top: T,
pub bottom: T,
}
impl<T> Rect<T> {
pub(crate) fn map<R, F>(self, f: F) -> Rect<R>
where
F: Fn(T) -> R,
{
Rect { start: f(self.start), end: f(self.end), top: f(self.top), bottom: f(self.bottom) }
}
}
impl<T> Rect<T>
where
T: Add<Output = T> + Copy + Clone,
{
pub(crate) fn horizontal(&self) -> T {
self.start + self.end
}
pub(crate) fn vertical(&self) -> T {
self.top + self.bottom
}
pub(crate) fn main(&self, direction: style::FlexDirection) -> T {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.start + self.end,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.top + self.bottom,
}
}
pub(crate) fn cross(&self, direction: style::FlexDirection) -> T {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.top + self.bottom,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.start + self.end,
}
}
}
impl<T> Rect<T>
where
T: Copy + Clone,
{
pub(crate) fn main_start(&self, direction: style::FlexDirection) -> T {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.start,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.top,
}
}
pub(crate) fn main_end(&self, direction: style::FlexDirection) -> T {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.end,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.bottom,
}
}
pub(crate) fn cross_start(&self, direction: style::FlexDirection) -> T {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.top,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.start,
}
}
pub(crate) fn cross_end(&self, direction: style::FlexDirection) -> T {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.bottom,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.end,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Size<T> {
pub width: T,
pub height: T,
}
impl Size<()> {
pub fn undefined() -> Size<Number> {
Size { width: Number::Undefined, height: Number::Undefined }
}
}
impl<T> Size<T> {
pub(crate) fn map<R, F>(self, f: F) -> Size<R>
where
F: Fn(T) -> R,
{
Size { width: f(self.width), height: f(self.height) }
}
pub(crate) fn set_main(&mut self, direction: style::FlexDirection, value: T) {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.width = value,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.height = value,
}
}
pub(crate) fn set_cross(&mut self, direction: style::FlexDirection, value: T) {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.height = value,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.width = value,
}
}
pub(crate) fn main(self, direction: style::FlexDirection) -> T {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.width,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.height,
}
}
pub(crate) fn cross(self, direction: style::FlexDirection) -> T {
match direction {
style::FlexDirection::Row | style::FlexDirection::RowReverse => self.height,
style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.width,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Point<T> {
pub x: T,
pub y: T,
}

36
styles/src/stretch/id.rs Normal file
View file

@ -0,0 +1,36 @@
//! Identifier for a Node
//!
//!
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Id {
id: u32,
generation: u32,
}
pub(crate) struct Allocator {
new_id: u32,
free_ids: Vec<Id>,
}
impl Allocator {
pub fn new() -> Self {
Allocator { new_id: 0, free_ids: Vec::new() }
}
pub fn allocate(&mut self) -> Id {
// TODO: better balancing
match self.free_ids.pop() {
Some(id) => Id { id: id.id, generation: id.generation + 1 },
None => {
let id = self.new_id;
self.new_id += 1;
Id { id, generation: 0 }
}
}
}
pub fn free(&mut self, ids: &[Id]) {
self.free_ids.extend(ids);
}
}

35
styles/src/stretch/mod.rs Normal file
View file

@ -0,0 +1,35 @@
pub mod geometry;
pub mod node;
pub mod number;
pub mod result;
mod algo;
mod id;
pub use crate::node::Stretch;
use core::any::Any;
#[derive(Debug)]
pub enum Error {
InvalidNode(node::Node),
Measure(Box<Any>),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
Error::InvalidNode(ref node) => write!(f, "Invalid node {:?}", node),
Error::Measure(_) => write!(f, "Error during measurement"),
}
}
}
impl std::error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::InvalidNode(_) => "The node is not part of the stretch instance",
Error::Measure(_) => "Error occurred inside a measurement function",
}
}
}

239
styles/src/stretch/node.rs Normal file
View file

@ -0,0 +1,239 @@
use core::any::Any;
use std::collections::HashMap;
use std::ops::Drop;
use std::sync::Mutex;
use lazy_static::lazy_static;
use crate::geometry::Size;
use crate::stretch::id;
use crate::number::Number;
use crate::result::{Cache, Layout};
use crate::styles::*;
use crate::Error;
type MeasureFunc = Box<Fn(Size<Number>) -> Result<Size<f32>, Box<Any>> + Send + Sync + 'static>;
lazy_static! {
/// Global stretch instance id allocator.
static ref INSTANCE_ALLOCATOR: Mutex<id::Allocator> = Mutex::new(id::Allocator::new());
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Node {
instance: id::Id,
local: id::Id,
}
pub(crate) struct Storage<T>(HashMap<Node, T>);
impl<T> Storage<T> {
pub fn new() -> Self {
Storage(HashMap::new())
}
pub fn get(&self, node: Node) -> Result<&T, Error> {
match self.0.get(&node) {
Some(v) => Ok(v),
None => Err(Error::InvalidNode(node)),
}
}
pub fn get_mut(&mut self, node: Node) -> Result<&mut T, Error> {
match self.0.get_mut(&node) {
Some(v) => Ok(v),
None => Err(Error::InvalidNode(node)),
}
}
pub fn insert(&mut self, node: Node, value: T) -> Option<T> {
self.0.insert(node, value)
}
}
impl<T> std::ops::Index<&Node> for Storage<T> {
type Output = T;
fn index(&self, idx: &Node) -> &T {
&(self.0)[idx]
}
}
pub struct Stretch {
id: id::Id,
nodes: id::Allocator,
pub(crate) style: Storage<Style>,
pub(crate) parents: Storage<Vec<Node>>,
pub(crate) children: Storage<Vec<Node>>,
pub(crate) measure: Storage<Option<MeasureFunc>>,
pub(crate) layout: Storage<Layout>,
pub(crate) layout_cache: Storage<Option<Cache>>,
pub(crate) is_dirty: Storage<bool>,
}
impl Stretch {
pub fn new() -> Self {
Stretch {
id: INSTANCE_ALLOCATOR.lock().unwrap().allocate(),
nodes: id::Allocator::new(),
style: Storage::new(),
parents: Storage::new(),
children: Storage::new(),
measure: Storage::new(),
layout: Storage::new(),
layout_cache: Storage::new(),
is_dirty: Storage::new(),
}
}
fn allocate_node(&mut self) -> Node {
let local = self.nodes.allocate();
Node { instance: self.id, local }
}
pub fn new_leaf(&mut self, style: Style, measure: MeasureFunc) -> Node {
let node = self.allocate_node();
self.style.insert(node, style);
self.parents.insert(node, Vec::with_capacity(1));
self.children.insert(node, Vec::with_capacity(0));
self.measure.insert(node, Some(measure));
self.layout.insert(node, Layout::new());
self.layout_cache.insert(node, None);
self.is_dirty.insert(node, true);
node
}
pub fn new_node(&mut self, style: Style, children: Vec<Node>) -> Result<Node, Error> {
let node = self.allocate_node();
for child in &children {
self.parents.get_mut(*child)?.push(node);
}
self.style.insert(node, style);
self.parents.insert(node, Vec::with_capacity(1));
self.children.insert(node, children);
self.measure.insert(node, None);
self.layout.insert(node, Layout::new());
self.layout_cache.insert(node, None);
self.is_dirty.insert(node, true);
Ok(node)
}
pub fn set_measure(&mut self, node: Node, measure: Option<MeasureFunc>) -> Result<(), Error> {
*self.measure.get_mut(node)? = measure;
self.mark_dirty(node)?;
Ok(())
}
pub fn add_child(&mut self, node: Node, child: Node) -> Result<(), Error> {
self.parents.get_mut(child)?.push(node);
self.children.get_mut(node)?.push(child);
self.mark_dirty(node)
}
pub fn set_children(&mut self, node: Node, children: Vec<Node>) -> Result<(), Error> {
// Remove node as parent from all its current children.
for child in self.children.get(node)? {
self.parents.get_mut(*child)?.retain(|p| *p != node);
}
*self.children.get_mut(node)? = Vec::with_capacity(children.len());
// Build up relation node <-> child
for child in children {
self.parents.get_mut(child)?.push(node);
self.children.get_mut(node)?.push(child);
}
self.mark_dirty(node)
}
pub fn remove_child(&mut self, node: Node, child: Node) -> Result<Node, Error> {
match self.children(node)?.iter().position(|n| *n == child) {
Some(index) => self.remove_child_at_index(node, index),
None => Err(Error::InvalidNode(child)),
}
}
pub fn remove_child_at_index(&mut self, node: Node, index: usize) -> Result<Node, Error> {
let child = self.children.get_mut(node)?.remove(index);
self.parents.get_mut(child)?.retain(|p| *p != node);
self.mark_dirty(node)?;
Ok(child)
}
pub fn replace_child_at_index(&mut self, node: Node, index: usize, child: Node) -> Result<Node, Error> {
self.parents.get_mut(child)?.push(node);
let old_child = std::mem::replace(&mut self.children.get_mut(node)?[index], child);
self.parents.get_mut(old_child)?.retain(|p| *p != node);
self.mark_dirty(node)?;
Ok(old_child)
}
pub fn children(&self, node: Node) -> Result<Vec<Node>, Error> {
self.children.get(node).map(Clone::clone)
}
pub fn child_count(&self, node: Node) -> Result<usize, Error> {
self.children.get(node).map(Vec::len)
}
pub fn set_style(&mut self, node: Node, style: Style) -> Result<(), Error> {
*self.style.get_mut(node)? = style;
self.mark_dirty(node)
}
pub fn style(&self, node: Node) -> Result<&Style, Error> {
self.style.get(node)
}
pub fn layout(&self, node: Node) -> Result<&Layout, Error> {
self.layout.get(node)
}
pub fn mark_dirty(&mut self, node: Node) -> Result<(), Error> {
fn mark_dirty_impl(
node: Node,
layout_cache: &mut Storage<Option<Cache>>,
is_dirty: &mut Storage<bool>,
parents: &Storage<Vec<Node>>,
) -> Result<(), Error> {
*layout_cache.get_mut(node)? = None;
*is_dirty.get_mut(node)? = true;
for parent in parents.get(node)? {
mark_dirty_impl(*parent, layout_cache, is_dirty, parents)?;
}
Ok(())
}
mark_dirty_impl(node, &mut self.layout_cache, &mut self.is_dirty, &self.parents)
}
pub fn dirty(&self, node: Node) -> Result<bool, Error> {
self.is_dirty.get(node).map(|v| *v)
}
pub fn compute_layout(&mut self, node: Node, size: Size<Number>) -> Result<(), Error> {
match self.layout.get(node) {
Ok(_) => self.compute(node, size).map_err(|err| Error::Measure(err)),
_ => Err(Error::InvalidNode(node)),
}
}
}
impl Drop for Stretch {
fn drop(&mut self) {
INSTANCE_ALLOCATOR.lock().unwrap().free(&[self.id]);
}
}

View file

@ -0,0 +1,220 @@
use core::ops;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Number {
Defined(f32),
Undefined,
}
pub trait ToNumber {
fn to_number(self) -> Number;
}
pub trait OrElse<T> {
fn or_else(self, other: T) -> T;
}
impl Default for Number {
fn default() -> Number {
Number::Undefined
}
}
impl OrElse<f32> for Number {
fn or_else(self, other: f32) -> f32 {
match self {
Number::Defined(val) => val,
Number::Undefined => other,
}
}
}
impl OrElse<Number> for Number {
fn or_else(self, other: Number) -> Number {
match self {
Number::Defined(_) => self,
Number::Undefined => other,
}
}
}
impl Number {
pub fn is_defined(self) -> bool {
match self {
Number::Defined(_) => true,
Number::Undefined => false,
}
}
pub fn is_undefined(self) -> bool {
match self {
Number::Defined(_) => false,
Number::Undefined => true,
}
}
}
pub trait MinMax<In, Out> {
fn maybe_min(self, rhs: In) -> Out;
fn maybe_max(self, rhs: In) -> Out;
}
impl MinMax<Number, Number> for Number {
fn maybe_min(self, rhs: Number) -> Number {
match self {
Number::Defined(val) => match rhs {
Number::Defined(other) => Number::Defined(val.min(other)),
Number::Undefined => self,
},
Number::Undefined => Number::Undefined,
}
}
fn maybe_max(self, rhs: Number) -> Number {
match self {
Number::Defined(val) => match rhs {
Number::Defined(other) => Number::Defined(val.max(other)),
Number::Undefined => self,
},
Number::Undefined => Number::Undefined,
}
}
}
impl MinMax<f32, Number> for Number {
fn maybe_min(self, rhs: f32) -> Number {
match self {
Number::Defined(val) => Number::Defined(val.min(rhs)),
Number::Undefined => Number::Undefined,
}
}
fn maybe_max(self, rhs: f32) -> Number {
match self {
Number::Defined(val) => Number::Defined(val.max(rhs)),
Number::Undefined => Number::Undefined,
}
}
}
impl MinMax<Number, f32> for f32 {
fn maybe_min(self, rhs: Number) -> f32 {
match rhs {
Number::Defined(val) => self.min(val),
Number::Undefined => self,
}
}
fn maybe_max(self, rhs: Number) -> f32 {
match rhs {
Number::Defined(val) => self.max(val),
Number::Undefined => self,
}
}
}
impl ToNumber for f32 {
fn to_number(self) -> Number {
Number::Defined(self)
}
}
impl ops::Add<f32> for Number {
type Output = Number;
fn add(self, rhs: f32) -> Number {
match self {
Number::Defined(val) => Number::Defined(val + rhs),
Number::Undefined => Number::Undefined,
}
}
}
impl ops::Add<Number> for Number {
type Output = Number;
fn add(self, rhs: Number) -> Number {
match self {
Number::Defined(val) => match rhs {
Number::Defined(other) => Number::Defined(val + other),
Number::Undefined => self,
},
Number::Undefined => Number::Undefined,
}
}
}
impl ops::Sub<f32> for Number {
type Output = Number;
fn sub(self, rhs: f32) -> Number {
match self {
Number::Defined(val) => Number::Defined(val - rhs),
Number::Undefined => Number::Undefined,
}
}
}
impl ops::Sub<Number> for Number {
type Output = Number;
fn sub(self, rhs: Number) -> Number {
match self {
Number::Defined(val) => match rhs {
Number::Defined(other) => Number::Defined(val - other),
Number::Undefined => self,
},
Number::Undefined => Number::Undefined,
}
}
}
impl ops::Mul<f32> for Number {
type Output = Number;
fn mul(self, rhs: f32) -> Number {
match self {
Number::Defined(val) => Number::Defined(val * rhs),
Number::Undefined => Number::Undefined,
}
}
}
impl ops::Mul<Number> for Number {
type Output = Number;
fn mul(self, rhs: Number) -> Number {
match self {
Number::Defined(val) => match rhs {
Number::Defined(other) => Number::Defined(val * other),
Number::Undefined => self,
},
Number::Undefined => Number::Undefined,
}
}
}
impl ops::Div<f32> for Number {
type Output = Number;
fn div(self, rhs: f32) -> Number {
match self {
Number::Defined(val) => Number::Defined(val / rhs),
Number::Undefined => Number::Undefined,
}
}
}
impl ops::Div<Number> for Number {
type Output = Number;
fn div(self, rhs: Number) -> Number {
match self {
Number::Defined(val) => match rhs {
Number::Defined(other) => Number::Defined(val / other),
Number::Undefined => self,
},
Number::Undefined => Number::Undefined,
}
}
}

View file

@ -0,0 +1,25 @@
use crate::stretch::algo::ComputeResult;
use crate::geometry::{Point, Size};
use crate::number::Number;
#[derive(Copy, Debug, Clone)]
pub struct Layout {
pub(crate) order: u32,
pub size: Size<f32>,
pub location: Point<f32>,
}
impl Layout {
pub(crate) fn new() -> Self {
Layout { order: 0, size: Size { width: 0.0, height: 0.0 }, location: Point { x: 0.0, y: 0.0 } }
}
}
#[derive(Debug, Clone)]
pub(crate) struct Cache {
pub(crate) node_size: Size<Number>,
pub(crate) parent_size: Size<Number>,
pub(crate) perform_layout: bool,
pub(crate) result: ComputeResult,
}

737
styles/src/styles.rs Normal file
View file

@ -0,0 +1,737 @@
/// Implements the various `Style` types used for computing Flexbox layouts,
/// along with appearance-based styles (`Color`s, etc).
#[cfg(feature="tokenize")]
use proc_macro2::{TokenStream, Ident, Span};
#[cfg(feature="tokenize")]
use quote::{quote, ToTokens};
pub use crate::geometry::{Rect, Size};
pub use crate::number::Number;
pub use crate::color::Color;
pub use crate::stretch::result::Layout;
/// Describes how items should be aligned.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum AlignItems {
FlexStart,
FlexEnd,
Center,
Baseline,
Stretch,
}
impl Default for AlignItems {
fn default() -> AlignItems {
AlignItems::Stretch
}
}
/// Describes how this item should be aligned.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum AlignSelf {
Auto,
FlexStart,
FlexEnd,
Center,
Baseline,
Stretch,
}
impl Default for AlignSelf {
fn default() -> AlignSelf {
AlignSelf::Auto
}
}
/// Describes how content should be aligned.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum AlignContent {
FlexStart,
FlexEnd,
Center,
Stretch,
SpaceBetween,
SpaceAround,
}
impl Default for AlignContent {
fn default() -> AlignContent {
AlignContent::Stretch
}
}
/// Describes how things should flow - particularly important for start/end positions.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Direction {
Inherit,
LTR,
RTL,
}
impl Default for Direction {
fn default() -> Direction {
Direction::Inherit
}
}
/// Describes whether an item is visible or not.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Display {
Flex,
None,
}
impl Default for Display {
fn default() -> Display {
Display::Flex
}
}
/// Describes how items should be aligned.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum FlexDirection {
Row,
Column,
RowReverse,
ColumnReverse,
}
impl Default for FlexDirection {
fn default() -> FlexDirection {
FlexDirection::Row
}
}
impl FlexDirection {
/// Checks if this is a row.
pub(crate) fn is_row(self) -> bool {
self == FlexDirection::Row || self == FlexDirection::RowReverse
}
/// Checks if this is a column.
pub(crate) fn is_column(self) -> bool {
self == FlexDirection::Column || self == FlexDirection::ColumnReverse
}
/// Checks if this is a reversed direction.
pub(crate) fn is_reverse(self) -> bool {
self == FlexDirection::RowReverse || self == FlexDirection::ColumnReverse
}
}
/// Describes how content should be justified.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum JustifyContent {
FlexStart,
FlexEnd,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly,
}
impl Default for JustifyContent {
fn default() -> JustifyContent {
JustifyContent::FlexStart
}
}
/// Describes how content should overflow.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Overflow {
Visible,
Hidden,
Scroll,
}
impl Default for Overflow {
fn default() -> Overflow {
Overflow::Visible
}
}
/// Describes how content should be positioned.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum PositionType {
Relative,
Absolute,
}
impl Default for PositionType {
fn default() -> PositionType {
PositionType::Relative
}
}
/// Describes how content should wrap.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum FlexWrap {
NoWrap,
Wrap,
WrapReverse,
}
impl Default for FlexWrap {
fn default() -> FlexWrap {
FlexWrap::NoWrap
}
}
/// Describes a Dimension; automatic, undefined, or a value.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Dimension {
Undefined,
Auto,
Points(f32),
Percent(f32),
}
impl Default for Dimension {
fn default() -> Dimension {
Dimension::Undefined
}
}
impl Dimension {
/// Internal method for Stretch.
pub(crate) fn resolve(self, parent_width: Number) -> Number {
match self {
Dimension::Points(points) => Number::Defined(points),
Dimension::Percent(percent) => parent_width * percent,
_ => Number::Undefined,
}
}
/// Whether this Dimension is defined by a value or not.
pub(crate) fn is_defined(self) -> bool {
match self {
Dimension::Points(_) => true,
Dimension::Percent(_) => true,
_ => false,
}
}
}
impl Default for Rect<Dimension> {
fn default() -> Rect<Dimension> {
Rect { start: Default::default(), end: Default::default(), top: Default::default(), bottom: Default::default() }
}
}
impl Default for Size<Dimension> {
fn default() -> Size<Dimension> {
Size { width: Dimension::Auto, height: Dimension::Auto }
}
}
/// Describes the backface-visibility for a view. This may be removed in a later release.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum BackfaceVisibility {
Visible,
Hidden
}
impl Default for BackfaceVisibility {
fn default() -> BackfaceVisibility {
BackfaceVisibility::Visible
}
}
/// Describes a font style.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum FontStyle {
Normal,
Italic,
Oblique
}
impl Default for FontStyle {
fn default() -> FontStyle {
FontStyle::Normal
}
}
/// Describes a font weight.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum FontWeight {
Normal,
Bold
}
impl Default for FontWeight {
fn default() -> FontWeight {
FontWeight::Normal
}
}
/// Describes how text should be aligned.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum TextAlignment {
Auto,
Left,
Right,
Center,
Justify
}
impl Default for TextAlignment {
fn default() -> TextAlignment {
TextAlignment::Auto
}
}
/// Describes a border style.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum BorderStyle {
None, // The CSS value is None, but it's a reserved term in Rust ;P
Hidden,
Solid
}
impl Default for BorderStyle {
fn default() -> BorderStyle {
BorderStyle::None
}
}
/// Describes how a Font Family
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum FontFamily {
SansSerif // @TODO This is tricky because of &str/String/Copy. Revisit later.
}
impl Default for FontFamily {
fn default() -> Self {
FontFamily::SansSerif
}
}
/// `Style` is passed into the Stretch Flexbox rendering system to produce a computed
/// `Layout`. This is also passed to native nodes, to transform into per-platform style
/// commands.
#[derive(Copy, Clone, Debug)]
pub struct Style {
pub display: Display,
pub position_type: PositionType,
pub direction: Direction,
pub flex_direction: FlexDirection,
pub flex_wrap: FlexWrap,
pub overflow: Overflow,
pub align_items: AlignItems,
pub align_self: AlignSelf,
pub align_content: AlignContent,
pub justify_content: JustifyContent,
pub position: Rect<Dimension>,
pub margin: Rect<Dimension>,
pub padding: Rect<Dimension>,
pub border: Rect<Dimension>,
pub flex_grow: f32,
pub flex_shrink: f32,
pub flex_basis: Dimension,
pub size: Size<Dimension>,
pub min_size: Size<Dimension>,
pub max_size: Size<Dimension>,
pub aspect_ratio: Number,
// Appearance-based styles
pub background_color: Color
}
impl Default for Style {
fn default() -> Style {
Style {
display: Default::default(),
position_type: Default::default(),
direction: Default::default(),
flex_direction: Default::default(),
flex_wrap: Default::default(),
overflow: Default::default(),
align_items: Default::default(),
align_self: Default::default(),
align_content: Default::default(),
justify_content: Default::default(),
position: Default::default(),
margin: Default::default(),
padding: Default::default(),
border: Default::default(),
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Dimension::Auto,
size: Default::default(),
min_size: Default::default(),
max_size: Default::default(),
aspect_ratio: Default::default(),
background_color: Color::transparent()
}
}
}
impl Style {
/// Determines the minimum main size, given flex direction.
pub(crate) fn min_main_size(&self, direction: FlexDirection) -> Dimension {
match direction {
FlexDirection::Row | FlexDirection::RowReverse => self.min_size.width,
FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.height,
}
}
/// Determines the maximum main size, given flex direction.
pub(crate) fn max_main_size(&self, direction: FlexDirection) -> Dimension {
match direction {
FlexDirection::Row | FlexDirection::RowReverse => self.max_size.width,
FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.height,
}
}
/// Determines the main margin start, given flex direction.
pub(crate) fn main_margin_start(&self, direction: FlexDirection) -> Dimension {
match direction {
FlexDirection::Row | FlexDirection::RowReverse => self.margin.start,
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.top,
}
}
/// Determines the main margin end, given flex direction.
pub(crate) fn main_margin_end(&self, direction: FlexDirection) -> Dimension {
match direction {
FlexDirection::Row | FlexDirection::RowReverse => self.margin.end,
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.bottom,
}
}
/// Determines the cross size, given flex direction.
pub(crate) fn cross_size(&self, direction: FlexDirection) -> Dimension {
match direction {
FlexDirection::Row | FlexDirection::RowReverse => self.size.height,
FlexDirection::Column | FlexDirection::ColumnReverse => self.size.width,
}
}
/// Determines the minimum cross size, given flex direction.
pub(crate) fn min_cross_size(&self, direction: FlexDirection) -> Dimension {
match direction {
FlexDirection::Row | FlexDirection::RowReverse => self.min_size.height,
FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.width,
}
}
/// Determines the maximum cross size, given flex direction.
pub(crate) fn max_cross_size(&self, direction: FlexDirection) -> Dimension {
match direction {
FlexDirection::Row | FlexDirection::RowReverse => self.max_size.height,
FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.width,
}
}
/// Determines the cross margin start, given flex direction.
pub(crate) fn cross_margin_start(&self, direction: FlexDirection) -> Dimension {
match direction {
FlexDirection::Row | FlexDirection::RowReverse => self.margin.top,
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.start,
}
}
/// Determines the cross margin end, given flex direction.
pub(crate) fn cross_margin_end(&self, direction: FlexDirection) -> Dimension {
match direction {
FlexDirection::Row | FlexDirection::RowReverse => self.margin.bottom,
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.end,
}
}
/// Determines the inherited align_self style, given a parent `&Style`.
pub(crate) fn align_self(&self, parent: &Style) -> AlignSelf {
if self.align_self == AlignSelf::Auto {
match parent.align_items {
AlignItems::FlexStart => AlignSelf::FlexStart,
AlignItems::FlexEnd => AlignSelf::FlexEnd,
AlignItems::Center => AlignSelf::Center,
AlignItems::Baseline => AlignSelf::Baseline,
AlignItems::Stretch => AlignSelf::Stretch,
}
} else {
self.align_self
}
}
}
/// These exist purely for use in the parser code.
///
/// A `Style` is what's used for a node; `Styles` are what's parsed and stored.
/// At render-time, the rendering engine takes n styles and reduces them down into 1 `Style`
/// that's applied to the node in question.
#[derive(Debug)]
pub enum Styles {
AlignContent(AlignContent),
AlignItems(AlignItems),
AlignSelf(AlignSelf),
AspectRatio(Number),
BackfaceVisibility(BackfaceVisibility),
BackgroundColor(Color),
BorderColor(Color),
BorderEndColor(Color),
BorderBottomColor(Color),
BorderLeftColor(Color),
BorderRightColor(Color),
BorderTopColor(Color),
BorderStartColor(Color),
BorderStyle(BorderStyle),
BorderEndStyle(BorderStyle),
BorderBottomStyle(BorderStyle),
BorderLeftStyle(BorderStyle),
BorderRightStyle(BorderStyle),
BorderTopStyle(BorderStyle),
BorderStartStyle(BorderStyle),
BorderWidth(f32),
BorderEndWidth(f32),
BorderBottomWidth(f32),
BorderLeftWidth(f32),
BorderRightWidth(f32),
BorderTopWidth(f32),
BorderStartWidth(f32),
BorderRadius(f32),
BorderBottomEndRadius(f32),
BorderBottomLeftRadius(f32),
BorderBottomRightRadius(f32),
BorderBottomStartRadius(f32),
BorderTopLeftRadius(f32),
BorderTopRightRadius(f32),
BorderTopEndRadius(f32),
BorderTopStartRadius(f32),
Bottom(f32),
Direction(Direction),
Display(Display),
End(f32),
FlexBasis(f32),
FlexDirection(FlexDirection),
FlexGrow(f32),
FlexShrink(f32),
FlexWrap(FlexWrap),
FontFamily(FontFamily),
FontLineHeight(f32),
FontSize(f32),
FontStyle(FontStyle),
FontWeight(FontWeight),
Height(f32),
JustifyContent(JustifyContent),
Left(f32),
MarginBottom(f32),
MarginEnd(f32),
MarginLeft(f32),
MarginRight(f32),
MarginStart(f32),
MarginTop(f32),
MaxHeight(f32),
MaxWidth(f32),
MinHeight(f32),
MinWidth(f32),
Opacity(f32),
Overflow(Overflow),
PaddingBottom(f32),
PaddingEnd(f32),
PaddingLeft(f32),
PaddingRight(f32),
PaddingStart(f32),
PaddingTop(f32),
PositionType(PositionType),
Right(f32),
Start(f32),
TextAlignment(TextAlignment),
TextColor(Color),
TextDecorationColor(Color),
TextShadowColor(Color),
TintColor(Color),
Top(f32),
Width(f32)
}
/// A method for tokenizing a `Color` for a given attribute (e.g, `BackgroundColor`).
#[cfg(feature="tokenize")]
fn color_tokens(tokens: &mut TokenStream, color: &Color, style: &str) {
let red = color.red;
let green = color.green;
let blue = color.blue;
let alpha = color.alpha;
let s = Ident::new(style, Span::call_site());
tokens.extend(quote!(Styles::#s(Color {
red: #red,
green: #green,
blue: #blue,
alpha: #alpha
})));
}
/// Converts `Styles` into tokenized `Styles` representations, for use in the `styles! {}` macro.
#[cfg(feature="tokenize")]
impl ToTokens for Styles {
fn to_tokens(&self, tokens: &mut TokenStream) { match self {
Styles::AlignContent(align_content) => { match align_content {
AlignContent::FlexStart => tokens.extend(quote!(Styles::AlignContent(AlignContent::FlexStart))),
AlignContent::FlexEnd => tokens.extend(quote!(Styles::AlignContent(AlignContent::FlexEnd))),
AlignContent::Center => tokens.extend(quote!(Styles::AlignContent(AlignContent::Center))),
AlignContent::Stretch => tokens.extend(quote!(Styles::AlignContent(AlignContent::Stretch))),
AlignContent::SpaceAround => tokens.extend(quote!(Styles::AlignContent(AlignContent::SpaceAround))),
AlignContent::SpaceBetween => tokens.extend(quote!(Styles::AlignContent(AlignContent::SpaceBetween)))
}},
Styles::AlignItems(align_items) => { match align_items {
AlignItems::FlexStart => tokens.extend(quote!(Styles::AlignItems(AlignItems::FlexStart))),
AlignItems::FlexEnd => tokens.extend(quote!(Styles::AlignItems(AlignItems::FlexEnd))),
AlignItems::Center => tokens.extend(quote!(Styles::AlignItems(AlignItems::Center))),
AlignItems::Baseline => tokens.extend(quote!(Styles::AlignItems(AlignItems::Baseline))),
AlignItems::Stretch => tokens.extend(quote!(Styles::AlignItems(AlignItems::Stretch)))
}},
Styles::AlignSelf(align_self) => { match align_self {
AlignSelf::Auto => tokens.extend(quote!(Styles::AlignSelf(AlignSelf::Auto))),
AlignSelf::FlexStart => tokens.extend(quote!(Styles::AlignSelf(AlignSelf::FlexStart))),
AlignSelf::FlexEnd => tokens.extend(quote!(Styles::AlignSelf(AlignSelf::FlexEnd))),
AlignSelf::Center => tokens.extend(quote!(Styles::AlignSelf(AlignSelf::Center))),
AlignSelf::Baseline => tokens.extend(quote!(Styles::AlignSelf(AlignSelf::Baseline))),
AlignSelf::Stretch => tokens.extend(quote!(Styles::AlignSelf(AlignSelf::Stretch)))
}},
Styles::AspectRatio(_) => {},
Styles::BackfaceVisibility(visibility) => { match visibility {
BackfaceVisibility::Visible => tokens.extend(quote!(Styles::BackfaceVisibility(BackfaceVisibility::Visible))),
BackfaceVisibility::Hidden => tokens.extend(quote!(Styles::BackfaceVisibility(BackfaceVisibility::Hidden)))
}},
Styles::BackgroundColor(color) => color_tokens(tokens, color, "BackgroundColor"),
Styles::BorderColor(color) => color_tokens(tokens, color, "BorderColor"),
Styles::BorderEndColor(color) => color_tokens(tokens, color, "BorderEndColor"),
Styles::BorderBottomColor(color) => color_tokens(tokens, color, "BorderBottomColor"),
Styles::BorderLeftColor(color) => color_tokens(tokens, color, "BorderLeftColor"),
Styles::BorderRightColor(color) => color_tokens(tokens, color, "BorderRightColor"),
Styles::BorderTopColor(color) => color_tokens(tokens, color, "BorderTopColor"),
Styles::BorderStartColor(color) => color_tokens(tokens, color, "BorderStartColor"),
Styles::BorderStyle(_) => {},
Styles::BorderEndStyle(_) => {},
Styles::BorderBottomStyle(_) => {},
Styles::BorderLeftStyle(_) => {},
Styles::BorderRightStyle(_) => {},
Styles::BorderTopStyle(_) => {},
Styles::BorderStartStyle(_) => {},
Styles::BorderWidth(border_width) => tokens.extend(quote!(Styles::BorderWidth(#border_width))),
Styles::BorderEndWidth(border_end_width) => tokens.extend(quote!(Styles::BorderEndWidth(#border_end_width))),
Styles::BorderBottomWidth(border_bottom_width) => tokens.extend(quote!(Styles::BorderBottomWidth(#border_bottom_width))),
Styles::BorderLeftWidth(border_left_width) => tokens.extend(quote!(Styles::BorderLeftWidth(#border_left_width))),
Styles::BorderRightWidth(border_right_width) => tokens.extend(quote!(Styles::BorderRightWidth(#border_right_width))),
Styles::BorderTopWidth(border_top_width) => tokens.extend(quote!(Styles::BorderTopWidth(#border_top_width))),
Styles::BorderStartWidth(border_start_width) => tokens.extend(quote!(Styles::BorderStartWidth(#border_start_width))),
Styles::BorderRadius(border_radius) => tokens.extend(quote!(Styles::BorderRadius(#border_radius))),
Styles::BorderBottomEndRadius(border_bottom_end_radius) => tokens.extend(quote!(Styles::BorderBottomEndRadius(#border_bottom_end_radius))),
Styles::BorderBottomLeftRadius(border_bottom_left_radius) => tokens.extend(quote!(Styles::BorderBottomLeftRadius(#border_bottom_left_radius))),
Styles::BorderBottomRightRadius(border_bottom_right_radius) => tokens.extend(quote!(Styles::BorderBottomRightRadius(#border_bottom_right_radius))),
Styles::BorderBottomStartRadius(border_bottom_start_radius) => tokens.extend(quote!(Styles::BorderBottomStartRadius(#border_bottom_start_radius))),
Styles::BorderTopLeftRadius(border_top_left_radius) => tokens.extend(quote!(Styles::BorderTopLeftRadius(#border_top_left_radius))),
Styles::BorderTopRightRadius(border_top_right_radius) => tokens.extend(quote!(Styles::BorderTopRightRadius(#border_top_right_radius))),
Styles::BorderTopEndRadius(border_top_end_radius) => tokens.extend(quote!(Styles::BorderTopEndRadius(#border_top_end_radius))),
Styles::BorderTopStartRadius(border_top_start_radius) => tokens.extend(quote!(Styles::BorderTopStartRadius(#border_top_start_radius))),
Styles::Bottom(bottom) => tokens.extend(quote!(Styles::Bottom(#bottom))),
Styles::Direction(direction) => { match direction {
Direction::Inherit => tokens.extend(quote!(Styles::Direction(Direction::Inherit))),
Direction::LTR => tokens.extend(quote!(Styles::Direction(Direction::LTR))),
Direction::RTL => tokens.extend(quote!(Styles::Direction(Direction::RTL)))
}},
Styles::Display(display) => { match display {
Display::Flex => tokens.extend(quote!(Styles::Display(Display::Flex))),
Display::None => tokens.extend(quote!(Styles::Display(Display::None)))
}},
Styles::End(end) => tokens.extend(quote!(Styles::End(#end))),
Styles::FlexBasis(flex_basis) => tokens.extend(quote!(Styles::FlexBasis(#flex_basis))),
Styles::FlexDirection(direction) => { match direction {
FlexDirection::Row => tokens.extend(quote!(Styles::FlexDirection(FlexDirection::Row))),
FlexDirection::Column => tokens.extend(quote!(Styles::FlexDirection(FlexDirection::Column))),
FlexDirection::RowReverse => tokens.extend(quote!(Styles::FlexDirection(FlexDirection::RowReverse))),
FlexDirection::ColumnReverse => tokens.extend(quote!(Styles::FlexDirection(FlexDirection::ColumnReverse)))
}},
Styles::FlexGrow(flex_grow) => tokens.extend(quote!(Styles::FlexGrow(#flex_grow))),
Styles::FlexShrink(flex_shrink) => tokens.extend(quote!(Styles::FlexShrink(#flex_shrink))),
Styles::FlexWrap(wrap) => { match wrap {
FlexWrap::NoWrap => tokens.extend(quote!(Styles::FlexWrap(FlexWrap::NoWrap))),
FlexWrap::Wrap => tokens.extend(quote!(Styles::FlexWrap(FlexWrap::Wrap))),
FlexWrap::WrapReverse => tokens.extend(quote!(Styles::FlexWrap(FlexWrap::WrapReverse)))
}},
Styles::FontFamily(_family) => {},
Styles::FontLineHeight(line_height) => tokens.extend(quote!(Styles::LineHeight(#line_height))),
Styles::FontSize(font_size) => tokens.extend(quote!(Styles::FontSize(#font_size))),
Styles::FontStyle(_style) => {},
Styles::FontWeight(_weight) => {},
Styles::Height(height) => tokens.extend(quote!(Styles::Height(#height))),
Styles::JustifyContent(justify) => { match justify {
JustifyContent::FlexStart => tokens.extend(quote!(Styles::JustifyContent(JustifyContent::FlexStart))),
JustifyContent::FlexEnd => tokens.extend(quote!(Styles::JustifyContent(JustifyContent::FlexEnd))),
JustifyContent::Center => tokens.extend(quote!(Styles::JustifyContent(JustifyContent::Center))),
JustifyContent::SpaceBetween => tokens.extend(quote!(Styles::JustifyContent(JustifyContent::SpaceBetween))),
JustifyContent::SpaceAround => tokens.extend(quote!(Styles::JustifyContent(JustifyContent::SpaceAround))),
JustifyContent::SpaceEvenly => tokens.extend(quote!(Styles::JustifyContent(JustifyContent::SpaceEvenly)))
}},
Styles::Left(left) => tokens.extend(quote!(Styles::Left(#left))),
Styles::MarginBottom(margin_bottom) => tokens.extend(quote!(Styles::MarginBottom(#margin_bottom))),
Styles::MarginEnd(margin_end) => tokens.extend(quote!(Styles::MarginEnd(#margin_end))),
Styles::MarginLeft(margin_left) => tokens.extend(quote!(Styles::MarginLeft(#margin_left))),
Styles::MarginRight(margin_right) => tokens.extend(quote!(Styles::MarginRight(#margin_right))),
Styles::MarginStart(margin_start) => tokens.extend(quote!(Styles::MarginStart(#margin_start))),
Styles::MarginTop(top) => tokens.extend(quote!(Styles::Top(#top))),
Styles::MaxHeight(max_height) => tokens.extend(quote!(Styles::MaxHeight(#max_height))),
Styles::MaxWidth(max_width) => tokens.extend(quote!(Styles::MaxWidth(#max_width))),
Styles::MinHeight(min_height) => tokens.extend(quote!(Styles::MinHeight(#min_height))),
Styles::MinWidth(min_width) => tokens.extend(quote!(Styles::MinWidth(#min_width))),
Styles::Opacity(opacity) => tokens.extend(quote!(Styles::Opacity(#opacity))),
Styles::Overflow(overflow) => { match overflow {
Overflow::Visible => tokens.extend(quote!(Styles::Overflow(Overflow::Visible))),
Overflow::Hidden => tokens.extend(quote!(Styles::Overflow(Overflow::Hidden))),
Overflow::Scroll => tokens.extend(quote!(Styles::Overflow(Overflow::Scroll)))
}},
Styles::PaddingBottom(padding_bottom) => tokens.extend(quote!(Styles::PaddingBottom(#padding_bottom))),
Styles::PaddingEnd(padding_end) => tokens.extend(quote!(Styles::PaddingEnd(#padding_end))),
Styles::PaddingLeft(padding_left) => tokens.extend(quote!(Styles::PaddingLeft(#padding_left))),
Styles::PaddingRight(padding_right) => tokens.extend(quote!(Styles::PaddingRight(#padding_right))),
Styles::PaddingStart(padding_start) => tokens.extend(quote!(Styles::PaddingStart(#padding_start))),
Styles::PaddingTop(padding_top) => tokens.extend(quote!(Styles::PaddingTop(#padding_top))),
Styles::PositionType(position_type) => { match position_type {
PositionType::Relative => tokens.extend(quote!(Styles::PositionType(PositionType::Relative))),
PositionType::Absolute => tokens.extend(quote!(Styles::PositionType(PositionType::Absolute)))
}},
Styles::Right(right) => tokens.extend(quote!(Styles::Right(#right))),
Styles::Start(start) => tokens.extend(quote!(Styles::Start(#start))),
Styles::TextAlignment(alignment) => { match alignment {
TextAlignment::Auto => tokens.extend(quote!(Styles::TextAlignment(TextAlignment::Auto))),
TextAlignment::Left => tokens.extend(quote!(Styles::TextAlignment(TextAlignment::Left))),
TextAlignment::Right => tokens.extend(quote!(Styles::TextAlignment(TextAlignment::Right))),
TextAlignment::Center => tokens.extend(quote!(Styles::TextAlignment(TextAlignment::Center))),
TextAlignment::Justify => tokens.extend(quote!(Styles::TextAlignment(TextAlignment::Justify)))
}},
Styles::TextColor(color) => color_tokens(tokens, color, "TextColor"),
Styles::TextDecorationColor(color) => color_tokens(tokens, color, "TextDecorationColor"),
Styles::TextShadowColor(color) => color_tokens(tokens, color, "TextShadowColor"),
Styles::TintColor(color) => color_tokens(tokens, color, "TintColor"),
Styles::Top(top) => tokens.extend(quote!(Styles::Top(#top))),
Styles::Width(width) => tokens.extend(quote!(Styles::Width(#width)))
}}
}

304
styles/src/styles_parser.rs Normal file
View file

@ -0,0 +1,304 @@
//! CSS parsing logic. Mostly relies from the rust-cssparser crate,
//! slightly modified to fit the `Styles` structure we want internally.
use cssparser::{
AtRuleParser, BasicParseError, CowRcStr,
DeclarationListParser, DeclarationParser,
Parser, ParseError, QualifiedRuleParser,
SourceLocation, Token
};
use crate::styles::*;
/// Represents a style rule, a `key: [values...];` pair.
#[derive(Debug)]
pub struct Rule {
pub key: String,
pub styles: Vec<Styles>
}
/// The parser itself.
#[derive(Debug)]
pub struct RuleParser;
/// Some type information for our parser.
impl<'i> AtRuleParser<'i> for RuleParser {
type PreludeBlock = ();
type PreludeNoBlock = ();
type AtRule = Rule;
type Error = BasicParseError<'i>;
}
/// The actual work our parser does. Walks style rules and attempts to
/// extract the key/value pairings from a given stylesheet string.
impl<'i> QualifiedRuleParser<'i> for RuleParser {
type Prelude = String;
type QualifiedRule = Rule;
type Error = BasicParseError<'i>;
/// Parses out the selector.
fn parse_prelude<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
let location = input.current_source_location();
let selector = match input.next()? {
Token::Ident(ref element_name) => element_name.to_string(),
t => { return Err(location.new_unexpected_token_error(t.clone())); }
};
// If there's a next, someone is writing their code assuming cascading. Let's... warn them.
/*match input.next()? {
Ok(_) => {},
Err(e) => {}
};*/
Ok(selector)
}
/// Parses the block (`{...}`) into a Rule struct.
fn parse_block<'t>(
&mut self,
key: Self::Prelude,
_location: SourceLocation,
input: &mut Parser<'i, 't>,
) -> Result<Self::QualifiedRule, ParseError<'i, Self::Error>> {
let styles = DeclarationListParser::new(input, StyleParser {}).collect::<Vec<_>>();
Ok(Rule {
key: key,
styles: styles.into_iter().filter_map(|decl| {
if !decl.is_ok() {
eprintln!("{:?}", decl);
}
decl.ok()
}).collect()
})
}
}
/// Contains logic for matching CSS attributes to their `Styles` counterpart.
#[derive(Debug)]
pub struct StyleParser;
/// Types, etc.
impl<'i> AtRuleParser<'i> for StyleParser {
type PreludeBlock = ();
type PreludeNoBlock = ();
type AtRule = Styles;
type Error = BasicParseError<'i>;
}
/// A utility method for dereferencing a value, to make some code later on a bit more clean.
fn ident<'a>(token: &'a Token) -> &'a str {
match token {
Token::Ident(ref value) => &*value,
_ => ""
}
}
impl<'i> DeclarationParser<'i> for StyleParser {
type Declaration = Styles;
type Error = BasicParseError<'i>;
/// Parses a value (e.g, `background-color: #307ace;`) into a `Styles` value.
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<Self::Declaration, ParseError<'i, Self::Error>> {
let style = match &*name {
"align-content" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"flex-start" => Styles::AlignContent(AlignContent::FlexStart),
"flex-end" => Styles::AlignContent(AlignContent::FlexEnd),
"center" => Styles::AlignContent(AlignContent::Center),
"stretch" => Styles::AlignContent(AlignContent::Stretch),
"space-between" => Styles::AlignContent(AlignContent::SpaceBetween),
"space-around" => Styles::AlignContent(AlignContent::SpaceAround),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"align-items" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"flex-start" => Styles::AlignItems(AlignItems::FlexStart),
"flex-end" => Styles::AlignItems(AlignItems::FlexEnd),
"center" => Styles::AlignItems(AlignItems::Center),
"baseline" => Styles::AlignItems(AlignItems::Baseline),
"stretch" => Styles::AlignItems(AlignItems::Stretch),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"align_self" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"auto" => Styles::AlignSelf(AlignSelf::Auto),
"flex-start" => Styles::AlignSelf(AlignSelf::FlexStart),
"flex-end" => Styles::AlignSelf(AlignSelf::FlexEnd),
"center" => Styles::AlignSelf(AlignSelf::Center),
"baseline" => Styles::AlignSelf(AlignSelf::Baseline),
"stretch" => Styles::AlignSelf(AlignSelf::Stretch),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
// @TODO: Aspect Ratio... could be string, no? Should this be handled better?
"aspect-ratio" => Styles::AspectRatio(Number::Defined(parse_floaty_mcfloatface_value(input)?)),
"backface-visibility" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"visible" => Styles::BackfaceVisibility(BackfaceVisibility::Visible),
"hidden" => Styles::BackfaceVisibility(BackfaceVisibility::Hidden),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"background-color" => Styles::BackgroundColor(Color::parse(input)?),
// Border values~
"border-color" => Styles::BorderColor(Color::parse(input)?),
"border-top-color" => Styles::BorderTopColor(Color::parse(input)?),
"border-bottom-color" => Styles::BorderBottomColor(Color::parse(input)?),
"border-left-color" => Styles::BorderLeftColor(Color::parse(input)?),
"border-right-color" => Styles::BorderRightColor(Color::parse(input)?),
"bottom" => Styles::Bottom(parse_floaty_mcfloatface_value(input)?),
"color" => Styles::TextColor(Color::parse(input)?),
"direction" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"inherit" => Styles::Direction(Direction::Inherit),
"ltr" => Styles::Direction(Direction::LTR),
"rtl" => Styles::Direction(Direction::RTL),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"display" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"flex" => Styles::Display(Display::Flex),
"none" => Styles::Display(Display::None),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"end" => Styles::End(parse_floaty_mcfloatface_value(input)?),
"flex-basis" => Styles::FlexBasis(parse_floaty_mcfloatface_value(input)?),
"flex-direction" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"row" => Styles::FlexDirection(FlexDirection::Row),
"row-reverse" => Styles::FlexDirection(FlexDirection::RowReverse),
"column" => Styles::FlexDirection(FlexDirection::Column),
"column-reverse" => Styles::FlexDirection(FlexDirection::ColumnReverse),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"flex-grow" => Styles::FlexGrow(parse_floaty_mcfloatface_value(input)?),
"flex-shrink" => Styles::FlexShrink(parse_floaty_mcfloatface_value(input)?),
"flex-wrap" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"no-wrap" => Styles::FlexWrap(FlexWrap::NoWrap),
"wrap" => Styles::FlexWrap(FlexWrap::Wrap),
"wrap-reverse" => Styles::FlexWrap(FlexWrap::WrapReverse),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
//FontFamily(FontFamily),
"font-size" => Styles::FontSize(parse_floaty_mcfloatface_value(input)?),
"font-style" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"normal" => Styles::FontStyle(FontStyle::Normal),
"italic" => Styles::FontStyle(FontStyle::Italic),
"oblique" => Styles::FontStyle(FontStyle::Oblique),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"font-weight" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"normal" => Styles::FontWeight(FontWeight::Normal),
"bold" => Styles::FontWeight(FontWeight::Bold),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"height" => Styles::Height(parse_floaty_mcfloatface_value(input)?),
"justify-content" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"flex-start" => Styles::JustifyContent(JustifyContent::FlexStart),
"flex-end" => Styles::JustifyContent(JustifyContent::FlexEnd),
"center" => Styles::JustifyContent(JustifyContent::Center),
"space-between" => Styles::JustifyContent(JustifyContent::SpaceBetween),
"space-around" => Styles::JustifyContent(JustifyContent::SpaceAround),
"space-evenly" => Styles::JustifyContent(JustifyContent::SpaceEvenly),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"left" => Styles::Left(parse_floaty_mcfloatface_value(input)?),
"line-height" => Styles::FontLineHeight(parse_floaty_mcfloatface_value(input)?),
"margin-bottom" => Styles::MarginBottom(parse_floaty_mcfloatface_value(input)?),
"margin-end" => Styles::MarginEnd(parse_floaty_mcfloatface_value(input)?),
"margin-left" => Styles::MarginLeft(parse_floaty_mcfloatface_value(input)?),
"margin-right" => Styles::MarginRight(parse_floaty_mcfloatface_value(input)?),
"margin-start" => Styles::MarginStart(parse_floaty_mcfloatface_value(input)?),
"margin-top" => Styles::MarginTop(parse_floaty_mcfloatface_value(input)?),
"max-height" => Styles::MaxHeight(parse_floaty_mcfloatface_value(input)?),
"max-width" => Styles::MaxWidth(parse_floaty_mcfloatface_value(input)?),
"min-height" => Styles::MinHeight(parse_floaty_mcfloatface_value(input)?),
"min-width" => Styles::MinWidth(parse_floaty_mcfloatface_value(input)?),
"opacity" => Styles::Opacity(parse_floaty_mcfloatface_value(input)?),
"overflow" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"visible" => Styles::Overflow(Overflow::Visible),
"hidden" => Styles::Overflow(Overflow::Hidden),
"scroll" => Styles::Overflow(Overflow::Scroll),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"padding-bottom" => Styles::PaddingBottom(parse_floaty_mcfloatface_value(input)?),
"padding-end" => Styles::PaddingEnd(parse_floaty_mcfloatface_value(input)?),
"padding-left" => Styles::PaddingLeft(parse_floaty_mcfloatface_value(input)?),
"padding-right" => Styles::PaddingRight(parse_floaty_mcfloatface_value(input)?),
"padding-start" => Styles::PaddingStart(parse_floaty_mcfloatface_value(input)?),
"padding-top" => Styles::PaddingTop(parse_floaty_mcfloatface_value(input)?),
"position" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"absolute" => Styles::PositionType(PositionType::Absolute),
"relative" => Styles::PositionType(PositionType::Relative),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"right" => Styles::Right(parse_floaty_mcfloatface_value(input)?),
"start" => Styles::Start(parse_floaty_mcfloatface_value(input)?),
"text-align" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) {
"auto" => Styles::TextAlignment(TextAlignment::Auto),
"left" => Styles::TextAlignment(TextAlignment::Left),
"right" => Styles::TextAlignment(TextAlignment::Right),
"center" => Styles::TextAlignment(TextAlignment::Center),
"justify" => Styles::TextAlignment(TextAlignment::Justify),
_ => { return Err(s.new_unexpected_token_error(t.clone())); }
}},
"text-decoration-color" => Styles::TextDecorationColor(Color::parse(input)?),
"text-shadow-color" => Styles::TextShadowColor(Color::parse(input)?),
"tint-color" => Styles::TintColor(Color::parse(input)?),
"top" => Styles::Top(parse_floaty_mcfloatface_value(input)?),
"width" => Styles::Width(parse_floaty_mcfloatface_value(input)?),
t => {
let location = input.current_source_location();
return Err(location.new_unexpected_token_error(Token::Ident(t.to_string().into())));
}
};
Ok(style)
}
}
/// A utility method for handling some float values.
/// Mostly used to reduce code verbosity in the massive switch table for `Styles` parsing.
fn parse_floaty_mcfloatface_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<f32, BasicParseError<'i>> {
let location = input.current_source_location();
let token = input.next()?;
match token {
Token::Number { value, .. } => Ok(*value),
_ => Err(location.new_basic_unexpected_token_error(token.clone()))
}
}