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:
Kyle Reed 2023-09-12 12:38:19 -07:00 committed by GitHub
parent 70e0f2913f
commit af424aa5f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 607 additions and 371 deletions

View file

@ -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 **************************************************************/