mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-08-13 06:17:42 +00:00
SymField rewrite (#1444)
* First WIP symfield * Cleanup widget code * Rebase and format * Fix 'to_integer' bug, fix siggen UI. * to_string_hex fix, unit tests for string code * Pass instance in callback * Fix on_change callbacks * Fix keyfob (not active) * to_byte_array, coaster tx cleanup * Add to_byte_array tests * Changes in ui_numbers * Fix ui_encoders * Format * Fix modemsetup view's symfields * Remove header * Format
This commit is contained in:
@@ -37,6 +37,29 @@ struct Style {
|
||||
Style invert() const;
|
||||
};
|
||||
|
||||
/* Sometimes mutation is just the more readable thing... */
|
||||
struct MutableStyle {
|
||||
const Font* font;
|
||||
Color background;
|
||||
Color foreground;
|
||||
|
||||
MutableStyle(const Style& s)
|
||||
: font{&s.font},
|
||||
background{s.background},
|
||||
foreground{s.foreground} {}
|
||||
|
||||
void invert() {
|
||||
std::swap(background, foreground);
|
||||
}
|
||||
|
||||
operator Style() const {
|
||||
return {
|
||||
.font = *font,
|
||||
.background = background,
|
||||
.foreground = foreground};
|
||||
}
|
||||
};
|
||||
|
||||
class Widget;
|
||||
|
||||
class Painter {
|
||||
|
@@ -1813,7 +1813,7 @@ void NumberField::set_value(int32_t new_value, bool trigger_change) {
|
||||
new_value = range.second + new_value + 1;
|
||||
}
|
||||
|
||||
new_value = clip_value(new_value);
|
||||
new_value = clip(new_value, range.first, range.second);
|
||||
|
||||
if (new_value != value()) {
|
||||
value_ = new_value;
|
||||
@@ -1868,169 +1868,182 @@ bool NumberField::on_touch(const TouchEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t NumberField::clip_value(int32_t value) {
|
||||
if (value > range.second) {
|
||||
value = range.second;
|
||||
}
|
||||
if (value < range.first) {
|
||||
value = range.first;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/* SymField **************************************************************/
|
||||
|
||||
SymField::SymField(
|
||||
Point parent_pos,
|
||||
size_t length,
|
||||
symfield_type type)
|
||||
: Widget{{parent_pos, {static_cast<ui::Dim>(8 * length), 16}}},
|
||||
length_{length},
|
||||
type_{type} {
|
||||
uint32_t c;
|
||||
Type type,
|
||||
bool explicit_edits)
|
||||
: Widget{{parent_pos, {char_width * (int)length, 16}}},
|
||||
type_{type},
|
||||
explicit_edits_{explicit_edits} {
|
||||
if (length == 0)
|
||||
length = 1;
|
||||
|
||||
// Auto-init
|
||||
if (type == SYMFIELD_OCT) {
|
||||
for (c = 0; c < length; c++)
|
||||
set_symbol_list(c, "01234567");
|
||||
} else if (type == SYMFIELD_DEC) {
|
||||
for (c = 0; c < length; c++)
|
||||
set_symbol_list(c, "0123456789");
|
||||
} else if (type == SYMFIELD_HEX) {
|
||||
for (c = 0; c < length; c++)
|
||||
set_symbol_list(c, "0123456789ABCDEF");
|
||||
} else if (type == SYMFIELD_ALPHANUM) {
|
||||
for (c = 0; c < length; c++)
|
||||
set_symbol_list(c, " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
selected_ = length - 1;
|
||||
value_.resize(length);
|
||||
|
||||
switch (type) {
|
||||
case Type::Oct:
|
||||
set_symbol_list("01234567");
|
||||
break;
|
||||
|
||||
case Type::Dec:
|
||||
set_symbol_list("0123456789");
|
||||
break;
|
||||
|
||||
case Type::Hex:
|
||||
set_symbol_list("0123456789ABCDEF");
|
||||
break;
|
||||
|
||||
case Type::Alpha:
|
||||
set_symbol_list(" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
break;
|
||||
|
||||
default:
|
||||
set_symbol_list("01");
|
||||
break;
|
||||
}
|
||||
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
uint16_t SymField::concatenate_4_octal_u16() {
|
||||
// input array 4 octal digits , return 12 bits, same order, trippled A4-A2-A1|B4-B2-B1|C4-C2-C1|D4-D2-D1
|
||||
uint32_t mul = 1;
|
||||
uint16_t v = 0;
|
||||
|
||||
if (type_ == SYMFIELD_OCT) {
|
||||
for (uint32_t c = 0; c < length_; c++) {
|
||||
v += values_[(length_ - 1 - c)] * mul;
|
||||
mul *= 8; // shift 3 bits to the right every new octal squawk digit
|
||||
}
|
||||
return v;
|
||||
} else
|
||||
return 0;
|
||||
SymField::SymField(
|
||||
Point parent_pos,
|
||||
size_t length,
|
||||
std::string symbol_list,
|
||||
bool explicit_edits)
|
||||
: SymField{parent_pos, length, Type::Custom, explicit_edits} {
|
||||
set_symbol_list(std::move(symbol_list));
|
||||
}
|
||||
|
||||
uint32_t SymField::value_dec_u32() {
|
||||
uint32_t mul = 1, v = 0;
|
||||
|
||||
if (type_ == SYMFIELD_DEC) {
|
||||
for (uint32_t c = 0; c < length_; c++) {
|
||||
v += values_[(length_ - 1 - c)] * mul;
|
||||
mul *= 10;
|
||||
}
|
||||
return v;
|
||||
} else
|
||||
char SymField::get_symbol(size_t index) const {
|
||||
if (index >= value_.length())
|
||||
return 0;
|
||||
|
||||
return value_[index];
|
||||
}
|
||||
|
||||
uint64_t SymField::value_hex_u64() {
|
||||
void SymField::set_symbol(size_t index, char symbol) {
|
||||
if (index >= value_.length())
|
||||
return;
|
||||
|
||||
set_symbol_internal(index, ensure_valid(symbol));
|
||||
}
|
||||
|
||||
size_t SymField::get_offset(size_t index) const {
|
||||
if (index >= value_.length())
|
||||
return 0;
|
||||
|
||||
// NB: Linear search - symbol lists are small.
|
||||
return symbols_.find(value_[index]);
|
||||
}
|
||||
|
||||
void SymField::set_offset(size_t index, size_t offset) {
|
||||
if (index >= value_.length() || offset >= symbols_.length())
|
||||
return;
|
||||
|
||||
set_symbol_internal(index, symbols_[offset]);
|
||||
}
|
||||
|
||||
void SymField::set_symbol_list(std::string symbol_list) {
|
||||
if (symbol_list.length() == 0)
|
||||
return;
|
||||
|
||||
symbols_ = std::move(symbol_list);
|
||||
ensure_all_symbols();
|
||||
}
|
||||
|
||||
void SymField::set_value(uint64_t value) {
|
||||
auto v = value;
|
||||
uint8_t radix = get_radix();
|
||||
|
||||
for (int i = value_.length() - 1; i >= 0; --i) {
|
||||
uint8_t temp = v % radix;
|
||||
value_[i] = uint_to_char(temp, radix);
|
||||
v /= radix;
|
||||
}
|
||||
}
|
||||
|
||||
void SymField::set_value(std::string_view value) {
|
||||
// Is new value too long?
|
||||
// TODO: Truncate instead? Which end?
|
||||
if (value.length() > value_.length())
|
||||
return;
|
||||
|
||||
// Right-align string in field.
|
||||
auto left_padding = value_.length() - value.length();
|
||||
value_ = std::string(static_cast<size_t>(left_padding), '\0') + std::string{value};
|
||||
ensure_all_symbols();
|
||||
}
|
||||
|
||||
uint64_t SymField::to_integer() const {
|
||||
uint64_t v = 0;
|
||||
uint64_t mul = 1;
|
||||
uint8_t radix = get_radix();
|
||||
|
||||
if (type_ != SYMFIELD_DEF) {
|
||||
for (uint32_t c = 0; c < length_; c++)
|
||||
v += (uint64_t)(values_[c]) << (4 * (length_ - 1 - c));
|
||||
return v;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string SymField::value_string() {
|
||||
std::string return_string{""};
|
||||
|
||||
if (type_ == SYMFIELD_ALPHANUM) {
|
||||
for (uint32_t c = 0; c < length_; c++) {
|
||||
return_string += symbol_list_[0][values_[c]];
|
||||
}
|
||||
for (int i = value_.length() - 1; i >= 0; --i) {
|
||||
auto temp = char_to_uint(value_[i], radix);
|
||||
v += temp * mul;
|
||||
mul *= radix;
|
||||
}
|
||||
|
||||
return return_string;
|
||||
return v;
|
||||
}
|
||||
|
||||
uint32_t SymField::get_sym(const uint32_t index) {
|
||||
if (index >= length_) return 0;
|
||||
|
||||
return values_[index];
|
||||
}
|
||||
|
||||
void SymField::set_sym(const uint32_t index, const uint32_t new_value) {
|
||||
if (index >= length_) return;
|
||||
|
||||
uint32_t clipped_value = clip_value(index, new_value);
|
||||
|
||||
if (clipped_value != values_[index]) {
|
||||
values_[index] = clipped_value;
|
||||
if (on_change) {
|
||||
on_change();
|
||||
}
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void SymField::set_length(const uint32_t new_length) {
|
||||
if ((new_length <= 32) && (new_length != length_)) {
|
||||
prev_length_ = length_;
|
||||
length_ = new_length;
|
||||
|
||||
// Clip eventual garbage from previous shorter word
|
||||
for (size_t n = 0; n < length_; n++)
|
||||
set_sym(n, values_[n]);
|
||||
|
||||
erase_prev_ = true;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void SymField::set_symbol_list(const uint32_t index, const std::string symbol_list) {
|
||||
if (index >= length_) return;
|
||||
|
||||
symbol_list_[index] = symbol_list;
|
||||
|
||||
// Re-clip symbol's value
|
||||
set_sym(index, values_[index]);
|
||||
const std::string& SymField::to_string() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
void SymField::paint(Painter& painter) {
|
||||
Point pt_draw = screen_pos();
|
||||
Point p = screen_pos();
|
||||
|
||||
if (erase_prev_) {
|
||||
painter.fill_rectangle({pt_draw, {(int)prev_length_ * 8, 16}}, Color::black());
|
||||
erase_prev_ = false;
|
||||
}
|
||||
for (size_t n = 0; n < value_.length(); n++) {
|
||||
auto c = value_[n];
|
||||
MutableStyle paint_style{style()};
|
||||
|
||||
for (size_t n = 0; n < length_; n++) {
|
||||
const auto text = symbol_list_[n].substr(values_[n], 1);
|
||||
// Only highlight while focused.
|
||||
if (has_focus()) {
|
||||
if (explicit_edits_) {
|
||||
// Invert the whole field on focus if explicit edits is enabled.
|
||||
paint_style.invert();
|
||||
} else if (n == selected_) {
|
||||
// Otherwise only highlight the selected symbol.
|
||||
paint_style.invert();
|
||||
}
|
||||
|
||||
const auto paint_style = (has_focus() && (n == selected_)) ? style().invert() : style();
|
||||
if (editing_ && n == selected_) {
|
||||
// Use 'bg_blue' style to indicate in editing mode.
|
||||
paint_style.foreground = Color::white();
|
||||
paint_style.background = Color::blue();
|
||||
}
|
||||
}
|
||||
|
||||
painter.draw_string(
|
||||
pt_draw,
|
||||
paint_style,
|
||||
text);
|
||||
|
||||
pt_draw += {8, 0};
|
||||
painter.draw_char(p, paint_style, c);
|
||||
p += {8, 0};
|
||||
}
|
||||
}
|
||||
|
||||
bool SymField::on_key(const KeyEvent key) {
|
||||
bool SymField::on_key(KeyEvent key) {
|
||||
// If explicit edits are enabled, only Select is handled when not in edit mode.
|
||||
if (explicit_edits_ && !editing_) {
|
||||
switch (key) {
|
||||
case KeyEvent::Select:
|
||||
editing_ = true;
|
||||
set_dirty();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case KeyEvent::Select:
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
editing_ = !editing_;
|
||||
set_dirty();
|
||||
return true;
|
||||
|
||||
case KeyEvent::Left:
|
||||
if (selected_ > 0) {
|
||||
@@ -2041,13 +2054,27 @@ bool SymField::on_key(const KeyEvent key) {
|
||||
break;
|
||||
|
||||
case KeyEvent::Right:
|
||||
if (selected_ < (length_ - 1)) {
|
||||
if (selected_ < (value_.length() - 1)) {
|
||||
selected_++;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent::Up:
|
||||
if (editing_) {
|
||||
on_encoder(1);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent::Down:
|
||||
if (editing_) {
|
||||
on_encoder(-1);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -2055,29 +2082,66 @@ bool SymField::on_key(const KeyEvent key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymField::on_encoder(const EncoderEvent delta) {
|
||||
int32_t new_value = (int)values_[selected_] + delta;
|
||||
bool SymField::on_encoder(EncoderEvent delta) {
|
||||
if (explicit_edits_ && !editing_)
|
||||
return false;
|
||||
|
||||
if (new_value >= 0)
|
||||
set_sym(selected_, values_[selected_] + delta);
|
||||
// TODO: Wrapping or carrying might be nice.
|
||||
int offset = get_offset(selected_) + delta;
|
||||
|
||||
offset = clip<int>(offset, 0, symbols_.length() - 1);
|
||||
set_offset(selected_, offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymField::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start) {
|
||||
bool SymField::on_touch(TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start)
|
||||
focus();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t SymField::clip_value(const uint32_t index, const uint32_t value) {
|
||||
size_t symbol_count = symbol_list_[index].length() - 1;
|
||||
char SymField::ensure_valid(char symbol) const {
|
||||
// NB: Linear search - symbol lists are small.
|
||||
auto pos = symbols_.find(symbol);
|
||||
return pos != std::string::npos ? symbol : symbols_[0];
|
||||
}
|
||||
|
||||
if (value > symbol_count)
|
||||
return symbol_count;
|
||||
else
|
||||
return value;
|
||||
void SymField::ensure_all_symbols() {
|
||||
auto temp = value_;
|
||||
|
||||
for (auto& c : value_)
|
||||
c = ensure_valid(c);
|
||||
|
||||
if (temp != value_) {
|
||||
if (on_change)
|
||||
on_change(*this);
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void SymField::set_symbol_internal(size_t index, char symbol) {
|
||||
if (value_[index] == symbol)
|
||||
return;
|
||||
|
||||
value_[index] = symbol;
|
||||
if (on_change)
|
||||
on_change(*this);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
uint8_t SymField::get_radix() const {
|
||||
switch (type_) {
|
||||
case Type::Oct:
|
||||
return 8;
|
||||
case Type::Dec:
|
||||
return 10;
|
||||
case Type::Hex:
|
||||
return 16;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Waveform **************************************************************/
|
||||
|
@@ -751,52 +751,93 @@ class NumberField : public Widget {
|
||||
const char fill_char;
|
||||
int32_t value_{0};
|
||||
bool can_loop{};
|
||||
|
||||
int32_t clip_value(int32_t value);
|
||||
};
|
||||
|
||||
/* A widget that allows for character-by-character editing of its value. */
|
||||
class SymField : public Widget {
|
||||
public:
|
||||
std::function<void(SymField&)> on_select{};
|
||||
std::function<void()> on_change{};
|
||||
std::function<void(SymField&)> on_change{};
|
||||
|
||||
enum symfield_type {
|
||||
SYMFIELD_OCT,
|
||||
SYMFIELD_DEC,
|
||||
SYMFIELD_HEX,
|
||||
SYMFIELD_ALPHANUM,
|
||||
SYMFIELD_DEF // User DEFined
|
||||
enum class Type {
|
||||
Custom,
|
||||
Oct,
|
||||
Dec,
|
||||
Hex,
|
||||
Alpha,
|
||||
};
|
||||
|
||||
SymField(Point parent_pos, size_t length, symfield_type type);
|
||||
/* When "explicit_edits" is true, the field must be selected before it can
|
||||
* be modified. This means that "slots" are not individually highlighted.
|
||||
* This makes navigation on Views with SymFields easier because the
|
||||
* whole control can be skipped over instead of one "slot" at a time. */
|
||||
|
||||
SymField(
|
||||
Point parent_pos,
|
||||
size_t length,
|
||||
Type type = Type::Dec,
|
||||
bool explicit_edits = false);
|
||||
|
||||
SymField(
|
||||
Point parent_pos,
|
||||
size_t length,
|
||||
std::string symbol_list,
|
||||
bool explicit_edits = false);
|
||||
|
||||
SymField(const SymField&) = delete;
|
||||
SymField(SymField&&) = delete;
|
||||
|
||||
uint32_t get_sym(const uint32_t index);
|
||||
void set_sym(const uint32_t index, const uint32_t new_value);
|
||||
void set_length(const uint32_t new_length);
|
||||
void set_symbol_list(const uint32_t index, const std::string symbol_list);
|
||||
uint16_t concatenate_4_octal_u16();
|
||||
uint32_t value_dec_u32();
|
||||
uint64_t value_hex_u64();
|
||||
std::string value_string();
|
||||
/* Gets the symbol (character) at the specified index. */
|
||||
char get_symbol(size_t index) const;
|
||||
|
||||
/* Sets the symbol (character) at the specified index. */
|
||||
void set_symbol(size_t index, char symbol);
|
||||
|
||||
/* Gets the symbol's offset in the symbol list at the specified index. */
|
||||
size_t get_offset(size_t index) const;
|
||||
|
||||
/* Sets the symbol's offset in the symbol list at the specified index. */
|
||||
void set_offset(size_t index, size_t offset);
|
||||
|
||||
/* Sets the list of allowable symbols for the field. */
|
||||
void set_symbol_list(std::string symbol_list);
|
||||
|
||||
/* Sets the integer value of the field. Only works for OCT/DEC/HEX. */
|
||||
void set_value(uint64_t value);
|
||||
|
||||
/* Sets the string value of the field. Characters that are not in
|
||||
* the symbol list will be set to the first symbol in the symbol list. */
|
||||
void set_value(std::string_view value);
|
||||
|
||||
/* Gets the integer value of the field for OCT/DEC/HEX. */
|
||||
uint64_t to_integer() const;
|
||||
|
||||
/* Gets the string value of the field. */
|
||||
const std::string& to_string() const;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_touch(TouchEvent event) override;
|
||||
|
||||
private:
|
||||
std::string symbol_list_[32] = {"01"}; // Failsafe init
|
||||
uint32_t values_[32] = {0};
|
||||
uint32_t selected_ = 0;
|
||||
size_t length_, prev_length_ = 0;
|
||||
bool erase_prev_ = false;
|
||||
symfield_type type_{};
|
||||
/* Ensure the specified symbol is in the symbol list. */
|
||||
char ensure_valid(char symbol) const;
|
||||
|
||||
int32_t clip_value(const uint32_t index, const uint32_t value);
|
||||
/* Ensures all the symbols are valid. */
|
||||
void ensure_all_symbols();
|
||||
|
||||
/* Sets without validation and fires on_change if necessary. */
|
||||
void set_symbol_internal(size_t index, char symbol);
|
||||
|
||||
/* Gets the radix for the current Type. */
|
||||
uint8_t get_radix() const;
|
||||
|
||||
std::string symbols_{};
|
||||
std::string value_{};
|
||||
Type type_ = Type::Custom;
|
||||
size_t selected_ = 0;
|
||||
bool editing_ = false;
|
||||
bool explicit_edits_ = true;
|
||||
};
|
||||
|
||||
class Waveform : public Widget {
|
||||
|
@@ -22,12 +22,13 @@
|
||||
#ifndef __UTILITY_H__
|
||||
#define __UTILITY_H__
|
||||
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <complex>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <complex>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#define LOCATE_IN_RAM __attribute__((section(".ramtext")))
|
||||
|
||||
@@ -141,6 +142,16 @@ constexpr std::enable_if_t<is_flags_type_v<TEnum>, bool> flags_enabled(TEnum val
|
||||
return (i_value & i_flags) == i_flags;
|
||||
}
|
||||
|
||||
// TODO: Constrain to integrals?
|
||||
/* Converts an integer into a byte array. */
|
||||
template <typename T, size_t N = sizeof(T)>
|
||||
constexpr std::array<uint8_t, N> to_byte_array(T value) {
|
||||
std::array<uint8_t, N> bytes{};
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
bytes[i] = (value >> ((N - i - 1) * 8)) & 0xFF;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* Returns value constrained to min and max. */
|
||||
template <class T>
|
||||
constexpr const T& clip(const T& value, const T& minimum, const T& maximum) {
|
||||
|
Reference in New Issue
Block a user