Skip to content

Commit

Permalink
feat: Absolute positioning (#370)
Browse files Browse the repository at this point in the history
* chore: Organize torin tests

* feat: Absolute positioning

* top, right, bottom and left

* tests

* docs

* remove printlns

* update docs
  • Loading branch information
marc2332 authored Nov 11, 2023
1 parent 72adc59 commit ebc5850
Show file tree
Hide file tree
Showing 13 changed files with 451 additions and 37 deletions.
40 changes: 39 additions & 1 deletion book/src/guides/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ Learn how the layout attributes work.
- [`fill`](#fill)
- [`direction`](#direction)
- [`padding`](#padding)
- [`margin`](#margin)
- [`main_align & cross_align`](#main_align--cross_align)
- [`margin`](#margin)
- [`position`](#position)

> ⚠️ Freya's layout is still somewhat limited.
Expand Down Expand Up @@ -255,4 +256,41 @@ fn app(cx: Scope) -> Element {
}
)
}
```

### position

Specify how you want the element to be positioned inside it's parent Area

Possible values for `position`:
- `stacked` (default)
- `absolute`

When using the `absolute` mode, you can also combine it with the following attributes:
- `position_top`
- `position_right`
- `position_bottom`
- `position_left`

These only support pixels.

Example:

```rust, no_run
fn app(cx: Scope) -> Element {
render!(
rect {
width: "100%",
height: "100%",
rect {
position: "absolute",
position_bottom: "15",
position_right: "15",
background: "black",
width: "100",
height: "100",
}
}
)
}
```
35 changes: 18 additions & 17 deletions crates/dom/src/dom_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,30 @@ impl<'a> DioxusDOMAdapter<'a> {
impl DOMAdapter<NodeId> for DioxusDOMAdapter<'_> {
fn get_node(&self, node_id: &NodeId) -> Option<Node> {
let node = self.rdom.get(*node_id)?;
let mut size = node.get::<LayoutState>().unwrap().clone();
let mut layout = node.get::<LayoutState>().unwrap().clone();

// The root node expands by default
if *node_id == self.rdom.root_id() {
size.width = Size::Percentage(Length::new(100.0));
size.height = Size::Percentage(Length::new(100.0));
layout.width = Size::Percentage(Length::new(100.0));
layout.height = Size::Percentage(Length::new(100.0));
}

Some(Node {
width: size.width,
height: size.height,
minimum_width: size.minimum_width,
minimum_height: size.minimum_height,
maximum_width: size.maximum_width,
maximum_height: size.maximum_height,
direction: size.direction,
padding: size.padding,
margin: size.margin,
main_alignment: size.main_alignment,
cross_alignment: size.cross_alignment,
offset_x: Length::new(size.offset_x),
offset_y: Length::new(size.offset_y),
has_layout_references: size.node_ref.is_some(),
width: layout.width,
height: layout.height,
minimum_width: layout.minimum_width,
minimum_height: layout.minimum_height,
maximum_width: layout.maximum_width,
maximum_height: layout.maximum_height,
direction: layout.direction,
padding: layout.padding,
margin: layout.margin,
main_alignment: layout.main_alignment,
cross_alignment: layout.cross_alignment,
offset_x: Length::new(layout.offset_x),
offset_y: Length::new(layout.offset_y),
has_layout_references: layout.node_ref.is_some(),
position: layout.position,
})
}

Expand Down
5 changes: 5 additions & 0 deletions crates/elements/src/elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ builder_constructors! {
name: String,
focusable: String,
margin: String,
position: String,
position_top: String,
position_right: String,
position_bottom: String,
position_left: String,
};
label {
color: String,
Expand Down
46 changes: 45 additions & 1 deletion crates/state/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct LayoutState {
pub offset_x: f32,
pub main_alignment: Alignment,
pub cross_alignment: Alignment,
pub position: Position,
pub node_ref: Option<UnboundedSender<NodeReferenceLayout>>,
}

Expand Down Expand Up @@ -57,6 +58,11 @@ impl State<CustomAttributeValues> for LayoutState {
"cross_align",
"reference",
"margin",
"position",
"position_top",
"position_right",
"position_bottom",
"position_left",
]))
.with_tag()
.with_text();
Expand Down Expand Up @@ -190,6 +196,43 @@ impl State<CustomAttributeValues> for LayoutState {
}
}
}
"position" => {
if let Some(value) = attr.value.as_text() {
if let Ok(position) = Position::parse(value) {
if layout.position.is_empty() {
layout.position = position;
}
}
}
}
"position_top" => {
if let Some(value) = attr.value.as_text() {
if let Ok(top) = value.parse::<f32>() {
layout.position.set_top(top * scale_factor);
}
}
}
"position_right" => {
if let Some(value) = attr.value.as_text() {
if let Ok(right) = value.parse::<f32>() {
layout.position.set_right(right * scale_factor);
}
}
}
"position_bottom" => {
if let Some(value) = attr.value.as_text() {
if let Ok(bottom) = value.parse::<f32>() {
layout.position.set_bottom(bottom * scale_factor);
}
}
}
"position_left" => {
if let Some(value) = attr.value.as_text() {
if let Ok(left) = value.parse::<f32>() {
layout.position.set_left(left * scale_factor);
}
}
}
"reference" => {
if let OwnedAttributeValue::Custom(CustomAttributeValues::Reference(
reference,
Expand Down Expand Up @@ -217,7 +260,8 @@ impl State<CustomAttributeValues> for LayoutState {
|| (layout.offset_x != self.offset_x)
|| (layout.offset_y != self.offset_y)
|| (layout.main_alignment != self.main_alignment)
|| (layout.cross_alignment != self.cross_alignment);
|| (layout.cross_alignment != self.cross_alignment)
|| (layout.position != self.position);

if changed {
torin_layout.lock().unwrap().invalidate(node_view.node_id());
Expand Down
2 changes: 2 additions & 0 deletions crates/state/src/values/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod font;
mod gaps;
mod gradient;
mod overflow;
mod position;
mod shadow;
mod size;
mod text_shadow;
Expand All @@ -26,6 +27,7 @@ pub use font::*;
pub use gaps::*;
pub use gradient::*;
pub use overflow::*;
pub use position::*;
pub use shadow::*;
pub use size::*;
pub use text_shadow::*;
16 changes: 16 additions & 0 deletions crates/state/src/values/position.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::Parse;
use torin::position::Position;

#[derive(Debug, PartialEq, Eq)]
pub struct ParsePositionError;

impl Parse for Position {
type Err = ParsePositionError;

fn parse(value: &str) -> Result<Self, Self::Err> {
Ok(match value {
"absolute" => Position::new_absolute(),
_ => Position::Stacked,
})
}
}
44 changes: 27 additions & 17 deletions crates/torin/src/measure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,21 @@ pub fn measure_node<Key: NodeKey>(
) -> (bool, NodeAreas) {
let must_run = layout.dirty.contains(&node_id) || layout.results.get(&node_id).is_none();
if must_run {
// 1. Create the initial Node area
let mut area = Rect::new(
available_parent_area.origin,
Size2D::new(node.padding.horizontal(), node.padding.vertical()),
);
// 1. Create the initial Node area size
let mut area_size = Size2D::new(node.padding.horizontal(), node.padding.vertical());

// 2. Compute the width and height given the size, the minimum size, the maximum size and margins
area.size.width = node.width.min_max(
area.size.width,
area_size.width = node.width.min_max(
area_size.width,
parent_area.size.width,
available_parent_area.size.width,
node.margin.left(),
node.margin.horizontal(),
&node.minimum_width,
&node.maximum_width,
);
area.size.height = node.height.min_max(
area.size.height,
area_size.height = node.height.min_max(
area_size.height,
parent_area.size.height,
available_parent_area.size.height,
node.margin.top(),
Expand All @@ -55,14 +52,21 @@ pub fn measure_node<Key: NodeKey>(
&node.maximum_height,
);

// 3. If available, run a custom layout measure function
// 3. Compute the origin of the area
let area_origin = node
.position
.get_origin(available_parent_area, parent_area, &area_size);

let mut area = Rect::new(area_origin, area_size);

// 4. If available, run a custom layout measure function
// This is useful when you use third-party libraries (e.g. rust-skia, cosmic-text) to measure text layouts
// When a Node is measured by a custom measurer function the inner children will be skipped
let measure_inner_children = if let Some(measurer) = measurer {
let custom_area =
measurer.measure(node_id, node, &area, parent_area, available_parent_area);

// 3.1. Compute the width and height again using the new custom area sizes
// 4.1. Compute the width and height again using the new custom area sizes
if let Some(custom_area) = custom_area {
if Size::Inner == node.width {
area.size.width = node.width.min_max(
Expand Down Expand Up @@ -94,11 +98,11 @@ pub fn measure_node<Key: NodeKey>(
true
};

// 4. Compute the inner area of the Node, which is basically the area inside the margins and paddings
// 5. Compute the inner area of the Node, which is basically the area inside the margins and paddings
let mut inner_area = {
let mut inner_area = area;

// 4.1. When having an unsized bound we set it to whatever is still available in the parent's area
// 5.1. When having an unsized bound we set it to whatever is still available in the parent's area
if Size::Inner == node.width {
inner_area.size.width = node.width.min_max(
available_parent_area.width(),
Expand Down Expand Up @@ -130,18 +134,18 @@ pub fn measure_node<Key: NodeKey>(
let mut inner_sizes = Size2D::default();

if measure_inner_children {
// 5. Create an area containing the available space inside the inner area
// 6. Create an area containing the available space inside the inner area
let mut available_area = inner_area;

// 5.1. Adjust the available area with the node offsets (mainly used by scrollviews)
// 6.1. 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 {
area: &mut area,
inner_area: &mut inner_area,
};

// 6. Measure the layout of this Node's children
// 7. Measure the layout of this Node's children
measure_inner_nodes(
&node_id,
node,
Expand Down Expand Up @@ -259,7 +263,13 @@ pub fn measure_inner_nodes<Key: NodeKey>(
);

// Stack the child into its parent
mode.stack_into_node(parent_node, available_area, &child_areas.area, inner_sizes);
mode.stack_into_node(
parent_node,
available_area,
&child_areas.area,
inner_sizes,
&child_data,
);

// Cache the child layout if it was mutated and inner nodes must be cache
if child_revalidated && must_cache_inner_nodes {
Expand Down
5 changes: 5 additions & 0 deletions crates/torin/src/measure_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ impl<'a> MeasureMode<'a> {
available_area: &mut Area,
content_area: &Area,
inner_sizes: &mut Size2D,
node_data: &Node,
) {
if node_data.position.is_absolute() {
return;
}

match parent_node.direction {
DirectionMode::Horizontal => {
// Move the available area
Expand Down
15 changes: 14 additions & 1 deletion crates/torin/src/node.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub use euclid::Rect;

use crate::{
alignment::Alignment, direction::DirectionMode, gaps::Gaps, geometry::Length, size::Size,
alignment::Alignment, direction::DirectionMode, gaps::Gaps, geometry::Length,
prelude::Position, size::Size,
};

/// Node layout configuration
Expand Down Expand Up @@ -36,6 +37,8 @@ pub struct Node {
/// Direction in which it's inner Nodes will be stacked
pub direction: DirectionMode,

pub position: Position,

/// A Node might depend on inner sizes but have a fixed position, like scroll views.
pub has_layout_references: bool,
}
Expand Down Expand Up @@ -146,6 +149,16 @@ impl Node {
}
}

/// Construct a new Node given a size and a position
pub fn from_size_and_position(width: Size, height: Size, position: Position) -> Self {
Self {
width,
height,
position,
..Default::default()
}
}

/// Has properties that depend on the inner Nodes?
pub fn does_depend_on_inner(&self) -> bool {
Size::Inner == self.width
Expand Down
2 changes: 2 additions & 0 deletions crates/torin/src/values/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
pub mod alignment;
pub mod direction;
pub mod gaps;
pub mod position;
pub mod size;

pub mod prelude {
pub use crate::alignment::*;
pub use crate::direction::*;
pub use crate::gaps::*;
pub use crate::position::*;
pub use crate::size::*;
}
Loading

0 comments on commit ebc5850

Please sign in to comment.