Skip to content

Goose97/orange

Repository files navigation

Package Documentation

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:

    Rendered result

  • 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, ...

Important

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

Examples

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.