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
endA 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.