Introduction

Anathema is a library for building text user interfaces using composable components, with a custom markup language.

It was created with the intent to give developers a fast and easy way to build text user interfaces (TUI) and ship the template(s) alongside the application, giving the end user the option to customise the layout.

By separating the layout from the rest of the application, reducing the amount of code needed to express your design, and featuring hot reloading it becomes incredibly fast to iterate over the design.

Why

  • Templates can be shipped with the application, giving the end user the ability to customise the entire layout of the application.
  • Easy to prototype with the markup language and runtime.

Alternatives

Getting started

Install

Add anathema to your Cargo.toml file.

...
[dependencies]
anathema = { git = "https://github.com/togglebyte/anathema/" }

Note

Even though efforts are made to keep this guide up to date there are possibilities of changes being made and published before they reach this guide.

At the time of writing, Anathema should be considered alpha.

A basic example

Render a border around three lines of text, placing the text in the middle of the terminal.

// src/main.rs
use std::fs::read_to_string;

use anathema::runtime::Runtime;
use anathema::templates::Document;
use anathema::backend::tui::TuiBackend;

fn main() {
    let template = read_to_string("template.aml").unwrap();

    let mut doc = Document::new(template);

    let mut backend = TuiBackend::builder()
        .enable_alt_screen()
        .enable_raw_mode()
        .hide_cursor()
        .finish()
        .unwrap();

    let mut runtime = Runtime::builder(doc, backend);
    runtime.finish().unwrap().run();
}
// template.aml
align [alignment: "center"]
    border
        vstack
            text [foreground: "yellow"] "You"
            text [foreground: "red"] "are"
            text [foreground: "blue"] "great!"

Prelude

Anathema has two convenience modules:

The prelude can be used to bootstrap your application, as it contains the Runtime, Document etc.

#![allow(unused)]
fn main() {
use anathema::prelude::*;
}

and components is suitable for modules that only contains Components.

#![allow(unused)]
fn main() {
use anathema::components::*;
}

See the crate documentation for more information.

Components

A component exposes event handling and state management. Any type that implements the anathema::widgets::components::Component trait is a component.

This trait has no required methods, however note that there are two required associated types:

  • State
  • Message

If the component does not need to handle state or receive messages these can be set to the unit type.

use anathema::widgets::components::Component;

struct MyComponent;

impl Component for MyComponent {
    type State = ();    // <-\ 
                        //    -- Set to () to ignore
    type Message = ();  // <-/
}

A component has to be registered with the runtime before it can be used in the template.

Template syntax

The component name is prefixed with an @ sign: @component_name, followed by optional associated events, attributes and external state.

@ means it's a component
|   Component name                   Attribute name
|   |         Component event        |      Attribute value
|   |         |        Parent event  |      |          External state
V   V         V        V             V      V          V
@component (click->parent_click) [attrib: "value"] { external_state: 123 }

Usage

Use a component in a template by prefixing the tag with the @ sign:

border
    @my_comp

Focus and receiving events

Mouse events are sent to all components, however key events are only sent to the component that currently has focus.

It is possible to assign focus to a component from another component using context.set_focus.

set_focus takes an attribute name and a value. It is possible to call set_focus on multiple components with a single frame. This will result in multiple components receiving both on_focus and on_blur until the last component in the focus queue which only receives on_focus.

Example

The following example would set focus on @component_a as it has a matching id. It's not required to name the attribute id, it can be any alphanumerical value (as long as it doesn't start with a number).

vstack
    @component_a [id: 1]
    @component_b [id: 2]
    @component_c [id: "not a number"]
fn on_key(
    &mut self,
    key: KeyEvent,
    state: &mut Self::State,
    mut elements: Elements<'_, '_>,
    mut context: Context<'_, Self::State>,
) { 
    context.set_focus("id", 1);
}

State

A state is a struct with named fields.

Each field has to be wrapped in a Value, e.g

#![allow(unused)]
fn main() {
name: Value<String>
}

or

#![allow(unused)]
fn main() {
names: Value<List<String>>
}

Updating state

There are two ways to update a field on State.

  1. state.field.set(new_value)
  2. *state.field.to_mut() = new_value

Important note about state values

When assigning a new value to a state, do not create a new instance of Value.

This is bad:

my_state.value = Value::new(123)

This is how it should be done:

my_state.some_value.set(123);
// or
*my_state.another_value.to_mut() = 123;

External state

A component can have internal state (mutable access) and external state (read-only).

To pass external state to a component provide a map in the template with a key-value pair:

@my_comp { key: "this is a value" }

To use the external state in the component refer to the keys in the map passed into the component:

// component template
text "the value is " key

Accessing external state from a component

It's possible to access external state from within the component, using context.external_get.

Example

@comp { key: 123 }
#![allow(unused)]
fn main() {
fn on_key(
    &mut self,
    key: KeyEvent,
    state: &mut Self::State,
    mut elements: Elements<'_, '_>,
    mut context: Context<'_, Self::State>,
) { 
    context.external_get("key").unwrap();
}
}

Internal state

Internal state is anything that implements the State trait.

Example

use anathema::state::{State, Value, List};

#[derive(State)]
struct MyState {
    name: Value<String>,
    numbers: Value<List<usize>>,
}

impl MyState {
    pub fn new() -> Self {
        Self {
            name: String::from("Lilly").into(),
            numbers: List::empty(),
        }
    }
}

let mut my_state = MyState::new();
my_state.numbers.push_back(1);
my_state.numbers.push_back(2);

runtime.register_component("my_comp", template, MyComponent, my_state);

Ignore fields

To store fields on the state that should be ignored by templates and widgets, decorate the fields with #[state_ignore].

Example

#![allow(unused)]
fn main() {
#[derive(State)]
struct MyState {
    word: Value<String>,
    
    #[state_ignore]
    ignored_value: usize,
}
}

Custom fields

Any type can be added as a field to a state as long as it implements anathema::state::State.

The only required function to implement is fn to_common(&self) -> Option<CommonVal<'_>> (without this the template has no way to render the value).

Types from third party crates can be wrapped using the new-type pattern.

Example

use anathema::component::*;
use anathema::state::CommonVal;

struct MyValue {
    a: i64,
    b: i64,
}

impl State for MyValue {
    fn to_common(&self) -> Option<CommonVal<'_>> {
        let total = self.a + self.b;
        Some(CommonVal::Int(total))
    }
}

#[derive(State)]
pub(super) struct MyState {
    my_val: Value<MyValue>,
}

Event handling

For a component to receive events it has to enable event handling by implementing one or more of the following methods:

on_key

Accept a key event and a mutable reference to the state (if one is associated with the component).

fn on_key(
    &mut self,
    key: KeyEvent,
    state: &mut Self::State,
    mut elements: Elements<'_, '_>,
    mut context: Context<'_, Self::State>,
) { }

on_mouse

Accept a mouse event and a mutable reference to the state (if one is associated with the component).

fn on_mouse(
    &mut self,
    mouse: MouseEvent,
    state: &mut Self::State,
    mut elements: Elements<'_, '_>,
    mut context: Context<'_, Self::State>,
) { }

on_focus

The component gained focus.

fn on_focus(
    &mut self, 
    state: &mut Self::State, 
    mut elements: Elements<'_, '_>,
    mut context: Context<'_, Self::State>,
) {}

on_blur

The component lost focus.

fn on_blur(
    &mut self, 
    state: &mut Self::State, 
    mut elements: Elements<'_, '_>,
    mut context: Context<'_, Self::State>,
) {}

Example

use anathema::widgets::components::events::{KeyCode, KeyEvent, MouseEvent};
use anathema::widgets::Elements;

impl Component for MyComponent {
    type Message = ();
    type State = MyState;

    fn on_key(
        &mut self,
        key: KeyEvent,
        state: &mut Self::State,
        mut elements: Elements<'_, '_>,
        mut context: Context<'_, Self::State>,
    ) {
        // Get mutable access to the name
        let mut name = state.name.to_mut();

        if let Some(c) = key.get_char() {
            name.push(c);
        }

        if let KeyCode::Enter = key.code {
            name.clear();
        }
    }

    fn on_mouse(
        &mut self,
        mouse: MouseEvent,
        state: &mut Self::State,
        mut elements: Elements<'_, '_>,
        mut context: Context<'_, Self::State>,
    ) {
        // Mouse event
    }
}

Messages

Communicate between components using messages and component ids.

An Emitter is used to send a message to any recipient. The recipient id is returned when calling register_component.

The Emitter has two functions:

  • emit (use in a sync context)
  • emit_async (use in an async context)

If the recipient no longer exist the message will be lost.

Implement the message method of the Component trait for a component to be able to receive messages.

The following example sets the component to accept Strings and starts a thread that sends a String to the component every second.

Example

use anathema::runtime::{Emitter, Runtime};

impl Component for MyComponent {
    type Message = String; // <- accept strings
    type State = MyState;

    fn message(
        &mut self,
        message: Self::Message, 
        state: &mut Self::State, 
        elements: Elements<'_, '_>,
        context: Context<'_, Self::State>
    ) {
        state.messages.push_back(message);
    }
}

// Send a string to a recipient every second
fn send_messages(emitter: Emitter, recipient: ComponentId<String>) {
    let mut counter = 0;

    loop {
        emitter.emit(recipient, format!("{counter} message"));
        counter += 1;
        std::thread::sleep_ms(1000);
    }
}

// Get the component id when registering the component
let recipient = runtime.register_component(
    "my_comp", 
    component_template,
    MyComponent::new(),
    MyState::new()
);

let emitter = runtime.emitter();
std::thread::spawn(move || {
    send_messages(emitter, recipient);
});

Element query

Both component events and messages provide an element query that can be used to access elements and attributes of the component.

Attributes can be read and written to.

Cast an element

It's possible to cast an element to a specific widget using the to method.

The to method returns a &mut T.

For an immutable reference use to_ref.

If the element type is unknown use try_to and try_to_ref respectively.

Example


fn on_key(
    &mut self,
    key: KeyEvent,
    state: &mut Self::State,
    mut elements: Elements<'_, '_>,
    context: Context<'_, Self::State>,
) {
    elements
        .by_tag("overflow")
        .by_attribute("abc", 123)
        .first(|el, _| {
            let overflow = el.to::<Overflow>();
            overflow.scroll_up();
        });
}

Filter elements

Example

fn on_mouse(
    &mut self,
    mouse: MouseEvent,
    state: &mut Self::State,
    elements: Elements<'_, '_>,
    context: Context<'_, Self::State>,
) { 
    elements
        .by_attribute("abc", 123)
        .each(|el, attributes| {
            attributes.set("background", "green");
        });
}

There are three methods to query the elements inside the component:

by_tag

This is the element tag name in the template, e.g text or overflow.

elements
    .by_tag("text")
    .each(|el, attributes| {
        attributes.set("background", "green");
    });

by_attribute

This is an attribute with a matching value on any element.

elements
    .by_attribute("background", "green")
    .each(|el, attributes| {
        attributes.set("background", "red");
    });

at_position

    fn on_mouse(
        &mut self,
        mouse: MouseEvent,
        state: &mut Self::State,
        elements: Elements<'_, '_>,
        context: Context<'_, Self::State>,
    ) {
        elements
            .at_position(mouse.pos())
            .each(|el, attributes| {
                attributes.set("background", "red");
            });
    }

Insert state value into attributes

It is possible to assign a value to an element using the set function on the attributes.

// Component event
fn on_key(
    &mut self,
    key: KeyEvent,
    state: &mut Self::State,
    elements: Elements<'_, '_>,
) { 
    elements
        .by_tag("position")
        .each(|el, attrs| {
            attrs.set("background", "red");
        });
}

Getting values from attributes

To read copy values from attributes use the get function. To get a reference (like a string slice) use get_ref.

Note that integers in the template can be auto cast to any integer type as long as the value is hard coded into the template (and not originating from state).

E.g i64 can be cast to a u8. However integers can not be cast to floats, and floats can not be cast to integers.

// Component event
fn on_key(
    &mut self,
    key: KeyEvent,
    state: &mut Self::State,
    elements: Elements<'_, '_>,
) { 
    elements
        .by_tag("position")
        .each(|el, attrs| {
            let boolean = attrs.get::<bool>("is_true");
            let string = attrs.get_ref("background");
        });
}

Third party components

Components can be made reusable by implementing placeholders and associated functions.

A component can have named placeholders for external children:

// The template for @mycomponent
border
    $my_children

When using a component with named children use the placeholder name to inject the elements:

for x in values
    @mycomponent
        $my_children
            text "hello world"

Associated functions

These look almost like callbacks however they run at the end of the update cycle, and not instantly.

To associate a function with the parent use the following syntax:

@input (text_change->update_username)

The @input component can now call context.publish:

#[derive(State)]
struct InputState {
    text: Value<String>,
}

//...

impl Component for Input {
    fn on_key(
        &mut self,
        key: KeyEvent,
        state: &mut Self::State,
        elements: Elements<'_, '_>,
        mut context: Context<'_, Self::State>,
    ) {
        context.publish("text_change", |state| &state.text);
    }
}

The parent component will receive the associated ident ("update_username") and the value (as a CommonVal).

impl Component for Parent {
    fn receive(
        &mut self,
        ident: &str,
        value: CommonVal<'_>,
        state: &mut Self::State,
        elements: Elements<'_, '_>,
        mut context: Context<'_, Self::State>,
    ) {
        if ident == "update_username" {
            let value = &*value.to_common_str();
        }
    }
}

Runtime

use anathema::runtime::Runtime;
use anathema::backend::tui::TuiBackend;

let doc = Document::new("text 'hello world'");

let backend = TuiBackend::builder()
    .enable_alt_screen()
    .enable_raw_mode()
    .enable_mouse()
    .hide_cursor()
    .finish()
    .unwrap();
    
let mut runtime = Runtime::builder(doc, backend).finish().unwrap();
runtime.fps = 30; // default

Registering components

Before components can be used in a template they have to be registered with the runtime.

let runtime = Runtime::builder(document, backend);

let component_id = runtime.register_component(
    "my_comp",                                  // <- tag
    "template.aml",                             // <- template
    MyComponent,                                // <- component instance
    ComponentState,                             // <- state
);

File path vs embedded template

If the template is passed as a string it is assumed to be a path and hot reloading will be enabled by default.

To to pass a template (rather than a path to a template) call to_template on the template string:

static TEMPLATE: &str = include_str!("template.aml");

let component_id = runtime.register_component(
    "my_comp",
    TEMPLATE.to_template(),
    MyComponent,
    ComponentState,
);

Hot reload

To disable hot reloading set the documents hot_reload = false.

let mut doc = Document::new("@index");
doc.hot_reload = false;

Multiple instances of a component

To repeatedly use a component in a template, e.g:

vstack
    @my_comp
    @my_comp
    @my_comp

The component has to be registered as a prototype using register_prototype (instead of registering_comonent):

#![allow(unused)]
fn main() {
runtime.register_prototype(
    "comp", 
    "text 'this is a template'",
    || MyComponent, 
    || ()
);
}

The main difference between registering a singular component vs a prototype is the closure creating an instance of the component and the state, rather than passing the actual component instance and state into the function.

Also note that prototypes does not have a component id and can not have messages emitted to them.

Global Events

The global shortcuts can be changed by modifying the runtime's global event handler. A custom global event handler is any struct that implements GlobalEvents. The global event handler can be set using the set_global_event_handler function.

The GlobalEvents trait has three functions: handle, ctrl_c and enable_tab_navigation.

enable_tab_navigation

Default: true

Returning a bool will enable/disable tabbing.

ctrl_c

fn ctrl_c(
    &mut self,
    event: Event,
    elements: &mut Elements<'_, '_>,
    global_context: &mut GlobalContext<'_>
) -> Option<Event> { }

Default: Some(event)

Returning Some(event) from this will cause the event to stop propagating and close down the runtime.

handle

fn handle(
    &mut self,
    event: Event,
    elements: &mut Elements<'_, '_>,
    ctx: &mut GlobalContext<'_>
) -> Option<Event> { }

This function receives any events that weren't caught by ctrl-c or tabbing. Returning None will stop the propagation of the event to the components.

Configuring the runtime

fps

Default: 30

The number of frames to (try to) render per second.

Backend

Configuring the backend

The following settings are available:

enable_raw_mode

Raw mode prevents inputs from being forwarded to the screen (the event system will pick them up but the terminal will not try to print them).

enable_mouse

Enable mouse support in the terminal (if it's supported).

enable_alt_screen

Creates an alternate screen for rendering. Restores the previous content of the terminal to its previous state when the runtime ends.

hide_cursor

Hides the cursor.

Quit on Ctrl-c

This is enabled by default on the TUI backend. To disable this set backend.quit_on_ctrl_c = false.

Templates

Anathema has a template language to describe user interfaces. This makes it possible to ship the template(s) together with the compiled binary so the application can be customised without having to be recompiled.

The element syntax looks as follows: <element> <attributes> <value>

   Element name                                         
   |    Start of (optional) attributes
   |    |    Attribute name                            
   |    |    |          Attribute value
   |    |    |          |     Attribute separator      End of attributes
   |    |    |          |     |                        | Values / Text
   |    |    |          |     |                        | |
   |    |    |          |     |                        | |------+-+-+
   |    |    |          |     |                        | |      | | |
element [optional: "attribute", separated: "by a comma"] "text" 1 2 ident

Elements don't receive events (only Components do) and should be thought of as something that is only drawn to screen.

There is for instance no "input" element. To represent an input the component would handle the key press events and simply pass the collected values as a string via state.

Comments

Use // to add comments to a template:

    text "I will render"
    // text "I will not"

Attributes

Element attributes are optional.

Attributes consists of a collection of ident, :, and value.

An ident has to start with a letter (a-zA-Z) and can contain one or more _.

Example:

element [attribute_a: "some string", attribute_b: false]

An attribute name is always an ident, however the value can be anything that implements State.

Default value types:

A literal value can be one of the following:

  • string: "Empty vessel, under the sun"
  • integer: 123
  • float: 1.23
  • hex: #fab or #ffaabb
  • boolean: true or false
  • list: [1, 2, 3]
  • map: {"key": "value"}

To access a state value use an ident (a string without spaces that is not wrapped in quotes).

text "string literal"
text ident

Elements and children

Some elements can have one or more children.

Example: a border element with a text element inside

border
    text "look, a border"

Loops

It's possible to loop over the elements of a collection. The syntax for loops are for <element> in <collection>.

Example: looping over static values

for value in [1, 2, "hello", 3]
    text value

Elements generated by the loop can be thought of as belonging to the parent element as the loop it self is not a element. See example below.

Example: mixing loops and elements

vstack
    text "start"
    for val in [1, 2, 3]
        text "some value: " val "."
    text "end"

The above example would render:

start
some value: 1.
some value: 2.
some value: 3.
end

For-loops also expose a loop variable that is set to the current loop iteration.

Example: loop variable

vstack
    for val in ["a", "b", "c", "d"]
        text "#" loop ": " val

The above example would render:

#0: a
#1: b
#2: c
#3: d

Note: For-loops do not work inside attributes, and can only produce elements.

If / Else

It's possible to use if and else to determine what to layout.

Example:

if true
    text "It's true!"
    
if value > 10
    text "Larger than ten"
else if value > 5
    text "Larger than five but less than ten"
else
    text "It's a small value..."

Operators

  • Equality ==
  • Greater than >
  • Greater than or equals >=
  • Less than <
  • Less than or equals <=
  • And &&
  • Or ||

Note: just like for-loops it's not possible to use if / else with attributes.

To determine the value of an attribute depending on some condition this should be handled by the state.

Elements

Even though it's possible to create custom elements Anathema aims to provide the necessary building blocks to represent almost any layout without the need to do so.

Default attributes

Elements that draw them selves (e.g text, span and border) support these default attributes:

foreground

Foreground colour

Valid values:

  • hex: #ffaabb
  • string: "green"

background

Background colour (see foreground for valid values)

bold

Valid values: true or false

italic

Valid values: true or false

display

Changes the behaviour of the rendering and layout step. show is default and renders the element. hide will not paint the element, but it will be part of the layout, exclude excludes it from layout as well, thus the element won't take up any space and be hidden.

Valid Values: show, hide or exclude

fill

Fill the unpainted space with a string.

Example:

border [width: 10, height: 5, fill: "+-"]
    text "Hello"
┌────────┐
│Hello-+-│
│+-+-+-+-│
│+-+-+-+-│
└────────┘

Default widgets

The following is a list of available widgets and their template tags:

Text (text)

Displays text.

String literals can be wrapped in either " or '.

To add styles to text in the middle of a string use a span element as the text element only accepts spans as children. Any other element will be ignored.

Example

text [foreground: "red"] "I'm a little sausage"

Attributes

wrap

Default is to wrap on word boundaries such as space and hyphen, and this method of wrapping will be used if no wrap attribute is given.

Valid values:

  • "overflow": the text is truncated when it can no longer fit
  • "break": the text will wrap once it can no longer fit

text_align

Note that text align will align the text within the element. The text element will size it self according to its constraint.

To right align text to the right side of the screen therefore requires the use of the alignment widget in combination with the text align attribute.

Default: left

Valid values:

  • "left"
  • "right"
  • "centre" | "center"

Example of right aligned text

border [width: 5 + 2]
    text [text_align: "right"] "hello you"
┌─────┐
│hello│
│  you│
└─────┘

Example of centre aligned text

border [width: 5 + 2]
    text [text_align: "centre"] "hello you"
┌─────┐
│hello│
│ you │
└─────┘

Span (span)

The span element is used to style text on the same line. Span can not be used on its own, it has to belong to a text element and does not accept children.

text "start"
    span "-middle-"
    span "end"
start-middle-end

Border (border)

The border accepts only a single child.

Example

border
    text "What a border!"
┌──────────────┐
│What a border!│
└──────────────┘

Attributes

width

The fixed width of the border

height

The fixed height of the border

min_width

The minimum width of the border

min_height

The minimum height of the border

sides

Valid values:

  • "top"
  • "right"
  • "bottom"
  • "left"

Sides specifies which sides of the border should be drawn.

This can be written as an individual side:

border [sides: "left"]
    text "What a border!"
│What a border!

or

border [sides: "top"]
    text "What a border!"
──────────────
What a border!

It can also be written as a list:

border [sides: ["left", "top"]]
    text "What a border!"
┌──────────────
│What a border!

border_style

The default border style is "thin".

For a thicker border style the value "tick" can be used:

border [border_style: "thick"]
    text  "What a border!"
╔══════════════╗
║What a border!║
╚══════════════╝

It's also possible to customise the border style by providing an eight character string as input, where each character represents a section of the border in the following order:

  1. top left
  2. top
  3. top right
  4. right
  5. bottom right
  6. bottom
  7. bottom left
  8. left

See the following example:

border [border_style: "12345678"]
    text  "What a border!"
1222222222222223
8What a border!4
7666666666666665

Alignment (align)

Alignment will inflate the wrapping element to use all the constraints.

This means it's not recommended to put an alignment widget inside an unconstrained widget such as a Overflow.

The alignment accepts at most one child.

Example

border [width: 16, height: 5]
    align [alignment: "centre"]
        text "centre"
┌──────────────┐
│              │
│    centre    │
│              │
└──────────────┘

Attributes

alignment

Valid values:

  • "top_left"
  • "top"
  • "top_right"
  • "right"
  • "bottom_right"
  • "bottom"
  • "bottom_left"
  • "left"
  • "centre" or "center"

VStack (vstack)

Vertically stack elements.

Accepts many children.

Example

vstack
    text "one"
    text "two"
one
two

Attributes

width

Fixed width

height

Fixed height

min_width

Minimum width

min_height

Minimum height

HStack (hstack)

Horizontally stack elements.

Accepts many children.

Example

hstack
    text "one"
    text "two"
onetwo

Attributes

width

Fixed width

height

Fixed height

min_width

Minimum width

min_height

Minimum height

ZStack (zstack)

Stack elements on top of each other.

Accepts many children.

Example

zstack
    text "333"
    text "22"
    text "1"
123

Attributes

width

Fixed width

height

Fixed height

min_width

Minimum width

min_height

Minimum height

Row (row)

Horizontal lay out elements where all the elements are centred vertically.

Row accepts multiple children.

Example

row
    text "a"
    border
        text "b"
    text "c"
 ┌─┐
a│b│c
 └─┘

Column (column)

Vertical layout of elements where all the elements are centred horizontally.

Column accepts multiple children.

Example

column
    text "a"
    border
        text "b"
    text "c"
 a
┌─┐
│b│
└─┘
 c

Expand (expand)

Expand the element to fill the remaining space.

Accepts one child.

The layout process works as follows:

First all elements that are not expand or spacer will be laid out. The remaining space will be distributed between expand then spacer in that order, meaning if one expand exists followed by a spacer the expand will consume all remaining space, leaving the spacer zero sized.

The size is distributed evenly between all expands.

To alter the distribution factor set the factor attribute.

Example

border [width: 10, height: 11]
    vstack
        expand
            border
                expand
                    text "top"
        expand
            border
                expand
                    text "bottom"
        text "footer"
┌────────┐
│┌──────┐│
││top   ││
││      ││
│└──────┘│
│┌──────┐│
││bottom││
││      ││
│└──────┘│
│footer  │
└────────┘

Attributes

factor

The factor decides the amount of space to distribute between the expands.

Given a height of 3 and two expand widgets, the height would be divided by two.

If one of the expand widgets had a factor of two, then it would receive 2 of the total height, and the remaining widget would receive 1.

axis

Expand along an axis.

Valid values:

  • "horz" | "horizontal"
  • "vert" | "vertical"

Position (position)

Absolute or relative position of the child.

Accepts at most one child.

Attributes

placement

Either relative (default) or absolute

left

Position the left side of the element with an offset of the given value.

Position the right side of the element with an offset of the given value.

border [width: 10, height: 5]
    position [top: 1, right: 1, placement: "relative"]
        text "Hi"
┌────────┐
│      Hi│
│        │
│        │
└────────┘

top

Position the top side of the element with an offset of the given value.

bottom

Position the bottom side of the element with an offset of the given value.

Spacer (spacer)

A spacer should only be used inside an hstack or a vstack.

The spacer element will push the size value along a given axis to consume the remaining available space.

A vertical spacer has no horizontal size, and a horizontal spacer has no vertical size.

The spacer accepts no children and has no visible rendering.

Example

border
    hstack
        text "Hi"
        spacer
┌─────────────────────────┐
│Hi                       │
└─────────────────────────┘

without the spacer:

border
    hstack
        text "Hi"
┌──┐
│Hi│
└──┘

Overflow (overflow)

The Overflow allows the elements to overflow along a given axis.

It's important to note that the overflow is an unbounded element. This means that elements can be laid out infinitely along a given axis.

Providing the overflow with a very large collection would produce a very large element tree.

An Overflow offset has to be changed via events rather than template values (if the offset came from a State it wouldn't be possible to know when the offset was exceeded should it change as a result of a key press for example. Even though the Offset could clamp the offset, the offset it self would not be clamped).

Example

border [height: 4, width: 10]
    overflow
        text "1"
        text "2"
        text "3"
        text "4"
┌────────┐
│1       │
│2       │
└────────┘
struct MyComponent {
    // ...
    fn on_key(
        &mut self,
        key: KeyEvent,
        state: &mut Self::State,
        mut elements: Elements<'_, '_>,
        mut context: Context<'_, Self::State>,
    ) {
        if let Some(c) = key.get_char() {
            elements.by_tag("overflow").first(|el, _| {
                let overflow = el.to::<Overflow>();
                match c {
                    'k' => overflow.scroll_up(),
                    'j' => overflow.scroll_down(),
                    _ => {}
                }
            });
        }
    }
}

Attributes

direction

Specifies the direction to lay out the elements.

Default value: "forwards"

Valid values:

  • "back" or "backwards" or "backward"
  • "fwd" or "forwards" or "forward"
border [height: 5, width: 10]
    overflow [direction: "backward"]
        text "1"
        text "2"
┌────────┐
│        │
│2       │
│1       │
└────────┘

axis

Specify along which axis to layout the children.

Valid values:

  • "horz" | "horizontal"
  • "vert" | "vertical"

clamp

Clamp the offset, preventing the content to scroll out of view

Default value: true

unconstrained

If this is set to true, both axis are unconstrained. If this is false, only the given axis is unconstrained.

Default value: false

  • false

Methods

scroll_up()

Scroll the Overflow forward.

This is analogous to Overflow::scroll(Direction::Forward, 1).

scroll_up_by(n: u32)

Scroll the Overflow forward n number of lines.

This is analogous to Overflow::scroll(Direction::Forward, n).

scroll_down()

Scroll the overflow backward.

This is analogous to Overflow::scroll(Direction::Backward, 1).

scroll_down_by(n: u32)

Scroll the overflow backward n number of lines.

This is analogous to Overflow::scroll(Direction::Backward, n).

scroll_right()

Scroll the overflow forward.

This is analogous to Overflow::scroll(Direction::Forward, 1).

scroll_right_by(n: u32)

Scroll the overflow forward n number of lines.

This is analogous to Overflow::scroll(Direction::Forward, n).

scroll_left()

Scroll the overflow backward.

This is analogous to Overflow::scroll(Direction::Backward, 1).

scroll_left_by(n: u32)

Scroll the overflow backward n number of lines.

This is analogous to Overflow::scroll(Direction::Backward, n).

scroll_to(pos: Pos)

Scroll the overflow to a given position.

Padding (padding)

Add padding around an element

Padding only accepts a single child.

Specifying only the padding attribute will apply the value to all sides. This can be overridden by specifying a side, such as bottom, right etc.

border
    padding [padding: 1]
        text "What a border!"
┌────────────────┐
│                │
│ What a border! │
│                │
└────────────────┘

Attributes

padding

Value applied to all sides of the element. This value will be overridden by any specifics, such as left or top etc.

top

Top padding

right

Right side padding

bottom

Bottom padding

left

Left side padding

Canvas (canvas)

A canvas is an element that can be drawn on.

The canvas accepts no children.

The canvas should be manipulated in Rust code:

The element will consume all available space and expand to fit inside the parent.

Example

#![allow(unused)]
fn main() {
use anathema::backend::tui::{Color, Style};

fn on_key(
    &mut self,
    key: KeyEvent,
    state: &mut Self::State,
    elements: Elements<'_, '_>,
) {
    let mut style = Style::reset();
    style.fg = Some(Color::Red);

    elements.by_tag("canvas").first(|el, _| {
        let canvas = el.to::<Canvas>();
        canvas.put('a', style, (0, 0));
        canvas.put('b', style, (1, 1));
        canvas.put('c', style, (2, 2));
    });
}
}
border [width: 16, height: 5]
    canvas
┌──────────────┐
│ a            │
│  b           │
│   c          │
└──────────────┘

Attributes

width

height

Methods

erase(pos: impl Into<LocalPos>)

Erase a character at a given position in local canvas space

get(pos: impl Into<LocalPos>) -> Option<(&mut char, &mut CanvasAttribs)>

Get a mutable reference to the character and attributes at a given position.

put(c: char, style: Style, pos: LocalPos)

Put a character with a style at a given position.

translate(pos: Pos) -> LocalPos

Translate global coords to local (to the canvas) coords.

Container (container)

A container element.

The container accepts at most one child.

Example

border [width: 16, height: 5]
    container [width: 1]
        text "ab"
┌──────────────┐
│a             │
│b             │
│              │
└──────────────┘

Attributes

width

height

min_width

min_height

max_width

max_height

background