photon-ui 0.4.4

Blazing fast minimal TUI
Documentation
use photon_ui::{
    Component,
    Layer,
    RenderError,
    Rendered,
    Shadow,
    TUI,
    TestTerminal,
    components::Text,
};

struct StaticComponent {
    lines: Vec<String>,
}

impl StaticComponent {
    fn new(lines: &[&str]) -> Self {
        Self {
            lines: lines.iter().map(|s| s.to_string()).collect(),
        }
    }
}

impl Component for StaticComponent {
    fn render(&self, _width: u16) -> Result<Rendered, RenderError> {
        Ok(Rendered {
            lines: self.lines.clone(),
            cursor: None,
            images: Vec::new(),
        })
    }
}

#[test]
fn tui_adds_base_layer_automatically() {
    let term = TestTerminal::new(80, 24);
    let tui = TUI::new(Box::new(term));
    assert_eq!(tui.layer_count(), 1);
}

#[test]
fn tui_mount_adds_to_base_layer() {
    let term = TestTerminal::new(80, 24);
    let mut tui = TUI::new(Box::new(term));
    tui.mount(Box::new(Text::new("hello", 0, 0)));
    assert_eq!(tui.layer_count(), 1);
}

#[test]
fn tui_add_layer_returns_index() {
    let term = TestTerminal::new(80, 24);
    let mut tui = TUI::new(Box::new(term));
    let idx = tui.add_layer(Layer::new());
    assert_eq!(idx, 1);
    assert_eq!(tui.layer_count(), 2);
}

#[test]
fn tui_insert_layer_at_bottom() {
    let term = TestTerminal::new(80, 24);
    let mut tui = TUI::new(Box::new(term));
    let mut bottom = Layer::new();
    bottom.mount(Box::new(StaticComponent::new(&["bottom"])));
    tui.insert_layer(0, bottom);
    assert_eq!(tui.layer_count(), 2);
}

#[test]
fn tui_remove_layer_shifts_focus() {
    let term = TestTerminal::new(80, 24);
    let mut tui = TUI::new(Box::new(term));
    tui.add_layer(Layer::new());
    tui.set_focused_layer(1);
    let removed = tui.remove_layer(1);
    assert!(removed.is_some());
    assert_eq!(tui.layer_count(), 1);
}

#[test]
fn tui_layer_mut_allows_modification() {
    let term = TestTerminal::new(80, 24);
    let mut tui = TUI::new(Box::new(term));
    let idx = tui.add_layer(Layer::new());
    if let Some(layer) = tui.layer_mut(idx) {
        layer.mount(Box::new(StaticComponent::new(&["modified"])));
    }
    assert!(tui.render_frame().is_ok());
}

#[test]
fn tui_top_layer_render_does_not_panic() {
    let term = TestTerminal::new(80, 24);
    let mut tui = TUI::new(Box::new(term));

    let mut bottom = Layer::new();
    bottom.mount(Box::new(StaticComponent::new(&["bottom content"])));
    tui.add_layer(bottom);

    let mut top = Layer::new();
    top.mount(Box::new(StaticComponent::new(&["top content"])));
    tui.add_layer(top);

    assert!(tui.render_frame().is_ok());
}

#[test]
fn tui_layer_dim_shadow_render_does_not_panic() {
    let term = TestTerminal::new(80, 24);
    let mut tui = TUI::new(Box::new(term));

    let mut bottom = Layer::new();
    bottom.mount(Box::new(StaticComponent::new(&["lower text"])));
    tui.add_layer(bottom);

    let mut top = Layer::new();
    top.mount(Box::new(StaticComponent::new(&["ABC"])));
    top.shadow = Shadow::Dim {
        style: "\x1b[2m".into(),
    };
    tui.add_layer(top);

    assert!(tui.render_frame().is_ok());
}

#[test]
fn tui_layer_drop_shadow_render_does_not_panic() {
    let term = TestTerminal::new(80, 24);
    let mut tui = TUI::new(Box::new(term));

    let mut bottom = Layer::new();
    bottom.mount(Box::new(StaticComponent::new(&["background"])));
    tui.add_layer(bottom);

    let mut top = Layer::new();
    top.mount(Box::new(StaticComponent::new(&["panel"])));
    top.shadow = Shadow::Drop {
        style: "\x1b[2m".into(),
        offset_x: 1,
        offset_y: 1,
    };
    tui.add_layer(top);

    assert!(tui.render_frame().is_ok());
}

#[test]
fn tui_layer_api_does_not_break_existing_overlay_modal() {
    use photon_ui::{
        Anchor,
        Overlay,
        OverlayConstraints,
        OverlayPosition,
    };

    let term = TestTerminal::new(80, 24);
    let mut tui = TUI::new(Box::new(term));
    tui.mount(Box::new(Text::new("background", 0, 0)));
    let overlay = Overlay {
        content: Box::new(Text::new("popup", 0, 0)),
        position: OverlayPosition::Anchor(Anchor::Center),
        constraints: OverlayConstraints {
            min_width: 5,
            max_height: 3,
            margin: 1,
            offset_x: 0,
            offset_y: 0,
            visible: None,
        },
    };
    tui.add_overlay(overlay);
    tui.show_modal(Box::new(Text::new("modal", 0, 0)));
    assert!(tui.render_frame().is_ok());
    tui.dismiss_modal();
    tui.clear_overlays();
    assert!(tui.render_frame().is_ok());
}