In a Livebook notebook, Kino shows a piece as HTML, and Kino.animate/3 plays it. These examples were written against Kino 0.19.

# needs: a notebook's setup cell
Mix.install([
  {:kino, "~> 0.19"},
  {:ascii, "~> 0.3.0"}
])

A helper

Ascii.Render.HTML gives the inside of a <pre>, escaped, with coloured pieces as spans of their palette; wrap it and hand it to Kino.HTML:

# needs: kino
defmodule Art do
  @style "margin:0;line-height:1.2;letter-spacing:0;white-space:pre;font-family:monospace;font-size:12px"

  def kino(%Ascii.Frame{} = frame) do
    ground = if frame.colors && frame.ground, do: ";background:" <> frame.ground, else: ""
    html = ["<pre style=\"", @style, ground, "\">", Ascii.Render.HTML.render(frame), "</pre>"]
    Kino.HTML.new(IO.iodata_to_binary(html))
  end
end

A still

# needs: kino
Ascii.new!("elixir") |> Ascii.frame(0.0) |> elem(0) |> Art.kino()

Playing

Kino.animate/3 calls a function every interval with the tick number and a state of your choosing, and shows what it returns. Keep the animation as the state, so pieces with state between frames play correctly:

# needs: kino
{:ok, anim} = Ascii.new("donut")
interval = round(1000 / anim.fps)

Kino.animate(interval, anim, fn i, anim ->
  {frame, anim} = Ascii.frame(anim, i * interval / 1000)
  {:cont, Art.kino(frame), anim}
end)

Return :halt instead of {:cont, …} to stop, for example after a number of ticks.

Live data

The UI and data pieces draw your own data (see Live data). Take new readings up with Ascii.put_options!/2 as the animation plays, here once a second, and the gauge's needle swings to each:

# needs: kino
{:ok, anim} = Ascii.new("gauge", options: %{label: "run queue", max: 20, value: 0})

Kino.animate(100, anim, fn i, anim ->
  anim =
    if rem(i, 10) == 0,
      do: Ascii.put_options!(anim, value: :erlang.statistics(:total_run_queue_lengths)),
      else: anim

  {frame, anim} = Ascii.frame(anim, i * 0.1)
  {:cont, Art.kino(frame), anim}
end)

Options and controls

Kino's inputs make a small explorer. Options typed into a form are checked against the piece's defaults, so a mistyped key comes back as an error rather than a crash:

# needs: kino
piece = Kino.Input.select("piece", Enum.map(Ascii.pieces(category: :logos), &{&1, &1}))
paper = Kino.Input.checkbox("paper")
Kino.Layout.grid([piece, paper], columns: 2)
# needs: kino, and the cell above
{:ok, anim} = Ascii.new(Kino.Input.read(piece), paper: Kino.Input.read(paper))

Kino.animate(33, anim, fn i, anim ->
  {frame, anim} = Ascii.frame(anim, i * 0.033)
  {:cont, Art.kino(frame), anim}
end)

Text and terminal output

For a text piece, Kino.Text is enough, and with terminal: true it renders ANSI escapes, so Ascii.Render.ANSI works too (in 256 colours, to be safe):

# needs: kino
{frame, _} = Ascii.new!("arch-linux") |> Ascii.frame(0.0)
frame |> Ascii.Render.ANSI.render(mode: :ansi256) |> IO.iodata_to_binary() |> Kino.Text.new(terminal: true)

Scenes are 200×100 cells: at a small font size they fit a notebook's width. They take a moment to set up (Ascii.new/2), so make the animation once and keep it in a variable.