Description
taffy
version
0.7.5
Platform
Rust
What you did
use taffy::prelude::*;
use taffy::util::print_tree;
fn main() {
let mut tree: TaffyTree<()> = TaffyTree::new();
let text = tree
.new_leaf(Style {
size: Size {
width: length(1.0),
height: length(1.0),
},
..Default::default()
})
.unwrap();
let flex_container = tree
.new_with_children(
Style {
justify_content: Some(JustifyContent::End),
border: Rect {
top: length(1.0),
left: length(1.0),
right: length(1.0),
bottom: length(1.0),
},
grid_row: Line {
start: line(1),
end: span(4),
},
grid_column: Line {
start: line(2),
end: span(1),
},
..Default::default()
},
&[text],
)
.unwrap();
let grid_container = tree
.new_with_children(
Style {
size: Size {
width: percent(1.0),
height: percent(1.0),
},
display: Display::Grid,
// 12 by 12 grid
grid_template_rows: vec![repeat(GridTrackRepetition::Count(12), vec![fr(1.0)])],
grid_template_columns: vec![repeat(GridTrackRepetition::Count(12), vec![fr(1.0)])],
..Default::default()
},
&[flex_container],
)
.unwrap();
tree.compute_layout(
grid_container,
Size {
width: AvailableSpace::Definite(127.0),
height: AvailableSpace::Definite(29.0),
},
)
.unwrap();
print_tree(&tree, grid_container);
}
What went wrong
The above layout print this:
TREE
└── GRID [x: 0 y: 0 w: 127 h: 29 content_w: 21 content_h: 10 border: l:0 r:0 t:0 b:0, padding: l:0 r:0 t:0 b:0] (NodeId(4294967299))
└── FLEX ROW [x: 11 y: 0 w: 10 h: 10 content_w: 9 content_h: 2 border: l:1 r:1 t:1 b:1, padding: l:0 r:0 t:0 b:0] (NodeId(4294967298))
└── LEAF [x: 9 y: 1 w: 1 h: 1 content_w: 0 content_h: 0 border: l:0 r:0 t:0 b:0, padding: l:0 r:0 t:0 b:0] (NodeId(4294967297))
Notice the leaf node, the parent width is 10 with border of 1. The leaf node is aligned at the right edge (justify content = end) of the parent. It should be 8, instead of 9. It produces a layout like this (-
means empty pixel):
-----------┌────────┐---------------------------------------------------------------------------------------------------------
-----------│--------A---------------------------------------------------------------------------------------------------------
-----------│--------│----------------------------------------------------------------------------------------------------------
-----------│--------│----------------------------------------------------------------------------------------------------------
-----------│--------│----------------------------------------------------------------------------------------------------------
-----------│--------│----------------------------------------------------------------------------------------------------------
-----------│--------│----------------------------------------------------------------------------------------------------------
-----------│--------│----------------------------------------------------------------------------------------------------------
-----------│--------│----------------------------------------------------------------------------------------------------------
-----------└────────┘----------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------
The letter A is the position of the leaf node. As you can see, it is overlapping with the border.
Additional information
Context
I'm building a terminal UI application and using taffy as the layout engine. Due to the nature of terminal UI, off by one pixel errors are very noticeable.
My theory
Since it doesn't happen consistently, only on some specific viewport sizes, I suspect it is caused by rounding errors. Maybe these lines 🤔 ?
In here, the relative location of a child node is always rounded. It doesn't take into consideration how the parent size changes due to rounding. In my example, the parent node is shrink by 1 (from 11 to 10) after rounding, but the child node still round up. The situation is reversed if the parent grow by 1, it will create a gap of 1 pixel between the child and the parent border.
I attempted to fix it my making the child aware of how the parent's size changes. Each time we perform rounding for a child node, we pass a scale factor: how much the parent node have grow/shrink:
scale = layout.size.width / unrounded_layout.size.width
The child location (x, y) will be scale up according to the scale factor:
layout.location.x = round(unrounded_layout.location.x * scale_factor.width)
layout.location.y = round(unrounded_layout.location.y * scale_factor.height)
It did fix my cases but broke a lot of tests.
Activity