Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Support overflow property #412

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions scripts/gentest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ async fn main() {
info!("spawning webdriver client and collecting test descriptions");
let client = ClientBuilder::native().capabilities(caps.clone()).connect(webdriver_url).await.unwrap();

asserts_non_zero_width_scrollbars(client.clone()).await;

let mut test_descs = vec![];
for (name, fixture_path) in fixtures {
test_descs.push(test_root_element(client.clone(), name, fixture_path).await);
Expand Down Expand Up @@ -103,6 +105,27 @@ async fn main() {
Command::new("cargo").arg("fmt").current_dir(repo_root).status().unwrap();
}

async fn asserts_non_zero_width_scrollbars(client: Client) {
// Load minimal test page defined in the string
const TEST_PAGE: &str = r#"data:text/html;charset=utf-8,<html><body><div style="overflow:scroll" /></body></html>"#;
client.goto(TEST_PAGE).await.unwrap();

// Determine the width of the scrollbar
let scrollbar_width = client
.execute("return document.body.firstChild.clientWidth - document.body.firstChild.offsetWidth;", vec![])
.await
.unwrap();
let Value::Number(scrollbar_width) = scrollbar_width else { panic!("Error retrieving scrollbar_width") };
let scrollbar_width = scrollbar_width.as_f64().unwrap();

if scrollbar_width == 0.0 {
panic!(concat!(
"\n\n Error: Scrollbar width of zero detected. This test generation script must be run with scrollbars set to take up space.\n",
" On macOS this can be done by setting Show Scrollbars to 'always' in the Appearance section of the System Settings app.\n\n"
))
}
}

async fn test_root_element(client: Client, name: String, fixture_path: impl AsRef<Path>) -> (String, Value) {
let fixture_path = fixture_path.as_ref();

Expand Down Expand Up @@ -352,13 +375,30 @@ fn generate_node(ident: &str, node: &Value) -> TokenStream {
_ => quote!(),
};

let overflow = match style["overflow"] {
Value::String(ref value) => match value.as_ref() {
"hidden" => quote!(overflow: taffy::style::Overflow::Hidden,),
"scroll" => quote!(overflow: taffy::style::Overflow::Scroll,),
_ => quote!(),
},
_ => quote!(),
fn quote_overflow(overflow: &Value) -> Option<TokenStream> {
match overflow {
Value::String(ref value) => match value.as_ref() {
"hidden" => Some(quote!(taffy::style::Overflow::Hidden)),
"scroll" => Some(quote!(taffy::style::Overflow::Scroll)),
"auto" => Some(quote!(taffy::style::Overflow::Auto)),
_ => None,
},
_ => None,
}
}
let overflow_x = quote_overflow(&style["overflowX"]);
let overflow_y = quote_overflow(&style["overflowY"]);
let (overflow, scrollbar_width) = if overflow_x.is_some() || overflow_y.is_some() {
let overflow_x = overflow_x.unwrap_or(quote!(taffy::style::Overflow::Visible));
let overflow_y = overflow_y.unwrap_or(quote!(taffy::style::Overflow::Visible));
let overflow = quote!(overflow: taffy::geometry::Point { x: #overflow_x, y: #overflow_y },);
let scrollbar_width = quote_number_prop("scrollbar_width", style, |value: f32| {
let value = value as u8;
quote!(#value)
});
(overflow, scrollbar_width)
} else {
(quote!(), quote!())
};

let align_items = match style["alignItems"] {
Expand Down Expand Up @@ -538,6 +578,7 @@ fn generate_node(ident: &str, node: &Value) -> TokenStream {
#flex_direction
#flex_wrap
#overflow
#scrollbar_width
#align_items
#align_self
#justify_items
Expand Down
13 changes: 12 additions & 1 deletion scripts/gentest/test_helper.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@

function getScrollBarWidth() {
let el = document.createElement("div");
el.style.cssText = "overflow:scroll; visibility:hidden; position:absolute;";
document.body.appendChild(el);
let width = el.offsetWidth - el.clientWidth;
el.remove();
return width;
}

class TrackSizingParser {
static INITIAL_CHAR_REGEX = /[a-z-A-Z0-9]/;
static TOKEN_CHAR_REGEX = /[-\.a-z-A-Z0-9%]/;
Expand Down Expand Up @@ -188,7 +197,9 @@ function describeElement(e) {
writingMode: parseEnum(e.style.writingMode),

flexWrap: parseEnum(e.style.flexWrap),
overflow: parseEnum(e.style.overflow),
overflowX: parseEnum(e.style.overflowX),
overflowY: parseEnum(e.style.overflowY),
scrollbarWidth: getScrollBarWidth(),

alignItems: parseEnum(e.style.alignItems),
alignSelf: parseEnum(e.style.alignSelf),
Expand Down
58 changes: 32 additions & 26 deletions src/compute/flexbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::prelude::{TaffyMaxContent, TaffyMinContent};
use crate::resolve::{MaybeResolve, ResolveOrZero};
use crate::style::{
AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, Display, FlexWrap, JustifyContent,
LengthPercentageAuto, Position,
LengthPercentageAuto, Overflow, Position,
};
use crate::style::{FlexDirection, Style};
use crate::sys::Vec;
Expand Down Expand Up @@ -63,14 +63,16 @@ struct FlexItem {
/// The cross-alignment of this item
align_self: AlignSelf,

/// The overflow style of the item
overflow: Point<Overflow>,
/// The flex shrink style of the item
flex_shrink: f32,
/// The flex grow style of the item
flex_grow: f32,

/// The minimum size of the item. This differs from min_size above because it also
/// takes into account content based automatic minimum sizes
resolved_minimum_size: Size<f32>,
resolved_minimum_main_size: f32,

/// The final offset of this item
inset: Rect<Option<f32>>,
Expand Down Expand Up @@ -250,7 +252,7 @@ fn compute_preliminary(
NODE_LOGGER.labelled_log("item.inner_flex_basis", item.inner_flex_basis);
NODE_LOGGER.labelled_debug_log("item.hypothetical_outer_size", item.hypothetical_outer_size);
NODE_LOGGER.labelled_debug_log("item.hypothetical_inner_size", item.hypothetical_inner_size);
NODE_LOGGER.labelled_debug_log("item.resolved_minimum_size", item.resolved_minimum_size);
NODE_LOGGER.labelled_debug_log("item.resolved_minimum_main_size", item.resolved_minimum_main_size);
}

// 4. Determine the main size of the flex container
Expand Down Expand Up @@ -495,14 +497,15 @@ fn generate_anonymous_flex_items(tree: &impl LayoutTree, node: Node, constants:
padding: child_style.padding.resolve_or_zero(constants.node_inner_size.width),
border: child_style.border.resolve_or_zero(constants.node_inner_size.width),
align_self: child_style.align_self.unwrap_or(constants.align_items),
overflow: child_style.overflow,
flex_grow: child_style.flex_grow,
flex_shrink: child_style.flex_shrink,
flex_basis: 0.0,
inner_flex_basis: 0.0,
violation: 0.0,
frozen: false,

resolved_minimum_size: Size::zero(),
resolved_minimum_main_size: 0.0,
hypothetical_inner_size: Size::zero(),
hypothetical_outer_size: Size::zero(),
target_size: Size::zero(),
Expand Down Expand Up @@ -704,25 +707,29 @@ fn determine_flex_base_size(
// be set to their usual values in the cross axis so that wrapping content can wrap correctly.
//
// See https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution
let min_content_size = {
let child_parent_size = Size::NONE.with_cross(dir, cross_axis_parent_size);
let child_available_space = Size::MIN_CONTENT.with_cross(dir, cross_axis_available_space);

GenericAlgorithm::measure_size(
tree,
child.node,
Size::NONE,
child_parent_size,
child_available_space,
SizingMode::ContentSize,
)
};
let style_min_main_size =
child.min_size.or(child.overflow.map(Overflow::maybe_into_automatic_min_size).into()).main(dir);

child.resolved_minimum_main_size = style_min_main_size.unwrap_or({
let min_content_size = {
let child_parent_size = Size::NONE.with_cross(dir, cross_axis_parent_size);
let child_available_space = Size::MIN_CONTENT.with_cross(dir, cross_axis_available_space);

GenericAlgorithm::measure_size(
tree,
child.node,
Size::NONE,
child_parent_size,
child_available_space,
SizingMode::ContentSize,
)
};

// 4.5. Automatic Minimum Size of Flex Items
// https://www.w3.org/TR/css-flexbox-1/#min-size-auto
let clamped_min_content_size = min_content_size.maybe_min(child.size).maybe_min(child.max_size);
child.resolved_minimum_size =
child.min_size.unwrap_or(clamped_min_content_size).maybe_max(padding_border_axes_sums);
// 4.5. Automatic Minimum Size of Flex Items
// https://www.w3.org/TR/css-flexbox-1/#min-size-auto
let clamped_min_content_size = min_content_size.maybe_min(child.size).maybe_min(child.max_size);
clamped_min_content_size.maybe_max(padding_border_axes_sums).main(dir)
});
}
}

Expand Down Expand Up @@ -883,12 +890,11 @@ fn determine_container_main_size(
let flex_basis_min = clamping_basis.filter(|_| item.flex_shrink == 0.0);
let flex_basis_max = clamping_basis.filter(|_| item.flex_grow == 0.0);

let resolved_min = item.resolved_minimum_size.main(constants.dir);
let min_main_size = style_min
.maybe_max(flex_basis_min)
.or(flex_basis_min)
.unwrap_or(resolved_min)
.max(resolved_min);
.unwrap_or(item.resolved_minimum_main_size)
.max(item.resolved_minimum_main_size);
let max_main_size =
style_max.maybe_min(flex_basis_max).or(flex_basis_max).unwrap_or(f32::INFINITY);

Expand Down Expand Up @@ -1159,7 +1165,7 @@ fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants, orig
// If the item’s target main size was made larger by this, it’s a min violation.

let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
let resolved_min_main: Option<f32> = child.resolved_minimum_size.main(constants.dir).into();
let resolved_min_main: Option<f32> = child.resolved_minimum_main_size.into();
let max_main = child.max_size.main(constants.dir);
let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
child.violation = clamped - child.target_size.main(constants.dir);
Expand Down
50 changes: 32 additions & 18 deletions src/compute/grid/types/grid_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ use super::GridTrack;
use crate::axis::AbstractAxis;
use crate::compute::grid::OriginZeroLine;
use crate::compute::{GenericAlgorithm, LayoutAlgorithm};
use crate::geometry::{Line, Rect, Size};
use crate::geometry::{Line, Point, Rect, Size};
use crate::layout::SizingMode;
use crate::math::MaybeMath;
use crate::node::Node;
use crate::prelude::LayoutTree;
use crate::resolve::{MaybeResolve, ResolveOrZero};
use crate::style::{
AlignItems, AlignSelf, AvailableSpace, LengthPercentageAuto, MaxTrackSizingFunction, MinTrackSizingFunction, Style,
AlignItems, AlignSelf, AvailableSpace, Dimension, LengthPercentageAuto, MaxTrackSizingFunction,
MinTrackSizingFunction, Overflow, Style,
};
use core::ops::Range;

Expand All @@ -33,6 +34,16 @@ pub(in super::super) struct GridItem {
/// (in origin-zero coordinates)
pub column: Line<OriginZeroLine>,

/// The item's overflow style
pub overflow: Point<Overflow>,
/// The item's size style
pub size: Size<Dimension>,
/// The item's min_size style
pub min_size: Size<Dimension>,
/// The item's max_size style
pub max_size: Size<Dimension>,
/// The item's aspect_ratio style
pub aspect_ratio: Option<f32>,
/// The item's margin style
pub margin: Rect<LengthPercentageAuto>,
/// The item's align_self property, or the parent's align_items property is not set
Expand Down Expand Up @@ -88,6 +99,11 @@ impl GridItem {
source_order,
row: row_span,
column: col_span,
overflow: style.overflow,
size: style.size,
min_size: style.min_size,
max_size: style.max_size,
aspect_ratio: style.aspect_ratio,
margin: style.margin,
align_self: style.align_self.unwrap_or(parent_align_items),
justify_self: style.justify_self.unwrap_or(parent_justify_items),
Expand Down Expand Up @@ -207,17 +223,15 @@ impl GridItem {
/// allow percentage sizes further down the tree to resolve properly in some cases
fn known_dimensions(
&self,
tree: &mut impl LayoutTree,
inner_node_size: Size<Option<f32>>,
grid_area_size: Size<Option<f32>>,
) -> Size<Option<f32>> {
let margins = self.margins_axis_sums_with_baseline_shims(inner_node_size.width);

let style = tree.style(self.node);
let aspect_ratio = style.aspect_ratio;
let inherent_size = style.size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
let min_size = style.min_size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
let max_size = style.max_size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
let aspect_ratio = self.aspect_ratio;
let inherent_size = self.size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
let min_size = self.min_size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
let max_size = self.max_size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);

let grid_area_minus_item_margins_size = grid_area_size.maybe_sub(margins);

Expand All @@ -228,8 +242,8 @@ impl GridItem {
// - Alignment style is "stretch"
// - The node is not absolutely positioned
// - The node does not have auto margins in this axis.
if style.margin.left != LengthPercentageAuto::Auto
&& style.margin.right != LengthPercentageAuto::Auto
if self.margin.left != LengthPercentageAuto::Auto
&& self.margin.right != LengthPercentageAuto::Auto
&& self.justify_self == AlignSelf::Stretch
{
return grid_area_minus_item_margins_size.width;
Expand All @@ -246,8 +260,8 @@ impl GridItem {
// - Alignment style is "stretch"
// - The node is not absolutely positioned
// - The node does not have auto margins in this axis.
if style.margin.top != LengthPercentageAuto::Auto
&& style.margin.bottom != LengthPercentageAuto::Auto
if self.margin.top != LengthPercentageAuto::Auto
&& self.margin.bottom != LengthPercentageAuto::Auto
&& self.align_self == AlignSelf::Stretch
{
return grid_area_minus_item_margins_size.height;
Expand Down Expand Up @@ -327,7 +341,7 @@ impl GridItem {
available_space: Size<Option<f32>>,
inner_node_size: Size<Option<f32>>,
) -> f32 {
let known_dimensions = self.known_dimensions(tree, inner_node_size, available_space);
let known_dimensions = self.known_dimensions(inner_node_size, available_space);
GenericAlgorithm::measure_size(
tree,
self.node,
Expand Down Expand Up @@ -366,7 +380,7 @@ impl GridItem {
available_space: Size<Option<f32>>,
inner_node_size: Size<Option<f32>>,
) -> f32 {
let known_dimensions = self.known_dimensions(tree, inner_node_size, available_space);
let known_dimensions = self.known_dimensions(inner_node_size, available_space);
GenericAlgorithm::measure_size(
tree,
self.node,
Expand Down Expand Up @@ -412,15 +426,15 @@ impl GridItem {
known_dimensions: Size<Option<f32>>,
inner_node_size: Size<Option<f32>>,
) -> f32 {
let style = tree.style(self.node);
let size = style
let size = self
.size
.maybe_resolve(inner_node_size)
.maybe_apply_aspect_ratio(style.aspect_ratio)
.maybe_apply_aspect_ratio(self.aspect_ratio)
.get(axis)
.or_else(|| {
style.min_size.maybe_resolve(inner_node_size).maybe_apply_aspect_ratio(style.aspect_ratio).get(axis)
self.min_size.maybe_resolve(inner_node_size).maybe_apply_aspect_ratio(self.aspect_ratio).get(axis)
})
.or_else(|| self.overflow.get(axis).maybe_into_automatic_min_size())
.unwrap_or_else(|| {
// Automatic minimum size. See https://www.w3.org/TR/css-grid-1/#min-size-auto

Expand Down
Loading