組み込み(RXマイコン)向けGUI管理クラスの実装

主に、RX65N Envision kit 用にまともなGUI管理を用意しておこうと作業を進めている。

既に、Windows、OS-X、用マルチプラットホームのGUI管理があるのだが、こちらは、リッチな環境向けであり、かなり高機能となっている。
描画はOpenGLで行っている為、そのようなインフラが無いシステムでは、ルックアンドフィールなど多くの面で適合しない。
組み込みマイコン用では、グラフィックスの能力も低いし、解像度も低いので、高機能過ぎるのは逆に使いにくいし、リソースも食ってしまい実用的ではない。

既に、RX65N用には、emWin GUI ライブラリがあり、利用できるようだが、C言語ベースなのでとても使う気にならない。
※GUIのような複雑な機構をC言語ベースで実装する合理的な理由が見当たらない。

そこで、glfw_app/widgets で養ったエンジニアリングを土台に、機能を絞って、設計をやり直してみた。
既に、1度実装した経験があるので、アプリを作る場合の利便性や、どのような構成、構造にすれば良いかは十分理解しているつもりなので、組み込み環境を考えながら実装する。
C++ ベースの GUI では、各GUIに応答する機能の実装は、ラムダ式を使う事で、非常にシンプルに書けるのが判っているが、それ以外の方法でも GUI の応答をサービスする方法を提供する必要性がある。

組み込み系なので、new、delete などの記憶管理を使わない方針で進めた。
※経験的に、テンプレートデザインパターンを使うと何とでもなると判っている。
また、オペレーターやテンプレートなど C++ の機能を盛り込む事で、アプリケーション実装時の「判り易さ」を追及できる。
RX65N 専用では無いので、DRW2D のようなハードウェアーを使った描画機能が無い場合も考えて、ソフトウェアだけで描画する事も視野に入れて、そこそこ見栄え良く、軽く、簡単に扱えるように、実験などしながら進めている。

RX65Nで基本的な部分が動作したら、glfw_app で動作するエミュレーションを実装して、機能をチェック等を実機で行わなくても良いようにする予定でいる。
※実機確認では、作業効率が悪すぎると思われる。
GUI の部品としては、非常に多くあるが、簡単に実装できるものと、最低限必要な物を最初に実装して、後は、必要に応じて追加していこうと思う。

GUI の見た目は、経験的に、ビットマップで細かく作るより、円、直線などを組み合わせた幾何学的な図形をベースにする方が良いと思う。
これは、プログラムで細かく描画する事になるが、その方がリソースの節約にもなり、制御がしやすく、応用が利く。
※様々なサイズに対応出来る。
※たとえば、少なくとも3つの状態が必要となる場合。
・通常の状態
・活性した状態(ボタンが押された等)
・ストールした状態(表示されているが、無効になっている状態)
もしビットマップで作る場合、これらの状態を用意しておく必要があり、それだけでも実用的とは思えないし、ビットマップリソースをリンクするツールが必要となる。
自分が作りたい「ルック」が欲しいのなら、近いGUIのソースをコピーして、それを自分の要求する見た目に合わせて改造すれば良いだろうと思う。

  • フレーム
  • グループ
  • ボタン
  • チェックボックス
  • ラジオボタン
  • スライダー

全ての部品は、親子関係を持つ事が出来る。(構造的には、親の部品を知っている)
※親が全ての子供を知っている方が良い場合が多いのだが、構造が複雑になってしまう。
もし、全ての子供を知りたい場合、管理リストをスキャンして子供のリストを作るしか無い。

また、親子関係を利用する事で、相対的な部品位置で管理できたり、部品をグループ化して、全体を制御したり出来る。

「ラジオボタン」は、自分の変化により他の部品の状態を変える必要があるので、グループ化しておき、親を指定しておく。
その中のボタンで変化が起きた場合、親が同一のリストを作成して、他のボタンの状態を自動で変更する。

「スライダー」は、縦、横のサイズにより、水平か垂直か判断して対応する。
通常、部品からフォーカスが外れると、部品のハンドリングが終了してしまう、しかしその仕様だと、スライダーのような部品では、操作性が悪い(スライダーの領域は通常狭いのと、フォーカスしたままスライドするのが困難)なので、小細工として、スライダーの場合は、初期に領域内でタッチされた時にフォーカスを有効にして、タッチが外れるまでホールドする事で、操作性を改善している。

RX65N envision kit
// widget の定義
    gui::button     button_;
    gui::check      check_;
    gui::group      group_;
    gui::radio      radio1_;
    gui::radio      radio2_;
    gui::radio      radio3_;
    gui::slider     slider_;

// widget のコンストラクタ
setup() noexcept :
    button_(vtx::srect( 30, 20, 80, 30), "OK"),
    check_(vtx::srect(  30, 70 + 40*0, 0, 0), "Check"),
    group_(vtx::srect(  30, 70 + 40*1, 0, 0)),
    radio1_(vtx::srect( 30, 70 + 40*1, 0, 0), "Red"),
    radio2_(vtx::srect( 30, 70 + 40*2, 0, 0), "Green"),
    radio3_(vtx::srect( 30, 70 + 40*3, 0, 0), "Blue"),
    slider_(vtx::srect(150,20, 120, 0))
{
    // ラジオボタンのグループ化
    group_ + radio1_ + radio2_ + radio3_;
}

// 初期化
void init() noexcept override
{
    button_.enable();
    // ボタンが押された時の応答ラムダ式
    button_.at_select_func() = [this](uint32_t id) {
        change_scene(scene_id::root_menu);
    };
    check_.enable();
    // チェックボックスの応答ラムダ式
    check_.at_select_func() = [this](bool ena) {
        utils::format("Check: %d\n") % static_cast<int>(ena);
    };
    radio1_.enable();
    radio2_.enable();
    radio3_.enable();
    slider_.enable();
}

// シーン終了処理
void exit() noexcept override
{
    button_.enable(false);
    check_.enable(false);
    radio1_.enable(false);
    radio2_.enable(false);
    radio3_.enable(false);
    slider_.enable(false);
}

※GUI の構築と、それに対応する「応答」などを含めたアプリケーションプログラム。

GUI関連のソースコードは:

graphics/widget.hpp
graphics/widget_director.hpp
graphics/group.hpp
graphics/frame.hpp
graphics/button.hpp
graphics/check.hpp
graphics/radio.hpp
graphics/slider.hpp

graphics/color.hpp
graphics/graphics.hpp

となっており、他のヘッダーにも依存しているが、内部は、なるべく依存が低くなるようにしているつもり。(実際はレビューしないといけない)
また、widget の追加と削除を行う関数をスタティック扱いとして参照している。(この仕組みは、推奨されるべきものでは無いが、現状の設計ではそのようにしている、今後より良い方法に変更していくかもしれない・・