From 8057f943f219cd80bbf04080773ed79eaaf35922 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 11 Nov 2023 16:39:10 -0600 Subject: [PATCH 1/7] feat(input): reworked input stack * Have touchscreen and gamepad input run consistently, within its own task - so that the getter functions merely return the latest data for more consistent performance and to enable more reuse throughout the code * Update espp submodule to have latest code including keypad_driver * Update gui and menu to support the new keypad driver (including the kludge around how we have to deal with the dropdowns) * Update cart to allow start+select to show the menu * Update main to remove old code for allowing gamepad input only on rom menu since its now supported everywhere --- components/box-emu-hal/CMakeLists.txt | 2 +- components/box-emu-hal/include/input.h | 3 + components/box-emu-hal/src/input.cpp | 184 +++++++++++++++++-------- components/espp | 2 +- components/gui/include/gui.hpp | 47 +++---- components/gui/src/gui.cpp | 162 ++++++++++++++++++++-- components/menu/include/menu.hpp | 11 +- components/menu/src/menu.cpp | 86 ++++++++++++ main/cart.hpp | 13 +- main/main.cpp | 21 +-- 10 files changed, 411 insertions(+), 120 deletions(-) diff --git a/components/box-emu-hal/CMakeLists.txt b/components/box-emu-hal/CMakeLists.txt index 407fa9b0..b1c1e239 100644 --- a/components/box-emu-hal/CMakeLists.txt +++ b/components/box-emu-hal/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( INCLUDE_DIRS "include" SRC_DIRS "src" - REQUIRES "driver" "heap" "fatfs" "esp_lcd" "esp_psram" "spi_flash" "nvs_flash" "codec" "display" "display_drivers" "mcp23x17" "input_drivers" "tt21100" "gt911" "drv2605" "event_manager" "i2c" + REQUIRES "driver" "heap" "fatfs" "esp_lcd" "esp_psram" "spi_flash" "nvs_flash" "codec" "display" "display_drivers" "mcp23x17" "input_drivers" "tt21100" "gt911" "drv2605" "event_manager" "i2c" "task" "timer" ) diff --git a/components/box-emu-hal/include/input.h b/components/box-emu-hal/include/input.h index 424e75dc..bc307a4d 100644 --- a/components/box-emu-hal/include/input.h +++ b/components/box-emu-hal/include/input.h @@ -1,5 +1,7 @@ #pragma once +#include "lvgl.h" + #ifdef __cplusplus extern "C" { @@ -21,6 +23,7 @@ extern "C" void init_input(); void get_input_state(struct InputState *state); + lv_indev_t *get_keypad_input_device(); void touchpad_read(unsigned char *num_touch_points, unsigned short* x, unsigned short* y, unsigned char* btn_state); #ifdef __cplusplus diff --git a/components/box-emu-hal/src/input.cpp b/components/box-emu-hal/src/input.cpp index 9561e0f2..252135f9 100644 --- a/components/box-emu-hal/src/input.cpp +++ b/components/box-emu-hal/src/input.cpp @@ -1,38 +1,133 @@ +#include + #include "input.h" #include "hal_i2c.hpp" -#include "task.hpp" - #include "mcp23x17.hpp" - +#include "timer.hpp" #include "touchpad_input.hpp" +#include "keypad_input.hpp" using namespace std::chrono_literals; using namespace box_hal; +struct TouchpadData { + uint8_t num_touch_points = 0; + uint16_t x = 0; + uint16_t y = 0; + uint8_t btn_state = 0; +}; + static std::shared_ptr mcp23x17; static std::shared_ptr touch_driver; static std::shared_ptr touchpad; +static std::shared_ptr keypad; +static std::shared_ptr gamepad_timer; +static struct InputState gamepad_state; +static std::mutex gamepad_state_mutex; +static TouchpadData touchpad_data; +static std::mutex touchpad_data_mutex; /** * Touch Controller configuration */ void touchpad_read(uint8_t* num_touch_points, uint16_t* x, uint16_t* y, uint8_t* btn_state) { - *num_touch_points = 0; + std::lock_guard lock(touchpad_data_mutex); + *num_touch_points = touchpad_data.num_touch_points; + *x = touchpad_data.x; + *y = touchpad_data.y; + *btn_state = touchpad_data.btn_state; +} + +void keypad_read(bool *up, bool *down, bool *left, bool *right, bool *enter, bool *escape) { + InputState state; + get_input_state(&state); + *up = state.up; + *down = state.down; + *left = state.left; + *right = state.right; + + *enter = state.a; + *escape = state.b; +} + +void update_touchpad_input() { // get the latest data from the device std::error_code ec; bool new_data = touch_driver->update(ec); if (ec) { fmt::print("error updating touch_driver: {}\n", ec.message()); + std::lock_guard lock(touchpad_data_mutex); + touchpad_data = {}; return; } if (!new_data) { + std::lock_guard lock(touchpad_data_mutex); + touchpad_data = {}; return; } - // now hand it off - touch_driver->get_touch_point(num_touch_points, x, y); - *btn_state = touch_driver->get_home_button_state(); + // get the latest data from the touchpad + TouchpadData temp_data; + touch_driver->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); + temp_data.btn_state = touch_driver->get_home_button_state(); + // update the touchpad data + std::lock_guard lock(touchpad_data_mutex); + touchpad_data = temp_data; +} + +void update_gamepad_input() { + bool is_a_pressed = false; + bool is_b_pressed = false; + bool is_x_pressed = false; + bool is_y_pressed = false; + bool is_select_pressed = false; + bool is_start_pressed = false; + bool is_up_pressed = false; + bool is_down_pressed = false; + bool is_left_pressed = false; + bool is_right_pressed = false; + if (!mcp23x17) { + fmt::print("cannot get input state: mcp23x17 not initialized properly!\n"); + return; + } + // pins are active low + // start, select = A0, A1 + std::error_code ec; + auto a_pins = mcp23x17->get_pins(espp::Mcp23x17::Port::A, ec); + if (ec) { + fmt::print("error getting pins from mcp23x17: {}\n", ec.message()); + return; + } + // d-pad, abxy = B0-B3, B4-B7 + auto b_pins = mcp23x17->get_pins(espp::Mcp23x17::Port::B, ec); + if (ec) { + fmt::print("error getting pins from mcp23x17: {}\n", ec.message()); + return; + } + is_a_pressed = !(b_pins & 1<<4); + is_b_pressed = !(b_pins & 1<<5); + is_x_pressed = !(b_pins & 1<<6); + is_y_pressed = !(b_pins & 1<<7); + is_start_pressed = !(a_pins & 1<<0); + is_select_pressed = !(a_pins & 1<<1); + is_up_pressed = !(b_pins & 1<<0); + is_down_pressed = !(b_pins & 1<<1); + is_left_pressed = !(b_pins & 1<<2); + is_right_pressed = !(b_pins & 1<<3); + { + std::lock_guard lock(gamepad_state_mutex); + gamepad_state.a = is_a_pressed; + gamepad_state.b = is_b_pressed; + gamepad_state.x = is_x_pressed; + gamepad_state.y = is_y_pressed; + gamepad_state.start = is_start_pressed; + gamepad_state.select = is_select_pressed; + gamepad_state.up = is_up_pressed; + gamepad_state.down = is_down_pressed; + gamepad_state.left = is_left_pressed; + gamepad_state.right = is_right_pressed; + } } static std::atomic initialized = false; @@ -78,56 +173,35 @@ void init_input() { .log_level = espp::Logger::Verbosity::WARN }); + fmt::print("Initializing keypad\n"); + keypad = std::make_shared(espp::KeypadInput::Config{ + .read = keypad_read, + .log_level = espp::Logger::Verbosity::WARN + }); + + fmt::print("Initializing input task\n"); + gamepad_timer = std::make_shared(espp::Timer::Config{ + .name = "Input timer", + .period = 20ms, + .callback = []() { + update_touchpad_input(); + update_gamepad_input(); + return false; + }, + .log_level = espp::Logger::Verbosity::WARN}); + initialized = true; } -extern "C" void get_input_state(struct InputState* state) { - bool is_a_pressed = false; - bool is_b_pressed = false; - bool is_x_pressed = false; - bool is_y_pressed = false; - bool is_select_pressed = false; - bool is_start_pressed = false; - bool is_up_pressed = false; - bool is_down_pressed = false; - bool is_left_pressed = false; - bool is_right_pressed = false; - if (!mcp23x17) { - fmt::print("cannot get input state: mcp23x17 not initialized properly!\n"); - return; +extern "C" lv_indev_t *get_keypad_input_device() { + if (!keypad) { + fmt::print("cannot get keypad input device: keypad not initialized properly!\n"); + return nullptr; } - // pins are active low - // start, select = A0, A1 - std::error_code ec; - auto a_pins = mcp23x17->get_pins(espp::Mcp23x17::Port::A, ec); - if (ec) { - fmt::print("error getting pins from mcp23x17: {}\n", ec.message()); - return; - } - // d-pad, abxy = B0-B3, B4-B7 - auto b_pins = mcp23x17->get_pins(espp::Mcp23x17::Port::B, ec); - if (ec) { - fmt::print("error getting pins from mcp23x17: {}\n", ec.message()); - return; - } - is_a_pressed = !(b_pins & 1<<4); - is_b_pressed = !(b_pins & 1<<5); - is_x_pressed = !(b_pins & 1<<6); - is_y_pressed = !(b_pins & 1<<7); - is_start_pressed = !(a_pins & 1<<0); - is_select_pressed = !(a_pins & 1<<1); - is_up_pressed = !(b_pins & 1<<0); - is_down_pressed = !(b_pins & 1<<1); - is_left_pressed = !(b_pins & 1<<2); - is_right_pressed = !(b_pins & 1<<3); - state->a = is_a_pressed; - state->b = is_b_pressed; - state->x = is_x_pressed; - state->y = is_y_pressed; - state->start = is_start_pressed; - state->select = is_select_pressed; - state->up = is_up_pressed; - state->down = is_down_pressed; - state->left = is_left_pressed; - state->right = is_right_pressed; + return keypad->get_input_device(); +} + +extern "C" void get_input_state(struct InputState* state) { + std::lock_guard lock(gamepad_state_mutex); + *state = gamepad_state; } diff --git a/components/espp b/components/espp index c07e2fdb..28308cca 160000 --- a/components/espp +++ b/components/espp @@ -1 +1 @@ -Subproject commit c07e2fdb344856dc7b1c01234a2e16bd8d3ea0d7 +Subproject commit 28308ccad7a7e9c1a37686b53d6f18875372c7d9 diff --git a/components/gui/include/gui.hpp b/components/gui/include/gui.hpp index 6670008a..f4b6f31d 100644 --- a/components/gui/include/gui.hpp +++ b/components/gui/include/gui.hpp @@ -10,6 +10,7 @@ #include "task.hpp" #include "logger.hpp" +#include "input.h" #include "hal_events.hpp" #include "i2s_audio.h" #include "video_setting.hpp" @@ -82,40 +83,14 @@ class Gui { void pause() { paused_ = true; + freeze_focus(); } void resume() { update_shared_state(); paused_ = false; + focus_rommenu(); } - void next() { - // protect since this function is called from another thread context - std::lock_guard lk(mutex_); - if (roms_.size() == 0) { - return; - } - // focus the next rom - focused_rom_++; - if (focused_rom_ >= roms_.size()) focused_rom_ = 0; - auto rom = roms_[focused_rom_]; - focus_rom(rom); - } - - void previous() { - // protect since this function is called from another thread context - std::lock_guard lk(mutex_); - if (roms_.size() == 0) { - return; - } - // focus the previous rom - focused_rom_--; - if (focused_rom_ < 0) focused_rom_ = roms_.size() - 1; - auto rom = roms_[focused_rom_]; - focus_rom(rom); - } - - void focus_rom(lv_obj_t *new_focus, bool scroll_to_view=true); - void set_haptic_waveform(int new_waveform) { if (new_waveform > 123) { new_waveform = 1; @@ -141,7 +116,12 @@ class Gui { void init_ui(); void deinit_ui(); + void freeze_focus(); + void focus_rommenu(); + void focus_settings(); + void load_rom_screen(); + void load_settings_screen(); void update_shared_state() { set_mute(is_muted()); @@ -151,6 +131,8 @@ class Gui { VideoSetting get_video_setting(); + void on_rom_focused(lv_obj_t *new_focus); + void on_mute_button_pressed(const std::vector& data) { set_mute(is_muted()); } @@ -203,6 +185,7 @@ class Gui { case LV_EVENT_SHORT_CLICKED: break; case LV_EVENT_PRESSED: + case LV_EVENT_CLICKED: gui->on_pressed(e); break; case LV_EVENT_VALUE_CHANGED: @@ -211,6 +194,10 @@ class Gui { case LV_EVENT_LONG_PRESSED: break; case LV_EVENT_KEY: + gui->on_key(e); + break; + case LV_EVENT_FOCUSED: + gui->on_rom_focused(lv_event_get_target(e)); break; default: break; @@ -219,6 +206,7 @@ class Gui { void on_pressed(lv_event_t *e); void on_value_changed(lv_event_t *e); + void on_key(lv_event_t *e); // LVLG gui objects std::vector boxart_paths_; @@ -229,6 +217,9 @@ class Gui { lv_anim_t rom_label_animation_template_; lv_style_t rom_label_style_; + lv_group_t *rom_screen_group_; + lv_group_t *settings_screen_group_; + Jpeg decoder_; play_haptic_fn play_haptic_; diff --git a/components/gui/src/gui.cpp b/components/gui/src/gui.cpp index dfdfba9c..8231e3a9 100644 --- a/components/gui/src/gui.cpp +++ b/components/gui/src/gui.cpp @@ -36,9 +36,11 @@ void Gui::add_rom(const std::string& name, const std::string& image_path) { // make the rom's button auto new_rom = lv_btn_create(ui_rompanel); lv_obj_set_size(new_rom, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_add_flag( new_rom, LV_OBJ_FLAG_SCROLL_ON_FOCUS); - lv_obj_clear_flag( new_rom, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(new_rom, LV_OBJ_FLAG_SCROLL_ON_FOCUS); + lv_obj_clear_flag(new_rom, LV_OBJ_FLAG_SCROLLABLE); lv_obj_add_event_cb(new_rom, &Gui::event_callback, LV_EVENT_PRESSED, static_cast(this)); + lv_obj_add_event_cb(new_rom, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(new_rom, &Gui::event_callback, LV_EVENT_FOCUSED, static_cast(this)); lv_obj_center(new_rom); // set the rom's label text auto label = lv_label_create(new_rom); @@ -54,11 +56,13 @@ void Gui::add_rom(const std::string& name, const std::string& image_path) { boxart_paths_.push_back(image_path); if (focused_rom_ == -1) { // if we don't have a focused rom, then focus this newly added rom! - focus_rom(new_rom); + on_rom_focused(new_rom); } + // add the rom to the rom screen group + lv_group_add_obj(rom_screen_group_, new_rom); } -void Gui::focus_rom(lv_obj_t *new_focus, bool scroll_to_view) { +void Gui::on_rom_focused(lv_obj_t* new_focus) { std::lock_guard lk(mutex_); if (roms_.size() == 0) { return; @@ -74,11 +78,7 @@ void Gui::focus_rom(lv_obj_t *new_focus, bool scroll_to_view) { } // focus lv_obj_add_state(new_focus, LV_STATE_CHECKED); - - if (scroll_to_view) { - lv_obj_scroll_to_view(new_focus, LV_ANIM_ON); - } - + // lv_obj_scroll_to_view(new_focus, LV_ANIM_ON); // update the boxart auto boxart_path = boxart_paths_[focused_rom_].c_str(); focused_boxart_ = make_boxart(boxart_path); @@ -91,11 +91,24 @@ void Gui::update_haptic_waveform_label() { } void Gui::deinit_ui() { + // delete the groups + lv_group_del(rom_screen_group_); + lv_group_del(settings_screen_group_); + // delete the ui lv_obj_del(ui_romscreen); lv_obj_del(ui_settingsscreen); } void Gui::init_ui() { + // make 2 groups: + // 1. rom screen + // 2. settings screen + rom_screen_group_ = lv_group_create(); + settings_screen_group_ = lv_group_create(); + + // get the KEYPAD indev + lv_indev_set_group(get_keypad_input_device(), rom_screen_group_); + ui_init(); // make the label scrolling animation @@ -129,12 +142,43 @@ void Gui::init_ui() { lv_obj_add_event_cb(ui_hapticdownbutton, &Gui::event_callback, LV_EVENT_PRESSED, static_cast(this)); lv_obj_add_event_cb(ui_hapticupbutton, &Gui::event_callback, LV_EVENT_PRESSED, static_cast(this)); lv_obj_add_event_cb(ui_hapticplaybutton, &Gui::event_callback, LV_EVENT_PRESSED, static_cast(this)); + + // now do the same events for all the same buttons but for the LV_EVENT_KEY + lv_obj_add_event_cb(ui_settingsbutton, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_playbutton, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_videosettingdropdown, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_volumeupbutton, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_volumedownbutton, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_mutebutton, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_hapticdownbutton, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_hapticupbutton, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_hapticplaybutton, &Gui::event_callback, LV_EVENT_KEY, static_cast(this)); + // ensure the waveform is set and the ui is updated set_haptic_waveform(haptic_waveform_); + + // add all the settings buttons to the settings screen group + lv_group_add_obj(settings_screen_group_, ui_mutebutton); + lv_group_add_obj(settings_screen_group_, ui_volumedownbutton); + lv_group_add_obj(settings_screen_group_, ui_volumeupbutton); + lv_group_add_obj(settings_screen_group_, ui_videosettingdropdown); + lv_group_add_obj(settings_screen_group_, ui_hapticdownbutton); + lv_group_add_obj(settings_screen_group_, ui_hapticupbutton); + lv_group_add_obj(settings_screen_group_, ui_hapticplaybutton); + + focus_rommenu(); } void Gui::load_rom_screen() { + logger_.info("Loading rom screen"); lv_scr_load(ui_romscreen); + focus_rommenu(); +} + +void Gui::load_settings_screen() { + logger_.info("Loading settings screen"); + lv_scr_load(ui_settingsscreen); + focus_settings(); } void Gui::on_value_changed(lv_event_t *e) { @@ -154,7 +198,8 @@ void Gui::on_pressed(lv_event_t *e) { // is it the settings button? bool is_settings_button = (target == ui_settingsbutton); if (is_settings_button) { - // TODO: DO SOMETHING HERE! + // set the settings screen group as the default group + lv_group_set_default(settings_screen_group_); return; } // volume controls @@ -193,12 +238,107 @@ void Gui::on_pressed(lv_event_t *e) { bool is_play_button = (target == ui_playbutton); if (is_play_button) { ready_to_play_ = true; + freeze_focus(); return; } // or is it one of the roms? if (std::find(roms_.begin(), roms_.end(), target) != roms_.end()) { // it's one of the roms, focus it! this was pressed, so don't scroll (it // will already scroll) - focus_rom(target, false); + on_rom_focused(target); + } +} + +void Gui::freeze_focus() { + logger_.debug("Freezing focus"); + // freeze the focus + lv_group_focus_freeze(rom_screen_group_, true); + lv_group_focus_freeze(settings_screen_group_, true); + // set editing false for the settings screen group + lv_group_set_editing(settings_screen_group_, false); +} + +void Gui::focus_rommenu() { + freeze_focus(); + // focus the rom screen group + logger_.debug("Focusing rom screen group"); + lv_group_focus_freeze(rom_screen_group_, false); + lv_indev_set_group(get_keypad_input_device(), rom_screen_group_); +} + +void Gui::focus_settings() { + freeze_focus(); + // focus the rom screen group + logger_.debug("Focusing settings screen group"); + lv_group_focus_freeze(settings_screen_group_, false); + // NOTE: we don't set editing here since we use it to manage the dropdown + lv_indev_set_group(get_keypad_input_device(), settings_screen_group_); +} + +void Gui::on_key(lv_event_t *e) { + // print the key + auto key = lv_indev_get_key(lv_indev_get_act()); + // get which screen is currently loaded + auto current_screen = lv_scr_act(); + bool is_rom_screen = (current_screen == ui_romscreen); + bool is_settings_screen = (current_screen == ui_settingsscreen); + bool is_settings_edit = lv_group_get_editing(settings_screen_group_); + + // see if the target is the videosettingdropdown + lv_obj_t * target = lv_event_get_target(e); + // TODO: this is a really hacky way of getting the dropdown to work within a + // group when managed by the keypad input device. I'm not sure if there's a + // better way to do this, but this works for now. + bool is_video_setting = (target == ui_videosettingdropdown); + if (key == LV_KEY_ESC) { + // if we're in the settings screen group, then go back to the rom screen + if (is_settings_screen) { + if (!is_settings_edit) { + load_rom_screen(); + } else { + // otherwise, close the dropdown + lv_dropdown_close(ui_videosettingdropdown); + lv_group_set_editing(settings_screen_group_, false); + } + } else if (is_rom_screen) { + load_settings_screen(); + } + } else if (key == LV_KEY_ENTER) { + if (is_rom_screen) { + // play the focused rom + ready_to_play_ = true; + } else if (is_settings_screen) { + // handle some specific things we need to do for the dropdown -.- + // They say that in v9 they will have a better way to do this... + if (is_video_setting) { + if (is_settings_edit) { + // lv_dropdown_close(ui_videosettingdropdown); + lv_group_set_editing(settings_screen_group_, false); + } else { + // lv_dropdown_open(ui_videosettingdropdown); + lv_group_set_editing(settings_screen_group_, true); + } + } + } + } else if (key == LV_KEY_RIGHT || key == LV_KEY_DOWN) { + if (is_settings_screen) { + if (!is_settings_edit) { + // if we're in the settings screen group, then focus the next item + lv_group_focus_next(settings_screen_group_); + } + } else if (is_rom_screen) { + // focus the next rom + lv_group_focus_next(rom_screen_group_); + } + } else if (key == LV_KEY_LEFT || key == LV_KEY_UP) { + if (is_settings_screen) { + if (!is_settings_edit) { + // if we're in the settings screen group, then focus the next item + lv_group_focus_prev(settings_screen_group_); + } + } else if (is_rom_screen) { + // focus the next rom + lv_group_focus_prev(rom_screen_group_); + } } } diff --git a/components/menu/include/menu.hpp b/components/menu/include/menu.hpp index d411dde2..cc8a2199 100644 --- a/components/menu/include/menu.hpp +++ b/components/menu/include/menu.hpp @@ -9,6 +9,7 @@ #include "task.hpp" #include "logger.hpp" +#include "input.h" #include "hal_events.hpp" #include "i2s_audio.h" #include "video_setting.hpp" @@ -95,12 +96,16 @@ class Menu { void set_video_setting(VideoSetting setting); bool is_paused() { return paused_; } - void pause() { paused_ = true; } + void pause() { + paused_ = true; + lv_group_focus_freeze(group_, true); + } void resume() { update_shared_state(); update_slot_display(); update_pause_image(); paused_ = false; + lv_group_focus_freeze(group_, false); } protected: @@ -156,6 +161,7 @@ class Menu { case LV_EVENT_LONG_PRESSED: break; case LV_EVENT_KEY: + menu->on_key(e); break; default: break; @@ -164,6 +170,9 @@ class Menu { void on_pressed(lv_event_t *e); void on_value_changed(lv_event_t *e); + void on_key(lv_event_t *e); + + lv_group_t *group_{nullptr}; // LVLG menu objects lv_img_dsc_t state_image_; diff --git a/components/menu/src/menu.cpp b/components/menu/src/menu.cpp index bd2f6732..acb469fd 100644 --- a/components/menu/src/menu.cpp +++ b/components/menu/src/menu.cpp @@ -10,6 +10,13 @@ void Menu::init_ui() { // save the previous screen to return to it when we destroy ourselves. previous_screen_ = lv_scr_act(); + // create the default group + group_ = lv_group_create(); + lv_group_set_default(group_); + + // get the KEYPAD indev + lv_indev_set_group(get_keypad_input_device(), group_); + // now initialize our UI menu_ui_init(); @@ -31,13 +38,46 @@ void Menu::init_ui() { lv_obj_add_event_cb(ui_volume_dec_btn, &Menu::event_callback, LV_EVENT_PRESSED, static_cast(this)); lv_obj_add_event_cb(ui_volume_mute_btn, &Menu::event_callback, LV_EVENT_PRESSED, static_cast(this)); + // // now do all the same buttons but with the LV_EVENT_KEY event + lv_obj_add_event_cb(ui_resume_btn, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_reset_btn, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_quit_btn, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_load_btn, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_save_btn, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_btn_slot_dec, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_btn_slot_inc, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_volume_inc_btn, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_volume_dec_btn, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + lv_obj_add_event_cb(ui_volume_mute_btn, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + + lv_obj_add_event_cb(ui_Dropdown2, &Menu::event_callback, LV_EVENT_KEY, static_cast(this)); + // video settings lv_obj_add_event_cb(ui_Dropdown2, &Menu::event_callback, LV_EVENT_VALUE_CHANGED, static_cast(this)); + + // // add all the buttons to the group + lv_group_add_obj(group_, ui_resume_btn); + lv_group_add_obj(group_, ui_volume_mute_btn); + lv_group_add_obj(group_, ui_volume_dec_btn); + lv_group_add_obj(group_, ui_volume_inc_btn); + lv_group_add_obj(group_, ui_btn_slot_dec); + lv_group_add_obj(group_, ui_btn_slot_inc); + lv_group_add_obj(group_, ui_load_btn); + lv_group_add_obj(group_, ui_save_btn); + lv_group_add_obj(group_, ui_Dropdown2); + lv_group_add_obj(group_, ui_reset_btn); + lv_group_add_obj(group_, ui_quit_btn); + + // now focus the resume button + lv_group_focus_obj(ui_resume_btn); + lv_group_focus_freeze(group_, false); } void Menu::deinit_ui() { lv_scr_load(previous_screen_); lv_obj_del(ui_Screen1); + // delete the group + lv_group_del(group_); } void Menu::update_slot_display() { @@ -238,3 +278,49 @@ void Menu::on_pressed(lv_event_t *e) { return; } } + +void Menu::on_key(lv_event_t *e) { + // get the target of the event + lv_obj_t * target = lv_event_get_target(e); + // determine if this is the dropdown and, if so if it is open + // TODO: this is a really hacky way of getting the dropdown to work within a + // group when managed by the keypad input device. I'm not sure if there's a + // better way to do this, but this works for now. + bool is_video_setting = (target == ui_Dropdown2); + bool is_video_edit = lv_group_get_editing(group_); + auto key = lv_indev_get_key(lv_indev_get_act()); + // now handle the keys + if (key == LV_KEY_ESC) { + if (!is_video_edit) { + // if we're editing the dropdown, then close the menu + action_callback_(Action::RESUME); + } else { + // otherwise, close the dropdown + lv_dropdown_close(ui_Dropdown2); + lv_group_set_editing(group_, false); + } + } else if (key == LV_KEY_ENTER) { + // handle some specific things we need to do for the dropdown -.- + // They say that in v9 they will have a better way to do this... + if (is_video_setting) { + if (is_video_edit) { + // lv_dropdown_close(ui_videosettingdropdown); + lv_group_set_editing(group_, false); + } else { + // lv_dropdown_open(ui_videosettingdropdown); + lv_group_set_editing(group_, true); + } + } + } else if (key == LV_KEY_RIGHT || key == LV_KEY_DOWN) { + if (!is_video_edit) { + // if we're in the settings screen group, then focus the next item + lv_group_focus_next(group_); + } + } else if (key == LV_KEY_LEFT || key == LV_KEY_UP) { + if (!is_video_edit) { + // if we're in the settings screen group, then focus the next item + lv_group_focus_prev(group_); + } + } + +} diff --git a/main/cart.hpp b/main/cart.hpp index b49f0b13..349ad32c 100644 --- a/main/cart.hpp +++ b/main/cart.hpp @@ -163,10 +163,17 @@ class Cart { virtual bool run() { running_ = true; // handle touchpad so we can know if the user presses the menu - uint8_t _num_touches, _btn_state; - uint16_t _x,_y; + uint8_t _num_touches = 0, _btn_state = false; + uint16_t _x = 0 ,_y = 0; touchpad_read(&_num_touches, &_x, &_y, &_btn_state); - if (_btn_state) { + // also get the gamepad input state so we can know if the user presses the + // start/select buttons together to bring up the menu + InputState state; + get_input_state(&state); + // if the user presses the menu button or the start/select buttons, then + // pause the game and show the menu + bool show_menu = _btn_state || (state.start && state.select); + if (show_menu) { logger_.warn("Menu pressed!"); pre_menu(); // take a screenshot before we show the menu diff --git a/main/main.cpp b/main/main.cpp index c1b85397..f34e94d5 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -36,7 +36,7 @@ extern std::shared_ptr display; using namespace std::chrono_literals; -bool operator==(const InputState& lhs, const InputState& rhs) { +static bool operator==(const InputState& lhs, const InputState& rhs) { return lhs.a == rhs.a && lhs.b == rhs.b && @@ -133,26 +133,7 @@ extern "C" void app_main(void) { while (true) { // reset gui ready to play and user_quit gui.ready_to_play(false); - - struct InputState prev_state; - struct InputState curr_state; - get_input_state(&prev_state); - get_input_state(&curr_state); while (!gui.ready_to_play()) { - // TODO: would be better to make this an actual LVGL input device instead - // of this.. - get_input_state(&curr_state); - if (curr_state != prev_state) { - prev_state = curr_state; - if (curr_state.up) { - gui.previous(); - } else if (curr_state.down) { - gui.next(); - } else if (curr_state.start) { - // same as play button was pressed, just exit the loop! - break; - } - } std::this_thread::sleep_for(50ms); } From 2c8f160baed3e6e78455402bddd9bc4f1f5cff3a Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 11 Nov 2023 16:43:01 -0600 Subject: [PATCH 2/7] espp: update --- components/espp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/espp b/components/espp index 28308cca..393144fc 160000 --- a/components/espp +++ b/components/espp @@ -1 +1 @@ -Subproject commit 28308ccad7a7e9c1a37686b53d6f18875372c7d9 +Subproject commit 393144fc707066ba7d8a13fc05f834d9d69f9858 From 5e3076faf6cb09b99c8efae3e38f14caaa4b3924 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 11 Nov 2023 18:35:15 -0600 Subject: [PATCH 3/7] update input timer name --- components/box-emu-hal/src/input.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/box-emu-hal/src/input.cpp b/components/box-emu-hal/src/input.cpp index 252135f9..b8db8581 100644 --- a/components/box-emu-hal/src/input.cpp +++ b/components/box-emu-hal/src/input.cpp @@ -23,7 +23,7 @@ static std::shared_ptr mcp23x17; static std::shared_ptr touch_driver; static std::shared_ptr touchpad; static std::shared_ptr keypad; -static std::shared_ptr gamepad_timer; +static std::shared_ptr input_timer; static struct InputState gamepad_state; static std::mutex gamepad_state_mutex; static TouchpadData touchpad_data; @@ -180,7 +180,7 @@ void init_input() { }); fmt::print("Initializing input task\n"); - gamepad_timer = std::make_shared(espp::Timer::Config{ + input_timer = std::make_shared(espp::Timer::Config{ .name = "Input timer", .period = 20ms, .callback = []() { From cbceec3a3a4f9c39c34444c8c8a395e00f4e564f Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 11 Nov 2023 18:43:30 -0600 Subject: [PATCH 4/7] make sure to use transitions from SLS when changing screen. add event registration for close button to ensure we focus properly when closing settings --- components/gui/src/gui.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/components/gui/src/gui.cpp b/components/gui/src/gui.cpp index 8231e3a9..d6ddf73e 100644 --- a/components/gui/src/gui.cpp +++ b/components/gui/src/gui.cpp @@ -2,6 +2,7 @@ extern "C" { #include "ui.h" +#include "ui_helpers.h" #include "ui_comp.h" } @@ -128,6 +129,7 @@ void Gui::init_ui() { // rom screen navigation lv_obj_add_event_cb(ui_settingsbutton, &Gui::event_callback, LV_EVENT_PRESSED, static_cast(this)); + lv_obj_add_event_cb(ui_closebutton, &Gui::event_callback, LV_EVENT_PRESSED, static_cast(this)); lv_obj_add_event_cb(ui_playbutton, &Gui::event_callback, LV_EVENT_PRESSED, static_cast(this)); // video settings @@ -171,13 +173,13 @@ void Gui::init_ui() { void Gui::load_rom_screen() { logger_.info("Loading rom screen"); - lv_scr_load(ui_romscreen); + _ui_screen_change( ui_romscreen, LV_SCR_LOAD_ANIM_MOVE_LEFT, 100, 0); focus_rommenu(); } void Gui::load_settings_screen() { logger_.info("Loading settings screen"); - lv_scr_load(ui_settingsscreen); + _ui_screen_change( ui_settingsscreen, LV_SCR_LOAD_ANIM_MOVE_RIGHT, 100, 0); focus_settings(); } @@ -199,7 +201,7 @@ void Gui::on_pressed(lv_event_t *e) { bool is_settings_button = (target == ui_settingsbutton); if (is_settings_button) { // set the settings screen group as the default group - lv_group_set_default(settings_screen_group_); + focus_settings(); return; } // volume controls @@ -241,6 +243,11 @@ void Gui::on_pressed(lv_event_t *e) { freeze_focus(); return; } + bool is_close_button = (target == ui_closebutton); + if (is_close_button) { + focus_rommenu(); + return; + } // or is it one of the roms? if (std::find(roms_.begin(), roms_.end(), target) != roms_.end()) { // it's one of the roms, focus it! this was pressed, so don't scroll (it From 5b5952b342a337493663c77800c07258a87def70 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 11 Nov 2023 21:30:41 -0600 Subject: [PATCH 5/7] feat(ui): update button styles so focused is highlighted red so it is easy to see what button is selected when using the gamepad --- components/gui/include/gui.hpp | 3 +++ components/gui/src/gui.cpp | 16 ++++++++++++++++ components/menu/include/menu.hpp | 3 ++- components/menu/src/menu.cpp | 17 +++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/components/gui/include/gui.hpp b/components/gui/include/gui.hpp index f4b6f31d..259f3ba2 100644 --- a/components/gui/include/gui.hpp +++ b/components/gui/include/gui.hpp @@ -214,6 +214,9 @@ class Gui { std::atomic focused_rom_{-1}; lv_img_dsc_t focused_boxart_; + // style for buttons + lv_style_t button_style_; + lv_anim_t rom_label_animation_template_; lv_style_t rom_label_style_; diff --git a/components/gui/src/gui.cpp b/components/gui/src/gui.cpp index d6ddf73e..d939ee59 100644 --- a/components/gui/src/gui.cpp +++ b/components/gui/src/gui.cpp @@ -168,6 +168,22 @@ void Gui::init_ui() { lv_group_add_obj(settings_screen_group_, ui_hapticupbutton); lv_group_add_obj(settings_screen_group_, ui_hapticplaybutton); + // set the focused style for all the buttons to have a red border + lv_style_init(&button_style_); + lv_style_set_border_color(&button_style_, lv_palette_main(LV_PALETTE_RED)); + lv_style_set_border_width(&button_style_, 2); + + lv_obj_add_style(ui_settingsbutton, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_playbutton, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_closebutton, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_volumeupbutton, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_volumedownbutton, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_mutebutton, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_hapticupbutton, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_hapticdownbutton, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_hapticplaybutton, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_videosettingdropdown, &button_style_, LV_STATE_FOCUSED); + focus_rommenu(); } diff --git a/components/menu/include/menu.hpp b/components/menu/include/menu.hpp index cc8a2199..edf205f9 100644 --- a/components/menu/include/menu.hpp +++ b/components/menu/include/menu.hpp @@ -172,9 +172,10 @@ class Menu { void on_value_changed(lv_event_t *e); void on_key(lv_event_t *e); + // LVLG menu objects + lv_style_t button_style_; lv_group_t *group_{nullptr}; - // LVLG menu objects lv_img_dsc_t state_image_; lv_img_dsc_t paused_image_; diff --git a/components/menu/src/menu.cpp b/components/menu/src/menu.cpp index acb469fd..b06bff6b 100644 --- a/components/menu/src/menu.cpp +++ b/components/menu/src/menu.cpp @@ -68,6 +68,23 @@ void Menu::init_ui() { lv_group_add_obj(group_, ui_reset_btn); lv_group_add_obj(group_, ui_quit_btn); + // set the focused style for all the buttons to have a red border + lv_style_init(&button_style_); + lv_style_set_border_color(&button_style_, lv_palette_main(LV_PALETTE_RED)); + lv_style_set_border_width(&button_style_, 2); + + lv_obj_add_style(ui_resume_btn, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_volume_mute_btn, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_volume_dec_btn, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_volume_inc_btn, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_btn_slot_dec, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_btn_slot_inc, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_load_btn, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_save_btn, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_Dropdown2, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_reset_btn, &button_style_, LV_STATE_FOCUSED); + lv_obj_add_style(ui_quit_btn, &button_style_, LV_STATE_FOCUSED); + // now focus the resume button lv_group_focus_obj(ui_resume_btn); lv_group_focus_freeze(group_, false); From a00d9032406dc280f892dd1470e0b380bd5a6781 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 11 Nov 2023 21:32:26 -0600 Subject: [PATCH 6/7] feat(input): update so that start/select also act as enter/escape for the gui --- components/box-emu-hal/src/input.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/box-emu-hal/src/input.cpp b/components/box-emu-hal/src/input.cpp index b8db8581..cfe87084 100644 --- a/components/box-emu-hal/src/input.cpp +++ b/components/box-emu-hal/src/input.cpp @@ -48,8 +48,8 @@ void keypad_read(bool *up, bool *down, bool *left, bool *right, bool *enter, boo *left = state.left; *right = state.right; - *enter = state.a; - *escape = state.b; + *enter = state.a || state.start; + *escape = state.b || state.select; } void update_touchpad_input() { From 6a4ba7438a49a9b38d005752fd256b566dd7ea73 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 11 Nov 2023 22:50:37 -0600 Subject: [PATCH 7/7] readme: update --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cc7bb419..a161f903 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Emulator(s) running on ESP BOX with custom pcb and 3d printed enclosure :) -https://user-images.githubusercontent.com/213467/236730090-56c3bd64-86e4-4b9b-a909-0b363fab4fc6.mp4 +https://github.com/esp-cpp/esp-box-emu/assets/213467/3b77f6bd-4c42-417a-9eb7-a648f31b4008 -https://user-images.githubusercontent.com/213467/220791336-eb24116d-0958-4ab7-88bd-f6a5bd6d7eb1.mp4 +https://user-images.githubusercontent.com/213467/236730090-56c3bd64-86e4-4b9b-a909-0b363fab4fc6.mp4 As of https://github.com/esp-cpp/esp-box-emu/pull/34 I am starting to add initial support for the new ESP32-S3-BOX-3. @@ -238,6 +238,8 @@ I2C Pinout (shared with touchscreen chip above): ## Videos +https://user-images.githubusercontent.com/213467/220791336-eb24116d-0958-4ab7-88bd-f6a5bd6d7eb1.mp4 + ### Gameboy Color This video shows settings page (with audio control and video scaling control), and then Links Awakening DX. While running the ROMs, the video scaling can be toggled through the three options (Original, Fit, and Fill) using the BOOT button on the side of the ESP-S3-BOX and the audio output can be toggled on / off using the MUTE button on the top.