Skip to content

Commit

Permalink
feat: flex support (#920)
Browse files Browse the repository at this point in the history
* feat: flex support

* move flex to content

* flex tests

* flex docs

* fmt
  • Loading branch information
marc2332 authored Nov 10, 2024
1 parent b4be42b commit 00ca902
Show file tree
Hide file tree
Showing 10 changed files with 570 additions and 21 deletions.
6 changes: 4 additions & 2 deletions crates/elements/src/_docs/attributes/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ Accepted values:

- `normal` (default): Uses parent bounds.
- `fit`: Uses parent bounds but later shrunks to the size of the biggest element inside.
- `flex`: Marks the container as flex container, children of this element will be able to use `size`/`size(n)` in their `width` and `height` attributes.

The `fit` mode will allow the inner elements using `width: fill-min` to expand to the biggest element inside this element.

### Example
### `fit`

The `fit` mode will allow the inner elements using `width: fill-min` to expand to the biggest element inside this element.

```rust, no_run
# use freya::prelude::*;
Expand Down
27 changes: 27 additions & 0 deletions crates/elements/src/_docs/size_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,30 @@
//! )
//! }
//! ```
//!
//! #### Flex Factor
//!
//! When being a children of an element with `content: flex` you may change the growth factor of the size attributes.
//!
//! ```rust, no_run
//! # use freya::prelude::*;
//! fn app() -> Element {
//! rsx!(
//! rect {
//! content: "flex",
//! width: "200",
//! height: "200",
//! rect {
//! height: "flex(1)",
//! width: "100%",
//! background: "red"
//! }
//! rect {
//! height: "flex(3)",
//! width: "100%",
//! background: "blue"
//! }
//! }
//! )
//! }
//! ```
1 change: 1 addition & 0 deletions crates/state/src/values/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ impl Parse for Content {
fn parse(value: &str) -> Result<Self, ParseError> {
Ok(match value {
"fit" => Content::Fit,
"flex" => Content::Flex,
_ => Content::Normal,
})
}
Expand Down
10 changes: 10 additions & 0 deletions crates/state/src/values/size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ impl Parse for Size {
fn parse(value: &str) -> Result<Self, ParseError> {
if value == "auto" {
Ok(Size::Inner)
} else if value == "flex" {
Ok(Size::Flex(Length::new(1.0)))
} else if value.contains("flex") {
Ok(Size::Flex(Length::new(
value
.replace("flex(", "")
.replace(')', "")
.parse::<f32>()
.map_err(|_| ParseError)?,
)))
} else if value == "fill" {
Ok(Size::Fill)
} else if value == "fill-min" {
Expand Down
111 changes: 99 additions & 12 deletions crates/torin/src/measure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::{
AreaModel,
DirectionMode,
LayoutMetadata,
Length,
Torin,
},
};
Expand Down Expand Up @@ -308,8 +309,9 @@ where
) {
let children = self.dom_adapter.children_of(parent_node_id);

let mut initial_phase_flex_grows = FxHashMap::default();
let mut initial_phase_sizes = FxHashMap::default();
let mut initial_phase_inner_sizes = *inner_sizes;
let mut initial_phase_inner_sizes = Size2D::default();

// Used to calculate the spacing and some alignments
let (non_absolute_children_len, first_child, last_child) = if parent_node.spacing.get() > 0.
Expand Down Expand Up @@ -342,16 +344,18 @@ where
)
};

// Initial phase: Measure the size and position of the children if the parent has a
// non-start cross alignment, non-start main aligment of a fit-content.
if parent_node.cross_alignment.is_not_start()
let needs_initial_phase = parent_node.cross_alignment.is_not_start()
|| parent_node.main_alignment.is_not_start()
|| parent_node.content.is_fit()
{
let mut initial_phase_area = *area;
let mut initial_phase_inner_area = *inner_area;
let mut initial_phase_available_area = *available_area;
|| parent_node.content.is_flex();

let mut initial_phase_area = *area;
let mut initial_phase_inner_area = *inner_area;
let mut initial_phase_available_area = *available_area;

// Initial phase: Measure the size and position of the children if the parent has a
// non-start cross alignment, non-start main aligment of a fit-content.
if needs_initial_phase {
// Measure the children
for child_id in children.iter() {
let Some(child_data) = self.dom_adapter.get_node(child_id) else {
Expand Down Expand Up @@ -382,20 +386,74 @@ where
Self::stack_child(
&mut initial_phase_available_area,
parent_node,
&child_data,
&mut initial_phase_area,
&mut initial_phase_inner_area,
&mut initial_phase_inner_sizes,
&child_areas.area,
is_last_child,
Phase::Initial,
);

if parent_node.cross_alignment.is_not_start()
|| parent_node.main_alignment.is_spaced()
{
initial_phase_sizes.insert(*child_id, child_areas.area.size);
}

if parent_node.content.is_flex() {
match parent_node.direction {
DirectionMode::Vertical => {
if let Some(ff) = child_data.height.flex_grow() {
initial_phase_flex_grows.insert(*child_id, ff);
}
}
DirectionMode::Horizontal => {
if let Some(ff) = child_data.width.flex_grow() {
initial_phase_flex_grows.insert(*child_id, ff);
}
}
}
}
}
}

let initial_available_area = *available_area;

let flex_grows = initial_phase_flex_grows
.values()
.cloned()
.reduce(|acc, v| acc + v)
.unwrap_or_default()
.max(Length::new(1.0));

let flex_axis = AlignAxis::new(&parent_node.direction, AlignmentDirection::Main);

let flex_available_width = initial_available_area.width() - initial_phase_inner_sizes.width;
let flex_available_height =
initial_available_area.height() - initial_phase_inner_sizes.height;

let initial_phase_inner_sizes_with_flex =
initial_phase_flex_grows
.values()
.fold(initial_phase_inner_sizes, |mut acc, f| {
let flex_grow_per = f.get() / flex_grows.get() * 100.;

match flex_axis {
AlignAxis::Height => {
let size = flex_available_height / 100. * flex_grow_per;
acc.height += size;
}
AlignAxis::Width => {
let size = flex_available_width / 100. * flex_grow_per;
acc.width += size;
}
}

acc
});

if needs_initial_phase {
if parent_node.main_alignment.is_not_start() {
// Adjust the available and inner areas of the Main axis
Self::shrink_area_to_fit_when_unbounded(
Expand All @@ -410,7 +468,7 @@ where
Self::align_content(
available_area,
&initial_phase_inner_area,
&initial_phase_inner_sizes,
&initial_phase_inner_sizes_with_flex,
&parent_node.main_alignment,
&parent_node.direction,
AlignmentDirection::Main,
Expand Down Expand Up @@ -442,14 +500,33 @@ where

let mut adapted_available_area = *available_area;

if parent_node.content.is_flex() {
let flex_grow = initial_phase_flex_grows.get(&child_id);

if let Some(flex_grow) = flex_grow {
let flex_grow_per = flex_grow.get() / flex_grows.get() * 100.;

match flex_axis {
AlignAxis::Height => {
let size = flex_available_height / 100. * flex_grow_per;
adapted_available_area.size.height = size;
}
AlignAxis::Width => {
let size = flex_available_width / 100. * flex_grow_per;
adapted_available_area.size.width = size;
}
}
}
}

// Only the stacked children will be aligned
if parent_node.main_alignment.is_spaced() && !child_data.position.is_absolute() {
// Align the Main axis if necessary
Self::align_position(
AlignmentDirection::Main,
&mut adapted_available_area,
&initial_available_area,
&initial_phase_inner_sizes,
&initial_phase_inner_sizes_with_flex,
&parent_node.main_alignment,
&parent_node.direction,
non_absolute_children_len,
Expand Down Expand Up @@ -492,11 +569,13 @@ where
Self::stack_child(
available_area,
parent_node,
&child_data,
area,
inner_area,
inner_sizes,
&child_areas.area,
is_last_child,
Phase::Final,
);
}

Expand Down Expand Up @@ -612,11 +691,13 @@ where
fn stack_child(
available_area: &mut Area,
parent_node: &Node,
child_node: &Node,
parent_area: &mut Area,
inner_area: &mut Area,
inner_sizes: &mut Size2D,
child_area: &Area,
is_last_sibiling: bool,
phase: Phase,
) {
// Only apply the spacing to elements after `i > 0` and `i < len - 1`
let spacing = (!is_last_sibiling)
Expand All @@ -630,7 +711,10 @@ where
available_area.size.width -= child_area.size.width + spacing.get();

inner_sizes.height = child_area.height().max(inner_sizes.height);
inner_sizes.width += child_area.width() + spacing.get();
inner_sizes.width += spacing.get();
if !child_node.width.is_flex() || phase == Phase::Final {
inner_sizes.width += child_area.width();
}

// Keep the biggest height
if parent_node.height.inner_sized() {
Expand All @@ -656,7 +740,10 @@ where
available_area.size.height -= child_area.size.height + spacing.get();

inner_sizes.width = child_area.width().max(inner_sizes.width);
inner_sizes.height += child_area.height() + spacing.get();
inner_sizes.height += spacing.get();
if !child_node.height.is_flex() || phase == Phase::Final {
inner_sizes.height += child_area.height();
}

// Keep the biggest width
if parent_node.width.inner_sized() {
Expand Down
6 changes: 6 additions & 0 deletions crates/torin/src/values/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ pub enum Content {
#[default]
Normal,
Fit,
Flex,
}

impl Content {
pub fn is_fit(&self) -> bool {
self == &Self::Fit
}

pub fn is_flex(&self) -> bool {
self == &Self::Flex
}
}

impl Content {
pub fn pretty(&self) -> String {
match self {
Self::Normal => "normal".to_owned(),
Self::Fit => "fit".to_owned(),
Self::Flex => "flex".to_owned(),
}
}
}
22 changes: 15 additions & 7 deletions crates/torin/src/values/size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub enum Size {
RootPercentage(Length),
InnerPercentage(Length),
DynamicCalculations(Box<Vec<DynamicCalculation>>),
Flex(Length),
}

impl Default for Size {
Expand All @@ -30,6 +31,17 @@ impl Default for Size {
}

impl Size {
pub fn flex_grow(&self) -> Option<Length> {
match self {
Self::Flex(f) => Some(*f),
_ => None,
}
}

pub fn is_flex(&self) -> bool {
matches!(self, Self::Flex(_))
}

pub fn inner_sized(&self) -> bool {
matches!(
self,
Expand Down Expand Up @@ -58,6 +70,7 @@ impl Size {
Size::FillMinimum => "fill-min".to_string(),
Size::RootPercentage(p) => format!("{}% of root", p.get()),
Size::InnerPercentage(p) => format!("{}% of auto", p.get()),
Size::Flex(f) => format!("flex({}", f.get()),
}
}

Expand All @@ -76,14 +89,9 @@ impl Size {
run_calculations(calculations.deref(), parent_value, root_value).unwrap_or(0.0),
),
Size::Fill => Some(available_parent_value),
Size::FillMinimum => {
if phase == Phase::Initial {
None
} else {
Some(available_parent_value)
}
}
Size::FillMinimum if phase == Phase::Final => Some(available_parent_value),
Size::RootPercentage(per) => Some(root_value / 100.0 * per.get()),
Size::Flex(_) if phase == Phase::Final => Some(available_parent_value),
_ => None,
}
}
Expand Down
Loading

0 comments on commit 00ca902

Please sign in to comment.