use super::{FlexBox, FlexBoxProps, LayoutDirection, LayoutManagement,
PerformPositioningAndSizing, SurfaceProps};
use crate::{height, throws, unwrap_or_err, width, CommonResult, InlineVec, LayoutError,
LayoutErrorType, Pos, RenderPipeline, ReqSizePc, Size, TuiStyle,
TuiStylesheet};
#[derive(Clone, Debug, Default)]
pub struct Surface {
pub origin_pos: Pos,
pub box_size: Size,
pub stack_of_boxes: Vec<FlexBox>,
pub stylesheet: TuiStylesheet,
pub render_pipeline: RenderPipeline,
}
#[derive(Copy, Clone, Debug, Default)]
pub struct SurfaceBounds {
pub origin_pos: Pos,
pub box_size: Size,
}
mod surface_bounds_impl {
use super::{Surface, SurfaceBounds};
impl From<&Surface> for SurfaceBounds {
fn from(surface: &Surface) -> Self {
Self {
origin_pos: surface.origin_pos,
box_size: surface.box_size,
}
}
}
}
#[macro_export]
macro_rules! surface {
(
stylesheet: $arg_stylesheet : expr
) => {
$crate::Surface {
stylesheet: $arg_stylesheet,
..Default::default()
}
};
(
origin_pos: $arg_origin_pos : expr,
box_size: $arg_box_size : expr,
stylesheet: $arg_stylesheet : expr
) => {
$crate::Surface {
origin_pos: $arg_origin_pos,
box_size: $arg_box_size,
stylesheet: $arg_stylesheet,
..Default::default()
}
};
}
impl LayoutManagement for Surface {
fn surface_start(
&mut self,
SurfaceProps { pos, size }: SurfaceProps,
) -> CommonResult<()> {
throws!({
if !self.no_boxes_added() {
LayoutError::new_error_result(
LayoutErrorType::MismatchedSurfaceStart,
LayoutError::format_msg_with_stack_len(
&self.stack_of_boxes,
"Stack of boxes should be empty",
),
)?;
}
self.origin_pos = pos;
self.box_size = size;
});
}
fn surface_end(&mut self) -> CommonResult<()> {
throws!({
if !self.no_boxes_added() {
LayoutError::new_error_result(
LayoutErrorType::MismatchedSurfaceEnd,
LayoutError::format_msg_with_stack_len(
&self.stack_of_boxes,
"Stack of boxes should be empty",
),
)?;
}
});
}
fn box_start(&mut self, flex_box_props: FlexBoxProps) -> CommonResult<()> {
throws!({
if self.no_boxes_added() {
self.add_root_box(flex_box_props)
} else {
self.add_non_root_box(flex_box_props)
}?;
});
}
fn box_end(&mut self) -> CommonResult<()> {
throws!({
if self.no_boxes_added() {
LayoutError::new_error_result(
LayoutErrorType::MismatchedBoxEnd,
LayoutError::format_msg_with_stack_len(
&self.stack_of_boxes,
"Stack of boxes should not be empty",
),
)?;
}
self.stack_of_boxes.pop();
});
}
}
impl PerformPositioningAndSizing for Surface {
fn current_box(&mut self) -> CommonResult<&mut FlexBox> {
if self.no_boxes_added() {
LayoutError::new_error_result_with_only_type(
LayoutErrorType::StackOfBoxesShouldNotBeEmpty,
)?;
}
if let Some(it) = self.stack_of_boxes.last_mut() {
Ok(it)
} else {
LayoutError::new_error_result_with_only_type(
LayoutErrorType::StackOfBoxesShouldNotBeEmpty,
)?
}
}
fn no_boxes_added(&self) -> bool { self.stack_of_boxes.is_empty() }
fn update_insertion_pos_for_next_box(
&mut self,
allocated_size: Size,
) -> CommonResult<Pos> {
let current_box = self.current_box()?;
let current_insertion_pos = current_box.insertion_pos_for_next_box;
let current_insertion_pos = unwrap_or_err! {
current_insertion_pos,
LayoutErrorType::ErrorCalculatingNextBoxPos
};
let new_pos: Pos = current_insertion_pos + allocated_size;
let new_pos: Pos = match current_box.dir {
LayoutDirection::Vertical => new_pos * (width(0) + height(1)),
LayoutDirection::Horizontal => new_pos * (width(1) + height(0)),
};
current_box.insertion_pos_for_next_box = new_pos.into();
Ok(new_pos)
}
fn add_non_root_box(&mut self, flex_box_props: FlexBoxProps) -> CommonResult<()> {
throws!({
let container_box = self.current_box()?;
let container_bounds = container_box.bounds_size;
let maybe_cascaded_style: Option<TuiStyle> =
cascade_styles(container_box, &flex_box_props);
let ReqSizePc {
width_pc,
height_pc,
} = flex_box_props.requested_size_percent;
let requested_size_allocation = {
let width_val = width_pc.apply_to(*container_bounds.col_width);
let height_val = height_pc.apply_to(*container_bounds.row_height);
width(width_val) + height(height_val)
};
let origin_pos = unwrap_or_err! {
container_box.insertion_pos_for_next_box,
LayoutErrorType::BoxCursorPositionUndefined
};
self.update_insertion_pos_for_next_box(requested_size_allocation)?;
self.stack_of_boxes.push(make_non_root_box_with_style(
flex_box_props,
origin_pos,
container_bounds,
maybe_cascaded_style,
));
});
}
fn add_root_box(&mut self, flex_box_props: FlexBoxProps) -> CommonResult<()> {
throws!({
let ReqSizePc {
width_pc,
height_pc,
} = flex_box_props.requested_size_percent;
let bounds_size = {
let width_val = width_pc.apply_to(*self.box_size.col_width);
let height_val = height_pc.apply_to(*self.box_size.row_height);
width(width_val) + height(height_val)
};
self.stack_of_boxes.push(make_root_box_with_style(
flex_box_props,
self.origin_pos,
bounds_size,
));
});
}
}
fn make_non_root_box_with_style(
FlexBoxProps {
id,
dir,
requested_size_percent:
ReqSizePc {
width_pc,
height_pc,
},
maybe_styles: _,
}: FlexBoxProps,
origin_pos: Pos,
container_bounds: Size,
maybe_cascaded_style: Option<TuiStyle>,
) -> FlexBox {
let bounds_size = {
let width_val = width_pc.apply_to(*container_bounds.col_width);
let height_val = height_pc.apply_to(*container_bounds.row_height);
width(width_val) + height(height_val)
};
let (style_adjusted_origin_pos, style_adjusted_bounds_size) =
adjust_with_style(maybe_cascaded_style, origin_pos, bounds_size);
FlexBox {
id,
dir,
origin_pos,
bounds_size,
style_adjusted_origin_pos,
style_adjusted_bounds_size,
requested_size_percent: ReqSizePc {
width_pc,
height_pc,
},
maybe_computed_style: maybe_cascaded_style,
insertion_pos_for_next_box: None,
}
}
fn make_root_box_with_style(
FlexBoxProps {
id,
dir,
requested_size_percent,
maybe_styles,
}: FlexBoxProps,
origin_pos: Pos,
bounds_size: Size,
) -> FlexBox {
let computed_style = TuiStylesheet::compute(&maybe_styles);
let (style_adjusted_origin_pos, style_adjusted_bounds_size) =
adjust_with_style(computed_style, origin_pos, bounds_size);
FlexBox {
id,
dir,
origin_pos,
bounds_size,
style_adjusted_origin_pos,
style_adjusted_bounds_size,
requested_size_percent,
maybe_computed_style: computed_style,
insertion_pos_for_next_box: Some(origin_pos),
}
}
fn adjust_with_style(
maybe_computed_style: Option<TuiStyle>,
origin_pos: Pos,
bounds_size: Size,
) -> (Pos, Size) {
let mut style_adjusted_origin_pos = origin_pos;
let mut style_adjusted_bounds_size = bounds_size;
if let Some(style) = maybe_computed_style
&& let Some(padding) = style.padding {
style_adjusted_origin_pos += padding;
style_adjusted_bounds_size -= padding * 2;
}
(style_adjusted_origin_pos, style_adjusted_bounds_size)
}
fn cascade_styles(
parent_box: &FlexBox,
self_box_props: &FlexBoxProps,
) -> Option<TuiStyle> {
let mut style_vec = InlineVec::<TuiStyle>::new();
if let Some(parent_style) = parent_box.get_computed_style() {
style_vec.push(parent_style);
}
if let Some(ref self_styles) = self_box_props.maybe_styles {
self_styles.iter().for_each(|style| style_vec.push(*style));
}
if style_vec.is_empty() {
None
} else {
TuiStylesheet::compute(&Some(style_vec))
}
}