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: Support space-between/space-around/space-evenly alignments #758

Merged
merged 7 commits into from
Jul 9, 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
3 changes: 3 additions & 0 deletions crates/core/src/skia/skia_measurer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ pub fn align_main_align_paragraph(node: &DioxusNode, area: &Area, paragraph: &Pa
Alignment::Start => 0.,
Alignment::Center => (area.height() / 2.0) - (paragraph.height() / 2.0),
Alignment::End => area.height() - paragraph.height(),
Alignment::SpaceBetween => 0.,
Alignment::SpaceEvenly => 0.,
Alignment::SpaceAround => 0.,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Accepted values for both attributes are:
- `start` (default): At the begining of the axis
- `center`: At the center of the axis
- `end`: At the end of the axis
- `space-between`(only for `main_align`): Distributed among the available space
- `space-around` (only for `main_align`): Distributed among the available space with small margins in the sides
- `space-between` (only for `main_align`): Distributed among the available space with the same size of margins in the sides and in between the elements.

When using the `vertical` direction, `main_align` will be the Y axis and `cross_align` will be the X axis. But when using the `horizontal` direction, the
`main_align` will be the X axis and the `cross_align` will be the Y axis.
Expand Down
3 changes: 3 additions & 0 deletions crates/state/src/values/alignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ impl Parse for Alignment {
Ok(match value {
"center" => Alignment::Center,
"end" => Alignment::End,
"space-between" => Alignment::SpaceBetween,
"space-evenly" => Alignment::SpaceEvenly,
"space-around" => Alignment::SpaceAround,
_ => Alignment::Start,
})
}
Expand Down
18 changes: 18 additions & 0 deletions crates/state/tests/parse_display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ fn parse_end_alignment() {
assert_eq!(alignment, Ok(Alignment::End));
}

#[test]
fn parse_space_between_alignment() {
let alignment = Alignment::parse("space-between");
assert_eq!(alignment, Ok(Alignment::SpaceBetween));
}

#[test]
fn parse_space_around_alignment() {
let alignment = Alignment::parse("space-around");
assert_eq!(alignment, Ok(Alignment::SpaceAround));
}

#[test]
fn parse_space_evenly_alignment() {
let alignment = Alignment::parse("space-evenly");
assert_eq!(alignment, Ok(Alignment::SpaceEvenly));
}

#[test]
fn parse_fallback_alignment() {
let alignment = Alignment::parse("Hello, World!");
Expand Down
78 changes: 78 additions & 0 deletions crates/torin/src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ pub trait AreaModel {
// The area without any outer gap (e.g margin)
fn after_gaps(&self, margin: &Gaps) -> Area;

// Adjust the available area with the node offsets (mainly used by scrollviews)
fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length);

// Align the content of this node.
fn align_content(
&mut self,
available_area: &Area,
Expand All @@ -27,6 +29,19 @@ pub trait AreaModel {
direction: &DirectionMode,
alignment_direction: AlignmentDirection,
);

// Align the position of this node.
#[allow(clippy::too_many_arguments)]
fn align_position(
&mut self,
initial_available_area: &Area,
inner_sizes: &Size2D,
alignment: &Alignment,
direction: &DirectionMode,
alignment_direction: AlignmentDirection,
siblings_len: usize,
child_position: usize,
);
}

impl AreaModel for Area {
Expand Down Expand Up @@ -85,6 +100,68 @@ impl AreaModel for Area {
},
}
}

fn align_position(
&mut self,
initial_available_area: &Area,
inner_sizes: &Size2D,
alignment: &Alignment,
direction: &DirectionMode,
alignment_direction: AlignmentDirection,
siblings_len: usize,
child_position: usize,
) {
let axis = get_align_axis(direction, alignment_direction);

match axis {
AlignAxis::Height => match alignment {
Alignment::SpaceBetween if child_position > 0 => {
let all_gaps_sizes = initial_available_area.height() - inner_sizes.height;
let gap_size = all_gaps_sizes / (siblings_len - 1) as f32;
self.origin.y += gap_size;
}
Alignment::SpaceEvenly => {
let all_gaps_sizes = initial_available_area.height() - inner_sizes.height;
let gap_size = all_gaps_sizes / (siblings_len + 1) as f32;
self.origin.y += gap_size;
}
Alignment::SpaceAround => {
let all_gaps_sizes = initial_available_area.height() - inner_sizes.height;
let one_gap_size = all_gaps_sizes / siblings_len as f32;
let gap_size = if child_position == 0 || child_position == siblings_len {
one_gap_size / 2.
} else {
one_gap_size
};
self.origin.y += gap_size;
}
_ => {}
},
AlignAxis::Width => match alignment {
Alignment::SpaceBetween if child_position > 0 => {
let all_gaps_sizes = initial_available_area.width() - inner_sizes.width;
let gap_size = all_gaps_sizes / (siblings_len - 1) as f32;
self.origin.x += gap_size;
}
Alignment::SpaceEvenly => {
let all_gaps_sizes = initial_available_area.width() - inner_sizes.width;
let gap_size = all_gaps_sizes / (siblings_len + 1) as f32;
self.origin.x += gap_size;
}
Alignment::SpaceAround => {
let all_gaps_sizes = initial_available_area.width() - inner_sizes.width;
let one_gap_size = all_gaps_sizes / siblings_len as f32;
let gap_size = if child_position == 0 || child_position == siblings_len {
one_gap_size / 2.
} else {
one_gap_size
};
self.origin.x += gap_size;
}
_ => {}
},
}
}
}

pub fn get_align_axis(
Expand All @@ -108,6 +185,7 @@ pub enum AlignmentDirection {
Cross,
}

#[derive(Debug)]
pub enum AlignAxis {
Height,
Width,
Expand Down
26 changes: 21 additions & 5 deletions crates/torin/src/measure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ pub fn measure_node<Key: NodeKey>(
// Create an area containing the available space inside the inner area
let mut available_area = inner_area;

// Adjust the available area with the node offsets (mainly used by scrollviews)
available_area.move_with_offsets(&node.offset_x, &node.offset_y);

let mut measurement_mode = MeasureMode::ParentIsNotCached {
Expand Down Expand Up @@ -290,6 +289,7 @@ pub fn measure_inner_nodes<Key: NodeKey>(
let children = dom_adapter.children_of(parent_node_id);

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

// 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.
Expand All @@ -299,7 +299,6 @@ pub fn measure_inner_nodes<Key: NodeKey>(
{
let mut initial_phase_mode = mode.to_owned();
let mut initial_phase_mode = initial_phase_mode.to_mut();
let mut initial_phase_inner_sizes = *inner_sizes;
let mut initial_phase_available_area = *available_area;

// 1. Measure the children
Expand Down Expand Up @@ -336,7 +335,8 @@ pub fn measure_inner_nodes<Key: NodeKey>(
&child_data,
);

if parent_node.cross_alignment.is_not_start() {
if parent_node.cross_alignment.is_not_start() || parent_node.main_alignment.is_spaced()
{
initial_phase_sizes.insert(*child_id, child_areas.area.size);
}
}
Expand Down Expand Up @@ -369,18 +369,34 @@ pub fn measure_inner_nodes<Key: NodeKey>(
}
}

let initial_available_area = *available_area;

// Final phase: measure the children with all the axis and sizes adjusted
for child_id in children {
for (child_n, child_id) in children.into_iter().enumerate() {
let Some(child_data) = dom_adapter.get_node(&child_id) else {
continue;
};

let mut adapted_available_area = *available_area;

if parent_node.main_alignment.is_spaced() {
// Align the Main axis if necessary
adapted_available_area.align_position(
&initial_available_area,
&initial_phase_inner_sizes,
&parent_node.main_alignment,
&parent_node.direction,
AlignmentDirection::Main,
initial_phase_sizes.len(),
child_n,
);
}

if parent_node.cross_alignment.is_not_start() {
let initial_phase_size = initial_phase_sizes.get(&child_id);

if let Some(initial_phase_size) = initial_phase_size {
// 1. Align the Cross axis if necessary
// Align the Cross axis if necessary
adapted_available_area.align_content(
available_area,
initial_phase_size,
Expand Down
13 changes: 13 additions & 0 deletions crates/torin/src/values/alignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,31 @@ pub enum Alignment {
Start,
Center,
End,
SpaceBetween,
SpaceEvenly,
SpaceAround,
}

impl Alignment {
pub fn is_not_start(&self) -> bool {
*self != Self::Start
}

pub fn is_spaced(&self) -> bool {
matches!(
self,
Self::SpaceBetween | Self::SpaceAround | Self::SpaceEvenly
)
}

pub fn pretty(&self) -> String {
match self {
Alignment::Start => "start".to_string(),
Alignment::Center => "center".to_string(),
Alignment::End => "end".to_string(),
Alignment::SpaceBetween => "space-between".to_string(),
Alignment::SpaceEvenly => "space-evenly".to_string(),
Alignment::SpaceAround => "space-around".to_string(),
}
}
}
Loading
Loading