tty-form 1.0.0

Provides generic terminal form capabilities.
Documentation
use std::io::stdout;
use termion::event::Key;
use termion::raw::IntoRawMode;
use tty_form::form::Form;
use tty_form::step::Step;
use tty_form::Result;
use tty_interface::config::Configuration;
use tty_interface::mode::{CursorMode, RenderMode};
use tty_interface::Interface;

fn main() {
    run_form().expect("Execute form");
}

fn run_form() -> Result<()> {
    let mut form = Form::new(vec![
        Step::new(vec![
            Box::new(Dynamic::new("D0".to_string())),
            Box::new(Literal::new("E0S1".to_string())),
            Box::new(Literal::new("E1S1\nE1S2\nE1S3".to_string())),
            Box::new(Literal::new("E2S1".to_string())),
            Box::new(Literal::new("E3S1".to_string())),
            Box::new(Literal::new("E4S1\nE4S2".to_string())),
            Box::new(Literal::new("E5S1".to_string())),
            Box::new(Literal::new("E6S1\nE6S2".to_string())),
        ]),
        Step::new(vec![
            Box::new(Literal::new("E7S1\nE7S2".to_string())),
            Box::new(Literal::new("E8S1".to_string())),
            Box::new(Dynamic::new("D1".to_string())),
            Box::new(Literal::new("E9S1".to_string())),
            Box::new(Literal::new("E10S1\nE10S2".to_string())),
        ]),
    ]);

    let stdout = stdout();
    let mut stdout = stdout.lock().into_raw_mode()?;
    let config = Configuration::new(CursorMode::Relative, RenderMode::Relative);
    let mut interface = Interface::new_with_configuration(&mut stdout, config)?;

    form.execute(&mut interface)?;

    Ok(())
}

use tty_form::coordinator::Coordinator;
use tty_form::element::literal::Literal;
use tty_form::element::{Element, ElementId};
use tty_form::layout::LayoutAccessor;
use tty_interface::line::LineId;
use tty_interface::position::RelativePosition;
use tty_interface::segment::SegmentId;

pub struct Dynamic {
    id: Option<ElementId>,
    update: usize,
    text: String,

    debug: Option<SegmentId>,
    s1_id: Option<SegmentId>,
    s2_id: Option<SegmentId>,
    l1_id: Option<LineId>,
}

impl Dynamic {
    pub fn new(text: String) -> Self {
        Self {
            debug: None,
            id: None,
            update: 0,
            s1_id: None,
            s2_id: None,
            l1_id: None,
            text,
        }
    }

    fn id(&self) -> &ElementId {
        self.id.as_ref().unwrap()
    }
}

impl Element for Dynamic {
    fn set_id(&mut self, element_id: ElementId) {
        self.id = Some(element_id)
    }

    fn render(&mut self, coordinator: &mut Coordinator) -> Result<()> {
        if self.debug.is_none() {
            let debug = coordinator.add_segment(self.id());
            self.debug = Some(debug.identifier());
        }

        let debug = coordinator.get_segment_mut(self.id(), &self.debug.unwrap());
        debug.set_text(&format!("[status: update={}]", self.update));

        match self.update {
            0 => {
                if self.s1_id.is_none() {
                    let segment = coordinator.add_segment(self.id());
                    self.s1_id = Some(segment.identifier());
                    segment.set_text(&format!("{}S0", &self.text));
                }

                if self.s2_id.is_some() {
                    coordinator.remove_segment(self.id(), &self.s2_id.unwrap());
                    self.s2_id = None;
                }
            }
            1 => {
                if self.s2_id.is_none() {
                    let segment = coordinator.insert_segment(self.id(), 0);
                    self.s2_id = Some(segment.identifier());
                    segment.set_text(&format!("{}S1", &self.text));
                }

                if self.l1_id.is_some() {
                    coordinator.remove_line(self.id(), &self.l1_id.unwrap());
                    self.l1_id = None;
                }
            }
            2 => {
                if self.l1_id.is_none() {
                    let line = coordinator.add_line(self.id());
                    self.l1_id = Some(line.identifier());
                    let segment = line.add_segment();
                    segment.set_text(&format!("{}S2", &self.text));
                }
            }
            _ => {}
        }

        let line_id = coordinator.get_inline_line_id(self.id());
        coordinator.set_cursor(RelativePosition::new(line_id, self.s1_id.unwrap(), 0));

        Ok(())
    }

    fn update_layout(&mut self, _layout_accessor: &mut LayoutAccessor) {}

    fn is_input(&self) -> bool {
        true
    }

    fn captures_enter(&self) -> bool {
        false
    }

    fn update(&mut self, key: Key) -> bool {
        match key {
            Key::Right => self.update += 1,
            Key::Left => {
                if self.update > 0 {
                    self.update -= 1;
                }
            }
            _ => {}
        };

        false
    }
}