25
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElixirでGUI・Scenic [入門編1]

Last updated at Posted at 2020-07-16

1.はじめに

ElixirのGUIライブラリ「Scenic」を使って、簡単なウィンドウアプリを作ってみます。

scenicのバージョンが0.10系から0.11系になり、当記事がそのままではビルドできなくなりました。0.11系対応の記事はこちらの投稿を参照願います。

本記事では、Ver.0.10系でビルドする手順を示します。

(作例)
image.png

シリーズ

ステップ 概要
入門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」のウィンドウが表示されて、コンソールにはウィンドウ上で発生したイベントが表示されます。

image.png

(2)コードの読み解き

まずはコードを眺めてみます。

ウィンドウ

ウィンドウのソースは、lib/scenes/home.exになります。
image.png

ウィンドウに表示するコントロール(で表現良いのかな?)は、init関数のgraph=Graph.build(以下に記述します。
各コントロールをパイプ|>でつないで、複数のコントロールを配置するように指定しています。

home.ex
  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 ●
-----------------

コードを修正します。

home.ex
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

image.png

4.ボタンのイベントで文字と図形を書き換える

ウィンドウ上のボタンの操作で、文字や図形を書き換えてみます。

コードの追記①

init関数に、新たに4つのコントロールを追記します。

home.ex
    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:は、ボタンの色のテーマ指定です。
サンプル
image.png
指定できる内容はこちらになります。

コードの追記②

ボタンイベントを受け取る関数を追加します。

既に書かれている、def handle_input(event, _context, state) 関数のあとに、下記のソースコードを追記して下さい。

home.ex
  @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ボタンをクリックすると、文字列とランプに見立てた図形が点灯/消灯します。

ON・緑点灯
image.png

OFF・消灯
image.png

5.まとめ

今回は、取っ掛かりとして、簡単なウィンドウアプリを作ってみました。

次回は、複数プロジェクト間のイベントのやりとりを試してみます。

6.(おまけ)イベントの読み解き

イベントを受け取る関数

この関数は、最初から書かれています。
イベントの内容をそのまま文字で出力します。

home.ex
  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}
キーの同時押しの例

キーを押した順番にイベントが発生します。

例:[Ctrl-Shift-A]を押して離したときのイベントの流れ
 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}}

一番最後の数字は、同時押しの数っぽいですが、微妙に数が合わない・・・

7.参考資料

25
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?