use crate::android_interface::{FromTaffyLayout as _, LayoutChangedResponseUnchangedWithState};
use crate::into_taffy::TryIntoTaffy;
use dc_bundle::jni_layout::{Layout, LayoutChangedResponse};
use dc_bundle::layout_style::LayoutStyle;
use dc_bundle::Error;
use log::{error, trace};
use std::collections::{HashMap, HashSet};
use taffy::prelude::{AvailableSpace, Size};
use taffy::{NodeId, TaffyTree};
struct Customizations {
sizes: HashMap<i32, Size<u32>>,
}
impl Customizations {
fn new() -> Self {
Customizations { sizes: HashMap::new() }
}
fn add_size(&mut self, layout_id: i32, width: u32, height: u32) {
self.sizes.insert(layout_id, Size { width, height });
}
fn get_size(&self, layout_id: i32) -> Option<&Size<u32>> {
self.sizes.get(&layout_id)
}
fn remove(&mut self, layout_id: &i32) {
self.sizes.remove(layout_id);
}
}
pub struct LayoutManager {
taffy: TaffyTree<i32>,
layouts: HashMap<taffy::tree::NodeId, Layout>,
customizations: Customizations,
layout_state: i32,
layout_id_to_taffy_node: HashMap<i32, taffy::tree::NodeId>,
taffy_node_to_layout_id: HashMap<taffy::tree::NodeId, i32>,
layout_id_to_name: HashMap<i32, String>,
root_layout_ids: HashSet<i32>,
#[allow(clippy::type_complexity)]
measure_func: Box<
dyn FnMut(
Size<Option<f32>>,
Size<AvailableSpace>,
NodeId,
Option<&mut i32>,
&taffy::Style,
) -> Size<f32>
+ Sync
+ Send,
>,
}
impl LayoutManager {
pub fn new(
mut measure_func: impl FnMut(i32, f32, f32, f32, f32) -> (f32, f32) + Sync + Send + 'static,
) -> Self {
let layout_measure_func = move |size: Size<Option<f32>>,
available_size: Size<AvailableSpace>,
_node_id: NodeId,
layout_id: Option<&mut i32>,
_style: &taffy::Style|
-> Size<f32> {
let layout_id = if let Some(&mut id) = layout_id { id } else { return Size::ZERO };
let width = size.width.unwrap_or(0.0);
let height = size.height.unwrap_or(0.0);
let available_width = match available_size.width {
AvailableSpace::Definite(w) => w,
AvailableSpace::MaxContent => f32::MAX,
AvailableSpace::MinContent => 0.0,
};
let available_height = match available_size.height {
AvailableSpace::Definite(h) => h,
AvailableSpace::MaxContent => f32::MAX,
AvailableSpace::MinContent => 0.0,
};
let result = measure_func(layout_id, width, height, available_width, available_height);
Size { width: result.0, height: result.1 }
};
LayoutManager {
taffy: TaffyTree::new(),
layouts: HashMap::new(),
customizations: Customizations::new(),
layout_state: 0,
layout_id_to_taffy_node: HashMap::new(),
taffy_node_to_layout_id: HashMap::new(),
layout_id_to_name: HashMap::new(),
root_layout_ids: HashSet::new(),
measure_func: Box::new(layout_measure_func),
}
}
fn update_layout_internal(
&mut self,
layout_id: i32,
parent_layout_id: i32,
changed: &mut HashMap<i32, Layout>,
) {
let node = self.layout_id_to_taffy_node.get(&layout_id);
if let Some(node) = node {
let layout = self.taffy.layout(*node);
if let Ok(layout) = layout {
let layout = Layout::from_taffy_layout(layout);
let old_layout = self.layouts.get(node);
let mut layout_changed = false;
if let Some(old_layout) = old_layout {
if &layout != old_layout {
layout_changed = true;
}
} else {
layout_changed = true;
}
if layout_changed {
changed.insert(layout_id, layout.clone());
if parent_layout_id >= 0 {
if let std::collections::hash_map::Entry::Vacant(e) =
changed.entry(parent_layout_id)
{
let parent_node = self.layout_id_to_taffy_node.get(&parent_layout_id);
if let Some(parent_node) = parent_node {
let parent_layout = self.layouts.get(parent_node);
if let Some(parent_layout) = parent_layout {
e.insert(parent_layout.clone());
}
}
}
}
self.layouts.insert(*node, layout);
}
}
let children_result = self.taffy.children(*node);
match children_result {
Ok(children) => {
for child in children {
let child_layout_id = self.taffy_node_to_layout_id.get(&child);
if let Some(child_layout_id) = child_layout_id {
self.update_layout_internal(*child_layout_id, layout_id, changed);
}
}
}
Err(e) => {
error!("taffy children error: {}", e);
}
}
}
}
fn update_layout(&mut self, layout_id: i32) -> HashMap<i32, Layout> {
let mut changed: HashMap<i32, Layout> = HashMap::new();
self.update_layout_internal(layout_id, -1, &mut changed);
changed
}
pub fn get_node_layout(&self, layout_id: i32) -> Option<Layout> {
let node = self.layout_id_to_taffy_node.get(&layout_id);
if let Some(node) = node {
let layout = self.taffy.layout(*node);
if let Ok(layout) = layout {
return Some(Layout::from_taffy_layout(layout));
}
}
None
}
pub fn update_children(&mut self, parent_layout_id: i32, children: &[i32]) {
if let Some(parent_node) = self.layout_id_to_taffy_node.get(&parent_layout_id) {
let child_nodes: Vec<_> = children
.iter()
.filter_map(|child_id| self.layout_id_to_taffy_node.get(child_id).copied())
.collect();
if let Err(e) = self.taffy.set_children(*parent_node, child_nodes.as_slice()) {
error!("error setting children! {:?}", e);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn add_style(
&mut self,
layout_id: i32,
parent_layout_id: i32,
child_index: i32,
style: LayoutStyle,
name: String,
use_measure_func: bool,
fixed_width: Option<i32>,
fixed_height: Option<i32>,
) -> Result<(), Error> {
let mut node_style: taffy::style::Style = (&style).try_into_taffy()?;
self.apply_customizations(layout_id, &mut node_style);
self.apply_fixed_size(&mut node_style, fixed_width, fixed_height);
let node_context = if use_measure_func { Some(layout_id) } else { None };
let node = self.layout_id_to_taffy_node.get(&layout_id);
if let Some(node) = node {
if let Err(e) = self.taffy.set_style(*node, node_style) {
error!("taffy set_style error: {}", e);
}
let _ = self.taffy.set_node_context(*node, node_context);
return Ok(());
}
let node = match self.taffy.new_leaf(node_style) {
Ok(node) => node,
Err(e) => {
error!("taffy_new_leaf error: {}", e);
return Ok(());
}
};
let _ = self.taffy.set_node_context(node, node_context);
self.taffy_node_to_layout_id.insert(node, layout_id);
self.layout_id_to_taffy_node.insert(layout_id, node);
self.layout_id_to_name.insert(layout_id, name.clone());
match self.layout_id_to_taffy_node.get(&parent_layout_id) {
Some(parent_node) => {
self.root_layout_ids.remove(&layout_id);
if child_index < 0 {
} else {
let children_result = self.taffy.children(*parent_node);
match children_result {
Ok(mut children) => {
children.insert(child_index as usize, node);
let set_children_result =
self.taffy.set_children(*parent_node, children.as_ref());
if let Some(e) = set_children_result.err() {
error!("taffy set_children error: {}", e);
}
}
Err(e) => {
error!("taffy_children error: {}", e);
}
}
}
}
None => {
self.root_layout_ids.insert(layout_id);
}
}
Ok(())
}
pub fn remove_view(
&mut self,
layout_id: i32,
root_layout_id: i32,
compute_layout: bool,
) -> LayoutChangedResponse {
let taffy_node = self.layout_id_to_taffy_node.get(&layout_id);
if let Some(taffy_node) = taffy_node {
let parent = self.taffy.parent(*taffy_node);
if let Some(parent) = parent {
let dirty_result = self.taffy.mark_dirty(parent);
if let Some(e) = dirty_result.err() {
error!("taffy dirty error: {}", e);
}
}
let remove_result = self.taffy.remove(*taffy_node);
match remove_result {
Ok(removed_node) => {
self.taffy_node_to_layout_id.remove(&removed_node);
self.layout_id_to_taffy_node.remove(&layout_id);
self.layout_id_to_name.remove(&layout_id);
self.root_layout_ids.remove(&layout_id);
self.customizations.remove(&layout_id);
}
Err(e) => {
error!("taffy remove error: {}", e);
}
}
} else {
error!("no taffy node for layout_id {}", layout_id);
}
if compute_layout {
self.compute_node_layout(root_layout_id)
} else {
LayoutChangedResponse::unchanged_with_state(self.layout_state)
}
}
pub fn mark_dirty(&mut self, layout_id: i32) {
if let Some(&taffy_node) = self.layout_id_to_taffy_node.get(&layout_id) {
if let Err(e) = self.taffy.mark_dirty(taffy_node) {
error!("taffy: mark dirty error: {}", e);
}
}
}
pub fn set_node_size(
&mut self,
layout_id: i32,
root_layout_id: i32,
width: u32,
height: u32,
) -> LayoutChangedResponse {
let node = self.layout_id_to_taffy_node.get(&layout_id);
if let Some(node) = node {
let result = self.taffy.style(*node);
match result {
Ok(style) => {
let mut new_style = style.clone();
new_style.min_size.width = taffy::prelude::Dimension::Length(width as f32);
new_style.min_size.height = taffy::prelude::Dimension::Length(height as f32);
new_style.size.width = taffy::prelude::Dimension::Length(width as f32);
new_style.size.height = taffy::prelude::Dimension::Length(height as f32);
new_style.max_size.width = taffy::prelude::Dimension::Length(width as f32);
new_style.max_size.height = taffy::prelude::Dimension::Length(height as f32);
let result = self.taffy.set_style(*node, new_style);
if let Some(e) = result.err() {
error!("taffy set_style error: {}", e);
} else {
self.customizations.add_size(layout_id, width, height);
}
}
Err(e) => {
error!("taffy style error: {}", e);
}
}
}
self.compute_node_layout(root_layout_id)
}
fn apply_customizations(&self, layout_id: i32, style: &mut taffy::style::Style) {
let size = self.customizations.get_size(layout_id);
if let Some(size) = size {
style.min_size.width = taffy::prelude::Dimension::Length(size.width as f32);
style.min_size.height = taffy::prelude::Dimension::Length(size.height as f32);
style.size.width = taffy::prelude::Dimension::Length(size.width as f32);
style.size.height = taffy::prelude::Dimension::Length(size.height as f32);
style.max_size.width = taffy::prelude::Dimension::Length(size.width as f32);
style.max_size.height = taffy::prelude::Dimension::Length(size.height as f32);
}
}
fn apply_fixed_size(
&self,
style: &mut taffy::style::Style,
fixed_width: Option<i32>,
fixed_height: Option<i32>,
) {
if let Some(fixed_width) = fixed_width {
style.min_size.width = taffy::prelude::Dimension::Length(fixed_width as f32);
}
if let Some(fixed_height) = fixed_height {
style.min_size.height = taffy::prelude::Dimension::Length(fixed_height as f32);
}
}
pub fn compute_node_layout(&mut self, layout_id: i32) -> LayoutChangedResponse {
trace!("compute_node_layout {}", layout_id);
let node = self.layout_id_to_taffy_node.get(&layout_id);
if let Some(node) = node {
let result = self.taffy.compute_layout_with_measure(
*node,
Size {
height: AvailableSpace::Definite(500.0),
width: AvailableSpace::Definite(500.0),
},
&mut self.measure_func,
);
if let Some(e) = result.err() {
error!("compute_node_layout_internal: compute_layoute error: {}", e);
}
}
let changed_layouts = self.update_layout(layout_id);
self.layout_state += 1;
LayoutChangedResponse {
layout_state: self.layout_state,
changed_layouts,
..Default::default()
}
}
pub fn print_layout(self, layout_id: i32, print_func: fn(String) -> ()) {
self.print_layout_recurse(layout_id, "".to_string(), print_func);
}
fn print_layout_recurse(&self, layout_id: i32, space: String, print_func: fn(String) -> ()) {
let layout_node = self.layout_id_to_taffy_node.get(&layout_id);
if let Some(layout_node) = layout_node {
let layout = self.taffy.layout(*layout_node);
if let Ok(layout) = layout {
let name_result = self.layout_id_to_name.get(&layout_id);
if let Some(name) = name_result {
print_func(format!("{}Node {} {}:", space, name, layout_id));
print_func(format!(" {}{:?}", space, layout));
}
let children_result = self.taffy.children(*layout_node);
if let Ok(children) = children_result {
for child in children {
let layout_id = self.taffy_node_to_layout_id.get(&child);
if let Some(child_layout_id) = layout_id {
self.print_layout_recurse(
*child_layout_id,
format!("{} ", space),
print_func,
);
}
}
}
}
}
}
pub fn print_layout_as_html(self, layout_id: i32, print_func: fn(String) -> ()) {
let root_node_id = if let Some(node_id) = self.layout_id_to_taffy_node.get(&layout_id) {
*node_id
} else {
return;
};
crate::debug::print_tree_as_html(&self.taffy, root_node_id, print_func);
}
}
#[cfg(test)]
mod tests {
use super::*;
use dc_bundle::geometry::{DimensionProto, DimensionRect, Size as GeoSize};
use dc_bundle::positioning::{
item_spacing, AlignContent, AlignItems, AlignSelf, FlexDirection, ItemSpacing,
JustifyContent, PositionType,
};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::Mutex;
fn create_valid_layout_style() -> LayoutStyle {
let mut style = LayoutStyle::default();
let rect = DimensionRect {
start: DimensionProto::new_points(0.0),
end: DimensionProto::new_points(0.0),
top: DimensionProto::new_points(0.0),
bottom: DimensionProto::new_points(0.0),
..Default::default()
};
style.padding = Some(rect.clone()).into();
style.margin = Some(rect).into();
style.width = DimensionProto::new_auto();
style.height = DimensionProto::new_auto();
style.min_width = DimensionProto::new_auto();
style.min_height = DimensionProto::new_auto();
style.max_width = DimensionProto::new_auto();
style.max_height = DimensionProto::new_auto();
style.left = DimensionProto::new_auto();
style.right = DimensionProto::new_auto();
style.top = DimensionProto::new_auto();
style.bottom = DimensionProto::new_auto();
style.flex_basis = DimensionProto::new_auto();
style.item_spacing = Some(ItemSpacing {
ItemSpacingType: Some(item_spacing::ItemSpacingType::Fixed(0)),
..Default::default()
})
.into();
style.align_content = AlignContent::ALIGN_CONTENT_FLEX_START.into();
style.justify_content = JustifyContent::JUSTIFY_CONTENT_FLEX_START.into();
style.align_items = AlignItems::ALIGN_ITEMS_FLEX_START.into();
style.align_self = AlignSelf::ALIGN_SELF_AUTO.into();
style.flex_direction = FlexDirection::FLEX_DIRECTION_ROW.into();
style.position_type = PositionType::POSITION_TYPE_RELATIVE.into();
style
}
#[test]
fn test_customizations() {
let mut customizations = Customizations::new();
let layout_id = 1;
let width = 100;
let height = 200;
customizations.add_size(layout_id, width, height);
let size = customizations.get_size(layout_id);
assert_eq!(size, Some(&Size { width, height }));
customizations.remove(&layout_id);
let size = customizations.get_size(layout_id);
assert_eq!(size, None);
}
fn create_layout_manager() -> LayoutManager {
LayoutManager::new(|_, _, _, _, _| (0.0, 0.0))
}
#[test]
fn test_measure_func_called() {
let called = Arc::new(AtomicBool::new(false));
let called_clone = called.clone();
let mut layout_manager = LayoutManager::new(move |_, _, _, _, _| {
called_clone.store(true, Ordering::SeqCst);
(0.0, 0.0)
});
let layout_id = 1;
layout_manager
.add_style(
layout_id,
-1,
0,
create_valid_layout_style(),
"node".to_string(),
true,
None,
None,
)
.unwrap();
layout_manager.compute_node_layout(layout_id);
assert!(called.load(Ordering::SeqCst));
}
#[test]
fn test_add_style_and_get_layout() {
let mut layout_manager = create_layout_manager();
let layout_id = 1;
let style = create_valid_layout_style();
let name = "test_node".to_string();
layout_manager.add_style(layout_id, -1, 0, style, name, false, None, None).unwrap();
layout_manager.compute_node_layout(layout_id);
let layout = layout_manager.get_node_layout(layout_id);
assert!(layout.is_some());
}
#[test]
fn test_add_child_node() {
let mut layout_manager = create_layout_manager();
let parent_id = 1;
let child_id = 2;
layout_manager
.add_style(
parent_id,
-1,
0,
create_valid_layout_style(),
"parent".to_string(),
false,
None,
None,
)
.unwrap();
layout_manager
.add_style(
child_id,
parent_id,
0,
create_valid_layout_style(),
"child".to_string(),
false,
None,
None,
)
.unwrap();
let parent_node = layout_manager.layout_id_to_taffy_node.get(&parent_id).unwrap();
let children = layout_manager.taffy.children(*parent_node).unwrap();
assert_eq!(children.len(), 1);
let child_node = layout_manager.layout_id_to_taffy_node.get(&child_id).unwrap();
assert_eq!(children[0], *child_node);
}
#[test]
fn test_remove_view() {
let mut layout_manager = create_layout_manager();
let parent_id = 1;
let child_id = 2;
layout_manager
.add_style(
parent_id,
-1,
0,
create_valid_layout_style(),
"parent".to_string(),
false,
None,
None,
)
.unwrap();
layout_manager
.add_style(
child_id,
parent_id,
0,
create_valid_layout_style(),
"child".to_string(),
false,
None,
None,
)
.unwrap();
let parent_node = layout_manager.layout_id_to_taffy_node.get(&parent_id).unwrap();
let children_before_remove = layout_manager.taffy.children(*parent_node).unwrap();
assert_eq!(children_before_remove.len(), 1);
layout_manager.remove_view(child_id, parent_id, true);
let parent_node_after_remove =
layout_manager.layout_id_to_taffy_node.get(&parent_id).unwrap();
let children_after_remove =
layout_manager.taffy.children(*parent_node_after_remove).unwrap();
assert_eq!(children_after_remove.len(), 0);
assert!(layout_manager.layout_id_to_taffy_node.get(&child_id).is_none());
}
#[test]
fn test_set_node_size() {
let mut layout_manager = create_layout_manager();
let layout_id = 1;
let width = 200;
let height = 300;
layout_manager
.add_style(
layout_id,
-1,
0,
create_valid_layout_style(),
"node".to_string(),
false,
None,
None,
)
.unwrap();
layout_manager.set_node_size(layout_id, layout_id, width, height);
let layout = layout_manager.get_node_layout(layout_id).unwrap();
assert_eq!(layout.width, width as f32);
assert_eq!(layout.height, height as f32);
let custom_size = layout_manager.customizations.get_size(layout_id);
assert_eq!(custom_size, Some(&Size { width, height }));
}
#[test]
fn test_update_children() {
let mut layout_manager = create_layout_manager();
let parent_id = 1;
let child1_id = 2;
let child2_id = 3;
layout_manager
.add_style(
parent_id,
-1,
0,
create_valid_layout_style(),
"parent".to_string(),
false,
None,
None,
)
.unwrap();
layout_manager
.add_style(
child1_id,
-1,
0,
create_valid_layout_style(),
"child1".to_string(),
false,
None,
None,
)
.unwrap();
layout_manager
.add_style(
child2_id,
-1,
0,
create_valid_layout_style(),
"child2".to_string(),
false,
None,
None,
)
.unwrap();
let parent_node = layout_manager.layout_id_to_taffy_node.get(&parent_id).unwrap();
let children_before = layout_manager.taffy.children(*parent_node).unwrap();
assert_eq!(children_before.len(), 0);
layout_manager.update_children(parent_id, &vec![child1_id, child2_id]);
let parent_node_after = layout_manager.layout_id_to_taffy_node.get(&parent_id).unwrap();
let children_after = layout_manager.taffy.children(*parent_node_after).unwrap();
assert_eq!(children_after.len(), 2);
let child1_node = layout_manager.layout_id_to_taffy_node.get(&child1_id).unwrap();
let child2_node = layout_manager.layout_id_to_taffy_node.get(&child2_id).unwrap();
assert_eq!(children_after[0], *child1_node);
assert_eq!(children_after[1], *child2_node);
}
#[test]
fn test_mark_dirty() {
let mut layout_manager = create_layout_manager();
let layout_id = 1;
layout_manager
.add_style(
layout_id,
-1,
0,
create_valid_layout_style(),
"node".to_string(),
false,
None,
None,
)
.unwrap();
layout_manager.mark_dirty(layout_id);
layout_manager.mark_dirty(999); }
static PRINT_OUTPUT: std::sync::LazyLock<Mutex<Vec<String>>> =
std::sync::LazyLock::new(|| Mutex::new(Vec::new()));
fn test_print_callback(s: String) {
PRINT_OUTPUT.lock().unwrap().push(s);
}
#[test]
fn test_print_layout() {
let mut layout_manager = create_layout_manager();
let parent_id = 1;
let child_id = 2;
layout_manager
.add_style(
parent_id,
-1,
0,
create_valid_layout_style(),
"parent".to_string(),
false,
None,
None,
)
.unwrap();
layout_manager
.add_style(
child_id,
parent_id,
0,
create_valid_layout_style(),
"child".to_string(),
false,
None,
None,
)
.unwrap();
layout_manager.compute_node_layout(parent_id);
PRINT_OUTPUT.lock().unwrap().clear();
layout_manager.print_layout(parent_id, test_print_callback);
let output = PRINT_OUTPUT.lock().unwrap();
assert_eq!(output.len(), 4);
assert_eq!(output[0], "Node parent 1:");
assert!(output[1].starts_with(" Layout {"));
assert_eq!(output[2], " Node child 2:");
assert!(output[3].starts_with(" Layout {"));
}
fn create_sized_style(width: f32, height: f32) -> LayoutStyle {
let mut style = create_valid_layout_style();
style.width = DimensionProto::new_points(width);
style.height = DimensionProto::new_points(height);
style.bounding_box = Some(GeoSize { width, height, ..Default::default() }).into();
style
}
fn create_container_style(width: f32, height: f32, direction: FlexDirection) -> LayoutStyle {
let mut style = create_sized_style(width, height);
style.flex_direction = direction.into();
style
}
#[test]
fn test_horizontal_layout_children_side_by_side() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_container_style(200.0, 100.0, FlexDirection::FLEX_DIRECTION_ROW),
"parent".into(),
false,
None,
None,
)
.unwrap();
lm.add_style(2, 1, 0, create_sized_style(60.0, 40.0), "a".into(), false, None, None)
.unwrap();
lm.add_style(3, 1, 1, create_sized_style(80.0, 50.0), "b".into(), false, None, None)
.unwrap();
lm.compute_node_layout(1);
let la = lm.get_node_layout(2).unwrap();
let lb = lm.get_node_layout(3).unwrap();
assert_eq!(la.left, 0.0);
assert_eq!(la.width, 60.0);
assert_eq!(lb.left, 60.0);
assert_eq!(lb.width, 80.0);
}
#[test]
fn test_vertical_layout_children_stacked() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_container_style(100.0, 200.0, FlexDirection::FLEX_DIRECTION_COLUMN),
"parent".into(),
false,
None,
None,
)
.unwrap();
lm.add_style(2, 1, 0, create_sized_style(50.0, 30.0), "a".into(), false, None, None)
.unwrap();
lm.add_style(3, 1, 1, create_sized_style(60.0, 40.0), "b".into(), false, None, None)
.unwrap();
lm.compute_node_layout(1);
let la = lm.get_node_layout(2).unwrap();
let lb = lm.get_node_layout(3).unwrap();
assert_eq!(la.top, 0.0);
assert_eq!(la.height, 30.0);
assert_eq!(lb.top, 30.0);
assert_eq!(lb.height, 40.0);
}
#[test]
fn test_absolute_positioning() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_sized_style(200.0, 200.0),
"parent".into(),
false,
None,
None,
)
.unwrap();
let mut abs_style = create_sized_style(50.0, 50.0);
abs_style.position_type = PositionType::POSITION_TYPE_ABSOLUTE.into();
abs_style.left = DimensionProto::new_points(10.0);
abs_style.top = DimensionProto::new_points(20.0);
lm.add_style(2, 1, 0, abs_style, "abs_child".into(), false, None, None).unwrap();
lm.compute_node_layout(1);
let lc = lm.get_node_layout(2).unwrap();
assert_eq!(lc.left, 10.0);
assert_eq!(lc.top, 20.0);
assert_eq!(lc.width, 50.0);
assert_eq!(lc.height, 50.0);
}
#[test]
fn test_nested_layout() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_container_style(300.0, 300.0, FlexDirection::FLEX_DIRECTION_ROW),
"root".into(),
false,
None,
None,
)
.unwrap();
lm.add_style(
2,
1,
0,
create_container_style(150.0, 200.0, FlexDirection::FLEX_DIRECTION_COLUMN),
"col".into(),
false,
None,
None,
)
.unwrap();
lm.add_style(3, 2, 0, create_sized_style(100.0, 40.0), "gc_a".into(), false, None, None)
.unwrap();
lm.add_style(4, 2, 1, create_sized_style(100.0, 60.0), "gc_b".into(), false, None, None)
.unwrap();
lm.compute_node_layout(1);
let la = lm.get_node_layout(3).unwrap();
let lb = lm.get_node_layout(4).unwrap();
assert_eq!(la.top, 0.0);
assert_eq!(la.height, 40.0);
assert_eq!(lb.top, 40.0);
assert_eq!(lb.height, 60.0);
}
#[test]
fn test_padding_offsets_children() {
let mut lm = create_layout_manager();
let mut parent_style = create_sized_style(200.0, 200.0);
parent_style.padding = Some(DimensionRect {
start: DimensionProto::new_points(10.0),
end: DimensionProto::new_points(10.0),
top: DimensionProto::new_points(20.0),
bottom: DimensionProto::new_points(20.0),
..Default::default()
})
.into();
lm.add_style(1, -1, 0, parent_style, "parent".into(), false, None, None).unwrap();
lm.add_style(2, 1, 0, create_sized_style(50.0, 50.0), "child".into(), false, None, None)
.unwrap();
lm.compute_node_layout(1);
let lc = lm.get_node_layout(2).unwrap();
assert_eq!(lc.left, 10.0);
assert_eq!(lc.top, 20.0);
}
#[test]
fn test_flex_grow_distributes_space() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_container_style(300.0, 100.0, FlexDirection::FLEX_DIRECTION_ROW),
"parent".into(),
false,
None,
None,
)
.unwrap();
let mut sa = create_valid_layout_style();
sa.height = DimensionProto::new_points(50.0);
sa.bounding_box = Some(GeoSize { width: 0.0, height: 50.0, ..Default::default() }).into();
sa.flex_grow = 1.0;
let mut sb = create_valid_layout_style();
sb.height = DimensionProto::new_points(50.0);
sb.bounding_box = Some(GeoSize { width: 0.0, height: 50.0, ..Default::default() }).into();
sb.flex_grow = 2.0;
lm.add_style(2, 1, 0, sa, "a".into(), false, None, None).unwrap();
lm.add_style(3, 1, 1, sb, "b".into(), false, None, None).unwrap();
lm.compute_node_layout(1);
assert_eq!(lm.get_node_layout(2).unwrap().width, 100.0);
assert_eq!(lm.get_node_layout(3).unwrap().width, 200.0);
}
#[test]
fn test_removal_relayouts_siblings() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_container_style(300.0, 100.0, FlexDirection::FLEX_DIRECTION_ROW),
"parent".into(),
false,
None,
None,
)
.unwrap();
lm.add_style(2, 1, 0, create_sized_style(50.0, 50.0), "a".into(), false, None, None)
.unwrap();
lm.add_style(3, 1, 1, create_sized_style(60.0, 50.0), "b".into(), false, None, None)
.unwrap();
lm.add_style(4, 1, 2, create_sized_style(70.0, 50.0), "c".into(), false, None, None)
.unwrap();
lm.compute_node_layout(1);
assert_eq!(lm.get_node_layout(4).unwrap().left, 110.0);
lm.remove_view(3, 1, true);
assert_eq!(lm.get_node_layout(4).unwrap().left, 50.0);
}
#[test]
fn test_fixed_size_overrides_style() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_sized_style(100.0, 100.0),
"node".into(),
false,
Some(200),
None,
)
.unwrap();
lm.compute_node_layout(1);
assert!(lm.get_node_layout(1).unwrap().width >= 200.0);
}
#[test]
fn test_percentage_sizing() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_sized_style(400.0, 300.0),
"parent".into(),
false,
None,
None,
)
.unwrap();
let mut pct = create_valid_layout_style();
pct.width = DimensionProto::new_percent(0.5);
pct.height = DimensionProto::new_percent(0.25);
lm.add_style(2, 1, 0, pct, "child".into(), false, None, None).unwrap();
lm.compute_node_layout(1);
let lc = lm.get_node_layout(2).unwrap();
assert_eq!(lc.width, 200.0);
assert_eq!(lc.height, 75.0);
}
#[test]
fn test_layout_state_increments() {
let mut lm = create_layout_manager();
lm.add_style(1, -1, 0, create_sized_style(100.0, 100.0), "node".into(), false, None, None)
.unwrap();
let s1 = lm.compute_node_layout(1).layout_state;
let s2 = lm.compute_node_layout(1).layout_state;
assert_eq!(s2, s1 + 1);
}
#[test]
fn test_child_ordering_in_column() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_container_style(200.0, 400.0, FlexDirection::FLEX_DIRECTION_COLUMN),
"parent".into(),
false,
None,
None,
)
.unwrap();
for i in 0..4 {
lm.add_style(
10 + i,
1,
i,
create_sized_style(100.0, 50.0),
format!("child_{}", i),
false,
None,
None,
)
.unwrap();
}
lm.compute_node_layout(1);
for i in 0..4 {
let l = lm.get_node_layout(10 + i).unwrap();
assert_eq!(l.top, (i as f32) * 50.0, "child {} top mismatch", i);
assert_eq!(l.height, 50.0, "child {} height mismatch", i);
}
}
#[test]
fn test_update_children_reorders_layout() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_container_style(200.0, 100.0, FlexDirection::FLEX_DIRECTION_ROW),
"parent".into(),
false,
None,
None,
)
.unwrap();
lm.add_style(2, 1, 0, create_sized_style(60.0, 50.0), "a".into(), false, None, None)
.unwrap();
lm.add_style(3, 1, 1, create_sized_style(80.0, 50.0), "b".into(), false, None, None)
.unwrap();
lm.compute_node_layout(1);
assert_eq!(lm.get_node_layout(2).unwrap().left, 0.0);
assert_eq!(lm.get_node_layout(3).unwrap().left, 60.0);
lm.update_children(1, &vec![3, 2]);
lm.compute_node_layout(1);
assert_eq!(lm.get_node_layout(3).unwrap().left, 0.0);
assert_eq!(lm.get_node_layout(2).unwrap().left, 80.0);
}
#[test]
fn test_absolute_position_in_autolayout() {
let mut lm = create_layout_manager();
lm.add_style(
1,
-1,
0,
create_container_style(400.0, 300.0, FlexDirection::FLEX_DIRECTION_ROW),
"autolayout_parent".into(),
false,
None,
None,
)
.unwrap();
lm.add_style(
2,
1,
0,
create_sized_style(100.0, 50.0),
"flex_child".into(),
false,
None,
None,
)
.unwrap();
let mut abs_style = create_sized_style(80.0, 60.0);
abs_style.position_type = PositionType::POSITION_TYPE_ABSOLUTE.into();
abs_style.left = DimensionProto::new_points(150.0);
abs_style.top = DimensionProto::new_points(100.0);
lm.add_style(3, 1, 1, abs_style, "abs_child".into(), false, None, None).unwrap();
lm.add_style(
4,
1,
2,
create_sized_style(120.0, 50.0),
"flex_child2".into(),
false,
None,
None,
)
.unwrap();
lm.compute_node_layout(1);
let l_flex1 = lm.get_node_layout(2).unwrap();
assert_eq!(l_flex1.left, 0.0, "First flex child should start at left=0");
let l_abs = lm.get_node_layout(3).unwrap();
assert_eq!(l_abs.left, 150.0, "Absolute child should use left inset");
assert_eq!(l_abs.top, 100.0, "Absolute child should use top inset");
assert_eq!(l_abs.width, 80.0);
assert_eq!(l_abs.height, 60.0);
let l_flex2 = lm.get_node_layout(4).unwrap();
assert_eq!(
l_flex2.left, 100.0,
"Second flex child should be placed after first (at 100.0), ignoring absolute child"
);
}
}