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 Component
s.
#![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
.
state.field.set(new_value)
*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 String
s 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 Component
s 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
orfalse
- 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 (template tag:
text
) - Span (template tag:
span
) - Border (template tag:
border
) - Alignment (template tag:
align
) - VStack (template tag:
vstack
) - HStack (template tag:
hstack
) - ZStack (template tag:
zstack
) - Expand (template tag:
expand
) - Spacer (template tag:
spacer
) - Position (template tag:
position
) - Overflow (template tag:
overflow
) - Canvas (template tag:
canvas
) - Container (template tag:
container
) - Column (template tag:
column
) - Row (template tag:
row
) - Padding (template tag:
padding
)
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 span
s 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:
- top left
- top
- top right
- right
- bottom right
- bottom
- bottom left
- 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 expand
s.
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 expand
s.
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.
right
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 │
│ │
└──────────────┘