前回 M5Core2でLVGLを動かすことができました。
いよいよ本命のM5PaperでLVGLを使ってみたいと思います。
前回のサンプルコードをM5Paperに合わせてちょっとだけ変更して動かしてみます。(M5Paper: LVGL Sample (with LGFX))
フォントサイズもそのままなので小さいですが、描画自体はできました。
ただ、描画切り替え時のちらつきが目立ちます。気になりますがとりあえず先に進みます。
ボタンとタッチイベント
タッチイベントの取得とWidgetsのイベントハンドリングも実験してみます。
タッチイベントを取得するには、コールバック関数の実装と登録が必要です。
// コールバック void my_touchpad_read(lv_indev_drv_t*, lv_indev_data_t* data) { M5.TP.update(); if (!M5.TP.isFingerUp()) { const tp_finger_t finger = M5.TP.readFinger(0); data->state = LV_INDEV_STATE_PR; data->point.x = finger.x; data->point.y = finger.y; } else { data->state = LV_INDEV_STATE_REL; } } void setup() { : // 登録 static lv_indev_drv_t indev_drv; ::lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touchpad_read; ::lv_indev_drv_register(&indev_drv); : }
後は、オブジェクト(Widget)を配置すればLVGLのハンドラがイベントをハンドリングしてくれます。
LVGL公式ドキュメントのQuick overview
で紹介されていたボタンのサンプルコードを流用してボタンを配置してみます。(ボタンのサイズと位置だけ変更しました)
M5PaperでLVGL with LGFX やってみたけれども、タッチのフィードバックで書き換えが入るから良くないのかなぁ?lv_conf.hとか見直してみたら変わるかな? pic.twitter.com/bIxdKiY58y
— イナバ (@hollyhockberry) 2021年10月7日
タッチはうまいこと反応してますが、やたらとチカチカする上に、たまにボタンが消えちゃってます。
やたらとチカチカするのは、イベント時のボタンがアニメーションしてるので書き換えが頻繁に入っているからでしょう。消えちゃうのは描画の指定が悪いのでしょうか・・?
といろいろ考えてると、またもフォローをいただきました。
恐らくですが、この辺りの問題だと思います…。https://t.co/ie2CdjkzfC
— らびやん (@lovyan03) 2021年10月7日
EPDの描画更新中にも処理は先に進むことができてしまうため、更新途中のエリアを書き換えると正しく表示されなくなります。
このサンプルコードのコメントを上から順に読み進めて頂ければ幸いです…。
なるほど・・・書き換え中の上書きが発生していそうですね。
ちゃんと調べてなくて恐縮ですが、LVGLの一連のレンダリングの最初と最後に何かイベントかコールバックが用意されてないか気になってます。
— らびやん (@lovyan03) 2021年10月7日
最初にstartWriteとwaitDisplayを呼び、最後にendWriteを呼べれば、部品単位のバラバラなリフレッシュを抑えて時間短縮と品質向上ができるのではないかなと…。
- LVGLのレンダリングについて
- オブジェクト(さしあたってはButton)のデザイン変更方法
LVGLのドキュメントを読んで、この辺りがわかれば良くなりそうですかね。
バッファ上書き抑止
ドキュメントを読み進めると、Porting
の章でバッファフラッシュのコールバックについての記述がありました。
LVGL might render the screen in multiple chunks and therefore call flush_cb multiple times. To see if the current one is the last chunk of rendering use lv_disp_flush_is_last(&disp_drv). Display interface — LVGL documentation
レンダリングの最後はlv_disp_flush_is_last()
で判定できるようです。レンダリングの開始を知る方法が見当たらなかったので、適宜判定が必要ですね。(startWrite()
とendWrite()
は対になっている必要があります)
void my_disp_flush(...) { if (gfx.getStartCount() <= 0) { gfx.startWrite(); } 〜バッファの書き込み処理〜 if (lv_disp_flush_is_last(disp)) { gfx.endWrite(); } ::lv_disp_flush_ready(disp); }
もしくは、SPI占有しちゃって問題なければsetup()時にstartWrite()しちゃって、レンダリングの最後にendWrite()じゃなくてdisplay()でも良いかもしれません。
ドキュメント斜め読みで、バッファフラッシュのCallbackでレンダリング最後の時だけLGFX::display()をコールしたら描画が抜けることがなくなった。更新中の書き換えを抑止してるわけじゃないので正解じゃない気がするけど、この辺りを調べて良い結果を報告できるといいな‥ pic.twitter.com/bnTLCCCvuq
— イナバ (@hollyhockberry) 2021年10月7日
ボタン押下時アニメーション
オブジェクトの外観は、lv_style_t
という構造体を作成してスタイルを設定できるようです。
ドキュメントによると、スタイルのコンセプトはCSSに大きく影響を受けているそうです。
lv_style_t
型のオブジェクトがクラスに相当し、単一もしくは複数のスタイルをオブジェクトに割り当てることができます。
オブジェクトは 通常(LV_STATE_DEFAULT
)、チェック(LV_STATE_CHECKED
)などの状態を持っており、それぞれ毎にスタイルを割り当てることができます。
スタイルのプロパティは色々用意されていて、トランジションも駆使すれば凝ったデザインのオブジェクトができそうです。
今回は極力簡素な振る舞いに変更したいので、
- 通常時(
LV_STATE_DEFAULT
)は黒ボーダーの白背景 - 押下時(
LV_STATE_PRESSED
)は黒背景
というスタイルを設定します。
通常時のスタイル設定
// スタイルのオブジェクト(自動変数にしたらダメです) lv_style_t style_btn; ::lv_style_init(&style_btn); // 角の丸め ::lv_style_set_radius(&style_btn, 10); // 背景の透明度(LV_OPA_COVER: 不透明) ::lv_style_set_bg_opa(&style_btn, LV_OPA_COVER); // 背景色 ::lv_style_set_bg_color(&style_btn, ::lv_color_white()); // ボーダーの色 ::lv_style_set_border_color(&style_btn, ::lv_color_black()); // ボーダーの透明度 ::lv_style_set_border_opa(&style_btn, LV_OPA_COVER); // ボーダーの太さ ::lv_style_set_border_width(&style_btn, 2);
押下時のスタイル設定
lv_style_t style_btn_pressed; ::lv_style_init(&style_btn_pressed); ::lv_style_set_bg_color(&style_btn_pressed, ::lv_color_black());
ラベルのスタイル設定
ボタンの子要素にラベルがいますが、標準のフォントサイズだと小さいので、大きめのフォントを設定します。
組み込みのフォントは lv_conf.h
で選択できます。
標準では14ptのフォントしか有効になっていないので、42ptのフォントを有効にしましょう。
lv_conf.h
の260行付近にある使用フォントの定義を0から1に変更します。
(略) #define LV_FONT_MONTSERRAT_12 0 #define LV_FONT_MONTSERRAT_14 1 #define LV_FONT_MONTSERRAT_16 0 #define LV_FONT_MONTSERRAT_18 0 (略) #define LV_FONT_MONTSERRAT_40 0 #define LV_FONT_MONTSERRAT_42 1 #define LV_FONT_MONTSERRAT_44 0 (略)
これでlv_font_montserrat_42
が実体化されるので、スタイルに設定可能となります。
lv_style_t style_label; ::lv_style_init(&style_label); ::lv_style_set_text_font(&style_label, &lv_font_montserrat_42);
スタイルの割り当て
ボタンに通常時と押下時のスタイルを割り当てます。
lv_obj_t * btn = ::lv_btn_create(::lv_scr_act()); // 全てのスタイルを削除 ::lv_obj_remove_style_all(btn); // 通常のスタイル ::lv_obj_add_style(btn, &style_btn, LV_STATE_DEFAULT); // 押下時のスタイル ::lv_obj_add_style(btn, &style_btn_pressed, LV_STATE_PRESSED);
ボタン内部のラベルにもスタイルを割り当てます。
lv_obj_t * label = ::lv_label_create(btn); ::lv_obj_add_style(label, &style_label, LV_STATE_DEFAULT);
ラベルはフォントの指定を上書きするだけなので、元のスタイルは削除せずにそのままです。
完成
実際に動作させた様子を動画でご確認ください。
ボタンのトランジションを無しにしたので書き換えは1度で完了しています。
また、押下時の背景を黒一色にしたので、書き換え時の反転が目立たなくなっています。狙い通りの結果が出て良かったです。
遅くなりましたがLVGLで何となく良さそうになりましたのでご報告します
— イナバ (@hollyhockberry) 2021年10月10日
主な対策はアドバイス通りレンダリング単位でのstartWrite/endWriteの実施と、ボタン押下時のスタイルを簡素にして描画時の反転を目立たなくする2点です。
フォローありがとうございました! pic.twitter.com/c3oEL8G1oD
まとめ
カレンダー表示を横着したいという欲求でLVGLを触り始めましたが、オブジェクトのスタイル変更までできるようになりました。
カレンダーのオブジェクトが希望通りの仕様かはこれから調べないとわかりませんが、それ以外のGUIはLVGLで構築するのも良さそうですね。