Memory management improv.

This commit is contained in:
Totoo
2026-04-30 15:00:19 +02:00
committed by GitHub
parent ab986a0378
commit 2bacbc80a8
26 changed files with 365 additions and 410 deletions
-1
View File
@@ -332,7 +332,6 @@ set(CPPSRC
${CPLD_H4M_DATA_CPP}
${HACKRF_CPLD_DATA_CPP}
ui_external_items_menu_loader.cpp
view_factory_base.cpp
)
set_source_files_properties(${CPPSRC} PROPERTIES COMPILE_FLAGS -flto) # Add lto flag to the non-external sources only
+29 -25
View File
@@ -131,35 +131,39 @@ void TVView::on_channel_spectrum(
// I was hoping that by doing this, I can have a longer buffer like 39936, then the frame will looks better vertically
// however this is useless until now.
for (size_t i = 0; i < 256; i++) {
// video_buffer[i+count*256] = spectrum_rgb4_lut[spectrum.db[i]];
video_buffer_int[i + count * 256] = 255 - spectrum.db[i];
// Left the original comment, but rewrote the function.
// Shift the last 128 pixels of the previous packet to the front
// This overlap handles an x_correction shift of up to 128 pixels seamlessly.
for (size_t i = 0; i < 128; i++) {
window_buffer[i] = window_buffer[i + 256];
}
count = count + 1;
if (count == 52 - 1) {
ui::Color line_buffer[128];
Coord line;
uint32_t bmp_px;
/*for (line = 0; line < 104; line++)
{
for (bmp_px = 0; bmp_px < 128; bmp_px++)
{
//line_buffer[bmp_px] = video_buffer[bmp_px+line*128];
line_buffer[bmp_px] = spectrum_rgb4_lut[video_buffer_int[bmp_px+line*128 + x_correction]];
}
// Load the new 256 pixels into the remainder of the buffer
for (size_t i = 0; i < 256; i++) {
window_buffer[i + 128] = 255 - spectrum.db[i];
}
display.render_line({ 0, line + 100 }, 128, line_buffer);
}*/
for (line = 0; line < 208; line = line + 2) {
for (bmp_px = 0; bmp_px < 128; bmp_px++) {
// line_buffer[bmp_px] = video_buffer[bmp_px+line*128];
line_buffer[bmp_px] = spectrum_rgb4_lut[video_buffer_int[bmp_px + line / 2 * 128 + x_correction]];
}
ui::Color line_buffer[128];
display.render_line({0, line + 100}, 128, line_buffer);
display.render_line({0, line + 101}, 128, line_buffer);
}
// Render the first line of this batch (matches original doubled-height behavior)
for (size_t bmp_px = 0; bmp_px < 128; bmp_px++) {
line_buffer[bmp_px] = spectrum_rgb4_lut[window_buffer[bmp_px + x_correction]];
}
display.render_line({0, (Coord)(count * 4 + 100)}, 128, line_buffer);
display.render_line({0, (Coord)(count * 4 + 101)}, 128, line_buffer);
// Render the second line of this batch
for (size_t bmp_px = 0; bmp_px < 128; bmp_px++) {
line_buffer[bmp_px] = spectrum_rgb4_lut[window_buffer[bmp_px + 128 + x_correction]];
}
display.render_line({0, (Coord)(count * 4 + 102)}, 128, line_buffer);
display.render_line({0, (Coord)(count * 4 + 103)}, 128, line_buffer);
count++;
// Reset at 52 (52 * 2 lines = 104 lines total)
if (count >= 52) {
count = 0;
}
}
+3 -2
View File
@@ -79,8 +79,9 @@ class TVView : public Widget {
void paint(Painter& painter) override;
void on_channel_spectrum(const ChannelSpectrum& spectrum);
void on_adjust_xcorr(uint8_t xcorr);
// ui::Color video_buffer[13312];
uint8_t video_buffer_int[13312 + 128]{0}; // 128 is for the over length caused by x_correction
// 256 samples from current callback + 128 overlap from the previous callback
uint8_t window_buffer[384]{0};
uint32_t count = 0;
uint8_t x_correction = 0;
@@ -77,6 +77,10 @@ void ExtSensorsView::on_any() {
has_data = true;
}
void ExtSensorsView::on_show() {
i2cdev::I2CDevManager::set_autoscan_interval(3);
}
void ExtSensorsView::on_gps(const GPSPosDataMessage* msg) {
on_any();
std::string tmp = to_string_decimal(msg->lat, 5);
@@ -78,6 +78,8 @@ class ExtSensorsView : public View {
void on_orientation(const OrientationDataMessage* msg);
void on_environment(const EnvironmentDataMessage* msg);
void on_show() override;
MessageHandlerRegistration message_handler_gps{
Message::ID::GPSPosData,
[this](Message* const p) {
+11 -10
View File
@@ -33,7 +33,7 @@ namespace ui::external_app::time_sink {
TimeSinkWaveformWidget::TimeSinkWaveformWidget(
Rect parent_rect,
const int16_t* data,
const int8_t* data,
size_t length,
Color color)
: Widget{parent_rect},
@@ -71,10 +71,10 @@ void TimeSinkWaveformWidget::set_persistence_frames(uint8_t frames) {
}
}
Coord TimeSinkWaveformWidget::sample_to_y(const Rect& r, int16_t sample) const {
Coord TimeSinkWaveformWidget::sample_to_y(const Rect& r, int8_t sample) const {
const int32_t y_center = r.top() + (r.height() / 2);
const int32_t y_span = std::max<int32_t>(1, r.height() - 1);
const int32_t y = y_center - (static_cast<int32_t>(sample) * y_span) / 65536;
const int32_t y = y_center - (static_cast<int32_t>(sample) * y_span) / 256;
return static_cast<Coord>(std::clamp<int32_t>(y, r.top(), r.bottom() - 1));
}
@@ -115,7 +115,6 @@ void TimeSinkWaveformWidget::paint(Painter& painter) {
current_y_[x] = sample_to_y(r, data_[src_index]);
}
// Draw first, then erase stale pixels so the trace never disappears mid-refresh.
for (size_t x = 0; x < columns; ++x) {
display.draw_pixel(
{static_cast<Coord>(r.left() + x), current_y_[x]},
@@ -126,13 +125,13 @@ void TimeSinkWaveformWidget::paint(Painter& painter) {
const size_t expired_slot = history_head_;
for (size_t x = 0; x < columns; ++x) {
const auto expired_y = history_y_[expired_slot][x];
const auto expired_y = sample_to_y(r, history_samples_[expired_slot][x]);
bool keep = (expired_y == current_y_[x]);
if (!keep) {
for (size_t i = 1; i < history_count_; ++i) {
const size_t slot = (history_head_ + i) % max_persistence_frames;
if (history_y_[slot][x] == expired_y) {
if (sample_to_y(r, history_samples_[slot][x]) == expired_y) {
keep = true;
break;
}
@@ -151,7 +150,10 @@ void TimeSinkWaveformWidget::paint(Painter& painter) {
}
const size_t tail_slot = (history_head_ + history_count_) % max_persistence_frames;
std::copy_n(current_y_.begin(), columns, history_y_[tail_slot].begin());
for (size_t x = 0; x < columns; ++x) {
const size_t src_index = (x * length_) / columns;
history_samples_[tail_slot][x] = data_[src_index];
}
++history_count_;
}
@@ -338,8 +340,7 @@ void TimeSinkView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
const size_t src_index =
(trigger_index + offset) % source_count;
const int32_t centered = static_cast<int32_t>(spectrum.db[src_index]) - 128;
const int32_t scaled = centered * 256;
waveform_buffer[x] = static_cast<int16_t>(std::clamp<int32_t>(scaled, -32768, 32767));
waveform_buffer[x] = static_cast<int8_t>(std::clamp<int32_t>(centered, -128, 127));
}
waveform.set_dirty();
@@ -349,4 +350,4 @@ void TimeSinkView::on_freqchg(int64_t freq) {
field_frequency.set_value(freq);
}
} // namespace ui::external_app::time_sink
} // namespace ui::external_app::time_sink
+6 -6
View File
@@ -40,7 +40,7 @@ constexpr size_t time_sink_waveform_points = 240;
class TimeSinkWaveformWidget : public Widget {
public:
TimeSinkWaveformWidget(Rect parent_rect, const int16_t* data, size_t length, Color color);
TimeSinkWaveformWidget(Rect parent_rect, const int8_t* data, size_t length, Color color);
TimeSinkWaveformWidget(const TimeSinkWaveformWidget&) = delete;
TimeSinkWaveformWidget(TimeSinkWaveformWidget&&) = delete;
TimeSinkWaveformWidget& operator=(const TimeSinkWaveformWidget&) = delete;
@@ -56,13 +56,13 @@ class TimeSinkWaveformWidget : public Widget {
static constexpr size_t max_persistence_frames = 16; // this is sad that we cant have 32 histories in ext app due to memory constraints
void reset_cache();
Coord sample_to_y(const Rect& r, int16_t sample) const;
Coord sample_to_y(const Rect& r, int8_t sample) const;
const int16_t* data_;
const int8_t* data_;
size_t length_;
Color color_;
std::array<Coord, max_columns> current_y_{};
std::array<std::array<Coord, max_columns>, max_persistence_frames> history_y_{};
std::array<std::array<int8_t, max_columns>, max_persistence_frames> history_samples_{};
size_t history_count_{0};
size_t history_head_{0};
uint8_t persistence_frames_{1};
@@ -120,7 +120,7 @@ class TimeSinkView : public View {
{"trigger_level"sv, &trigger_level},
}};
int16_t waveform_buffer[waveform_points]{0};
int8_t waveform_buffer[waveform_points]{0};
ChannelSpectrumFIFO* fifo = nullptr;
Labels labels{
@@ -235,4 +235,4 @@ class TimeSinkView : public View {
} // namespace ui::external_app::time_sink
#endif // __UI_TIME_SINK_APP_H__
#endif // __UI_TIME_SINK_APP_H__
+66 -62
View File
@@ -104,7 +104,7 @@ std::string to_string_bin(
if (l >= 33) l = 32;
char p[33];
for (uint8_t c = 0; c < l; c++) {
if (n & (1 << (l - 1 - c)))
if (n & (1UL << (l - 1 - c)))
p[c] = '1';
else
p[c] = '0';
@@ -130,28 +130,19 @@ std::string to_string_dec_uint(
return q;
}
std::string to_string_dec_int(
const int32_t n,
const int32_t l,
const char fill) {
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill) {
const size_t negative = (n < 0) ? 1 : 0;
uint32_t n_abs = negative ? -n : n;
char p[16];
uint32_t n_abs = negative ? static_cast<uint32_t>(-(int64_t)n) : static_cast<uint32_t>(n);
char p[24];
int32_t safe_l = std::min<int32_t>(l, sizeof(p) - 1);
auto term = p + sizeof(p) - 1;
auto q = to_string_dec_uint_pad_internal(term, n_abs, l - negative, fill);
// Add sign.
auto q = to_string_dec_uint_pad_internal(term, n_abs, safe_l - negative, fill);
if (negative) {
*(--q) = '-';
}
// Right justify.
// (This code is redundant and won't do anything if a fill character was specified)
while ((term - q) < l) {
*(--q) = ' ';
while ((term - q) < safe_l) {
*(--q) = (fill ? fill : ' ');
}
return q;
}
@@ -259,38 +250,32 @@ std::string to_string_time_ms(const uint32_t ms) {
return final_str;
}
static char* to_string_hex_internal(char* ptr, uint64_t value, uint8_t length) {
if (length == 0)
return ptr;
*(--ptr) = uint_to_char(value & 0xF, 16);
return to_string_hex_internal(ptr, value >> 4, length - 1);
}
std::string to_string_hex(uint64_t value, int32_t length) {
constexpr uint8_t buffer_length = 33;
char buffer[buffer_length];
char* ptr = &buffer[buffer_length - 1];
*ptr = '\0';
length = std::min<uint8_t>(buffer_length - 1, length);
return to_string_hex_internal(ptr, value, length);
length = std::min<int32_t>(buffer_length - 1, length);
for (int32_t i = 0; i < length; ++i) {
*(--ptr) = uint_to_char(value & 0xF, 16);
value >>= 4;
}
return std::string(ptr);
}
std::string to_string_hex_array(uint8_t* array, int32_t length) {
std::string str_return;
str_return.reserve(length * 2);
for (uint8_t i = 0; i < length; i++)
str_return += to_string_hex(array[i], 2);
return str_return;
std::string s;
s.resize(length * 2);
for (int i = 0; i < length; i++) {
s[i * 2] = uint_to_char((array[i] >> 4) & 0xF, 16);
s[i * 2 + 1] = uint_to_char(array[i] & 0xF, 16);
}
return s;
}
std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format) {
std::string string{""};
string.reserve(20);
if (format == YMDHMS) {
string += to_string_dec_uint(value.year(), 4) + "-" +
to_string_dec_uint(value.month(), 2, '0') + "-" +
@@ -307,20 +292,33 @@ std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format) {
}
std::string to_string_timestamp(const rtc::RTC& value) {
return to_string_dec_uint(value.year(), 4, '0') +
to_string_dec_uint(value.month(), 2, '0') +
to_string_dec_uint(value.day(), 2, '0') +
to_string_dec_uint(value.hour(), 2, '0') +
to_string_dec_uint(value.minute(), 2, '0') +
to_string_dec_uint(value.second(), 2, '0');
std::string result;
// YYYYMMDDHHMMSS = 14 characters
result.reserve(14);
result += to_string_dec_uint(value.year(), 4, '0');
result += to_string_dec_uint(value.month(), 2, '0');
result += to_string_dec_uint(value.day(), 2, '0');
result += to_string_dec_uint(value.hour(), 2, '0');
result += to_string_dec_uint(value.minute(), 2, '0');
result += to_string_dec_uint(value.second(), 2, '0');
return result;
}
std::string to_string_FAT_timestamp(const FATTimestamp& timestamp) {
return to_string_dec_uint((timestamp.FAT_date >> 9) + 1980) + "-" +
to_string_dec_uint((timestamp.FAT_date >> 5) & 0xF, 2, '0') + "-" +
to_string_dec_uint((timestamp.FAT_date & 0x1F), 2, '0') + " " +
to_string_dec_uint((timestamp.FAT_time >> 11), 2, '0') + ":" +
to_string_dec_uint((timestamp.FAT_time >> 5) & 0x3F, 2, '0');
std::string result;
// YYYY-MM-DD HH:MM = 16 characters
result.reserve(16);
result += to_string_dec_uint((timestamp.FAT_date >> 9) + 1980);
result += '-';
result += to_string_dec_uint((timestamp.FAT_date >> 5) & 0xF, 2, '0');
result += '-';
result += to_string_dec_uint((timestamp.FAT_date & 0x1F), 2, '0');
result += ' ';
result += to_string_dec_uint((timestamp.FAT_time >> 11), 2, '0');
result += ':';
result += to_string_dec_uint((timestamp.FAT_time >> 5) & 0x3F, 2, '0');
return result;
}
std::string to_string_file_size(uint32_t file_size) {
@@ -339,20 +337,25 @@ std::string to_string_file_size(uint32_t file_size) {
}
std::string to_string_mac_address(const uint8_t* macAddress, uint8_t length, bool noColon) {
std::string string;
string += to_string_hex(macAddress[0], 2);
for (int i = 1; i < length; i++) {
string += noColon ? to_string_hex(macAddress[i], 2) : ":" + to_string_hex(macAddress[i], 2);
if (length == 0 || macAddress == nullptr) return "";
std::string result;
// Size = 2 chars per byte + 1 colon between bytes (if used)
result.reserve((length * 2) + (noColon ? 0 : length - 1));
constexpr char hex_chars[] = "0123456789ABCDEF";
for (int i = 0; i < length; i++) {
// Append the colon separator (if not the first byte)
if (i > 0 && !noColon) {
result += ':';
}
result += hex_chars[(macAddress[i] >> 4) & 0x0F];
result += hex_chars[macAddress[i] & 0x0F];
}
return string;
return result;
}
std::string to_string_formatted_mac_address(const char* macAddress) {
std::string formattedAddress;
formattedAddress.reserve(17);
for (int i = 0; i < 12; i += 2) {
if (i > 0) {
formattedAddress += ':';
@@ -377,16 +380,16 @@ void generateRandomMacAddress(char* macAddress) {
uint64_t readUntil(File& file, char* result, std::size_t maxBufferSize, char delimiter) {
std::size_t bytesRead = 0;
if (maxBufferSize == 0) return 0;
while (true) {
char ch;
File::Result<File::Size> readResult = file.read(&ch, 1);
if (readResult.is_ok() && readResult.value() > 0) {
if (ch == delimiter) {
// Found a space character, stop reading
// Found the delimiter character, stop reading
break;
} else if (bytesRead < maxBufferSize) {
} else if (bytesRead < maxBufferSize - 1) {
// Append the character to the result if there's space
result[bytesRead++] = ch;
} else {
@@ -408,12 +411,13 @@ std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precisi
const uint32_t powers_of_ten[5] = {1, 10, 100, 1000, 10000};
std::string string{""};
uint32_t prefix_index = base_unit;
if (prefix_index > 6) prefix_index = 6;
double integer_part;
double fractional_part;
precision = std::min((uint32_t)4, precision);
while (n > 1000) {
while (n > 1000 && prefix_index < 6) {
n /= 1000.0;
prefix_index++;
}
@@ -426,7 +430,7 @@ std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precisi
if (precision)
string += '.' + to_string_dec_uint(fractional_part, precision, '0');
if (prefix_index != 3)
if (unit_prefix[prefix_index] != 0)
string += unit_prefix[prefix_index];
return string;
+20 -20
View File
@@ -28,6 +28,7 @@
#include "rtc_time.hpp"
#include "sd_card.hpp"
#include <algorithm>
#include "ui_external_items_menu_loader.hpp"
namespace ui {
@@ -60,6 +61,7 @@ BtnGridView::BtnGridView(
}
BtnGridView::~BtnGridView() {
ExternalItemsMenuLoader::unload_external_items();
}
void BtnGridView::set_max_rows(int rows) {
@@ -87,6 +89,7 @@ void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
remove_child(item.get());
menu_item_views.clear();
menu_item_views.shrink_to_fit();
}
button_w = screen_width / rows_;
@@ -137,6 +140,7 @@ void BtnGridView::set_arrow_down_enabled(bool enabled) {
void BtnGridView::clear() {
// clear vector and release memory, not using swap since it's causing capture to glitch/fault
menu_items.clear();
menu_items.shrink_to_fit();
// TODO(u-foka): Clean up my mess, move this somewhere to clear memory when the view is not visible, but not to be confused with clearing the menu items...
for (auto& item : menu_item_views)
@@ -144,10 +148,11 @@ void BtnGridView::clear() {
// clear vector and release memory, not using swap since it's causing capture to glitch/fault
menu_item_views.clear();
menu_item_views.shrink_to_fit();
}
void BtnGridView::add_items(std::initializer_list<GridItem> new_items, bool inhibit_update) {
for (auto item : new_items) {
for (const auto& item : new_items) {
if (!blacklisted_app(item))
menu_items.push_back(item);
}
@@ -389,39 +394,34 @@ bool BtnGridView::on_encoder(const EncoderEvent event) {
/* BlackList ******************************************************/
std::unique_ptr<char> blacklist_ptr{};
size_t blacklist_len{};
std::string blacklist_data{};
void load_blacklist() {
File f;
auto error = f.open(BLACKLIST);
if (error)
return;
// allocating two extra bytes for leading & trailing commas
blacklist_ptr = std::unique_ptr<char>(new char[f.size() + 2]);
if (f.read(blacklist_ptr.get() + 1, f.size())) {
blacklist_len = f.size() + 2;
// replace any CR/LF characters with comma delineator, and add comma prefix/suffix, to simplify searching
char* ptr = blacklist_ptr.get();
*ptr = ',';
*(ptr + blacklist_len - 1) = ',';
for (size_t i = 0; i < blacklist_len; i++, ptr++) {
if (*ptr == 0x0D || *ptr == 0x0A)
*ptr = ',';
// Resize string to fit file + 2 commas, filling it with commas by default
blacklist_data.assign(f.size() + 2, ',');
// Read directly into the string's buffer (offset by 1 to leave the first comma)
if (f.read(blacklist_data.data() + 1, f.size())) {
// Replace any CR/LF characters with commas
for (char& c : blacklist_data) {
if (c == '\r' || c == '\n') {
c = ',';
}
}
} else {
blacklist_data.clear(); // Clear if read fails
}
}
bool BtnGridView::blacklisted_app(GridItem new_item) {
std::string app_name = "," + new_item.text + ",";
if (blacklist_len < app_name.size())
if (blacklist_data.size() < app_name.size())
return false;
return std::search(blacklist_ptr.get(), blacklist_ptr.get() + blacklist_len, app_name.begin(), app_name.end()) < blacklist_ptr.get() + blacklist_len;
return blacklist_data.find(app_name) != std::string::npos;
}
void BtnGridView::page_up() {
+15 -14
View File
@@ -251,15 +251,13 @@ void GeoMap::map_read_line_bin(ui::Color* buffer, uint16_t pixels) {
}
}
} else {
ui::Color* zoom_out_buffer = new ui::Color[(pixels * (-map_zoom))];
ui::Color zoom_out_buffer[(pixels * (-map_zoom))];
map_file.read(zoom_out_buffer, (pixels * (-map_zoom)) << 1);
// Zoom out: Collapse each group of "-map_zoom" pixels into one pixel.
// Future TODO: Average each group of pixels (in both X & Y directions if possible).
for (int i = 0; i < width; i++) {
buffer[i] = zoom_out_buffer[i * (-map_zoom)];
}
delete[] zoom_out_buffer;
}
}
@@ -333,8 +331,9 @@ void GeoMap::set_osm_max_zoom(bool changeboth) {
for (uint8_t i = map_osm_zoom; i > 0; i--) {
int tile_x = lon2tile(lon_, i);
int tile_y = lat2tile(lat_, i);
std::string filename = "/OSM/" + to_string_dec_int(i) + "/" + to_string_dec_int(tile_x) + "/" + to_string_dec_int(tile_y) + ".bmp";
std::filesystem::path file_path(filename);
char path_buffer[64];
snprintf(path_buffer, sizeof(path_buffer), "/OSM/%d/%d/%d.bmp", i, tile_x, tile_y);
std::filesystem::path file_path(path_buffer);
if (file_exists(file_path)) {
map_osm_real_zoom = i;
if (changeboth) map_osm_zoom = i;
@@ -347,7 +346,7 @@ void GeoMap::set_osm_max_zoom(bool changeboth) {
// checks if the tile file presents or not. to determine if we got osm or not
uint8_t GeoMap::find_osm_file_tile() {
std::string filename = "/OSM/" + to_string_dec_int(0) + "/" + to_string_dec_int(0) + "/" + to_string_dec_int(0) + ".bmp";
std::string filename = "/OSM/0/0/0.bmp";
std::filesystem::path file_path(filename);
if (file_exists(file_path)) return 1;
return 0; // not found
@@ -456,22 +455,24 @@ bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int
display.fill_rectangle(error_rect, Theme::getInstance()->bg_darkest->background);
return false;
}
std::vector<ui::Color> line(clip_w);
map_line_buffer.resize(clip_w);
if (bmp.is_bottomup()) {
for (int y = clip_h - 1; y >= 0; --y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
bmp.read_next_px_cnt(line.data(), clip_w, false);
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
bmp.read_next_px_cnt(map_line_buffer.data(), clip_w, false);
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, map_line_buffer);
}
} else {
for (int y = 0; y < clip_h; ++y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
bmp.read_next_px_cnt(line.data(), clip_w, false);
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
bmp.read_next_px_cnt(map_line_buffer.data(), clip_w, false);
display.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, map_line_buffer);
}
}
return true;
@@ -479,11 +480,11 @@ bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int
void GeoMap::paint(Painter& painter) {
const auto r = screen_rect();
std::vector<ui::Color> map_line_buffer;
map_line_buffer.resize(r.width());
int16_t zoom_seek_x, zoom_seek_y;
if (!use_osm) {
map_line_buffer.resize(r.width());
// Ony redraw map if it moved by at least 1 pixel or the markers list was updated
if (map_zoom <= 1) {
// Zooming out, or no zoom
@@ -755,7 +756,7 @@ void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t siz
display.draw_pixel(origin, color); // 1 pixel indicating center pivot point of bearing symbol
}
void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color, const Color fontColor, const Color backColor) {
void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string& itemTag, const Color color, const Color fontColor, const Color backColor) {
const auto r = screen_rect();
int tagOffset = 10;
+4 -1
View File
@@ -247,7 +247,7 @@ class GeoMap : public Widget {
ui::Point item_rect_pixel(GeoMarker& item);
GeoPoint lat_lon_to_map_pixel(float lat, float lon);
void draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor = Color::white(), const Color backColor = Color::black());
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string& itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
void draw_markers(Painter& painter);
void draw_mypos(Painter& painter);
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
@@ -264,6 +264,9 @@ class GeoMap : public Widget {
double lat_to_pixel_y_tile(double lat, int zoom);
double tile_pixel_x_to_lon(int x, int zoom);
double tile_pixel_y_to_lat(int y, int zoom);
std::vector<ui::Color> map_line_buffer{};
uint8_t map_osm_zoom{5};
uint8_t map_osm_real_zoom{5};
double viewport_top_left_px = 0;
+2
View File
@@ -117,6 +117,7 @@ void MenuView::set_parent_rect(const Rect new_parent_rect) {
remove_child(item.get());
menu_item_views.clear();
menu_item_views.shrink_to_fit();
}
for (size_t c = 0; c < displayed_max; c++) {
@@ -149,6 +150,7 @@ void MenuView::clear() {
item->set_item(nullptr);
menu_items.clear();
menu_items.shrink_to_fit();
highlighted_item = 0;
offset = 0;
}
@@ -9,7 +9,13 @@
namespace ui {
/* static */ std::vector<DynamicBitmap<16, 16>> ExternalItemsMenuLoader::bitmaps;
/* static */ std::vector<std::unique_ptr<DynamicBitmap<16, 16>>> ExternalItemsMenuLoader::bitmaps;
// to save ram when entering an app
void ExternalItemsMenuLoader::unload_external_items() {
bitmaps.clear();
bitmaps.shrink_to_fit();
}
// iterates over all possible ext apps-s, and if it is runnable on the current system, it'll call the callback, and pass minimal info. used to print to console, and for autostart setting's app list. where the minimal info is enough
// please keep in sync with load_external_items
@@ -97,8 +103,9 @@ namespace ui {
}
}
/* static */ std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
std::vector<ExternalItemsMenuLoader::GridItemEx> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
bitmaps.clear();
bitmaps.shrink_to_fit();
std::vector<GridItemEx> external_apps;
@@ -126,8 +133,8 @@ namespace ui {
gridItem.color = Color((uint16_t)appInfo->icon_color);
auto dyn_bmp = DynamicBitmap<16, 16>{appInfo->bitmap_data};
gridItem.bitmap = dyn_bmp.bitmap();
auto dyn_bmp = std::make_unique<DynamicBitmap<16, 16>>(appInfo->bitmap_data);
gridItem.bitmap = dyn_bmp->bitmap();
bitmaps.push_back(std::move(dyn_bmp));
gridItem.on_select = [&nav, appInfo, i]() {
@@ -204,8 +211,8 @@ namespace ui {
if (versionMatches) {
gridItem.color = Color((uint16_t)application_information.icon_color);
auto dyn_bmp = DynamicBitmap<16, 16>{application_information.bitmap_data};
gridItem.bitmap = dyn_bmp.bitmap();
auto dyn_bmp = std::make_unique<DynamicBitmap<16, 16>>(application_information.bitmap_data);
gridItem.bitmap = dyn_bmp->bitmap();
bitmaps.push_back(std::move(dyn_bmp));
gridItem.on_select = [&nav, app_location, filePath]() {
@@ -255,8 +262,8 @@ namespace ui {
gridItem.color = Color((uint16_t)application_information.icon_color);
auto dyn_bmp = DynamicBitmap<16, 16>{application_information.bitmap_data};
gridItem.bitmap = dyn_bmp.bitmap();
auto dyn_bmp = std::make_unique<DynamicBitmap<16, 16>>(application_information.bitmap_data);
gridItem.bitmap = dyn_bmp->bitmap();
bitmaps.push_back(std::move(dyn_bmp));
gridItem.on_select = [&nav, app_location, filePath]() {
@@ -350,6 +357,8 @@ namespace ui {
if (checksum != EXT_APP_EXPECTED_CHECKSUM)
return false;
nav.pop();
nav.set_last_menu_went_deeper(true);
application_information.externalAppEntry(nav);
return true;
}
@@ -386,7 +395,8 @@ namespace ui {
}
}
nav.push<StandaloneView>(app_image);
nav.set_last_menu_went_deeper(true);
nav.replace<StandaloneView>(app_image);
return true;
}
@@ -400,7 +410,8 @@ namespace ui {
}
}
nav.push<StandaloneView>(app_image);
nav.set_last_menu_went_deeper(true);
nav.replace<StandaloneView>(app_image);
return true;
}
@@ -27,30 +27,44 @@
#include "ui_navigation.hpp"
#include "external_app.hpp"
#include "standalone_app.hpp"
#include <cstring>
#include "file.hpp"
#define EXT_APP_EXPECTED_CHECKSUM 0x00000000
namespace ui {
template <size_t Width, size_t Height>
class DynamicBitmap {
public:
static constexpr size_t buffer_size = Width * Height / (sizeof(uint8_t) * 8); // one bit per pixel
static constexpr size_t buffer_size = Width * Height / (sizeof(uint8_t) * 8);
// Main constructor
DynamicBitmap(const uint8_t data[buffer_size])
: _buffer(buffer_size, 0),
_bitmap{new Bitmap{{Width, Height}, &_buffer[0]}} {
memcpy(&_buffer[0], data, buffer_size);
: _bitmap{{Width, Height}, _buffer.data()} {
std::memcpy(_buffer.data(), data, buffer_size);
}
const Bitmap* bitmap() { return _bitmap.get(); }
DynamicBitmap(const DynamicBitmap& other)
: _buffer(other._buffer),
_bitmap{{Width, Height}, _buffer.data()} {}
DynamicBitmap(DynamicBitmap&& other) noexcept
: _buffer(std::move(other._buffer)),
_bitmap{{Width, Height}, _buffer.data()} {}
DynamicBitmap& operator=(const DynamicBitmap& other) = delete;
DynamicBitmap& operator=(DynamicBitmap&& other) noexcept = delete;
// Destructor (Default is fine, no heap memory to free manually)
~DynamicBitmap() = default;
const Bitmap* bitmap() const { return &_bitmap; }
private:
// Allocating both members so the class is movable without invalidation.
std::vector<uint8_t> _buffer;
std::unique_ptr<Bitmap> _bitmap;
// Order matters: _buffer must be declared before _bitmap
// so it is initialized first and its .data() pointer is valid.
std::array<uint8_t, buffer_size> _buffer{};
Bitmap _bitmap{};
};
class ExternalItemsMenuLoader {
@@ -65,9 +79,10 @@ class ExternalItemsMenuLoader {
static bool run_standalone_app(ui::NavigationView&, std::filesystem::path);
static bool run_module_app(ui::NavigationView&, uint8_t*, size_t);
static void load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback, bool module_included = false);
static void unload_external_items();
private:
static std::vector<DynamicBitmap<16, 16>> bitmaps;
static std::vector<std::unique_ptr<DynamicBitmap<16, 16>>> bitmaps;
};
} // namespace ui
+98 -85
View File
@@ -82,82 +82,61 @@ namespace pmem = portapack::persistent_memory;
namespace ui {
bool CstrCmp::operator()(const char* a, const char* b) const {
return strcmp(a, b) < 0;
}
static NavigationView::AppMap generate_app_map(const NavigationView::AppList& appList) {
NavigationView::AppMap out;
for (auto& app : appList) {
if (app.id == nullptr) {
// Skip items with no id
continue;
}
auto res = out.emplace(app.id, app);
if (!res.second) {
chDbgPanic("Application cannot be added, ID not unique!");
}
}
return out;
}
// TODO(u-foka): Check consistency of command names (where we add rx/tx postfix)
const NavigationView::AppList NavigationView::appList = {
/* HOME ******************************************************************/
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, new ViewFactory<ReceiversMenuView>()},
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
{nullptr, "Transceiver", HOME, Color::cyan(), &bitmap_icon_transceivers, new ViewFactory<TransceiversMenuView>()},
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, new ViewFactory<GamesMenuView>()},
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
/* RX ********************************************************************/
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSRXView>()},
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, new ViewFactory<AnalogAudioView>()},
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, new ViewFactory<BLERxView>()},
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
/* TX ********************************************************************/
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, new ViewFactory<EncodersView>()},
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
/* TRX ********************************************************************/
{"microphone", "Mic", TRX, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
/* UTILITIES *************************************************************/
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
{nullptr, "Debug", UTILITIES, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
// Dangerous apps.
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
};
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<ReceiversMenuView>(nav); }},
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TransmittersMenuView>(nav); }},
{nullptr, "Transceiver", HOME, Color::cyan(), &bitmap_icon_transceivers, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TransceiversMenuView>(nav); }},
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<ReconView>(nav); }},
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<CaptureAppView>(nav); }},
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<PlaylistView>(nav); }},
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<GlassView>(nav); }},
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<UtilitiesMenuView>(nav); }},
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<GamesMenuView>(nav); }},
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SettingsMenuView>(nav); }},
const NavigationView::AppMap NavigationView::appMap = generate_app_map(NavigationView::appList);
/* RX ********************************************************************/
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<ADSBRxView>(nav); }},
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<AISAppView>(nav); }},
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<APRSRXView>(nav); }},
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<AnalogAudioView>(nav); }},
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<BLERxView>(nav); }},
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<POCSAGAppView>(nav); }},
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SondeView>(nav); }},
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SearchView>(nav); }},
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<SubGhzDView>(nav); }},
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<WeatherView>(nav); }},
/* TX ********************************************************************/
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<APRSTXView>(nav); }},
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<BLETxView>(nav); }},
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<EncodersView>(nav); }},
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<RDSView>(nav); }},
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TouchTunesView>(nav); }},
/* TRX ********************************************************************/
{"microphone", "Mic", TRX, Color::green(), &bitmap_icon_microphone, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<MicTXView>(nav); }},
/* UTILITIES *************************************************************/
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<FileManagerView>(nav); }},
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<FrequencyManagerView>(nav); }},
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<IQTrimView>(nav); }},
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<TextEditorView>(nav); }},
{nullptr, "Debug", UTILITIES, Color::light_grey(), &bitmap_icon_debug, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<DebugMenuView>(nav); }},
// Dangerous apps.
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, [](NavigationView& nav) -> std::unique_ptr<View> { return std::make_unique<FlashUtilityView>(nav); }},
};
bool NavigationView::StartAppByName(const char* name) {
home(false);
auto it = appMap.find(name);
if (it != appMap.end()) {
push_view(std::unique_ptr<View>(it->second.viewFactory->produce(*this)));
return true;
for (const auto& app : appList) {
if (app.id != nullptr && strcmp(app.id, name) == 0) {
push_view(app.producer(*this));
return true;
}
}
return false;
}
@@ -497,9 +476,8 @@ void SystemStatusView::on_camera() {
auto error = png.create(path);
if (error)
return;
std::vector<ColorRGB888> row(ui::screen_width);
for (int i = 0; i < screen_height; i++) {
std::vector<ColorRGB888> row(ui::screen_width);
portapack::display.read_pixels({0, i, screen_width, 1}, row);
png.write_scanline(row);
}
@@ -655,6 +633,7 @@ void NavigationView::pop(bool trigger_update) {
free_view();
view_stack.pop_back();
view_stack.shrink_to_fit();
// NB: These are executed _after_ the view has been
// destroyed. The old view MUST NOT be referenced in
@@ -778,10 +757,22 @@ void NavigationView::handle_autostart() {
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
for (auto& app : NavigationView::appList) {
if (app.menuLocation == loc) {
const AppInfo* p_app = &app;
grid.add_item({app.displayName, app.iconColor, app.icon,
[&nav, &app]() {
i2cdev::I2CDevManager::set_autoscan_interval(0); //if i navigate away from any menu, turn off autoscan
nav.push_view(std::unique_ptr<View>(app.viewFactory->produce(nav))); }},
[&nav, p_app]() {
NavigationView& local_nav = nav;
if (p_app->menuLocation == HOME) {
local_nav.store_last_menu_name(p_app->displayName);
local_nav.set_last_menu_went_deeper(false);
auto new_view = p_app->producer(local_nav);
local_nav.push_view(std::move(new_view));
} else {
auto new_view = p_app->producer(local_nav);
local_nav.pop();
local_nav.set_last_menu_went_deeper(true);
local_nav.push_view(std::move(new_view));
}
}},
true);
}
};
@@ -808,11 +799,11 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
return a.desired_position < b.desired_position;
});
for (auto const& gridItem : externalItems) {
for (auto & gridItem : externalItems) {
if (gridItem.desired_position < 0) {
grid.add_item(gridItem, true);
grid.add_item(std::move(gridItem), true);
} else {
grid.insert_item(gridItem, gridItem.desired_position, true);
grid.insert_item(std::move(gridItem), gridItem.desired_position, true);
}
}
@@ -940,6 +931,25 @@ SystemView::SystemView(
{{0, 0},
{parent_rect.width(), status_view_height}});
status_view.on_back = [this]() {
if (this->navigation_view.view_stack_size() == 2) {
const AppInfo* lastmenu = nullptr;
const auto last_menu_name = this->navigation_view.get_last_menu_name();
this->navigation_view.store_last_menu_name("");
if (!last_menu_name.empty()) {
for (const auto& app : NavigationView::appList) {
if (app.displayName == last_menu_name) {
lastmenu = &app;
break;
}
}
if (lastmenu && this->navigation_view.get_last_menu_went_deeper()) {
this->navigation_view.pop();
this->navigation_view.push_view(lastmenu->producer(this->navigation_view));
return;
}
}
}
this->navigation_view.pop();
};
@@ -956,6 +966,7 @@ SystemView::SystemView(
navigation_view.on_view_changed = [this](const View& new_view) {
if (!this->navigation_view.is_top()) {
remove_child(&info_view);
i2cdev::I2CDevManager::set_autoscan_interval(0);
} else {
add_child(&info_view);
info_view.refresh();
@@ -994,7 +1005,8 @@ void SystemView::toggle_overlay() {
static uint8_t last_perf_counter_status = shared_memory.request_m4_performance_counter;
switch (++overlay_active) {
case 1:
this->add_child(&this->overlay);
overlay = std::make_unique<DfuMenu>(navigation_view);
this->add_child(overlay.get());
this->set_dirty();
shared_memory.request_m4_performance_counter = 1;
shared_memory.m4_performance_counter = 0;
@@ -1002,13 +1014,16 @@ void SystemView::toggle_overlay() {
shared_memory.m4_stack_usage = 0;
break;
case 2:
this->remove_child(&this->overlay);
this->add_child(&this->overlay2);
this->remove_child(overlay.get());
overlay.reset();
overlay2 = std::make_unique<DfuMenu2>(navigation_view);
this->add_child(overlay2.get());
this->set_dirty();
shared_memory.request_m4_performance_counter = 2;
break;
case 3:
this->remove_child(&this->overlay2);
this->remove_child(overlay2.get());
overlay2.reset();
this->set_dirty();
shared_memory.request_m4_performance_counter = last_perf_counter_status;
overlay_active = 0;
@@ -1019,15 +1034,13 @@ void SystemView::toggle_overlay() {
void SystemView::paint_overlay() {
static bool last_paint_state = false;
if (overlay_active) {
// paint background only every other second
if ((((chTimeNow() >> 10) & 0x01) == 0x01) == last_paint_state)
return;
last_paint_state = !last_paint_state;
if (overlay_active == 1)
this->overlay.set_dirty();
else
this->overlay2.set_dirty();
if (overlay_active == 1 && overlay)
overlay->set_dirty();
else if (overlay_active == 2 && overlay2)
overlay2->set_dirty();
}
}
+16 -13
View File
@@ -50,7 +50,6 @@
#include "lfsr_random.hpp"
#include "sd_card.hpp"
#include "external_app.hpp"
#include "view_factory.hpp"
#include "battery.hpp"
// for incrementing fake date when RTC battery is dead
@@ -60,6 +59,9 @@ using namespace sd_card;
namespace ui {
class NavigationView;
using ViewProducer = std::unique_ptr<View> (*)(NavigationView&);
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc);
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos, bool show_error_tile = true);
@@ -69,12 +71,7 @@ enum modal_t {
ABORT
};
class CstrCmp {
public:
bool operator()(const char* a, const char* b) const;
};
// Should only be used as part of the appList in NavigationView, the viewFactory will never be destroyed.
// Should only be used as part of the appList in NavigationView.
class AppInfo {
public:
const char* id; // MUST be unique! Used by serial command to start the app so it also has to make sense
@@ -82,7 +79,7 @@ class AppInfo {
app_location_t menuLocation;
Color iconColor;
const Bitmap* icon;
ViewFactoryBase* viewFactory; // Never destroyed, and I believe it's ok ;) Having a unique_ptr here breaks the initializer list of appList
ViewProducer producer;
};
struct AppInfoConsole {
@@ -137,15 +134,19 @@ class NavigationView : public View {
bool set_on_pop(std::function<void()> on_pop);
// App list is used to preserve order, so the menu items in the menu grid can stay in place
// App map is used to look up apps by id used by serial app start
using AppMap = std::map<const char*, const AppInfo&, CstrCmp>;
using AppList = std::vector<AppInfo>;
static const AppMap appMap;
static const AppList appList;
bool StartAppByName(const char* name); // Starts a View (app) by name stored in appListFC. This is to start apps from console
void handle_autostart();
void store_last_menu_name(const std::string& name) { last_menu_name_ = name; }
std::string get_last_menu_name() const { return last_menu_name_; }
size_t view_stack_size() const { return view_stack.size(); }
bool get_last_menu_went_deeper() { return last_menu_went_deeper; }
void set_last_menu_went_deeper(bool went_deeper) { last_menu_went_deeper = went_deeper; }
private:
struct ViewState {
std::unique_ptr<View> view;
@@ -158,6 +159,8 @@ class NavigationView : public View {
void free_view();
void update_view();
std::string last_menu_name_{}; // this stores the last menu name, when we replace the menu with the app, we'll know, where to navigate back
bool last_menu_went_deeper = false;
};
/* Holds widgets and grows dynamically toward the left.
@@ -451,8 +454,8 @@ class SystemView : public View {
SystemStatusView status_view{navigation_view};
InformationView info_view{navigation_view};
NotificationView notification_view{navigation_view};
DfuMenu overlay{navigation_view};
DfuMenu2 overlay2{navigation_view};
std::unique_ptr<DfuMenu> overlay{nullptr};
std::unique_ptr<DfuMenu2> overlay2{nullptr};
Context& context_;
};
+4 -5
View File
@@ -6,8 +6,8 @@ extern ui::SystemView* system_view_ptr;
namespace ui {
NotificationEntryView::NotificationEntryView(const NotificationEntry& entry, NotificationView* notifhandler)
: entry_(entry), notifhandler_(notifhandler) {
NotificationEntryView::NotificationEntryView(NotificationEntry entry, NotificationView* notifhandler)
: entry_(std::move(entry)), notifhandler_(notifhandler) {
add_children({&background, &border, &title_text, &message_text, &close_button});
border.set_outline(true);
if (entry_.icon != NOTIF_ICON_NONE) {
@@ -94,13 +94,12 @@ void NotificationView::rearrange_notifications() {
}
}
void NotificationView::add_notification(NotificationEntry& entry) {
void NotificationView::add_notification(NotificationEntry entry) {
if (notification_views_.size() >= max_notifications) {
notification_views_.erase(notification_views_.begin());
}
entry.id = ++curr_not_id;
notification_views_.push_back(std::make_unique<NotificationEntryView>(entry, this));
notification_views_.push_back(std::make_unique<NotificationEntryView>(std::move(entry), this));
rearrange_notifications();
}
+6 -7
View File
@@ -29,11 +29,11 @@ class NotificationEntry {
uint16_t id = 0;
NotificationEntry() = default;
NotificationEntry(const std::string& source_app, const std::string& title, const std::string& message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000, uint16_t id = 0)
: source_app(source_app), title(title), message(message), icon(icon), timeout(timeout), id(id) {}
NotificationEntry(std::string source_app, std::string title, std::string message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000, uint16_t id = 0)
: source_app(std::move(source_app)), title(std::move(title)), message(std::move(message)), icon(icon), timeout(timeout), id(id) {}
static NotificationEntry build(const std::string& source_app, const std::string& title, const std::string& message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000) {
return NotificationEntry{source_app, title, message, icon, timeout, 0};
static NotificationEntry build(std::string source_app, std::string title, std::string message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000) {
return NotificationEntry{std::move(source_app), std::move(title), std::move(message), icon, timeout, 0};
}
bool increase_time(uint16_t delta) {
@@ -47,8 +47,7 @@ class NotificationEntry {
class NotificationEntryView : public View {
public:
NotificationEntryView(const NotificationEntry& entry, NotificationView* notifhandler);
NotificationEntryView(NotificationEntry entry, NotificationView* notifhandler);
NotificationEntryView(const NotificationEntryView&) = delete;
NotificationEntryView& operator=(const NotificationEntryView&) = delete;
@@ -72,7 +71,7 @@ class NotificationView : public View {
NotificationView(NavigationView& nav);
~NotificationView();
void add_notification(NotificationEntry& entry);
void add_notification(NotificationEntry entry);
void remove_notification(uint16_t id);
void open_notification(std::string app_name);
+3
View File
@@ -99,6 +99,9 @@ RecordView::RecordView(
RecordView::~RecordView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
if (is_active()) {
capture_thread.reset();
}
}
void RecordView::focus() {
+4 -3
View File
@@ -704,6 +704,7 @@ static void printAppInfo(BaseSequentialStream* chp, ui::AppInfoConsole& element)
}
static void printAppInfo(BaseSequentialStream* chp, const ui::AppInfo& element) {
if (element.id == nullptr) return;
if (strlen(element.id) == 0) return;
chprintf(chp, element.id);
chprintf(chp, " ");
@@ -740,9 +741,9 @@ static void cmd_applist(BaseSequentialStream* chp, int argc, char* argv[]) {
if (!top_widget) return;
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
if (!nav) return;
// TODO(u-foka): Somehow order static and dynamic app lists together
for (auto& element : ui::NavigationView::appMap) { // Use the map as its ordered by id
printAppInfo(chp, element.second);
// todo U-foka : sort the list
for (auto& element : ui::NavigationView::appList) {
printAppInfo(chp, element);
}
ui::ExternalItemsMenuLoader::load_all_external_items_callback([chp](ui::AppInfoConsole& info) {
printAppInfo(chp, info);
-39
View File
@@ -1,39 +0,0 @@
/*
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
*
* 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 __VIEW_FACTORY_HPP__
#define __VIEW_FACTORY_HPP__
#include "view_factory_base.hpp"
namespace ui {
template <typename T>
class ViewFactory : public ViewFactoryBase {
public:
virtual std::unique_ptr<View> produce(NavigationView& nav) const override {
return std::unique_ptr<View>(new T(nav));
}
};
} // namespace ui
#endif //__VIEW_FACTORY_HPP__
@@ -1,28 +0,0 @@
/*
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
*
* 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.
*/
#include "view_factory_base.hpp"
namespace ui {
ViewFactoryBase::~ViewFactoryBase() {}
} // namespace ui
@@ -1,40 +0,0 @@
/*
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
*
* 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 __VIEW_FACTORY_BASE_HPP__
#define __VIEW_FACTORY_BASE_HPP__
#include <memory>
#include "ui_widget.hpp"
namespace ui {
class NavigationView;
class ViewFactoryBase {
public:
virtual ~ViewFactoryBase();
virtual std::unique_ptr<View> produce(NavigationView& nav) const = 0;
};
} // namespace ui
#endif //__VIEW_FACTORY_BASE_HPP__
+1 -3
View File
@@ -170,7 +170,7 @@ bool I2cDev::i2c_write(uint8_t* reg, uint8_t reg_size, uint8_t* data, uint8_t by
if (bytes == 0) return false;
// Create a new buffer to hold both reg and data
uint8_t total_size = reg_size + bytes;
uint8_t* buffer = new uint8_t[total_size];
uint8_t buffer[total_size];
// Copy the register data into the buffer
if (reg_size > 0 && reg) {
memcpy(buffer, reg, reg_size);
@@ -179,8 +179,6 @@ bool I2cDev::i2c_write(uint8_t* reg, uint8_t reg_size, uint8_t* data, uint8_t by
memcpy(buffer + reg_size, data, bytes);
// Transmit the combined data
bool result = i2cbus.transmit(addr, buffer, total_size, 150);
// Clean up the dynamically allocated buffer
delete[] buffer;
if (!result)
got_error();
else
+18 -19
View File
@@ -703,7 +703,7 @@ void Console::clear(bool clear_buffer = false) {
pos = {0, 0};
}
void Console::write(std::string message) {
void Console::write(const std::string& message) {
bool escape = false;
if (!hidden() && drawn()) {
@@ -754,7 +754,7 @@ void Console::getWidgetName(std::string& result) {
result = "Console";
}
void Console::writeln(std::string message) {
void Console::writeln(const std::string& message) {
write(message + "\n");
}
@@ -845,7 +845,7 @@ Checkbox::Checkbox(
set_focusable(true);
}
void Checkbox::set_text(const std::string value) {
void Checkbox::set_text(const std::string& value) {
text_ = value;
set_dirty();
}
@@ -977,7 +977,7 @@ Button::Button(
set_focusable(true);
}
void Button::set_text(const std::string value) {
void Button::set_text(const std::string& value) {
text_ = value;
set_dirty();
}
@@ -1124,7 +1124,7 @@ ButtonWithEncoder::ButtonWithEncoder(
set_focusable(true);
}
void ButtonWithEncoder::set_text(const std::string value) {
void ButtonWithEncoder::set_text(const std::string& value) {
text_ = value;
set_dirty();
}
@@ -1296,7 +1296,7 @@ NewButton::NewButton(
set_focusable(true);
}
void NewButton::set_text(const std::string value) {
void NewButton::set_text(const std::string& value) {
text_ = value;
set_dirty();
}
@@ -1381,17 +1381,17 @@ void NewButton::paint(Painter& painter) {
if (!text_.empty()) {
auto label_r = style.font.size_of(text_);
std::string text_to_draw = text_;
if (label_r.width() > r.width() - 2) {
// Truncate text to fit
size_t max_chars = (r.width() - 2) / style.font.char_width();
text_to_draw = text_.substr(0, max_chars);
label_r = style.font.size_of(text_to_draw);
}
if (bitmap_) {
y += spacing;
}
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y}, style, text_to_draw);
if (label_r.width() > r.width() - 2) {
size_t max_chars = (r.width() - 2) / style.font.char_width();
std::string text_to_draw = text_.substr(0, max_chars);
label_r = style.font.size_of(text_to_draw);
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y}, style, text_to_draw);
} else {
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y}, style, text_);
}
}
} else { // no valign
if (bitmap_) {
@@ -1406,15 +1406,14 @@ void NewButton::paint(Painter& painter) {
if (!text_.empty()) {
auto label_r = style.font.size_of(text_);
std::string text_to_draw = text_;
if (label_r.width() > r.width() - 2) {
// Truncate text to fit
size_t max_chars = (r.width() - 2) / style.font.char_width();
text_to_draw = text_.substr(0, max_chars);
std::string text_to_draw = text_.substr(0, max_chars);
label_r = style.font.size_of(text_to_draw);
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y + (r.height() - label_r.height()) / 2}, style, text_to_draw);
} else {
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y + (r.height() - label_r.height()) / 2}, style, text_);
}
painter.draw_string({r.left() + (r.width() - label_r.width()) / 2, y + (r.height() - label_r.height()) / 2}, style,
text_to_draw);
}
}
}
+6 -6
View File
@@ -360,8 +360,8 @@ class Console : public Widget {
Console(Rect parent_rect);
void clear(bool clear_buffer);
void write(std::string message);
void writeln(std::string message);
void write(const std::string& message);
void writeln(const std::string& message);
void paint(Painter&) override;
@@ -401,7 +401,7 @@ class Checkbox : public Widget {
Checkbox& operator=(const Checkbox&) = delete;
Checkbox& operator=(Checkbox&&) = delete;
void set_text(const std::string value);
void set_text(const std::string& value);
bool set_value(const bool value);
bool value() const;
@@ -439,7 +439,7 @@ class Button : public Widget {
: Button{{}, {}} {
}
void set_text(const std::string value);
void set_text(const std::string& value);
std::string text() const;
void paint(Painter& painter) override;
@@ -477,7 +477,7 @@ class ButtonWithEncoder : public Widget {
std::function<void()> on_change{};
void set_text(const std::string value);
void set_text(const std::string& value);
int32_t get_encoder_delta();
void set_encoder_delta(const int32_t delta);
std::string text() const;
@@ -516,7 +516,7 @@ class NewButton : public Widget {
}
void set_bitmap(const Bitmap* bitmap);
void set_text(const std::string value);
void set_text(const std::string& value);
void set_color(Color value);
void set_bg_color(Color value);
void set_vertical_center(bool value);