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

feat: flex support #920

Merged
merged 10 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading