1.はじめに
ElixirのGUIライブラリ「Scenic」を使って、簡単なウィンドウアプリを作ってみます。
scenicのバージョンが0.10系から0.11系になり、当記事がそのままではビルドできなくなりました。0.11系対応の記事はこちらの投稿を参照願います。
本記事では、Ver.0.10系でビルドする手順を示します。
シリーズ
ステップ | 概要 |
---|---|
入門1 | プロジェクトの作り方、デザイン、ボタンイベント |
入門2 | 複数プロジェクト間のイベント通信 |
(続くかも・・・) |
実行環境
ハードウェア | VirtualBox for Windows |
OS | Ubuntu Linux 20.04 LTS |
Elixir | Ver.1.9.1 |
$ uname -a
Linux ubuntuvb 5.4.0-40-generic #44-Ubuntu SMP Tue Jun 23 00:01:04 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ elixir -v
Erlang/OTP 22 [erts-10.6.4] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1]
Elixir 1.9.1 (compiled with Erlang/OTP 22)
2.インストール
インストールから公式のチュートリアルまでを試した内容を、別記事にまとめてますので、まずはこちらにチャレンジしてみてください。
最低限必要なインストール
①OpenGLの開発ライブラリ
$ sudo apt install libglfw3 libglfw3-dev libglew-dev
②Scenic Archive
$ mix archive.install hex scenic_new
3.まずはHelloWorld
テキストと図形を、ウィンドウに表示するまでを試してみます。
(1)プロジェクトの作成から実行まで
ひな形のプロジェクトを作成して、とりあえず実行します。
$ cd (ご自身のワーキングディレクトリに移動)
#scenicプロジェクトを作成
$ mix scenic.new hmihello
* creating .formatter.exs
* creating .gitignore
(・・・省略・・・)
You can also run it interactively like this:
$ iex -S mix
#プロジェクトのディレクトリに移動
$ cd hmihello
#必要なライブラリを取得
$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
New:
elixir_make 0.6.0
(・・・省略・・・)
* Getting font_metrics (Hex package)
* Getting msgpax (Hex package)
#実行
$ mix scenic.run
(※`iex -S mix`でもOK)
実行画面
「hmihello」のウィンドウが表示されて、コンソールにはウィンドウ上で発生したイベントが表示されます。
(2)コードの読み解き
まずはコードを眺めてみます。
ウィンドウ
ウィンドウのソースは、lib/scenes/home.ex
になります。
ウィンドウに表示するコントロール(で表現良いのかな?)は、init関数のgraph=Graph.build(
以下に記述します。
各コントロールをパイプ|>
でつないで、複数のコントロールを配置するように指定しています。
def init(_, opts) do
# get the width and height of the viewport. This is to demonstrate creating
# a transparent full-screen rectangle to catch user input
{:ok, %ViewPort.Status{size: {width, height}}} = ViewPort.info(opts[:viewport])
# show the version of scenic and the glfw driver
scenic_ver = Application.spec(:scenic, :vsn) |> to_string()
glfw_ver = Application.spec(:scenic, :vsn) |> to_string()
graph =
Graph.build(font: :roboto, font_size: @text_size)
|> add_specs_to_graph([
text_spec("scenic: v" <> scenic_ver, translate: {20, 40}),
text_spec("glfw: v" <> glfw_ver, translate: {20, 40 + @text_size}),
text_spec(@note, translate: {20, 120}),
rect_spec({width, height})
])
{:ok, graph, push: graph}
end
キーワード | 機能 |
---|---|
text_spec | テキストを配置します |
rect_spec | 四角形を描画します。ここではウィンドウサイズ一杯に無色の四角形が表示されてます |
translate: {x, y} | 描画する位置を、ウィンドウ左上基準に指定。(t: で省略表記できます) |
font_size: | フォントの大きさ |
(3)コードの修正
下記のようなイメージで、Hello World
の文字、両端に丸図形、そして下線を表示します。
● Hello World ●
-----------------
コードを修正します。
defmodule Hmihello.Scene.Home do
use Scenic.Scene
require Logger
alias Scenic.Graph
alias Scenic.ViewPort
import Scenic.Primitives
#↓ここの先頭のコメントを外してScenic.Componentsをインポート
import Scenic.Components
(・・・途中省略・・・)
def init(_, opts) do
# get the width and height of the viewport. This is to demonstrate creating
# a transparent full-screen rectangle to catch user input
{:ok, %ViewPort.Status{size: {width, height}}} = ViewPort.info(opts[:viewport])
# show the version of scenic and the glfw driver
scenic_ver = Application.spec(:scenic, :vsn) |> to_string()
glfw_ver = Application.spec(:scenic, :vsn) |> to_string()
graph =
Graph.build(font: :roboto, font_size: @text_size)
|> add_specs_to_graph([
#コメントアウト
# text_spec("scenic: v" <> scenic_ver, translate: {20, 40}),
# text_spec("glfw: v" <> glfw_ver, translate: {20, 40 + @text_size}),
# text_spec(@note, translate: {20, 120}),
# rect_spec({width, height})
#ここから追記
text_spec("Hello World", t: {40, 40}, font_size: 30),
circle_spec(10, fill: :blue, stroke: {2, :white}, t: {20, 30}),
circle_spec(10, fill: :yellow, stroke: {2, :red}, t: {190, 30}),
line_spec({{10, 50}, {200, 50}}, stroke: {4, :cyan}, cap: :round)
])
{:ok, graph, push: graph}
end
キーワード | 機能 | |
---|---|---|
circle_spec | 丸図形を描画します | |
line_spec | 直線を描画します | |
色指定 | こちら |
指定可能なコントロールはこちらを参考にして下さい。
実行
$ mix scenic.run
4.ボタンのイベントで文字と図形を書き換える
ウィンドウ上のボタンの操作で、文字や図形を書き換えてみます。
コードの追記①
init関数に、新たに4つのコントロールを追記します。
graph =
Graph.build(font: :roboto, font_size: @text_size)
|> add_specs_to_graph([
# text_spec("scenic: v" <> scenic_ver, translate: {20, 40}),
# text_spec("glfw: v" <> glfw_ver, translate: {20, 40 + @text_size}),
# text_spec(@note, translate: {20, 120}),
# rect_spec({width, height})
text_spec("Hello World", t: {40, 40}, font_size: 30),
circle_spec(10, fill: :blue, stroke: {2, :white}, t: {20, 30}),
circle_spec(10, fill: :yellow, stroke: {2, :red}, t: {190, 30}),
line_spec({{10, 50}, {200, 50}}, stroke: {4, :cyan}, cap: :round),
#ここから追記
text_spec("---", t: {80, 80}, font_size: 30, id: :event_text),
circle_spec(10, fill: :grey, stroke: {2, :white}, t: {60, 70}, id: :event_circle),
button_spec("ON", id: :btn_on, t: {40, 90}, theme: :success),
button_spec("OFF", id: :btn_off, t: {120, 90}, theme: :danger)
])
追記したコントロールには、全てid:
を付加しています。
書き換えするときのターゲット指定に、idの値が使われます。
theme:
は、ボタンの色のテーマ指定です。
サンプル
指定できる内容はこちらになります。
コードの追記②
ボタンイベントを受け取る関数を追加します。
既に書かれている、def handle_input(event, _context, state)
関数のあとに、下記のソースコードを追記して下さい。
@doc """
クリックイベントの受付
"""
def filter_event(event, _, graph) do
graph =
case event do
# ONボタン
{:click, :btn_on} ->
# 色を変更
Graph.modify(graph, :event_circle, &update_opts(&1, fill: :lawn_green))
# 文字を書き換え
|> Graph.modify(:event_text, &text(&1, "ON"))
# OFFボタン
{:click, :btn_off} ->
Graph.modify(graph, :event_circle, &update_opts(&1, fill: :grey))
|> Graph.modify(:event_text, &text(&1, "OFF"))
# それ以外
{_, _} ->
# 書き換え無し
graph
end
# イベント内容を表示
Logger.info("Received event: #{inspect(event)}")
{:cont, event, graph, push: graph}
end
書き換えはGraph.modify
関数を使います。
同時に複数の箇所を書き換える場合は、パイプでつないでまとめます。
実行
$ mix scenic.run
ONボタン、OFFボタンをクリックすると、文字列とランプに見立てた図形が点灯/消灯します。
5.まとめ
今回は、取っ掛かりとして、簡単なウィンドウアプリを作ってみました。
次回は、複数プロジェクト間のイベントのやりとりを試してみます。
6.(おまけ)イベントの読み解き
イベントを受け取る関数
この関数は、最初から書かれています。
イベントの内容をそのまま文字で出力します。
def handle_input(event, _context, state) do
Logger.info("Received event: #{inspect(event)}")
{:noreply, state}
end
ウィンドウのイベント例
ウィンドウの上を操作して、発生したイベントの一部を列挙します。
イベントの対象 | イベント名 | 発生条件 | 値 | 例 |
---|---|---|---|---|
マウス | :viewport_enter | ウィンドウの範囲内に入った | ウィンドウに対する{x, y} | {130.0, 464.0} |
マウス | :viewport_exit | ウィンドウの範囲外に出た | ウィンドウに対する{x,y} | {801.0, 43.0} |
マウス | :cursor_pos | ウィンドウの上を移動した | ウィンドウに対する{x,y} | {675.0, 24.0} |
マウス | :cursor_button | ボタンを押した | {ボタン, :press, 0, {x, y}} | {:left, :press, 0, {x, y}} |
マウス | :cursor_button | ボタンを離した | {ボタン, :release, 0, {x, y}} | {:right, :release, 0, {x, y}} |
キーボード | :key | キーを押した | {:key, {文字, :press, 0} | {:key, {"A", :release, 0} |
キーボード | :key | キーを離した | {:key, {文字, :release, 0} | {:key, {"A", :release, 0} |
キーの同時押しの例
キーを押した順番にイベントが発生します。
Received event: {:key, {"left_control", :press, 0}}
Received event: {:key, {"left_shift", :press, 2}}
Received event: {:key, {"A", :press, 3}}
Received event: {:key, {"A", :release, 3}}
Received event: {:key, {"left_shift", :release, 3}}
Received event: {:key, {"left_control", :release, 2}}
一番最後の数字は、同時押しの数っぽいですが、微妙に数が合わない・・・