use crate as deft;
use crate::style::YogaNode;
use deft_macros::mrc_object;
use yoga::{
Align, Context, Direction, Display, Edge, FlexDirection, Justify, MeasureMode, Node, NodeRef,
Overflow, PositionType, Size, StyleUnit, Wrap,
};
pub struct MeasureParams {
pub width: f32,
pub width_mode: MeasureMode,
pub height: f32,
pub height_mode: MeasureMode,
}
#[mrc_object]
struct CustomMeasureFn {
callback: Box<dyn FnMut(MeasureParams) -> Size>,
}
extern "C" fn custom_measure_shadow(
node_ref: NodeRef,
width: f32,
_width_mode: MeasureMode,
height: f32,
_height_mode: MeasureMode,
) -> Size {
if let Some(ctx) = Node::get_context_mut(&node_ref) {
if let Some(node_item) = ctx.downcast_mut::<NodeItem>() {
node_item.calculate_shadow_layout(width, height, Direction::LTR);
let sn = node_item._shadow_yn.as_ref().unwrap();
return yoga::Size {
width: sn.get_layout_width(),
height: sn.get_layout_height(),
};
}
}
unreachable!()
}
extern "C" fn custom_measure_fn(
node_ref: NodeRef,
width: f32,
width_mode: MeasureMode,
height: f32,
height_mode: MeasureMode,
) -> Size {
if let Some(ctx) = Node::get_context_mut(&node_ref) {
if let Some(func) = ctx.downcast_mut::<CustomMeasureFn>() {
let params = MeasureParams {
width,
width_mode,
height,
height_mode,
};
return (func.callback)(params);
}
}
unreachable!()
}
#[mrc_object]
pub struct NodeItem {
pub _yn: YogaNode,
_shadow_yn: Option<YogaNode>,
pub padding_top: StyleUnit,
pub padding_bottom: StyleUnit,
pub padding_left: StyleUnit,
pub padding_right: StyleUnit,
pub justify_content: Justify,
pub flex_direction: FlexDirection,
pub align_content: Align,
pub align_items: Align,
pub flex_wrap: Wrap,
pub column_gap: f32,
pub row_gap: f32,
pub position_type: PositionType,
pub display: Display,
pub width: StyleUnit,
pub height: StyleUnit,
pub max_width: StyleUnit,
pub max_height: StyleUnit,
pub min_width: StyleUnit,
pub min_height: StyleUnit,
pub margin_top: StyleUnit,
pub margin_right: StyleUnit,
pub margin_bottom: StyleUnit,
pub margin_left: StyleUnit,
pub flex_basis: StyleUnit,
pub top: StyleUnit,
pub right: StyleUnit,
pub bottom: StyleUnit,
pub left: StyleUnit,
pub overflow: Overflow,
pub flex: f32,
pub flex_grow: f32,
pub flex_shrink: f32,
pub align_self: Align,
pub direction: Direction,
pub border_top: f32,
pub border_right: f32,
pub border_bottom: f32,
pub border_left: f32,
pub measure_fn: Option<CustomMeasureFn>,
pub children: Vec<NodeItem>,
}
impl NodeItem {
pub fn new() -> Self {
let mut std_node = YogaNode::new();
NodeItemData {
_yn: YogaNode::new(),
_shadow_yn: None,
padding_top: std_node.get_style_padding_top(),
padding_bottom: std_node.get_style_padding_bottom(),
padding_left: std_node.get_style_padding_left(),
padding_right: std_node.get_style_padding_right(),
justify_content: std_node.get_justify_content(),
flex_direction: std_node.get_flex_direction(),
align_content: std_node.get_align_content(),
align_items: std_node.get_align_items(),
flex_wrap: std_node.get_flex_wrap(),
column_gap: std_node.get_column_gap(),
row_gap: std_node.get_row_gap(),
position_type: PositionType::Static,
display: Display::Flex,
width: std_node.get_style_width(),
height: std_node.get_style_height(),
max_width: std_node.get_style_max_width(),
max_height: std_node.get_style_max_height(),
min_width: std_node.get_style_min_width(),
min_height: std_node.get_style_min_height(),
margin_top: std_node.get_style_margin_top(),
margin_right: std_node.get_style_margin_right(),
margin_bottom: std_node.get_style_margin_bottom(),
margin_left: std_node.get_style_margin_left(),
flex_basis: std_node.get_flex_basis(),
top: std_node.get_style_position_top(),
right: std_node.get_style_position_right(),
bottom: std_node.get_style_position_bottom(),
left: std_node.get_style_position_left(),
overflow: Overflow::Hidden,
flex: std_node.get_flex(),
flex_grow: f32::NAN,
flex_shrink: f32::NAN,
align_self: std_node.get_align_self(),
direction: std_node.get_style_direction(),
border_top: 0.0,
border_right: 0.0,
border_bottom: 0.0,
border_left: 0.0,
measure_fn: None,
children: vec![],
}
.to_ref()
}
pub fn has_shadow(&self) -> bool {
match self.overflow {
Overflow::Visible => false,
Overflow::Hidden => false,
Overflow::Scroll => true,
}
}
pub fn calculate_layout(
&mut self,
available_width: f32,
available_height: f32,
direction: Direction,
) {
self.build_yoga_node(false);
self._yn
.calculate_layout(available_width, available_height, direction);
}
pub fn calculate_shadow_layout(
&mut self,
available_width: f32,
available_height: f32,
direction: Direction,
) {
self.build_yoga_node(true);
if let Some(sn) = &mut self._shadow_yn {
sn.calculate_layout(available_width, available_height, direction);
}
}
fn build_yoga_node(&mut self, is_shadow_root: bool) {
let visit_children = is_shadow_root || !self.has_shadow();
if visit_children {
for c in &mut self.children {
c.build_yoga_node(false);
}
}
let mut n = YogaNode::new();
let mut s = YogaNode::new();
n.set_position_type(self.position_type);
n.set_display(self.display);
n.set_width(self.width);
n.set_height(self.height);
n.set_max_width(self.max_width);
n.set_max_height(self.max_height);
n.set_min_width(self.min_width);
n.set_min_height(self.min_height);
n.set_margin(Edge::Top, self.margin_top);
n.set_margin(Edge::Bottom, self.margin_bottom);
n.set_margin(Edge::Left, self.margin_left);
n.set_margin(Edge::Right, self.margin_right);
n.set_flex_basis(self.flex_basis);
n.set_position(Edge::Top, self.top);
n.set_position(Edge::Bottom, self.bottom);
n.set_position(Edge::Left, self.left);
n.set_position(Edge::Right, self.right);
n.set_overflow(self.overflow);
n.set_flex(self.flex);
n.set_flex_grow(self.flex_grow);
n.set_flex_shrink(self.flex_shrink);
n.set_align_self(self.align_self);
n.set_border(Edge::Top, self.border_top);
n.set_border(Edge::Right, self.border_right);
n.set_border(Edge::Bottom, self.border_bottom);
n.set_border(Edge::Left, self.border_left);
if self.has_shadow() {
n.set_context(Some(Context::new(self.clone())));
n.set_measure_func(Some(custom_measure_shadow));
} else {
if let Some(measure_func) = self.measure_fn.clone() {
n.set_context(Some(Context::new(measure_func)));
n.set_measure_func(Some(custom_measure_fn));
}
}
let container = if self.has_shadow() { &mut s } else { &mut n };
container.set_padding(Edge::Top, self.padding_top);
container.set_padding(Edge::Bottom, self.padding_bottom);
container.set_padding(Edge::Left, self.padding_left);
container.set_padding(Edge::Right, self.padding_right);
container.set_justify_content(self.justify_content);
container.set_align_items(self.align_items);
container.set_align_content(self.align_content);
container.set_flex_direction(self.flex_direction);
container.set_align_items(self.align_items);
container.set_flex_wrap(self.flex_wrap);
container.set_column_gap(self.column_gap);
container.set_row_gap(self.row_gap);
container.set_direction(self.direction);
if visit_children {
let mut idx = 0;
for c in &mut self.children {
container.insert_child(&mut c._yn, idx);
idx += 1;
}
}
if is_shadow_root {
self._shadow_yn = if self.has_shadow() { Some(s) } else { None }
} else {
self._yn = n;
}
}
pub fn set_measure_func<C: 'static, F: FnMut(&mut C, MeasureParams) -> Size + 'static>(
&mut self,
mut context: C,
mut measure_func: F,
) {
let func = CustomMeasureFnData {
callback: Box::new(move |params| measure_func(&mut context, params)),
}
.to_ref();
self.measure_fn = Some(func);
}
}
#[cfg(test)]
mod tests {
use crate::style::node_item::NodeItem;
use ordered_float::OrderedFloat;
use yoga::{Direction, Overflow, StyleUnit};
#[test]
fn test_layout() {
let mut root = NodeItem::new();
let mut child1 = NodeItem::new();
let mut child2 = NodeItem::new();
child1.width = StyleUnit::Percent(OrderedFloat(50.0));
child1.height = StyleUnit::Point(OrderedFloat(10.0));
child2.width = StyleUnit::Percent(OrderedFloat(50.0));
child2.height = StyleUnit::Point(OrderedFloat(20.0));
root.calculate_layout(100.0, 100.0, Direction::LTR);
assert_eq!(root._yn.get_layout_width(), 100.0);
assert_eq!(root._yn.get_layout_height(), 100.0);
}
#[test]
fn test_overflow() {
let mut root = NodeItem::new();
let mut child1 = NodeItem::new();
let mut child2 = NodeItem::new();
root.overflow = Overflow::Scroll;
child1.width = StyleUnit::Percent(OrderedFloat(50.0));
child1.height = StyleUnit::Point(OrderedFloat(1000.0));
child2.width = StyleUnit::Percent(OrderedFloat(50.0));
child2.height = StyleUnit::Point(OrderedFloat(20.0));
root.calculate_layout(100.0, 100.0, Direction::LTR);
root.calculate_shadow_layout(100.0, 100.0, Direction::LTR);
let sn = root._shadow_yn.as_ref().unwrap();
assert_eq!(sn.get_layout_width(), 100.0);
assert_eq!(sn.get_layout_height(), 100.0);
}
}