diff --git a/src/css_parser.rs b/src/css_parser.rs index 09a1c2d5b..8ab75f613 100644 --- a/src/css_parser.rs +++ b/src/css_parser.rs @@ -79,6 +79,7 @@ pub enum ParsedCssProperty { Bottom(LayoutBottom), Padding(LayoutPadding), + Margin(LayoutMargin), FlexWrap(LayoutWrap), FlexDirection(LayoutDirection), @@ -126,6 +127,7 @@ impl_from!(LayoutRight, ParsedCssProperty::Right); impl_from!(LayoutLeft, ParsedCssProperty::Left); impl_from!(LayoutPadding, ParsedCssProperty::Padding); +impl_from!(LayoutMargin, ParsedCssProperty::Margin); impl_from!(LayoutWrap, ParsedCssProperty::FlexWrap); impl_from!(LayoutDirection, ParsedCssProperty::FlexDirection); @@ -188,6 +190,7 @@ impl ParsedCssProperty { "bottom" => Ok(parse_layout_bottom(value)?.into()), "padding" => Ok(parse_layout_padding(value)?.into()), + "margin" => Ok(parse_layout_margin(value)?.into()), "flex-wrap" => Ok(parse_layout_wrap(value)?.into()), "flex-direction" => Ok(parse_layout_direction(value)?.into()), @@ -272,6 +275,7 @@ pub enum CssParsingError<'a> { CssColorParseError(CssColorParseError<'a>), CssBorderRadiusParseError(CssBorderRadiusParseError<'a>), PaddingParseError(LayoutPaddingParseError<'a>), + MarginParseError(LayoutMarginParseError<'a>), /// Key is not supported, i.e. `#div { aldfjasdflk: 400px }` results in an /// `UnsupportedCssKey("aldfjasdflk", "400px")` error UnsupportedCssKey(&'a str, &'a str), @@ -289,6 +293,7 @@ impl_display!{ CssParsingError<'a>, { CssBackgroundParseError(e) => format!("{}", e), CssColorParseError(e) => format!("{}", e), PaddingParseError(e) => format!("{}", e), + MarginParseError(e) => format!("{}", e), UnsupportedCssKey(key, value) => format!("Unsupported Css-key: \"{}\" - value: \"{}\"", key, value), }} @@ -302,6 +307,7 @@ impl_from!(CssFontFamilyParseError<'a>, CssParsingError::CssFontFamilyParseError impl_from!(CssBackgroundParseError<'a>, CssParsingError::CssBackgroundParseError); impl_from!(CssBorderRadiusParseError<'a>, CssParsingError::CssBorderRadiusParseError); impl_from!(LayoutPaddingParseError<'a>, CssParsingError::PaddingParseError); +impl_from!(LayoutMarginParseError<'a>, CssParsingError::MarginParseError); impl<'a> From<(&'a str, &'a str)> for CssParsingError<'a> { fn from((a, b): (&'a str, &'a str)) -> Self { @@ -940,6 +946,48 @@ fn parse_layout_padding<'a>(input: &'a str) }) } +/// Represents a parsed CSS `padding` attribute +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct LayoutMargin { + pub top: Option, + pub bottom: Option, + pub left: Option, + pub right: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum LayoutMarginParseError<'a> { + PixelParseError(PixelParseError<'a>), + TooManyValues, + TooFewValues, +} + +impl_display!{ LayoutMarginParseError<'a>, { + PixelParseError(e) => format!("Could not parse pixel value: {}", e), + TooManyValues => format!("Too many values - margin property has a maximum of 4 values."), + TooFewValues => format!("Too few values - margin property has a minimum of 1 value."), +}} + +impl_from!(PixelParseError<'a>, LayoutMarginParseError::PixelParseError); + +fn parse_layout_margin<'a>(input: &'a str) +-> Result +{ + match parse_layout_padding(input) { + Ok(padding) => { + Ok(LayoutMargin { + top: padding.top, + left: padding.left, + right: padding.right, + bottom: padding.bottom, + }) + }, + Err(LayoutPaddingParseError::PixelParseError(e)) => Err(e.into()), + Err(LayoutPaddingParseError::TooManyValues) => Err(LayoutMarginParseError::TooManyValues), + Err(LayoutPaddingParseError::TooFewValues) => Err(LayoutMarginParseError::TooFewValues), + } +} + /// Parse a CSS border such as /// /// "5px solid red" @@ -1794,6 +1842,25 @@ pub enum LayoutDirection { ColumnReverse, } +/// Same as the `LayoutDirection`, but without the `-reverse` properties, used in the layout solver, +/// makes decisions based on horizontal / vertical direction easier to write. +/// Use `LayoutDirection::get_axis()` to get the axis for a given `LayoutDirection`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum LayoutAxis { + Horizontal, + Vertical, +} + +impl LayoutDirection { + pub fn get_axis(&self) -> LayoutAxis { + use self::{LayoutAxis::*, LayoutDirection::*}; + match self { + Row | RowReverse => Horizontal, + Column | ColumnReverse => Vertical, + } + } +} + /// Represents a parsed CSS `position` attribute - default: `Static` /// /// NOTE: No inline positioning is supported. @@ -1995,6 +2062,7 @@ pub struct RectLayout { pub left: Option, pub padding: Option, + pub margin: Option, } typed_pixel_value_parser!(parse_layout_width, LayoutWidth); diff --git a/src/display_list.rs b/src/display_list.rs index 5494a8eb9..78194c4da 100644 --- a/src/display_list.rs +++ b/src/display_list.rs @@ -1322,6 +1322,7 @@ fn populate_css_properties(rect: &mut DisplayRectangle, css_overrides: &FastHash // TODO: merge new padding with existing padding Padding(p) => { rect.layout.padding = Some(*p); }, + Margin(m) => { rect.layout.margin = Some(*m); }, FlexWrap(w) => { rect.layout.wrap = Some(*w); }, FlexDirection(d) => { rect.layout.direction = Some(*d); }, diff --git a/src/ui_solver.rs b/src/ui_solver.rs index b3a2b90a3..c4a8c2f5e 100644 --- a/src/ui_solver.rs +++ b/src/ui_solver.rs @@ -218,13 +218,6 @@ impl DomSolver { } } -#[test] -fn test_new_ui_solver_has_root_constraints() { - let mut solver = DomSolver::new(LogicalPosition::new(0.0, 0.0), LogicalSize::new(400.0, 600.0)); - assert!(solver.solver.suggest_value(solver.root_constraints.width_var, 400.0).is_ok()); - assert!(solver.solver.suggest_value(solver.root_constraints.width_var, 600.0).is_ok()); -} - impl UiSolver { pub(crate) fn new() -> Self { @@ -445,15 +438,48 @@ fn create_layout_constraints<'a, T: Layout>( layout_constraints } +#[derive(Debug, Copy, Clone, PartialEq)] +enum WhConstraint { + /// between min, max, Prefer::Max | Prefer::Min + Between(f32, f32, WhPrefer), + /// Value needs to be exactly X + EqualTo(f32), + /// Value can be anything + Unconstrained, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum WhPrefer { + Max, + Min, +} + +impl WhConstraint { + /// Returns the actual value of the constraint + pub fn actual_value(&self) -> Option { + use self::WhConstraint::*; + match self { + Between(min, max, prefer) => match prefer { WhPrefer::Min => Some(*min), WhPrefer::Max => Some(*max) }, + EqualTo(exact) => Some(*exact), + Unconstrained => None, + } + } + + pub fn min_needed_space(&self) -> f32 { + self.actual_value().unwrap_or(0.0) + } +} + macro_rules! determine_preferred { ($fn_name:ident, $width:ident, $min_width:ident, $max_width:ident) => ( - /// Returns the preferred width, given [width, min_width, max_width] - fn $fn_name(layout: &RectLayout) -> Option { + fn $fn_name(layout: &RectLayout) -> WhConstraint { let width = layout.$width.and_then(|w| Some(w.0.to_pixels())); let min_width = layout.$min_width.and_then(|w| Some(w.0.to_pixels())); let max_width = layout.$max_width.and_then(|w| Some(w.0.to_pixels())); + // TODO: correct for width / height less than 0 - "negative" width is impossible! + let (absolute_min, absolute_max) = { if let (Some(min), Some(max)) = (min_width, max_width) { if min_width < max_width { @@ -472,40 +498,120 @@ macro_rules! determine_preferred { if let Some(min_width) = absolute_min { if min_width < width && width < max_width { // normal: min_width < width < max_width - Some(width) + WhConstraint::EqualTo(width) } else if width > max_width { - Some(max_width) + WhConstraint::EqualTo(max_width) } else if width < min_width { - Some(min_width) + WhConstraint::EqualTo(min_width) } else { - None /* unreachable */ + WhConstraint::Unconstrained /* unreachable */ } } else { // width & max_width - Some(width.min(max_width)) + WhConstraint::EqualTo(width.min(max_width)) } } else if let Some(min_width) = absolute_min { // no max width, only width & min_width - Some(width.max(min_width)) + WhConstraint::EqualTo(width.max(min_width)) } else { // no min-width or max-width - Some(width) + WhConstraint::EqualTo(width) } } else { // no width, only min_width and max_width - absolute_min + if let Some(max_width) = absolute_max { + if let Some(min_width) = absolute_min { + WhConstraint::Between(min_width, max_width, WhPrefer::Min) + } else { + // TODO: check sign positive on max_width! + WhConstraint::Between(0.0, max_width, WhPrefer::Min) + } + } else { + if let Some(min_width) = absolute_min { + WhConstraint::Between(min_width, ::std::f32::MAX, WhPrefer::Min) + } else { + // no width, min_width or max_width + WhConstraint::Unconstrained + } + } + } + }) +} + +/* +use css_parser::{LayoutAxis, LayoutMargin, LayoutPadding}; + +#[derive(Debug, Copy, Clone)] +struct WidthCalculatedRect { + pub preferred_width: WhConstraint, + pub preferred_height: WhConstraint, + pub margin: LayoutMargin, + pub padding: LayoutPadding, +} + +impl WidthCalculatedRect { + /// Get the flex basis in the main direction + pub fn get_flex_basis(&self, parent_axis: &LayoutAxis) -> f32 { + match parent_axis { + Horizontal => { + self.preferred_width.unwrap_or(0.0) + + self.margin.left.unwrap_or(0.0) + + self.margin.right.unwrap_or(0.0) + + self.padding.right.unwrap_or(0.0) + + self.padding.left.unwrap_or(0.0) + }, + Vertical => { + self.preferred_height.unwrap_or(0.0) + + self.margin.top.unwrap_or(0.0) + + self.margin.bottom.unwrap_or(0.0) + + self.padding.top.unwrap_or(0.0) + + self.padding.bottom.unwrap_or(0.0) + }, } } - ) } +/// Fill out the width of all nodes that don't have a `first_child` +fn fill_out_width_of_innermost_nodes<'a>(arena: &Arena>, fill_out: &mut Arena<>) { + for (node_id, node) in arena.linear_iter().filter(|(node_id, node)| node.first_child.is_some()) { + + } +} +*/ + +/// Returns the preferred width, given [width, min_width, max_width] inside a RectLayout +/// or `None` if the height can't be determined from the node alone. +/// // fn determine_preferred_width(layout: &RectLayout) -> Option determine_preferred!(determine_preferred_width, width, min_width, max_width); +/// Returns the preferred height, given [height, min_height, max_height] inside a RectLayout +// or `None` if the height can't be determined from the node alone. +/// // fn determine_preferred_height(layout: &RectLayout) -> Option determine_preferred!(determine_preferred_height, height, min_height, max_height); +/// Returns the nearest common ancestor with a `position: relative` attribute +/// or `None` if there is no ancestor that has `position: relative`. Usually +/// used in conjunction with `position: absolute` +fn get_nearest_positioned_ancestor<'a>(start_node_id: NodeId, arena: &Arena>) +-> Option +{ + let mut current_node = start_node_id; + while let Some(parent) = arena[current_node].parent() { + // An element with position: absolute; is positioned relative to the nearest + // positioned ancestor (instead of positioned relative to the viewport, like fixed). + // + // A "positioned" element is one whose position is anything except static. + if let Some(LayoutPosition::Static) = arena[parent].data.layout.position { + current_node = parent; + } else { + return Some(parent); + } + } + None +} #[test] fn test_determine_preferred_width() { @@ -517,7 +623,7 @@ fn test_determine_preferred_width() { max_width: None, .. Default::default() }; - assert_eq!(determine_preferred_width(&layout), None); + assert_eq!(determine_preferred_width(&layout), WhConstraint::Unconstrained); let layout = RectLayout { width: Some(LayoutWidth(PixelValue::px(500.0))), @@ -525,7 +631,7 @@ fn test_determine_preferred_width() { max_width: None, .. Default::default() }; - assert_eq!(determine_preferred_width(&layout), Some(500.0)); + assert_eq!(determine_preferred_width(&layout), WhConstraint::EqualTo(500.0)); let layout = RectLayout { width: Some(LayoutWidth(PixelValue::px(500.0))), @@ -533,7 +639,7 @@ fn test_determine_preferred_width() { max_width: None, .. Default::default() }; - assert_eq!(determine_preferred_width(&layout), Some(600.0)); + assert_eq!(determine_preferred_width(&layout), WhConstraint::EqualTo(600.0)); let layout = RectLayout { width: Some(LayoutWidth(PixelValue::px(10000.0))), @@ -541,7 +647,7 @@ fn test_determine_preferred_width() { max_width: Some(LayoutMaxWidth(PixelValue::px(800.0))), .. Default::default() }; - assert_eq!(determine_preferred_width(&layout), Some(800.0)); + assert_eq!(determine_preferred_width(&layout), WhConstraint::EqualTo(800.0)); let layout = RectLayout { width: None, @@ -549,7 +655,7 @@ fn test_determine_preferred_width() { max_width: Some(LayoutMaxWidth(PixelValue::px(800.0))), .. Default::default() }; - assert_eq!(determine_preferred_width(&layout), Some(600.0)); + assert_eq!(determine_preferred_width(&layout), WhConstraint::Between(600.0, 800.0, WhPrefer::Min)); let layout = RectLayout { width: None, @@ -557,7 +663,7 @@ fn test_determine_preferred_width() { max_width: Some(LayoutMaxWidth(PixelValue::px(800.0))), .. Default::default() }; - assert_eq!(determine_preferred_width(&layout), None); + assert_eq!(determine_preferred_width(&layout), WhConstraint::Between(0.0, 800.0, WhPrefer::Min)); let layout = RectLayout { width: Some(LayoutWidth(PixelValue::px(1000.0))), @@ -565,7 +671,7 @@ fn test_determine_preferred_width() { max_width: Some(LayoutMaxWidth(PixelValue::px(800.0))), .. Default::default() }; - assert_eq!(determine_preferred_width(&layout), Some(800.0)); + assert_eq!(determine_preferred_width(&layout), WhConstraint::EqualTo(800.0)); let layout = RectLayout { width: Some(LayoutWidth(PixelValue::px(1200.0))), @@ -573,7 +679,7 @@ fn test_determine_preferred_width() { max_width: Some(LayoutMaxWidth(PixelValue::px(800.0))), .. Default::default() }; - assert_eq!(determine_preferred_width(&layout), Some(800.0)); + assert_eq!(determine_preferred_width(&layout), WhConstraint::EqualTo(800.0)); let layout = RectLayout { width: Some(LayoutWidth(PixelValue::px(1200.0))), @@ -581,26 +687,12 @@ fn test_determine_preferred_width() { max_width: Some(LayoutMaxWidth(PixelValue::px(400.0))), .. Default::default() }; - assert_eq!(determine_preferred_width(&layout), Some(400.0)); + assert_eq!(determine_preferred_width(&layout), WhConstraint::EqualTo(400.0)); } -/// Returns the nearest common ancestor with a `position: relative` attribute -/// or `None` if there is no ancestor that has `position: relative`. Usually -/// used in conjunction with `position: absolute` -fn get_nearest_positioned_ancestor<'a>(start_node_id: NodeId, arena: &Arena>) --> Option -{ - let mut current_node = start_node_id; - while let Some(parent) = arena[current_node].parent() { - // An element with position: absolute; is positioned relative to the nearest - // positioned ancestor (instead of positioned relative to the viewport, like fixed). - // - // A "positioned" element is one whose position is anything except static. - if let Some(LayoutPosition::Static) = arena[parent].data.layout.position { - current_node = parent; - } else { - return Some(parent); - } - } - None +#[test] +fn test_new_ui_solver_has_root_constraints() { + let mut solver = DomSolver::new(LogicalPosition::new(0.0, 0.0), LogicalSize::new(400.0, 600.0)); + assert!(solver.solver.suggest_value(solver.root_constraints.width_var, 400.0).is_ok()); + assert!(solver.solver.suggest_value(solver.root_constraints.width_var, 600.0).is_ok()); } \ No newline at end of file