Zenn
⌨️

【Wio Terminal/LvGL】Virtual keyboard

2025/02/24に公開

WioWio TerminalTerminalで仮想キーボード

simple keyboard
引用:https://docs.lvgl.io/7.11/widgets/keyboard.html#keyboard-with-text-area

画像のようなキーボードを作ることができる。画面上部は、キーボードの入力内容が表示されるテキストエリアである。

プログラム

simple_keyboard.ino
#include <lvgl.h>
#include <TFT_eSPI.h>

/* tick period */
constexpr uint16_t      TICK_PERIOD = 5;

/* TFT LCD */
static TFT_eSPI         TFT;
/* display buffer */
static lv_disp_buf_t    DISP_BUF;
/* color buffer */
static lv_color_t       COLOR_BUF[LV_HOR_RES_MAX * 10];

/* input device */
static lv_indev_t*      INPUT_DEVICE = nullptr;
/* keyboard group */
static lv_group_t*      KEYBOARD_GROUP = nullptr;
/* keyboard */
static lv_obj_t*        KEYBOARD = nullptr;
/* textarea */
static lv_obj_t*        TEXTAREA = nullptr;

/* 画面初期化 */
void display_flush(
    lv_disp_drv_t*      disp,
    const lv_area_t*    area,
    lv_color_t*         color_p
);

/* Reading input device (嘘) */
bool read_encoder(
    lv_indev_drv_t*     indev,
    lv_indev_data_t*    data
);

/* system tick */
static void tick_handler();

/* 設定 */
void backend_setup();

/* keyboard event callback */
static void keyboard_event_cb(
    lv_obj_t*           keyboard,
    lv_event_t          e
);

/* textarea event callback */
static void textarea_event_cb(
    lv_obj_t*           ta_local,
    lv_event_t          e
);

/* set keyboard */
static void set_keyboard();

/* set textarea */
static void set_textarea();

/* 画面要素 */
void set_contents();

/* 5way switch */
void wio_joy_handler();

void set_keyboard() {
    KEYBOARD = lv_keyboard_create(lv_scr_act(), NULL);
    lv_keyboard_set_cursor_manage(KEYBOARD, true);
    lv_obj_set_event_cb(KEYBOARD, keyboard_event_cb);
    lv_keyboard_set_textarea(KEYBOARD, TEXTAREA);
    lv_group_add_obj(KEYBOARD_GROUP, KEYBOARD);
}

void set_textarea() {
    /*Create a text area. The keyboard will write here*/
    TEXTAREA  = lv_textarea_create(lv_scr_act(), NULL);
    lv_obj_align(TEXTAREA, NULL, LV_ALIGN_IN_TOP_MID, 0, LV_DPI / 16);
    lv_obj_set_event_cb(TEXTAREA, textarea_event_cb);
    lv_textarea_set_text(TEXTAREA, "");
    lv_coord_t max_h = LV_VER_RES / 2 - LV_DPI / 8;
    if (lv_obj_get_height(TEXTAREA) > max_h)
        lv_obj_set_height(TEXTAREA, max_h);
}

void set_contents() {
    KEYBOARD_GROUP = lv_group_create();
    lv_indev_set_group(INPUT_DEVICE, KEYBOARD_GROUP);

    set_textarea();

    set_keyboard();
}

void wio_joy_handler() {
    /*
    ↑: LV_KEY_UP
    ↓: LV_KEY_DOWN
    ←: LV_KEY_LEFT
    →: LV_KEY_RIGHT

    PRESS:
    keyboard有効時: LV_KEY_ENTER
    keyboard無効時: LV_EVENT_CLICKED
    */
    if (digitalRead(WIO_5S_UP) == LOW) {
        /* groupに入力LV_KEY_UPを送る */
        if (KEYBOARD != nullptr) {
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_UP);
        }
    }
    else if (digitalRead(WIO_5S_DOWN) == LOW) {
        /* groupに入力LV_KEY_DOWNを送る */
        if (KEYBOARD != nullptr) {
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_DOWN);
        }
    }
    else if (digitalRead(WIO_5S_LEFT) == LOW) {
        /* groupに入力LV_KEY_LEFTを送る */
        if (KEYBOARD != nullptr) {
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_LEFT);
        }
    }
    else if (digitalRead(WIO_5S_RIGHT) == LOW) {
        /* groupに入力LV_KEY_RIGHTを送る */
        if (KEYBOARD != nullptr) {
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_RIGHT);
        }
    }
    else if (digitalRead(WIO_5S_PRESS) == LOW) {
        if (KEYBOARD != nullptr) {
            /* groupに入力LV_KEY_ENTERを送る */
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_ENTER);
            lv_event_send(KEYBOARD, LV_EVENT_VALUE_CHANGED, NULL);
        }
        else {
            /* LV_EVENT_CLICKEDを起こす */
            lv_event_send(TEXTAREA, LV_EVENT_CLICKED, NULL);
        }
    }
    else {
        return;
    }
    delay(200);
}

void setup() {
    /* 設定 */
    backend_setup();

    /* 画面構成 */
    set_contents();
}

void loop() {
    tick_handler();
    lv_task_handler();
    wio_joy_handler();
    delay(TICK_PERIOD);
}

simple_keyboard_settings.ino
/*
画面初期化
画面いっぱい色で塗り潰している
*/
void display_flush(
    lv_disp_drv_t*      disp,
    const lv_area_t*    area,
    lv_color_t*         color_p
) {
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

    TFT.startWrite();
    TFT.setAddrWindow(area->x1, area->y1, w, h);
    TFT.pushColors(&color_p->full, w * h, true);
    TFT.endWrite();

    /* callbackではこれを召喚しなければならない */
    lv_disp_flush_ready(disp);
}

/* Reading input device (嘘) */
bool read_encoder(
    lv_indev_drv_t*     indev,
    lv_indev_data_t*    data
) {
    /* no input */
    return false;
}

/* system tick */
void tick_handler() {
    lv_tick_inc(TICK_PERIOD);
}

/* keyboard event callback */
void keyboard_event_cb(lv_obj_t* keyboard, lv_event_t e) {
    /* default event callback */
    lv_keyboard_def_event_cb(KEYBOARD, e);
    switch (e) {
        case LV_EVENT_CANCEL:
            /* textarea連携解除 */
            lv_keyboard_set_textarea(KEYBOARD, NULL);
            /* グループ退去 */
            lv_group_remove_obj(KEYBOARD);
            /* 削除 */
            lv_obj_del(KEYBOARD);
            KEYBOARD = nullptr;
            /* clear all text */
            lv_textarea_set_text(TEXTAREA, "");
            break;

        case LV_EVENT_APPLY:
            /* textarea連携解除 */
            lv_keyboard_set_textarea(KEYBOARD, NULL);
            /* グループ退去 */
            lv_group_remove_obj(KEYBOARD);
            /* 削除 */
            lv_obj_del(KEYBOARD);
            KEYBOARD = nullptr;
            break;

        default:
            break;
    }
}

/* textarea event callback */
void textarea_event_cb(lv_obj_t* ta_local, lv_event_t e) {
    if (e == LV_EVENT_CLICKED && KEYBOARD == nullptr) {
        set_keyboard();
    }
}

/* 設定 */
void backend_setup() {
    /* lvglの初期化 */
    lv_init();

    /* TFTの初期設定 */
    TFT.begin();
    TFT.setRotation(3);

    /* 画面表示用bufferそのものの初期化 */
    lv_disp_buf_init(&DISP_BUF, COLOR_BUF, NULL, LV_HOR_RES_MAX * 10);

    /* 画面制御設定 */
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res    = LV_HOR_RES_MAX;
    disp_drv.ver_res    = LV_VER_RES_MAX;
    disp_drv.flush_cb   = display_flush;
    disp_drv.buffer     = &DISP_BUF;
    lv_disp_t* pDisplay = lv_disp_drv_register(&disp_drv);

    /* 入力の設定 */
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type      = LV_INDEV_TYPE_ENCODER;
    indev_drv.read_cb   = read_encoder;
    INPUT_DEVICE = lv_indev_drv_register(&indev_drv);

    /* enable 5way switch */
    pinMode(WIO_5S_UP, INPUT);
    pinMode(WIO_5S_DOWN, INPUT);
    pinMode(WIO_5S_LEFT, INPUT);
    pinMode(WIO_5S_RIGHT, INPUT);
    pinMode(WIO_5S_PRESS, INPUT);
}

概説

簡単に説明を付す。

テキストエリア

static lv_obj_t*        TEXTAREA = nullptr;

/* テキストエリアを作る */
void set_textarea() {
    TEXTAREA  = lv_textarea_create(lv_scr_act(), NULL);
    /* 配置 */
    lv_obj_align(TEXTAREA, NULL, LV_ALIGN_IN_TOP_MID, 0, LV_DPI / 16);
    /* callbackを設定する */
    lv_obj_set_event_cb(TEXTAREA, textarea_event_cb);
    /* テキストを初期化する */
    lv_textarea_set_text(TEXTAREA, "");
    /* 高さ */
    lv_coord_t max_h = LV_VER_RES / 2 - LV_DPI / 8;
    if (lv_obj_get_height(TEXTAREA) > max_h)
        lv_obj_set_height(TEXTAREA, max_h);
}

void textarea_event_cb(lv_obj_t* ta_local, lv_event_t e) {
    /* キーボードが消えている場合 */
    if (e == LV_EVENT_CLICKED && KEYBOARD == nullptr) {
        /* キーボードを作る */
        set_keyboard();
    }
}

テキストエリアは作るだけで良い。

キーボード

static lv_obj_t*        KEYBOARD = nullptr;
static lv_group_t*      KEYBOARD_GROUP = nullptr;

/* キーボードを作る */
void set_keyboard() {
    KEYBOARD = lv_keyboard_create(lv_scr_act(), NULL);
    /* カーソルを有効にする */
    lv_keyboard_set_cursor_manage(KEYBOARD, true);
    /* callbackを設定する */
    lv_obj_set_event_cb(KEYBOARD, keyboard_event_cb);
    /* テキストエリアを連携する */
    lv_keyboard_set_textarea(KEYBOARD, TEXTAREA);
    /* グループに追加する */
    lv_group_add_obj(KEYBOARD_GROUP, KEYBOARD);
}

/* キーボードのボタンが押された時の処理 */
void keyboard_event_cb(lv_obj_t* keyboard, lv_event_t e) {
    /* 通常のボタンに対する処理は既定のものを使う */
    lv_keyboard_def_event_cb(KEYBOARD, e);
    /* それ以外のボタン */
    switch (e) {
        /* ✕ボタンを押した時 */
        case LV_EVENT_CANCEL:
            /* textarea連携解除 */
            lv_keyboard_set_textarea(KEYBOARD, NULL);
            /* グループ退去 */
            lv_group_remove_obj(KEYBOARD);
            /* 削除 */
            lv_obj_del(KEYBOARD);
            KEYBOARD = nullptr;
            /* clear all text */
            lv_textarea_set_text(TEXTAREA, "");
            break;

        /* ✓ボタンを押した時 */
        case LV_EVENT_APPLY:
            /* textarea連携解除 */
            lv_keyboard_set_textarea(KEYBOARD, NULL);
            /* グループ退去 */
            lv_group_remove_obj(KEYBOARD);
            /* 削除 */
            lv_obj_del(KEYBOARD);
            KEYBOARD = nullptr;
            break;

        default:
            break;
    }
}

キーボードを作る際、テキストエリアを連携する。

lv_keyboard_set_textarea(KEYBOARD, TEXTAREA);

キーボードに対して何らかの方法で入力するため、グループに追加しておく必要がある。

lv_group_add_obj(KEYBOARD_GROUP, KEYBOARD);

キー入力やテキストエリアへの出力は、lv_keyboard_def_event_cb()で既に定義されている。

lv_keyboard_def_event_cb(KEYBOARD, e);

これを書いておけば、キー入力した際の動作は全て実装される。更に、連携したテキストエリアが有る場合、入力した文字がテキストエリアに出力される。

✕ボタンと✓ボタンに関する処理のみ、独自に定義している。どちらもキーボードが消える。✕ボタンを押した場合、テキストエリアの内容も消える。

スイッチの設定

void backend_setup() {/* enable 5way switch */
    pinMode(WIO_5S_UP, INPUT);
    pinMode(WIO_5S_DOWN, INPUT);
    pinMode(WIO_5S_LEFT, INPUT);
    pinMode(WIO_5S_RIGHT, INPUT);
    pinMode(WIO_5S_PRESS, INPUT);
}

void wio_joy_handler() {
    /*
    ↑: LV_KEY_UP
    ↓: LV_KEY_DOWN
    ←: LV_KEY_LEFT
    →: LV_KEY_RIGHT

    PRESS:
    keyboard有効時: LV_KEY_ENTER
    keyboard無効時: LV_EVENT_CLICKED
    */
    if (digitalRead(WIO_5S_UP) == LOW) {
        /* groupに入力LV_KEY_UPを送る */
        if (KEYBOARD != nullptr) {
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_UP);
        }
    }
    else if (digitalRead(WIO_5S_DOWN) == LOW) {
        /* groupに入力LV_KEY_DOWNを送る */
        if (KEYBOARD != nullptr) {
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_DOWN);
        }
    }
    else if (digitalRead(WIO_5S_LEFT) == LOW) {
        /* groupに入力LV_KEY_LEFTを送る */
        if (KEYBOARD != nullptr) {
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_LEFT);
        }
    }
    else if (digitalRead(WIO_5S_RIGHT) == LOW) {
        /* groupに入力LV_KEY_RIGHTを送る */
        if (KEYBOARD != nullptr) {
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_RIGHT);
        }
    }
    else if (digitalRead(WIO_5S_PRESS) == LOW) {
        if (KEYBOARD != nullptr) {
            /* groupに入力LV_KEY_ENTERを送る */
            lv_group_send_data(KEYBOARD_GROUP, LV_KEY_ENTER);
            lv_event_send(KEYBOARD, LV_EVENT_VALUE_CHANGED, NULL);
        }
        else {
            /* LV_EVENT_CLICKEDを起こす */
            lv_event_send(TEXTAREA, LV_EVENT_CLICKED, NULL);
        }
    }
    else {
        return;
    }
    delay(200);
}

WioWio TerminalTerminalに付いているスイッチについて定義する。四方への傾倒はそのままキーボード上のカーソル移動に、押下は決定に対応付ける。

なおキーボードが無い時は、押下することでテキストエリアにLV_EVENT_CLICKEDを送る。テキストエリアのcallbackcallbackに記述した通り、キーボードが表示される。

void textarea_event_cb(lv_obj_t* ta_local, lv_event_t e) {
    /* キーボードが消えている場合 */
    if (e == LV_EVENT_CLICKED && KEYBOARD == nullptr) {
        /* キーボードを作る */
        set_keyboard();
    }
}

Discussion

ログインするとコメントできます