Screen, touch and buttons with Scenic
Overview
Scenic is an Elixir UI framework. A scene describes what's on screen as a graph of primitives (text, rectangles, circles…) and receives input: touches and button presses.
Free the screen
The IEx console is drawn on the screen too. Detach it while Scenic runs:
fbcon =
Path.wildcard("/sys/class/vtconsole/vtcon*")
|> Enum.find(&(File.read!(Path.join(&1, "name")) =~ "frame buffer"))
File.write!(Path.join(fbcon, "bind"), "0")
File.write!("/sys/class/graphics/fb0/blank", "0")
A scene
This scene shows a title and a status line. Touching the screen draws a circle and shows the coordinates; the volume and power buttons show their name.
defmodule Demo.Scene do
use Scenic.Scene
alias Scenic.Graph
import Scenic.Primitives
@impl Scenic.Scene
def init(scene, _param, _opts) do
{width, height} = scene.viewport.size
graph =
Graph.build(font: :roboto, font_size: 72)
|> rect({width, height}, fill: {20, 24, 60})
|> text("Hello from Nerves!", fill: :white, translate: {60, 200})
|> text("Touch the screen or press a button",
id: :status,
font_size: 44,
fill: :light_gray,
translate: {60, 300}
)
request_input(scene, [:cursor_button, :key])
{:ok, scene |> assign(graph: graph) |> push_graph(graph)}
end
@impl Scenic.Scene
def handle_input({:cursor_button, {_button, 1, _mods, {x, y}}}, _id, scene) do
scene.assigns.graph
|> circle(40, fill: :yellow, translate: {x, y})
|> Graph.modify(:status, &text(&1, "Touch at #{round(x)}, #{round(y)}"))
|> show(scene)
end
def handle_input({:key, {key, 1, _mods}}, _id, scene) do
scene.assigns.graph
|> Graph.modify(:status, &text(&1, "Button: #{key}"))
|> show(scene)
end
def handle_input(_input, _id, scene), do: {:noreply, scene}
defp show(graph, scene), do: {:noreply, scene |> assign(graph: graph) |> push_graph(graph)}
end
Show it
A viewport connects a scene to a display. Scenic.Driver.Local renders
to the phone's screen and reads the touchscreen and buttons. The viewport
name makes a second start fail while one is already running, so two
drivers never fight over the screen.
{:ok, viewport} =
Scenic.ViewPort.start(
name: :fp3_screen,
size: {1080, 2160},
default_scene: Demo.Scene,
drivers: [[module: Scenic.Driver.Local]]
)
Now touch the screen and press the buttons. Edit the scene and run both cells again to try your own graph (stop the viewport first, below).
Stop
Scenic.ViewPort.stop(viewport)
File.write!(Path.join(fbcon, "bind"), "1")