diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index c3313820..c8d64bdd 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -67,14 +67,14 @@ void AudioState::react(const system_fsm::StorageMounted& ev) { void AudioState::react(const system_fsm::KeyUpChanged& ev) { if (ev.falling && sI2SOutput->AdjustVolumeUp()) { ESP_LOGI(kTag, "volume up!"); - events::Dispatch({}); + events::Ui().Dispatch(VolumeChanged{}); } } void AudioState::react(const system_fsm::KeyDownChanged& ev) { if (ev.falling && sI2SOutput->AdjustVolumeDown()) { ESP_LOGI(kTag, "volume down!"); - events::Dispatch({}); + events::Ui().Dispatch(VolumeChanged{}); } } diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp index 7d117cb4..ae4964a6 100644 --- a/src/audio/audio_task.cpp +++ b/src/audio/audio_task.cpp @@ -84,13 +84,11 @@ auto Timer::AddBytes(std::size_t bytes) -> void { } if (incremented) { - ESP_LOGI("timer", "new time %lu", current_seconds_); - /* - events::Dispatch(PlaybackUpdate{ + // ESP_LOGI("timer", "new time %lu", current_seconds_); + events::Audio().Dispatch(PlaybackUpdate{ .seconds_elapsed = current_seconds_, .seconds_total = 0, - }); - */ + }); } } diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp index 6a320a5a..811c2702 100644 --- a/src/audio/fatfs_audio_input.cpp +++ b/src/audio/fatfs_audio_input.cpp @@ -135,7 +135,7 @@ auto FileStreamer::CloseFile() -> void { ESP_LOGI(kTag, "closing file"); f_close(file_.get()); file_ = {}; - events::Dispatch({}); + events::Audio().Dispatch(internal::InputFileClosed{}); } FatfsAudioInput::FatfsAudioInput( @@ -296,7 +296,7 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> void { streamer_->Restart(std::move(file)); - events::Dispatch({}); + events::Audio().Dispatch(internal::InputFileOpened{}); } auto FatfsAudioInput::CloseCurrentFile() -> void { diff --git a/src/audio/track_queue.cpp b/src/audio/track_queue.cpp index 721329f9..6f17ad33 100644 --- a/src/audio/track_queue.cpp +++ b/src/audio/track_queue.cpp @@ -81,45 +81,57 @@ auto TrackQueue::GetUpcoming(std::size_t limit) const auto TrackQueue::AddNext(database::TrackId t) -> void { const std::lock_guard lock(mutex_); enqueued_.push_front(t); - events::Dispatch( - QueueUpdate{.current_changed = enqueued_.size() < 2}); + + QueueUpdate ev{.current_changed = enqueued_.size() < 2}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } auto TrackQueue::AddNext(std::shared_ptr src) -> void { const std::lock_guard lock(mutex_); enqueued_.push_front(src); - events::Dispatch( - QueueUpdate{.current_changed = enqueued_.size() < 2}); + + QueueUpdate ev{.current_changed = enqueued_.size() < 2}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } auto TrackQueue::IncludeNext(std::shared_ptr src) -> void { const std::lock_guard lock(mutex_); enqueued_.push_front(src); - events::Dispatch( - QueueUpdate{.current_changed = enqueued_.size() < 2}); + + QueueUpdate ev{.current_changed = enqueued_.size() < 2}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } auto TrackQueue::AddLast(database::TrackId t) -> void { const std::lock_guard lock(mutex_); enqueued_.push_back(t); - events::Dispatch( - QueueUpdate{.current_changed = enqueued_.size() < 2}); + + QueueUpdate ev{.current_changed = enqueued_.size() < 2}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } auto TrackQueue::AddLast(std::shared_ptr src) -> void { const std::lock_guard lock(mutex_); enqueued_.push_back(src); - events::Dispatch( - QueueUpdate{.current_changed = enqueued_.size() < 2}); + + QueueUpdate ev{.current_changed = enqueued_.size() < 2}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } auto TrackQueue::IncludeLast(std::shared_ptr src) -> void { const std::lock_guard lock(mutex_); enqueued_.push_back(src); - events::Dispatch( - QueueUpdate{.current_changed = enqueued_.size() < 2}); + + QueueUpdate ev{.current_changed = enqueued_.size() < 2}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } auto TrackQueue::Next() -> void { @@ -149,8 +161,9 @@ auto TrackQueue::Next() -> void { } } - events::Dispatch( - QueueUpdate{.current_changed = true}); + QueueUpdate ev{.current_changed = true}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } auto TrackQueue::Previous() -> void { @@ -161,7 +174,9 @@ auto TrackQueue::Previous() -> void { auto src = std::get>( enqueued_.front()); if (src->Previous()) { - events::Dispatch({}); + QueueUpdate ev{.current_changed = false}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); return; } } @@ -180,16 +195,19 @@ auto TrackQueue::Previous() -> void { } played_.pop_front(); - events::Dispatch( - QueueUpdate{.current_changed = true}); + QueueUpdate ev{.current_changed = true}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } auto TrackQueue::Clear() -> void { const std::lock_guard lock(mutex_); + QueueUpdate ev{.current_changed = !enqueued_.empty()}; played_.clear(); enqueued_.clear(); - events::Dispatch( - QueueUpdate{.current_changed = true}); + + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } } // namespace audio diff --git a/src/drivers/gpios.cpp b/src/drivers/gpios.cpp index 1d1f5281..f6293697 100644 --- a/src/drivers/gpios.cpp +++ b/src/drivers/gpios.cpp @@ -59,11 +59,8 @@ constexpr std::pair unpack(uint16_t ba) { } void interrupt_isr(void* arg) { - Gpios* instance = reinterpret_cast(arg); - auto listener = instance->listener(); - if (listener != nullptr) { - std::invoke(*listener); - } + SemaphoreHandle_t sem = reinterpret_cast(arg); + xSemaphoreGive(sem); } auto Gpios::Create() -> Gpios* { @@ -79,7 +76,7 @@ auto Gpios::Create() -> Gpios* { Gpios::Gpios() : ports_(pack(kPortADefault, kPortBDefault)), inputs_(0), - listener_(nullptr) { + read_pending_(xSemaphoreCreateBinary()) { gpio_config_t config{ .pin_bit_mask = static_cast(1) << GPIO_NUM_34, .mode = GPIO_MODE_INPUT, @@ -90,7 +87,6 @@ Gpios::Gpios() gpio_config(&config); gpio_install_isr_service(ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM); - gpio_isr_handler_add(GPIO_NUM_34, &interrupt_isr, this); } Gpios::~Gpios() { @@ -145,4 +141,8 @@ auto Gpios::Read() -> bool { return true; } +auto Gpios::InstallReadPendingISR() -> void { + gpio_isr_handler_add(GPIO_NUM_34, &interrupt_isr, read_pending_); +} + } // namespace drivers diff --git a/src/drivers/include/gpios.hpp b/src/drivers/include/gpios.hpp index da997843..5ac475bf 100644 --- a/src/drivers/include/gpios.hpp +++ b/src/drivers/include/gpios.hpp @@ -107,9 +107,8 @@ class Gpios : public IGpios { */ auto Read(void) -> bool; - auto listener() -> std::function* { return listener_; } - - auto set_listener(std::function* l) -> void { listener_ = l; } + auto InstallReadPendingISR() -> void; + auto IsReadPending() -> SemaphoreHandle_t { return read_pending_; } // Not copyable or movable. There should usually only ever be once instance // of this class, and that instance will likely have a static lifetime. @@ -122,7 +121,7 @@ class Gpios : public IGpios { std::atomic ports_; std::atomic inputs_; - std::function* listener_; + SemaphoreHandle_t read_pending_; }; } // namespace drivers diff --git a/src/events/event_queue.cpp b/src/events/event_queue.cpp index 8d60218a..d3a62ef6 100644 --- a/src/events/event_queue.cpp +++ b/src/events/event_queue.cpp @@ -6,34 +6,42 @@ #include "event_queue.hpp" +#include "audio_fsm.hpp" #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "freertos/queue.h" +#include "system_fsm.hpp" +#include "ui_fsm.hpp" namespace events { -static const std::size_t kMaxPendingEvents = 16; +namespace queues { +static Queue sSystemAndAudio; +static Queue sUi; -EventQueue::EventQueue() - : system_handle_(xQueueCreate(kMaxPendingEvents, sizeof(WorkItem*))), - ui_handle_(xQueueCreate(kMaxPendingEvents, sizeof(WorkItem*))) {} +auto SystemAndAudio() -> Queue* { + return &sSystemAndAudio; +} + +auto Ui() -> Queue* { + return &sUi; +} +} // namespace queues + +static Dispatcher sSystem{queues::SystemAndAudio()}; +static Dispatcher sAudio{queues::SystemAndAudio()}; +static Dispatcher sUi{queues::Ui()}; -auto ServiceQueue(QueueHandle_t queue, TickType_t max_wait_time) -> bool { - WorkItem* item; - if (xQueueReceive(queue, &item, max_wait_time)) { - (*item)(); - delete item; - return true; - } - return false; +auto System() -> Dispatcher& { + return sSystem; } -auto EventQueue::ServiceSystemAndAudio(TickType_t max_wait_time) -> bool { - return ServiceQueue(system_handle_, max_wait_time); +auto Audio() -> Dispatcher& { + return sAudio; } -auto EventQueue::ServiceUi(TickType_t max_wait_time) -> bool { - return ServiceQueue(ui_handle_, max_wait_time); +auto Ui() -> Dispatcher& { + return sUi; } } // namespace events diff --git a/src/events/include/event_queue.hpp b/src/events/include/event_queue.hpp index 95c331d5..1ea67446 100644 --- a/src/events/include/event_queue.hpp +++ b/src/events/include/event_queue.hpp @@ -7,6 +7,8 @@ #pragma once #include +#include +#include #include #include "audio_fsm.hpp" @@ -20,62 +22,78 @@ namespace events { -typedef std::function WorkItem; - -/* - * Handles communication of events between the system's state machines. Each - * event will be dispatched separately to each FSM, on the correct task for - * that FSM. - */ -class EventQueue { +class Queue { public: - static EventQueue& GetInstance() { - static EventQueue instance; - return instance; - } + Queue() : has_events_(xSemaphoreCreateBinary()), mut_(), events_() {} - template - auto DispatchFromISR(const Event& ev) -> bool { - WorkItem* item = new WorkItem([=]() { - tinyfsm::FsmList::template dispatch(ev); - }); - BaseType_t ret; - xQueueSendFromISR(system_handle_, &item, &ret); - return ret; + auto Add(std::function fn) { + { + std::lock_guard lock{mut_}; + events_.push(fn); + } + xSemaphoreGive(has_events_); } - template - auto Dispatch(const Event& ev) -> void { - WorkItem* item = new WorkItem( - [=]() { tinyfsm::FsmList::template dispatch(ev); }); - if (std::is_same()) { - xQueueSend(ui_handle_, &item, portMAX_DELAY); - } else { - xQueueSend(system_handle_, &item, portMAX_DELAY); + auto Service(TickType_t max_wait) -> bool { + bool res = xSemaphoreTake(has_events_, max_wait); + if (!res) { + return false; } - Dispatch(ev); - } - template - auto Dispatch(const Event& ev) -> void {} + bool had_work = false; + for (;;) { + std::function fn; + { + std::lock_guard lock{mut_}; + if (events_.empty()) { + return had_work; + } + had_work = true; + fn = events_.front(); + events_.pop(); + } + std::invoke(fn); + } + } - auto ServiceSystemAndAudio(TickType_t max_wait_time) -> bool; - auto ServiceUi(TickType_t max_wait_time) -> bool; + auto has_events() -> SemaphoreHandle_t { return has_events_; } - EventQueue(EventQueue const&) = delete; - void operator=(EventQueue const&) = delete; + Queue(Queue const&) = delete; + void operator=(Queue const&) = delete; private: - EventQueue(); + SemaphoreHandle_t has_events_; + std::mutex mut_; + std::queue> events_; +}; + +template +class Dispatcher { + public: + Dispatcher(Queue* queue) : queue_(queue) {} + + template + auto Dispatch(const Event& ev) -> void { + auto dispatch_fn = [=]() { + tinyfsm::FsmList::template dispatch(ev); + }; + queue_->Add(dispatch_fn); + } + + Dispatcher(Dispatcher const&) = delete; + void operator=(Dispatcher const&) = delete; - QueueHandle_t system_handle_; - QueueHandle_t ui_handle_; + private: + Queue* queue_; }; -template -auto Dispatch(const Event& ev) -> void { - EventQueue& queue = EventQueue::GetInstance(); - queue.Dispatch(ev); -} +namespace queues { +auto SystemAndAudio() -> Queue*; +auto Ui() -> Queue*; +} // namespace queues + +auto System() -> Dispatcher&; +auto Audio() -> Dispatcher&; +auto Ui() -> Dispatcher&; } // namespace events diff --git a/src/main/main.cpp b/src/main/main.cpp index e2c187b1..d283b01d 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -6,6 +6,9 @@ #include "freertos/portmacro.h" +#include "gpios.hpp" +#include "i2c.hpp" +#include "system_events.hpp" #include "tinyfsm.hpp" #include "audio_fsm.hpp" @@ -14,11 +17,24 @@ #include "ui_fsm.hpp" extern "C" void app_main(void) { + ESP_ERROR_CHECK(drivers::init_i2c()); + drivers::Gpios* gpios = system_fsm::SystemState::early_init_gpios(); + + QueueSetHandle_t set = xQueueCreateSet(2); + auto* event_queue = events::queues::SystemAndAudio(); + xQueueAddToSet(event_queue->has_events(), set); + xQueueAddToSet(gpios->IsReadPending(), set); + tinyfsm::FsmList::start(); - auto& queue = events::EventQueue::GetInstance(); while (1) { - queue.ServiceSystemAndAudio(portMAX_DELAY); + QueueSetMemberHandle_t member = xQueueSelectFromSet(set, portMAX_DELAY); + if (member == event_queue->has_events()) { + event_queue->Service(0); + } else if (member == gpios->IsReadPending()) { + xSemaphoreTake(member, 0); + events::System().Dispatch(system_fsm::internal::GpioInterrupt{}); + } } } diff --git a/src/system_fsm/booting.cpp b/src/system_fsm/booting.cpp index 076f4570..4686748e 100644 --- a/src/system_fsm/booting.cpp +++ b/src/system_fsm/booting.cpp @@ -29,36 +29,22 @@ namespace states { static const char kTag[] = "BOOT"; -static std::function sGpiosCallback = []() { - events::EventQueue::GetInstance().DispatchFromISR(internal::GpioInterrupt{}); -}; - auto Booting::entry() -> void { ESP_LOGI(kTag, "beginning tangara boot"); ESP_LOGI(kTag, "installing early drivers"); // I2C and SPI are both always needed. We can't even power down or show an // error without these. - ESP_ERROR_CHECK(drivers::init_i2c()); ESP_ERROR_CHECK(drivers::init_spi()); - - // These drivers are the bare minimum to even show an error. If these fail, - // then the system is completely hosed. - sGpios.reset(drivers::Gpios::Create()); - assert(sGpios != nullptr); - - sGpios->set_listener(&sGpiosCallback); + sGpios->InstallReadPendingISR(); // Start bringing up LVGL now, since we have all of its prerequisites. sTrackQueue.reset(new audio::TrackQueue()); - /* ESP_LOGI(kTag, "starting ui"); if (!ui::UiState::Init(sGpios.get(), sTrackQueue.get())) { - events::Dispatch( - FatalError()); + events::System().Dispatch(FatalError{}); return; } - */ // Install everything else that is certain to be needed. ESP_LOGI(kTag, "installing remaining drivers"); @@ -67,8 +53,8 @@ auto Booting::entry() -> void { sTagParser.reset(new database::TagParserImpl()); if (!sSamd || !sBattery) { - events::Dispatch( - FatalError()); + events::System().Dispatch(FatalError{}); + events::Ui().Dispatch(FatalError{}); return; } @@ -78,13 +64,14 @@ auto Booting::entry() -> void { ESP_LOGI(kTag, "starting audio"); if (!audio::AudioState::Init(sGpios.get(), sDatabase, sTagParser, sTrackQueue.get())) { - events::Dispatch( - FatalError()); + events::System().Dispatch(FatalError{}); + events::Ui().Dispatch(FatalError{}); return; } - events::Dispatch( - BootComplete()); + events::System().Dispatch(BootComplete{}); + events::Audio().Dispatch(BootComplete{}); + events::Ui().Dispatch(BootComplete{}); } auto Booting::exit() -> void { diff --git a/src/system_fsm/include/system_fsm.hpp b/src/system_fsm/include/system_fsm.hpp index 03b25156..6f0eb563 100644 --- a/src/system_fsm/include/system_fsm.hpp +++ b/src/system_fsm/include/system_fsm.hpp @@ -33,6 +33,8 @@ class SystemState : public tinyfsm::Fsm { public: virtual ~SystemState() {} + static auto early_init_gpios() -> drivers::Gpios*; + virtual void entry() {} virtual void exit() {} diff --git a/src/system_fsm/running.cpp b/src/system_fsm/running.cpp index a46cb8dc..0e988193 100644 --- a/src/system_fsm/running.cpp +++ b/src/system_fsm/running.cpp @@ -33,8 +33,10 @@ void Running::entry() { auto storage_res = drivers::SdStorage::Create(sGpios.get()); if (storage_res.has_error()) { ESP_LOGW(kTag, "failed to mount!"); - events::Dispatch( - StorageError()); + + events::System().Dispatch(StorageError{}); + events::Audio().Dispatch(StorageError{}); + events::Ui().Dispatch(StorageError{}); return; } sStorage.reset(storage_res.value()); @@ -45,16 +47,19 @@ void Running::entry() { auto database_res = database::Database::Open(sFileGatherer, sTagParser.get()); if (database_res.has_error()) { ESP_LOGW(kTag, "failed to open!"); - events::Dispatch( - StorageError()); + events::System().Dispatch(StorageError{}); + events::Audio().Dispatch(StorageError{}); + events::Ui().Dispatch(StorageError{}); return; } sDatabase.reset(database_res.value()); console::AppConsole::sDatabase = sDatabase; ESP_LOGI(kTag, "storage loaded okay"); - events::Dispatch( - StorageMounted{.db = sDatabase}); + StorageMounted ev{.db = sDatabase}; + events::System().Dispatch(ev); + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } void Running::exit() { @@ -63,8 +68,7 @@ void Running::exit() { } void Running::react(const StorageUnmountRequested& ev) { - events::Dispatch( - internal::ReadyToUnmount()); + events::System().Dispatch(internal::ReadyToUnmount{}); } void Running::react(const internal::ReadyToUnmount& ev) { diff --git a/src/system_fsm/system_fsm.cpp b/src/system_fsm/system_fsm.cpp index c029c6bf..27e57b22 100644 --- a/src/system_fsm/system_fsm.cpp +++ b/src/system_fsm/system_fsm.cpp @@ -7,6 +7,7 @@ #include "system_fsm.hpp" #include "audio_fsm.hpp" #include "event_queue.hpp" +#include "gpios.hpp" #include "relative_wheel.hpp" #include "system_events.hpp" #include "tag_parser.hpp" @@ -30,13 +31,18 @@ std::shared_ptr SystemState::sTrackQueue; console::AppConsole* SystemState::sAppConsole; +auto SystemState::early_init_gpios() -> drivers::Gpios* { + sGpios.reset(drivers::Gpios::Create()); + return sGpios.get(); +} + void SystemState::react(const FatalError& err) { if (!is_in_state()) { transit(); } } -void SystemState::react(const internal::GpioInterrupt& ev) { +void SystemState::react(const internal::GpioInterrupt&) { bool prev_key_up = sGpios->Get(drivers::Gpios::Pin::kKeyUp); bool prev_key_down = sGpios->Get(drivers::Gpios::Pin::kKeyDown); bool prev_key_lock = sGpios->Get(drivers::Gpios::Pin::kKeyLock); @@ -50,20 +56,23 @@ void SystemState::react(const internal::GpioInterrupt& ev) { bool has_headphones = !sGpios->Get(drivers::Gpios::Pin::kPhoneDetect); if (key_up != prev_key_up) { - events::Dispatch( - {.falling = prev_key_up}); + KeyUpChanged ev{.falling = prev_key_up}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } if (key_down != prev_key_down) { - events::Dispatch( - {.falling = prev_key_down}); + KeyDownChanged ev{.falling = prev_key_up}; + events::Audio().Dispatch(ev); + events::Ui().Dispatch(ev); } if (key_lock != prev_key_lock) { - events::Dispatch( - {.falling = prev_key_lock}); + KeyLockChanged ev{.falling = prev_key_up}; + events::System().Dispatch(ev); + events::Ui().Dispatch(ev); } if (has_headphones != prev_has_headphones) { - events::Dispatch( - {.falling = prev_has_headphones}); + HasPhonesChanged ev{.falling = prev_key_up}; + events::Audio().Dispatch(ev); } } diff --git a/src/ui/lvgl_task.cpp b/src/ui/lvgl_task.cpp index f746734f..06a6b28b 100644 --- a/src/ui/lvgl_task.cpp +++ b/src/ui/lvgl_task.cpp @@ -62,9 +62,9 @@ void LvglMain(std::weak_ptr weak_touch_wheel, TouchWheelEncoder encoder(weak_touch_wheel); std::shared_ptr current_screen; - auto& events = events::EventQueue::GetInstance(); + auto* events = events::queues::Ui(); while (1) { - while (events.ServiceUi(0)) { + while (events->Service(0)) { } std::shared_ptr screen = UiState::current_screen(); diff --git a/src/ui/screen_menu.cpp b/src/ui/screen_menu.cpp index 37254f92..4730db84 100644 --- a/src/ui/screen_menu.cpp +++ b/src/ui/screen_menu.cpp @@ -33,8 +33,7 @@ static void item_click_cb(lv_event_t* ev) { database::IndexInfo* index = reinterpret_cast(ev->user_data); - events::Dispatch( - internal::IndexSelected{.index = *index}); + events::Ui().Dispatch(internal::IndexSelected{.index = *index}); } Menu::Menu(std::vector indexes) : indexes_(indexes) { diff --git a/src/ui/screen_track_browser.cpp b/src/ui/screen_track_browser.cpp index 07977710..4a39578e 100644 --- a/src/ui/screen_track_browser.cpp +++ b/src/ui/screen_track_browser.cpp @@ -137,12 +137,11 @@ auto TrackBrowser::OnItemClicked(lv_event_t* ev) -> void { for (const auto& page : current_pages_) { for (std::size_t i = 0; i < page->values().size(); i++) { if (index == 0) { - events::Dispatch( - internal::RecordSelected{ - .initial_page = initial_page_, - .page = page, - .record = i, - }); + events::Ui().Dispatch(internal::RecordSelected{ + .initial_page = initial_page_, + .page = page, + .record = i, + }); return; } index--; diff --git a/src/ui/widget_top_bar.cpp b/src/ui/widget_top_bar.cpp index 9f192c84..e5d51350 100644 --- a/src/ui/widget_top_bar.cpp +++ b/src/ui/widget_top_bar.cpp @@ -20,7 +20,7 @@ namespace ui { namespace widgets { static void back_click_cb(lv_event_t* ev) { - events::Dispatch({}); + events::Ui().Dispatch(internal::BackPressed{}); } TopBar::TopBar(lv_obj_t* parent, const Configuration& config) {