tui-markup-renderer

Rust library to use TUI and markup to build UI terminal interfaces.

tl;dr;

Xml Code: xml <layout id="root" direction="vertical"> <block constraint="10"> <!-- Don't forget the size, 1 by default --> <p align="center"> Press q to quit. </p> </block> <block id="bts_block" constraint="6"> <button id="btn_hello" action="open_dialog" index="1"> Say Hello </button> </block> <dialog id="dlg" show="show_dialog" buttons="Okay" action="on_dialog_event"> <block id="dlg_block" border="all"> <p align="center"> Hello World!!! </p> </block> </dialog> </layout>

Rust Code:

```rust use crossterm::event::KeyCode::{self, Char}; use std::{collections::HashMap, io}; use tui::backend::CrosstermBackend; use tuimarkuprenderer::{eventresponse::EventResponse, markupparser::MarkupParser};

fn main() -> Result<(), Box> { // get access to StdOut let stdout = io::stdout(); // Get the backend for TUI let backend = CrosstermBackend::new(stdout); // prepare the internal state for the app info let state = Some(HashMap::new());

// prepare the markup parser
let mut mp = MarkupParser::new("./assets/layout.tml".to_string(), None, state);

// Dialogs generate button identifiers following the convention "on_<dialog id>_btn_<button name>"
mp.add_action("open_dialog", |state| {
    let mut state = state.clone();
    state.insert("show_dialog".to_string(), "true".to_string());
    EventResponse::STATE(state)
})
.add_action("on_dlg_btn_Okay", |state| {
    let mut state = state.clone();
    state.insert("show_dialog".to_string(), "false".to_string());
    EventResponse::STATE(state)
})
.ui_loop(backend, |key_event, mut state| {
    let mut pressed = "none";
    match key_event.code {
        KeyCode::Esc => {
            pressed = "close_dialog";
        }
        Char('q') => {
            pressed = "close";
        }
        _ => {}
    }

    match pressed {
        "close_dialog" => {
            state.insert("show_dialog".to_string(), "false".to_string());
            EventResponse::STATE(state)
        }
        "close" => {
            state.insert("show_dialog".to_string(), "false".to_string());
            EventResponse::QUIT
        }
        _ => EventResponse::NOOP,
    }
})

} ``` image

Explanation

How it works

As a developer is easier to create a known data structure describing the user interface.

Sample markup code:

xml <layout id="root" direction="vertical"> <container id="nav_container" constraint="5"> <p id="toolbar" title="Navigation" border="all" styles="fg:green"> This is the navigation </p> </container> <container id="body_container" constraint="10min"> <p id="body" title="Body" border="all" styles="fg:red"> This is a sample </p> </container> </layout>

generates:

Simple Layout

A more complex sample:

```xml

This is a sample

  <layout id="content_info" direction="horizontal">
    <container id="ats_container" constraint="20%">
      <block id="ats_block" title="Ats" border="all">

      </block>
    </container>
    <container id="cnt_container" constraint="20min">
      <block id="cnt_block" title="Cnt" border="all">

      </block>
    </container>
  </layout>

</block>

```

generates:

Sample Layout

Planned features

The Rules!

A Sample

Layout code:

Something better?

```xml

button {
  fg: red;
  bg: black;
}

button:focus {
  fg: white;
  bg: red;
}
#footer {
  bg:black;
  fg:blue;
}

Header sample

  <layout id="content_info" direction="horizontal">
    <container id="ats_container" constraint="20%" title="Ats" border="all">

      <layout id="vert_info" direction="vertical">
        <block id="ats_block" constraint="5">
          <button id="btn_hello" action="do_something" index="1" styles="fg:magenta" focus_styles="fg:white;bg:magenta"> Hello </button>
        </block>
        <block id="bts_block" constraint="5">
          <button id="btn_hello_2" action="do_something_else" index="3"> Simple </button>
        </block>
        <block id="bts_block" constraint="5">
          <button id="btn_hello_3" action="do_something_else" index="2"> World </button>
        </block>
      </layout>

    </container>
    <container id="cnt_container" constraint="20min">
      <block id="cnt_block" title="Cnt" border="all">
        <p>
          lorem ipsum dolor sit amet sample.
        </p>
      </block>
    </container>
  </layout>

</block>

Close Application

Do you want to close the application?

```

Rust Code:

```rust use clap::Parser; use crossterm::event::KeyCode::{Char, self}; use std::{collections::HashMap, io}; use tui::backend::CrosstermBackend; use tuimarkuprenderer::{ markupparser::MarkupParser, eventresponse::EventResponse, };

[derive(Parser, Debug)]

[command(author, version, about, long_about = None)]

struct Args { #[arg(short, long, defaultvaluet = String::from("run"))] executiontype: String, #[arg(short, long, defaultvaluet = String::from("./assets/layout1.tml"))] layout: String, #[arg(short, long, defaultvaluet = false)] printargs: bool, }

fn main() -> Result<(), Box> { let Args { layout, executiontype, printargs, } = Args::parse();

let stdout = io::stdout();
let backend = CrosstermBackend::new(stdout);
let state = Some(HashMap::new());

let mut mp = MarkupParser::new(layout.clone(), None, state);
mp.add_action(
    "do_something",
    |_state: &mut HashMap<String, String>| {
        println!("hello!!!");
        EventResponse::NOOP
    },
)
.add_action(
    "do_something_else",
    |_state: &mut HashMap<String, String>| {
        println!("world!!!");
        EventResponse::NOOP
    },
)
.add_action(
    "on_dlg1_btn_Yes",
    |_state: &mut HashMap<String, String>| {
        EventResponse::QUIT
    },
)
.add_action(
    "on_dlg1_btn_Cancel",
    |state: &mut HashMap<String, String>| {
        let key = "showQuitDialog".to_string();
        state.insert(key, "false".to_string());
        EventResponse::STATE(state.clone())
    },
)
;

if print_args {
    println!(
        "[layout: {}, execution_type: {}, print_args: {}]",
        layout, execution_type, print_args
    );
}

if execution_type == String::from("run") {
    // async move
    mp.ui_loop(backend, |key_event, state| {
        let mut new_state = state.clone();
        let key = "showQuitDialog".to_string();
        // let back_value = String::new();
        let mut pressed = '\n';
        match key_event.code {
            KeyCode::Esc => {
                pressed = '\r';
            }
            Char(character) => {
                pressed = character;
            }
            _ => {}
        }

        if pressed == '\r' {
            let new_value = "false";
            new_state.insert(
                key,
                new_value.to_string(),
            );
            return EventResponse::STATE(new_state);
        }

        if pressed == 'q' {
            let new_value = "true";
            new_state.insert(
                key,
                new_value.to_string(),
            );
            return EventResponse::STATE(new_state);
        }

        return EventResponse::NOOP;
    })
} else {
    env_logger::init();
    mp.test_check(backend)
}

}

```

Will generate this:

image

image

image