mirror of
https://github.com/ravinrabbid/DonCon2040.git
synced 2024-11-20 03:37:07 +01:00
Add menu backend
This commit is contained in:
parent
0f1fd87a07
commit
7a6bb4e182
@ -36,6 +36,7 @@ class StatusLed {
|
||||
StatusLed(const Config &config);
|
||||
|
||||
void setInputState(const Utils::InputState input_state);
|
||||
void setBrightness(const uint8_t brightness);
|
||||
|
||||
void update();
|
||||
};
|
||||
|
@ -57,6 +57,8 @@ struct InputState {
|
||||
InputState();
|
||||
|
||||
usb_report_t getReport(usb_mode_t mode);
|
||||
|
||||
bool checkHotkey();
|
||||
};
|
||||
|
||||
} // namespace Doncon::Utils
|
||||
|
87
include/utils/Menu.h
Normal file
87
include/utils/Menu.h
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef _UTILS_MENU_H_
|
||||
#define _UTILS_MENU_H_
|
||||
|
||||
#include "utils/InputState.h"
|
||||
#include "utils/SettingsStore.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Doncon::Utils {
|
||||
|
||||
class Menu {
|
||||
public:
|
||||
enum class Page {
|
||||
None,
|
||||
Main,
|
||||
DeviceMode,
|
||||
LedBrightness,
|
||||
Bootsel,
|
||||
BootselMsg,
|
||||
};
|
||||
|
||||
struct State {
|
||||
Page page;
|
||||
uint8_t selection;
|
||||
};
|
||||
|
||||
struct Descriptor {
|
||||
enum class Type {
|
||||
Root,
|
||||
Selection,
|
||||
Value,
|
||||
RebootInfo,
|
||||
};
|
||||
|
||||
enum class Action {
|
||||
None,
|
||||
|
||||
GotoPageDeviceMode,
|
||||
GotoPageLedBrightness,
|
||||
GotoPageBootsel,
|
||||
|
||||
ChangeUsbModeSwitchTatacon,
|
||||
ChangeUsbModeSwitchHoripad,
|
||||
ChangeUsbModeDS3,
|
||||
ChangeUsbModePS4Tatacon,
|
||||
ChangeUsbModeDS4,
|
||||
ChangeUsbModeXbox360,
|
||||
ChangeUsbModeDebug,
|
||||
|
||||
SetLedBrightness,
|
||||
|
||||
DoRebootToBootsel,
|
||||
};
|
||||
|
||||
Type type;
|
||||
std::string name;
|
||||
std::vector<std::pair<std::string, Action>> items;
|
||||
Page parent;
|
||||
};
|
||||
|
||||
const static std::map<Page, const Descriptor> descriptors;
|
||||
|
||||
private:
|
||||
std::shared_ptr<SettingsStore> m_store;
|
||||
bool m_active;
|
||||
State m_state;
|
||||
|
||||
uint8_t getCurrentSelection(Page page);
|
||||
void gotoPage(Page page);
|
||||
void performSelectionAction(Descriptor::Action action);
|
||||
void performValueAction(Descriptor::Action action, uint8_t value);
|
||||
|
||||
public:
|
||||
Menu(std::shared_ptr<SettingsStore> settings_store);
|
||||
|
||||
void activate();
|
||||
void update(const InputState::Controller &controller_state);
|
||||
bool active();
|
||||
State getState();
|
||||
};
|
||||
} // namespace Divacon::Utils
|
||||
|
||||
#endif // _UTILS_MENU_H_
|
@ -20,9 +20,10 @@ class SettingsStore {
|
||||
uint8_t in_use;
|
||||
usb_mode_t usb_mode;
|
||||
Peripherals::Drum::Config::Thresholds trigger_thresholds;
|
||||
uint8_t led_brightness;
|
||||
|
||||
uint8_t _padding[m_store_size - sizeof(uint8_t) - sizeof(usb_mode_t) -
|
||||
sizeof(Peripherals::Drum::Config::Thresholds)];
|
||||
sizeof(Peripherals::Drum::Config::Thresholds) - sizeof(uint8_t)];
|
||||
};
|
||||
static_assert(sizeof(Storecache) == m_store_size);
|
||||
|
||||
@ -49,6 +50,9 @@ class SettingsStore {
|
||||
void setTriggerThresholds(Peripherals::Drum::Config::Thresholds thresholds);
|
||||
Peripherals::Drum::Config::Thresholds getTriggerThresholds();
|
||||
|
||||
void setLedBrightness(uint8_t brightness);
|
||||
uint8_t getLedBrightness();
|
||||
|
||||
void scheduleReboot(bool bootsel = false);
|
||||
|
||||
void store();
|
||||
|
35
src/main.cpp
35
src/main.cpp
@ -3,6 +3,7 @@
|
||||
#include "peripherals/Drum.h"
|
||||
#include "peripherals/StatusLed.h"
|
||||
#include "usb/usb_driver.h"
|
||||
#include "utils/Menu.h"
|
||||
#include "utils/SettingsStore.h"
|
||||
|
||||
#include "GlobalConfiguration.h"
|
||||
@ -22,6 +23,7 @@ queue_t controller_input_queue;
|
||||
enum class ControlCommand {
|
||||
SetUsbMode,
|
||||
SetPlayerLed,
|
||||
SetLedBrightness,
|
||||
};
|
||||
|
||||
struct ControlMessage {
|
||||
@ -29,6 +31,7 @@ struct ControlMessage {
|
||||
union {
|
||||
usb_mode_t usb_mode;
|
||||
usb_player_led_t player_led;
|
||||
uint8_t brightness;
|
||||
} data;
|
||||
};
|
||||
|
||||
@ -60,6 +63,9 @@ void core1_task() {
|
||||
} else if (control_msg.data.player_led.type == USB_PLAYER_LED_COLOR) {
|
||||
}
|
||||
break;
|
||||
case ControlCommand::SetLedBrightness:
|
||||
led.setBrightness(control_msg.data.brightness);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +93,7 @@ int main() {
|
||||
Utils::InputState input_state;
|
||||
|
||||
auto settings_store = std::make_shared<Utils::SettingsStore>();
|
||||
Utils::Menu menu(settings_store);
|
||||
|
||||
Peripherals::Drum drum(Config::Default::drum_config);
|
||||
|
||||
@ -104,16 +111,40 @@ int main() {
|
||||
|
||||
ctrl_message = {ControlCommand::SetUsbMode, {.usb_mode = mode}};
|
||||
queue_add_blocking(&control_queue, &ctrl_message);
|
||||
|
||||
ctrl_message = {ControlCommand::SetLedBrightness, {.brightness = settings_store->getLedBrightness()}};
|
||||
queue_add_blocking(&control_queue, &ctrl_message);
|
||||
};
|
||||
|
||||
readSettings();
|
||||
|
||||
while (true) {
|
||||
drum.updateInputState(input_state);
|
||||
|
||||
queue_try_remove(&controller_input_queue, &input_state.controller);
|
||||
|
||||
usb_driver_send_and_receive_report(input_state.getReport(mode));
|
||||
if (menu.active()) {
|
||||
menu.update(input_state.controller);
|
||||
if (menu.active()) {
|
||||
// auto display_msg = menu.getState();
|
||||
// queue_add_blocking(&menu_display_queue, &display_msg);
|
||||
} else {
|
||||
settings_store->store();
|
||||
|
||||
// ControlMessage ctrl_message = {ControlCommand::ExitMenu, {}};
|
||||
// queue_add_blocking(&control_queue, &ctrl_message);
|
||||
}
|
||||
|
||||
readSettings();
|
||||
|
||||
} else if (input_state.checkHotkey()) {
|
||||
menu.activate();
|
||||
|
||||
// ControlMessage ctrl_message{ControlCommand::EnterMenu, {}};
|
||||
// queue_add_blocking(&control_queue, &ctrl_message);
|
||||
} else {
|
||||
usb_driver_send_and_receive_report(input_state.getReport(mode));
|
||||
}
|
||||
|
||||
usb_driver_task();
|
||||
|
||||
queue_try_add(&drum_input_queue, &input_state);
|
||||
|
@ -15,6 +15,8 @@ StatusLed::StatusLed(const Config &config) : m_config(config) {
|
||||
|
||||
void StatusLed::setInputState(const Utils::InputState input_state) { m_input_state = input_state; }
|
||||
|
||||
void StatusLed::setBrightness(const uint8_t brightness) { m_config.brightness = brightness; }
|
||||
|
||||
void StatusLed::update() {
|
||||
static float brightness_factor = m_config.brightness / static_cast<float>(UINT8_MAX);
|
||||
|
||||
|
@ -257,4 +257,24 @@ usb_report_t InputState::getDebugReport() {
|
||||
return {(uint8_t *)m_debug_report.c_str(), static_cast<uint16_t>(m_debug_report.size() + 1)};
|
||||
}
|
||||
|
||||
bool InputState::checkHotkey() {
|
||||
static uint32_t hold_since = 0;
|
||||
static bool hold_active = false;
|
||||
static const uint32_t hold_timeout = 2000;
|
||||
|
||||
if (controller.buttons.start && controller.buttons.select) {
|
||||
uint32_t now = to_ms_since_boot(get_absolute_time());
|
||||
if (!hold_active) {
|
||||
hold_active = true;
|
||||
hold_since = now;
|
||||
} else if ((now - hold_since) > hold_timeout) {
|
||||
hold_active = false;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
hold_active = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Doncon::Utils
|
||||
|
295
src/utils/Menu.cpp
Normal file
295
src/utils/Menu.cpp
Normal file
@ -0,0 +1,295 @@
|
||||
#include "utils/Menu.h"
|
||||
|
||||
namespace Doncon::Utils {
|
||||
|
||||
const std::map<Menu::Page, const Menu::Descriptor> Menu::descriptors = {
|
||||
{Menu::Page::Main, //
|
||||
{Menu::Descriptor::Type::Root, //
|
||||
"Settings", //
|
||||
{{"Mode", Menu::Descriptor::Action::GotoPageDeviceMode}, //
|
||||
{"Brightness", Menu::Descriptor::Action::GotoPageLedBrightness}, //
|
||||
{"BOOTSEL", Menu::Descriptor::Action::GotoPageBootsel}}, //
|
||||
Menu::Page::None}}, //
|
||||
|
||||
{Menu::Page::DeviceMode, //
|
||||
{Menu::Descriptor::Type::Selection, //
|
||||
"Mode", //
|
||||
{{"Swtch Tata", Menu::Descriptor::Action::ChangeUsbModeSwitchTatacon}, //
|
||||
{"Swtch Pro", Menu::Descriptor::Action::ChangeUsbModeSwitchHoripad}, //
|
||||
{"Dualshock3", Menu::Descriptor::Action::ChangeUsbModeDS3}, //
|
||||
{"PS4 Tata", Menu::Descriptor::Action::ChangeUsbModePS4Tatacon}, //
|
||||
{"Dualshock4", Menu::Descriptor::Action::ChangeUsbModeDS4}, //
|
||||
{"Xbox 360", Menu::Descriptor::Action::ChangeUsbModeXbox360}, //
|
||||
{"Debug", Menu::Descriptor::Action::ChangeUsbModeDebug}}, //
|
||||
Menu::Page::Main}}, //
|
||||
|
||||
{Menu::Page::LedBrightness, //
|
||||
{Menu::Descriptor::Type::Value, //
|
||||
"LED Brightness", //
|
||||
{{"", Menu::Descriptor::Action::SetLedBrightness}}, //
|
||||
Menu::Page::Main}}, //
|
||||
|
||||
{Menu::Page::Bootsel, //
|
||||
{Menu::Descriptor::Type::Selection, //
|
||||
"Reboot to BOOTSEL", //
|
||||
{{"Reboot?", Menu::Descriptor::Action::DoRebootToBootsel}}, //
|
||||
Menu::Page::Main}}, //
|
||||
|
||||
{Menu::Page::BootselMsg, //
|
||||
{Menu::Descriptor::Type::RebootInfo, //
|
||||
"Ready to Flash...", //
|
||||
{{"BOOTSEL", Menu::Descriptor::Action::None}}, //
|
||||
Menu::Page::Main}}, //
|
||||
};
|
||||
|
||||
Menu::Menu(std::shared_ptr<SettingsStore> settings_store)
|
||||
: m_store(settings_store), m_active(false), m_state({Page::Main, 0}){};
|
||||
|
||||
void Menu::activate() {
|
||||
m_state = {Page::Main, 0};
|
||||
m_active = true;
|
||||
}
|
||||
|
||||
static InputState::Controller::Buttons checkPressed(const InputState::Controller &controller_state) {
|
||||
struct ButtonState {
|
||||
enum State {
|
||||
Idle,
|
||||
RepeatDelay,
|
||||
Repeat,
|
||||
};
|
||||
State state;
|
||||
uint32_t pressed_since;
|
||||
uint32_t last_repeat;
|
||||
};
|
||||
|
||||
static const uint32_t repeat_delay = 1000;
|
||||
static const uint32_t repeat_interval = 20;
|
||||
|
||||
static ButtonState state_north = {ButtonState::State::Idle, 0, 0};
|
||||
static ButtonState state_east = {ButtonState::State::Idle, 0, 0};
|
||||
static ButtonState state_south = {ButtonState::State::Idle, 0, 0};
|
||||
static ButtonState state_west = {ButtonState::State::Idle, 0, 0};
|
||||
|
||||
InputState::Controller::Buttons result{false, false, false, false, false, false, false, false, false, false};
|
||||
|
||||
auto handle_button = [](ButtonState &button_state, bool input_state) {
|
||||
bool result = false;
|
||||
if (input_state) {
|
||||
uint32_t now = to_ms_since_boot(get_absolute_time());
|
||||
switch (button_state.state) {
|
||||
case ButtonState::State::Idle:
|
||||
result = true;
|
||||
button_state.state = ButtonState::State::RepeatDelay;
|
||||
button_state.pressed_since = now;
|
||||
break;
|
||||
case ButtonState::State::RepeatDelay:
|
||||
if ((now - button_state.pressed_since) > repeat_delay) {
|
||||
result = true;
|
||||
button_state.state = ButtonState::State::Repeat;
|
||||
button_state.last_repeat = now;
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
break;
|
||||
case ButtonState::State::Repeat:
|
||||
if ((now - button_state.last_repeat) > repeat_interval) {
|
||||
result = true;
|
||||
button_state.last_repeat = now;
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
result = false;
|
||||
button_state.state = ButtonState::State::Idle;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
result.north = handle_button(state_north, controller_state.buttons.north);
|
||||
result.east = handle_button(state_east, controller_state.buttons.east);
|
||||
result.south = handle_button(state_south, controller_state.buttons.south);
|
||||
result.west = handle_button(state_west, controller_state.buttons.west);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t Menu::getCurrentSelection(Menu::Page page) {
|
||||
switch (page) {
|
||||
case Page::DeviceMode:
|
||||
return static_cast<uint8_t>(m_store->getUsbMode());
|
||||
break;
|
||||
case Page::LedBrightness:
|
||||
return m_store->getLedBrightness();
|
||||
break;
|
||||
case Page::Main:
|
||||
case Page::Bootsel:
|
||||
case Page::BootselMsg:
|
||||
case Page::None:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Menu::gotoPage(Menu::Page page) {
|
||||
m_state.page = page;
|
||||
m_state.selection = getCurrentSelection(page);
|
||||
}
|
||||
|
||||
void Menu::performSelectionAction(Menu::Descriptor::Action action) {
|
||||
auto descriptor_it = descriptors.find(m_state.page);
|
||||
if (descriptor_it == descriptors.end()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case Descriptor::Action::GotoPageDeviceMode:
|
||||
gotoPage(Page::DeviceMode);
|
||||
break;
|
||||
case Descriptor::Action::GotoPageLedBrightness:
|
||||
gotoPage(Page::LedBrightness);
|
||||
break;
|
||||
case Descriptor::Action::GotoPageBootsel:
|
||||
gotoPage(Page::Bootsel);
|
||||
break;
|
||||
case Descriptor::Action::ChangeUsbModeSwitchTatacon:
|
||||
m_store->setUsbMode(USB_MODE_SWITCH_TATACON);
|
||||
gotoPage(descriptor_it->second.parent);
|
||||
break;
|
||||
case Descriptor::Action::ChangeUsbModeSwitchHoripad:
|
||||
m_store->setUsbMode(USB_MODE_SWITCH_HORIPAD);
|
||||
gotoPage(descriptor_it->second.parent);
|
||||
break;
|
||||
case Descriptor::Action::ChangeUsbModeDS3:
|
||||
m_store->setUsbMode(USB_MODE_DUALSHOCK3);
|
||||
gotoPage(descriptor_it->second.parent);
|
||||
break;
|
||||
case Descriptor::Action::ChangeUsbModePS4Tatacon:
|
||||
m_store->setUsbMode(USB_MODE_PS4_TATACON);
|
||||
gotoPage(descriptor_it->second.parent);
|
||||
break;
|
||||
case Descriptor::Action::ChangeUsbModeDS4:
|
||||
m_store->setUsbMode(USB_MODE_DUALSHOCK4);
|
||||
gotoPage(descriptor_it->second.parent);
|
||||
break;
|
||||
case Descriptor::Action::ChangeUsbModeXbox360:
|
||||
m_store->setUsbMode(USB_MODE_XBOX360);
|
||||
gotoPage(descriptor_it->second.parent);
|
||||
break;
|
||||
case Descriptor::Action::ChangeUsbModeDebug:
|
||||
m_store->setUsbMode(USB_MODE_DEBUG);
|
||||
gotoPage(descriptor_it->second.parent);
|
||||
break;
|
||||
case Descriptor::Action::SetLedBrightness:
|
||||
gotoPage(descriptor_it->second.parent);
|
||||
break;
|
||||
case Descriptor::Action::DoRebootToBootsel:
|
||||
m_store->scheduleReboot(true);
|
||||
gotoPage(Page::BootselMsg);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::performValueAction(Menu::Descriptor::Action action, uint8_t value) {
|
||||
auto descriptor_it = descriptors.find(m_state.page);
|
||||
if (descriptor_it == descriptors.end()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case Descriptor::Action::SetLedBrightness:
|
||||
m_store->setLedBrightness(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::update(const InputState::Controller &controller_state) {
|
||||
InputState::Controller::Buttons pressed = checkPressed(controller_state);
|
||||
|
||||
auto descriptor_it = descriptors.find(m_state.page);
|
||||
if (descriptor_it == descriptors.end()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (descriptor_it->second.type == Descriptor::Type::RebootInfo) {
|
||||
m_active = false;
|
||||
} else if (pressed.north) {
|
||||
switch (descriptor_it->second.type) {
|
||||
case Descriptor::Type::Value:
|
||||
if (m_state.selection > 0) {
|
||||
m_state.selection--;
|
||||
performValueAction(descriptor_it->second.items.at(0).second, m_state.selection);
|
||||
}
|
||||
break;
|
||||
case Descriptor::Type::Selection:
|
||||
case Descriptor::Type::Root:
|
||||
if (m_state.selection == 0) {
|
||||
m_state.selection = descriptor_it->second.items.size() - 1;
|
||||
} else {
|
||||
m_state.selection--;
|
||||
}
|
||||
break;
|
||||
case Descriptor::Type::RebootInfo:
|
||||
break;
|
||||
}
|
||||
} else if (pressed.west) {
|
||||
switch (descriptor_it->second.type) {
|
||||
case Descriptor::Type::Value:
|
||||
if (m_state.selection < UINT8_MAX) {
|
||||
m_state.selection++;
|
||||
performValueAction(descriptor_it->second.items.at(0).second, m_state.selection);
|
||||
}
|
||||
break;
|
||||
case Descriptor::Type::Selection:
|
||||
case Descriptor::Type::Root:
|
||||
if (m_state.selection == descriptor_it->second.items.size() - 1) {
|
||||
m_state.selection = 0;
|
||||
} else {
|
||||
m_state.selection++;
|
||||
}
|
||||
break;
|
||||
case Descriptor::Type::RebootInfo:
|
||||
break;
|
||||
}
|
||||
} else if (pressed.south) {
|
||||
switch (descriptor_it->second.type) {
|
||||
case Descriptor::Type::Value:
|
||||
case Descriptor::Type::Selection:
|
||||
gotoPage(descriptor_it->second.parent);
|
||||
break;
|
||||
case Descriptor::Type::Root:
|
||||
m_active = false;
|
||||
break;
|
||||
case Descriptor::Type::RebootInfo:
|
||||
break;
|
||||
}
|
||||
} else if (pressed.east) {
|
||||
switch (descriptor_it->second.type) {
|
||||
case Descriptor::Type::Value:
|
||||
performSelectionAction(descriptor_it->second.items.at(0).second);
|
||||
break;
|
||||
case Descriptor::Type::Selection:
|
||||
case Descriptor::Type::Root:
|
||||
performSelectionAction(descriptor_it->second.items.at(m_state.selection).second);
|
||||
break;
|
||||
case Descriptor::Type::RebootInfo:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::active() { return m_active; }
|
||||
|
||||
Menu::State Menu::getState() { return m_state; }
|
||||
|
||||
} // namespace Doncon::Utils
|
@ -11,7 +11,11 @@ namespace Doncon::Utils {
|
||||
static uint8_t read_byte(uint32_t offset) { return *(reinterpret_cast<uint8_t *>(XIP_BASE + offset)); }
|
||||
|
||||
SettingsStore::SettingsStore()
|
||||
: m_store_cache({m_magic_byte, Config::Default::usb_mode, Config::Default::drum_config.trigger_thresholds, {}}),
|
||||
: m_store_cache({m_magic_byte,
|
||||
Config::Default::usb_mode,
|
||||
Config::Default::drum_config.trigger_thresholds,
|
||||
Config::Default::led_config.brightness,
|
||||
{}}),
|
||||
m_dirty(true), m_scheduled_reboot(RebootType::None) {
|
||||
uint32_t current_page = m_flash_offset + m_flash_size - m_store_size;
|
||||
bool found_valid = false;
|
||||
@ -53,6 +57,14 @@ void SettingsStore::setTriggerThresholds(Peripherals::Drum::Config::Thresholds t
|
||||
}
|
||||
Peripherals::Drum::Config::Thresholds SettingsStore::getTriggerThresholds() { return m_store_cache.trigger_thresholds; }
|
||||
|
||||
void SettingsStore::setLedBrightness(uint8_t brightness) {
|
||||
if (m_store_cache.led_brightness != brightness) {
|
||||
m_store_cache.led_brightness = brightness;
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
uint8_t SettingsStore::getLedBrightness() { return m_store_cache.led_brightness; }
|
||||
|
||||
void SettingsStore::store() {
|
||||
if (m_dirty) {
|
||||
multicore_lockout_start_blocking();
|
||||
|
Loading…
Reference in New Issue
Block a user