From 951890eaffccf72be7e826fccfbc5d2e98c6e7ab Mon Sep 17 00:00:00 2001 From: Kyle Reed <3761006+kallanreed@users.noreply.github.com> Date: Fri, 29 Sep 2023 09:55:27 -0700 Subject: [PATCH] Add Binder helper methods (#1465) * Add binder, fix focus on remote btn delete * Use binder for freqman edit --- firmware/application/apps/ui_freqman.cpp | 68 +++----------- firmware/application/apps/ui_remote.cpp | 47 +++------- firmware/application/apps/ui_remote.hpp | 1 - firmware/application/binder.hpp | 108 +++++++++++++++++++++++ 4 files changed, 133 insertions(+), 91 deletions(-) create mode 100644 firmware/application/binder.hpp diff --git a/firmware/application/apps/ui_freqman.cpp b/firmware/application/apps/ui_freqman.cpp index 5fb3cc74..a48d96b0 100644 --- a/firmware/application/apps/ui_freqman.cpp +++ b/firmware/application/apps/ui_freqman.cpp @@ -23,6 +23,7 @@ #include "ui_freqman.hpp" +#include "binder.hpp" #include "event_m0.hpp" #include "portapack.hpp" #include "rtc_time.hpp" @@ -376,69 +377,26 @@ FrequencyEditView::FrequencyEditView( field_step.options().insert( field_step.options().begin(), {"None", -1}); - field_type.set_by_value((int32_t)entry_.type); - field_type.on_change = [this](size_t, auto value) { - entry_.type = static_cast(value); + bind(field_type, entry_.type, [this](auto) { refresh_ui(); - }; + }); - // TODO: this pattern should be able to be wrapped up. - field_freq_a.set_value(entry_.frequency_a); - field_freq_a.on_change = [this](rf::Frequency f) { - entry_.frequency_a = f; + bind(field_freq_a, entry_.frequency_a, nav, [this](auto) { refresh_ui(); - }; - field_freq_a.on_edit = [this]() { - auto freq_view = nav_.push(field_freq_a.value()); - freq_view->on_changed = [this](rf::Frequency f) { - field_freq_a.set_value(f); - }; - }; + }); - field_freq_b.set_value(entry_.frequency_b); - field_freq_b.on_change = [this](rf::Frequency f) { - entry_.frequency_b = f; + bind(field_freq_b, entry_.frequency_b, nav, [this](auto) { refresh_ui(); - }; - field_freq_b.on_edit = [this]() { - auto freq_view = nav_.push(field_freq_b.value()); - freq_view->on_changed = [this](rf::Frequency f) { - field_freq_b.set_value(f); - }; - }; + }); - field_modulation.set_by_value((int32_t)entry_.modulation); - field_modulation.on_change = [this](size_t, auto value) { - entry_.modulation = static_cast(value); + bind(field_modulation, entry_.modulation, [this](auto) { populate_bandwidth_options(); - }; + }); - field_bandwidth.set_by_value((int32_t)entry_.bandwidth); - field_bandwidth.on_change = [this](size_t, auto value) { - entry_.bandwidth = static_cast(value); - }; - - field_step.set_by_value((int32_t)entry_.step); - field_step.on_change = [this](size_t, auto value) { - entry_.step = static_cast(value); - }; - - field_tone.set_by_value((int32_t)entry_.tone); - field_tone.on_change = [this](size_t, auto value) { - entry_.tone = static_cast(value); - }; - - field_description.set_text(entry_.description); - field_description.on_change = [this](TextField& tf) { - entry_.description = tf.get_text(); - }; - field_description.on_select = [this](TextField& tf) { - temp_buffer_ = tf.get_text(); - text_prompt(nav_, temp_buffer_, FreqManBaseView::desc_edit_max, - [this, &tf](std::string& new_desc) { - tf.set_text(new_desc); - }); - }; + bind(field_bandwidth, entry_.bandwidth); + bind(field_step, entry_.step); + bind(field_tone, entry_.tone); + bind(field_description, entry_.description, nav_); button_save.on_select = [this](Button&) { if (on_save) diff --git a/firmware/application/apps/ui_remote.cpp b/firmware/application/apps/ui_remote.cpp index d4c973b5..7b0a1310 100644 --- a/firmware/application/apps/ui_remote.cpp +++ b/firmware/application/apps/ui_remote.cpp @@ -21,6 +21,7 @@ #include "ui_remote.hpp" +#include "binder.hpp" #include "convert.hpp" #include "file_reader.hpp" #include "io_convert.hpp" @@ -239,19 +240,9 @@ RemoteEntryEditView::RemoteEntryEditView( &button_done, }); - // TODO: It's time to make field bindings and clean this mess up. - field_name.on_change = [this](TextField& tf) { - entry_.name = tf.get_text(); - button_preview.set_text(entry_.name); - }; - field_name.on_select = [this, &nav](TextField& tf) { - temp_buffer_ = tf.get_text(); - text_prompt(nav, temp_buffer_, text_edit_max, - [this, &tf](std::string& str) { - tf.set_text(str); - }); - }; - field_name.set_text(entry_.name); + bind(field_name, entry_.name, nav, [this](auto& v) { + button_preview.set_text(v); + }); field_path.on_select = [this, &nav](TextField&) { auto open_view = nav.push(".C*"); @@ -262,31 +253,16 @@ RemoteEntryEditView::RemoteEntryEditView( }; }; - field_freq.on_edit = [this, &nav]() { - auto freq_view = nav.push(entry_.metadata.center_frequency); - freq_view->on_changed = [this](rf::Frequency f) { - entry_.metadata.center_frequency = f; - field_freq.set_value(f); - }; - }; - - field_icon_index.on_change = [this](int32_t v) { - entry_.icon = v; + bind(field_freq, entry_.metadata.center_frequency, nav); + bind(field_icon_index, entry_.icon, [this](auto v) { button_preview.set_bitmap(RemoteIcons::get(v)); - }; - field_icon_index.set_value(entry.icon); - - field_fg_color_index.on_change = [this](int32_t v) { - entry_.fg_color = v; + }); + bind(field_fg_color_index, entry_.fg_color, [this](auto v) { button_preview.set_color(RemoteColors::get(v)); - }; - field_fg_color_index.set_value(entry_.fg_color); - - field_bg_color_index.on_change = [this](int32_t v) { - entry_.bg_color = v; + }); + bind(field_bg_color_index, entry_.bg_color, [this](auto) { button_preview.set_dirty(); - }; - field_bg_color_index.set_value(entry_.bg_color); + }); button_delete.on_select = [this, &nav]() { nav.display_modal( @@ -485,6 +461,7 @@ void RemoteView::edit_button(RemoteButton& btn) { nav_.set_on_pop([this]() { refresh_ui(); set_needs_save(); + focus(); // Need to refocus after refreshing the buttons. }); edit_view->on_delete = [this](RemoteEntryModel& to_delete) { diff --git a/firmware/application/apps/ui_remote.hpp b/firmware/application/apps/ui_remote.hpp index 7c91cfde..bd1aa344 100644 --- a/firmware/application/apps/ui_remote.hpp +++ b/firmware/application/apps/ui_remote.hpp @@ -195,7 +195,6 @@ class RemoteEntryEditView : public View { private: RemoteEntryModel& entry_; - std::string temp_buffer_{}; void refresh_ui(); void load_path(std::filesystem::path&& path); diff --git a/firmware/application/binder.hpp b/firmware/application/binder.hpp new file mode 100644 index 00000000..eabbd03b --- /dev/null +++ b/firmware/application/binder.hpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 Kyle Reed + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __BINDER_H__ +#define __BINDER_H__ + +#include "ui.hpp" +#include "ui_navigation.hpp" +#include "ui_widget.hpp" + +namespace ui { + +/* Default no-op binding callback. */ +struct NoOp { + NoOp() {} + template + void operator()(T) const {} +}; + +/* The gist of the bind functions is to bind a control + * to a value. + * bind(control, value) + * + * An optional callback can be specified which will be called + * when the control's value is changed. + * bind(control, value, [](auto new_val) { ... }); + * + * Some controls need a reference to the NavigationView so + * edit dialogs (like text_prompt) can be launched. + * bind(control, value, nav, [](auto new_val) { ... }); + * + * Be careful with lifetime of captured objects. Most things + * are captured by reference so the caller will need to ensure + * adequate lifetime of the referenced instances. + */ + +template +void bind(NumberField& field, T& value, Fn fn = Fn{}) { + field.set_value(value); + field.on_change = [&value, fn](int32_t v) { + value = v; + fn(value); + }; +} + +template +void bind(OptionsField& field, T& value, Fn fn = Fn{}) { + field.set_by_value(static_cast(value)); + field.on_change = [&value, fn](size_t, auto v) { + value = static_cast(v); + fn(value); + }; +} + +template +void bind(FrequencyField& field, T& value, NavigationView& nav, Fn fn = Fn{}) { + field.set_value(value); + field.on_change = [&value, fn](rf::Frequency f) { + value = f; + fn(value); + }; + field.on_edit = [&field, &value, &nav]() { + auto freq_view = nav.push(value); + freq_view->on_changed = [&field](rf::Frequency f) { + field.set_value(f); + }; + }; +} + +template +void bind(TextField& field, T& value, NavigationView& nav, Fn fn = Fn{}) { + field.set_text(value); + field.on_change = [&value, fn](TextField& tf) { + value = tf.get_text(); + fn(value); + }; + // text_prompt needs a mutable working buffer. + // Capture a new string and make the lambda mutable so it can be modified. + field.on_select = [&nav, buf = std::string{}](TextField& tf) mutable { + buf = tf.get_text(); + text_prompt(nav, buf, /*max_length*/ 255, + [&tf](std::string& str) { + tf.set_text(str); + }); + }; +} + +} // namespace ui + +#endif /*__BINDER_H__*/ \ No newline at end of file