use crate::components::Box as RnkBox;
use crate::core::{AlignItems, Element, FlexDirection, JustifyContent};
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Position {
#[default]
Start,
Center,
End,
At(f32),
}
impl Position {
pub fn as_f32(&self) -> f32 {
match self {
Position::Start => 0.0,
Position::Center => 0.5,
Position::End => 1.0,
Position::At(v) => v.clamp(0.0, 1.0),
}
}
}
impl From<f32> for Position {
fn from(v: f32) -> Self {
Position::At(v)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PositionBucket {
Start,
Center,
End,
}
fn position_bucket(pos: Position) -> PositionBucket {
match pos {
Position::Start => PositionBucket::Start,
Position::Center => PositionBucket::Center,
Position::End => PositionBucket::End,
Position::At(v) if v <= 0.33 => PositionBucket::Start,
Position::At(v) if v >= 0.67 => PositionBucket::End,
Position::At(_) => PositionBucket::Center,
}
}
fn position_to_align_items(pos: Position) -> AlignItems {
match position_bucket(pos) {
PositionBucket::Start => AlignItems::FlexStart,
PositionBucket::Center => AlignItems::Center,
PositionBucket::End => AlignItems::FlexEnd,
}
}
fn position_to_justify_content(pos: Position) -> JustifyContent {
match position_bucket(pos) {
PositionBucket::Start => JustifyContent::FlexStart,
PositionBucket::Center => JustifyContent::Center,
PositionBucket::End => JustifyContent::FlexEnd,
}
}
pub fn join_horizontal(align: Position, elements: Vec<Element>) -> Element {
let align_items = position_to_align_items(align);
let mut container = RnkBox::new()
.flex_direction(FlexDirection::Row)
.align_items(align_items);
for elem in elements {
container = container.child(elem);
}
container.into_element()
}
pub fn join_vertical(align: Position, elements: Vec<Element>) -> Element {
let align_items = position_to_align_items(align);
let mut container = RnkBox::new()
.flex_direction(FlexDirection::Column)
.align_items(align_items);
for elem in elements {
container = container.child(elem);
}
container.into_element()
}
pub fn place_horizontal(width: u16, pos: Position, element: Element) -> Element {
let justify = position_to_justify_content(pos);
RnkBox::new()
.flex_direction(FlexDirection::Row)
.justify_content(justify)
.width(width)
.child(element)
.into_element()
}
pub fn place_vertical(height: u16, pos: Position, element: Element) -> Element {
let justify = position_to_justify_content(pos);
RnkBox::new()
.flex_direction(FlexDirection::Column)
.justify_content(justify)
.height(height)
.child(element)
.into_element()
}
pub fn place(
width: u16,
height: u16,
h_pos: Position,
v_pos: Position,
element: Element,
) -> Element {
let h_justify = position_to_justify_content(h_pos);
let v_justify = position_to_justify_content(v_pos);
let inner = RnkBox::new()
.flex_direction(FlexDirection::Row)
.justify_content(h_justify)
.width(width)
.child(element)
.into_element();
RnkBox::new()
.flex_direction(FlexDirection::Column)
.justify_content(v_justify)
.width(width)
.height(height)
.child(inner)
.into_element()
}
pub fn h_spacer() -> Element {
RnkBox::new().flex_grow(1.0).into_element()
}
pub fn v_spacer() -> Element {
RnkBox::new()
.flex_grow(1.0)
.flex_direction(FlexDirection::Column)
.into_element()
}
pub fn h_gap(width: u16) -> Element {
RnkBox::new().width(width).into_element()
}
pub fn v_gap(height: u16) -> Element {
RnkBox::new().height(height).into_element()
}
pub fn center_horizontal(width: u16, element: Element) -> Element {
place_horizontal(width, Position::Center, element)
}
pub fn center_vertical(height: u16, element: Element) -> Element {
place_vertical(height, Position::Center, element)
}
pub fn center(width: u16, height: u16, element: Element) -> Element {
place(width, height, Position::Center, Position::Center, element)
}
pub fn space_between(elements: Vec<Element>) -> Element {
RnkBox::new()
.flex_direction(FlexDirection::Row)
.justify_content(JustifyContent::SpaceBetween)
.children(elements)
.into_element()
}
pub fn space_around(elements: Vec<Element>) -> Element {
RnkBox::new()
.flex_direction(FlexDirection::Row)
.justify_content(JustifyContent::SpaceAround)
.children(elements)
.into_element()
}
pub fn space_evenly(elements: Vec<Element>) -> Element {
RnkBox::new()
.flex_direction(FlexDirection::Row)
.justify_content(JustifyContent::SpaceEvenly)
.children(elements)
.into_element()
}
pub fn pad_to_width(text: &str, width: usize, align: Position) -> String {
let text_width = unicode_width::UnicodeWidthStr::width(text);
if text_width >= width {
return text.to_string();
}
let padding = width - text_width;
match align {
Position::Start => format!("{}{}", text, " ".repeat(padding)),
Position::End => format!("{}{}", " ".repeat(padding), text),
Position::Center => {
let left = padding / 2;
let right = padding - left;
format!("{}{}{}", " ".repeat(left), text, " ".repeat(right))
}
Position::At(v) => {
let left = ((padding as f32) * v) as usize;
let right = padding - left;
format!("{}{}{}", " ".repeat(left), text, " ".repeat(right))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::components::Text;
#[test]
fn test_position_as_f32() {
assert_eq!(Position::Start.as_f32(), 0.0);
assert_eq!(Position::Center.as_f32(), 0.5);
assert_eq!(Position::End.as_f32(), 1.0);
assert_eq!(Position::At(0.25).as_f32(), 0.25);
}
#[test]
fn test_position_from_f32() {
let pos: Position = 0.75.into();
assert_eq!(pos, Position::At(0.75));
}
#[test]
fn test_position_clamp() {
assert_eq!(Position::At(1.5).as_f32(), 1.0);
assert_eq!(Position::At(-0.5).as_f32(), 0.0);
}
#[test]
fn test_position_threshold_mapping() {
assert_eq!(
position_to_justify_content(Position::At(0.33)),
JustifyContent::FlexStart
);
assert_eq!(
position_to_justify_content(Position::At(0.34)),
JustifyContent::Center
);
assert_eq!(
position_to_justify_content(Position::At(0.67)),
JustifyContent::FlexEnd
);
assert_eq!(
position_to_align_items(Position::At(0.66)),
AlignItems::Center
);
}
#[test]
fn test_pad_to_width_start() {
let result = pad_to_width("hello", 10, Position::Start);
assert_eq!(result, "hello ");
}
#[test]
fn test_pad_to_width_end() {
let result = pad_to_width("hello", 10, Position::End);
assert_eq!(result, " hello");
}
#[test]
fn test_pad_to_width_center() {
let result = pad_to_width("hello", 11, Position::Center);
assert_eq!(result, " hello ");
}
#[test]
fn test_pad_to_width_no_padding_needed() {
let result = pad_to_width("hello", 3, Position::Center);
assert_eq!(result, "hello");
}
#[test]
fn test_join_horizontal() {
let elem1 = Text::new("A").into_element();
let elem2 = Text::new("B").into_element();
let result = join_horizontal(Position::Center, vec![elem1, elem2]);
assert!(!result.children.is_empty());
}
#[test]
fn test_join_vertical() {
let elem1 = Text::new("A").into_element();
let elem2 = Text::new("B").into_element();
let result = join_vertical(Position::Center, vec![elem1, elem2]);
assert!(!result.children.is_empty());
}
#[test]
fn test_place_horizontal() {
let elem = Text::new("Test").into_element();
let result = place_horizontal(80, Position::Center, elem);
assert_eq!(result.style.width, crate::core::Dimension::Points(80.0));
}
#[test]
fn test_place_vertical() {
let elem = Text::new("Test").into_element();
let result = place_vertical(24, Position::Center, elem);
assert_eq!(result.style.height, crate::core::Dimension::Points(24.0));
}
#[test]
fn test_place() {
let elem = Text::new("Test").into_element();
let result = place(80, 24, Position::Center, Position::Center, elem);
assert_eq!(result.style.width, crate::core::Dimension::Points(80.0));
assert_eq!(result.style.height, crate::core::Dimension::Points(24.0));
}
#[test]
fn test_center() {
let elem = Text::new("Test").into_element();
let result = center(80, 24, elem);
assert_eq!(result.style.width, crate::core::Dimension::Points(80.0));
assert_eq!(result.style.height, crate::core::Dimension::Points(24.0));
}
#[test]
fn test_h_gap() {
let gap = h_gap(10);
assert_eq!(gap.style.width, crate::core::Dimension::Points(10.0));
}
#[test]
fn test_v_gap() {
let gap = v_gap(5);
assert_eq!(gap.style.height, crate::core::Dimension::Points(5.0));
}
#[test]
fn test_space_between() {
let elem1 = Text::new("A").into_element();
let elem2 = Text::new("B").into_element();
let result = space_between(vec![elem1, elem2]);
assert_eq!(result.style.justify_content, JustifyContent::SpaceBetween);
}
#[test]
fn test_space_around() {
let elem1 = Text::new("A").into_element();
let elem2 = Text::new("B").into_element();
let result = space_around(vec![elem1, elem2]);
assert_eq!(result.style.justify_content, JustifyContent::SpaceAround);
}
#[test]
fn test_space_evenly() {
let elem1 = Text::new("A").into_element();
let elem2 = Text::new("B").into_element();
let result = space_evenly(vec![elem1, elem2]);
assert_eq!(result.style.justify_content, JustifyContent::SpaceEvenly);
}
}