Guess I should put this under version control LOL
This commit is contained in:
commit
2035318460
73 changed files with 8836 additions and 0 deletions
23
styles/Cargo.toml
Normal file
23
styles/Cargo.toml
Normal 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
5
styles/README.md
Normal 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
667
styles/src/color.rs
Normal 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
15
styles/src/lib.rs
Normal 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
1363
styles/src/stretch/algo.rs
Normal file
File diff suppressed because it is too large
Load diff
136
styles/src/stretch/geometry.rs
Normal file
136
styles/src/stretch/geometry.rs
Normal 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
36
styles/src/stretch/id.rs
Normal 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
35
styles/src/stretch/mod.rs
Normal 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
239
styles/src/stretch/node.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
220
styles/src/stretch/number.rs
Normal file
220
styles/src/stretch/number.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
25
styles/src/stretch/result.rs
Normal file
25
styles/src/stretch/result.rs
Normal 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
737
styles/src/styles.rs
Normal 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
304
styles/src/styles_parser.rs
Normal 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()))
|
||||
}
|
||||
}
|
||||
Reference in a new issue