Orange is a framework to build TUI (terminal UI) applications in Elixir. Its high-level features are:
-
A DSL to describe UI component. The syntax is inspired by React. For example, an snippet like this:
rect style: [border: true, padding: {0, 1}, height: 10, width: 20] do span style: [color: :red] do "Hello" end span do "World" end end
will render this:
-
Support handling terminal events: currently, only keyboard events are supported.
-
Support custom components: you can create component from builtin primitives like rect, line, span. Custom components can encapsulate state and logic.
-
A collection of UI components: Input, VerticalScrollRect, ...
When using Orange, it is essential that you prevent the Erlang VM from reading stdin as it can interfere with the terminal events handling logic. You can achieve this via the -noinput
flag:
elixir --erl "-noinput" -S mix run --no-halt
First, we need to create a root component:
defmodule Counter.App do
@behaviour Orange.Component
import Orange.Macro
@impl true
# Each component can have an internal state
# Also, a component can subscribe to receive terminal events
def init(_attrs), do: %{state: %{count: 0}, events_subscription: true}
@impl true
def handle_event(event, state, _attrs) do
case event do
# Arrow up to increase counter
%Orange.Terminal.KeyEvent{code: :up} ->
%{state | count: state.count + 1}
# Arrow down to decrease counter
%Orange.Terminal.KeyEvent{code: :down} ->
%{state | count: state.count - 1}
%Orange.Terminal.KeyEvent{code: {:char, "q"}} ->
# Quit the application
Orange.stop()
state
_ ->
state
end
end
@impl true
def render(state, _attrs, _update) do
rect style: [border: true, padding: 1] do
"Counter: #{state.count}"
end
end
end
Then start the application:
Orange.start(Counter.App)
For more examples, see here.