Multi screen support, with dyn alignment (#2801)

This commit is contained in:
Totoo
2025-10-03 19:10:10 +02:00
committed by GitHub
parent 23cabb8b8a
commit 371b6b5079
161 changed files with 4042 additions and 4157 deletions
+7 -24
View File
@@ -37,7 +37,9 @@ BtnGridView::BtnGridView(
: keep_highlight{keep_highlight} {
set_parent_rect(new_parent_rect);
set_focusable(true);
if (screen_height == 480) {
button_h = 64;
}
button_pgup.set_focusable(false);
button_pgup.on_select = [this](Button&) {
if (arrow_up_enabled) {
@@ -76,30 +78,11 @@ int BtnGridView::rows() {
void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
button_h = 48; // btn_h_min;
/*
// DISABLED FOR NOW. TODO fix next, prev button pos
int min_remainder = parent_rect().size().height();
uint8_t max_button_count = 0;
for (int h = btn_h_min; h <= btn_h_max; ++h) {
int count = parent_rect().size().height() / h;
int remainder = parent_rect().size().height() % h;
// Prefer smaller remainder, then more buttons, then larger height
if (remainder < min_remainder ||
(remainder == min_remainder && count > max_button_count) ||
(remainder == min_remainder && count == max_button_count && h > button_h)) {
button_h = h;
min_remainder = remainder;
max_button_count = count;
}
}
*/
int space_available = parent_rect().size().height() - 16; // leave space for arrows
displayed_max = (parent_rect().size().height() / button_h);
button_pgup.set_parent_rect({0, (Coord)(displayed_max * button_h), screen_width / 2, 16});
button_pgdown.set_parent_rect({screen_width / 2, (Coord)(displayed_max * button_h), screen_width / 2, 16});
button_pgup.set_parent_rect({0, (Coord)(space_available), screen_width / 2, 16});
button_pgdown.set_parent_rect({screen_width / 2, (Coord)(space_available), screen_width / 2, 16});
displayed_max *= rows_;
@@ -115,10 +98,10 @@ void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
for (size_t c = 0; c < displayed_max; c++) {
auto item = std::make_unique<NewButton>();
add_child(item.get());
item->set_vertical_center(true);
item->set_parent_rect({(int)(c % rows_) * button_w,
(int)(c / rows_) * button_h,
button_w, button_h});
menu_item_views.push_back(std::move(item));
}
update_items();
+2 -5
View File
@@ -89,9 +89,8 @@ class BtnGridView : public View {
bool blacklisted_app(GridItem new_item);
void update_items();
void set_btn_min_max_height(uint8_t min, uint8_t max) {
btn_h_min = min;
btn_h_max = max;
void set_btn_height_fixed(uint8_t h) {
button_h = h;
}
protected:
@@ -99,8 +98,6 @@ class BtnGridView : public View {
private:
int rows_{3};
uint8_t btn_h_min{40};
uint8_t btn_h_max{60};
bool keep_highlight{false};
std::vector<GridItem> menu_items{};
+1
View File
@@ -31,6 +31,7 @@ namespace ui {
FreqManUIList::FreqManUIList(Rect parent_rect)
: Widget{parent_rect},
visible_lines_{(unsigned)parent_rect.height() / char_height} {
line_max_length = (parent_rect.width() - 8) / char_width;
this->set_focusable(true);
}
+1 -1
View File
@@ -62,7 +62,7 @@ class FreqManUIList : public Widget {
static constexpr int8_t char_height = 16;
static constexpr int8_t char_width = 8;
static constexpr int8_t line_max_length = 29;
uint8_t line_max_length = 29;
size_t visible_lines_{0};
FreqmanDB* db_{nullptr};
+7 -4
View File
@@ -38,7 +38,7 @@ GeoPos::GeoPos(
const alt_unit altitude_unit,
const spd_unit speed_unit)
: altitude_unit_(altitude_unit), speed_unit_(speed_unit) {
set_parent_rect({pos, {screen_width, 3 * 16}});
set_parent_rect({pos, {screen_width, UI_POS_HEIGHT(3)}});
add_children({&labels_position,
&label_spd_position,
@@ -231,6 +231,8 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
}
void GeoMap::map_read_line_bin(ui::Color* buffer, uint16_t pixels) {
const auto r = screen_rect();
ui::Dim width = r.width();
if (map_zoom == 1) {
map_file.read(buffer, pixels << 1);
} else if (map_zoom > 1) {
@@ -242,7 +244,7 @@ void GeoMap::map_read_line_bin(ui::Color* buffer, uint16_t pixels) {
// For 240 width, than means no check is needed for map_zoom values up to 6.
// (Rectangle height must also divide evenly into map_zoom or we get black lines at end of screen)
// Note that zooming in results in a map offset of (1/map_zoom) pixels to the right & downward directions (see zoom_pixel_offset).
for (int i = (geomap_rect_width / map_zoom) - 1; i >= 0; i--) {
for (int i = (width / map_zoom) - 1; i >= 0; i--) {
for (int j = 0; j < map_zoom; j++) {
buffer[(i * map_zoom) + j] = buffer[i];
}
@@ -253,7 +255,7 @@ void GeoMap::map_read_line_bin(ui::Color* buffer, uint16_t pixels) {
// 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 < geomap_rect_width; i++) {
for (int i = 0; i < width; i++) {
buffer[i] = zoom_out_buffer[i * (-map_zoom)];
}
delete[] zoom_out_buffer;
@@ -466,7 +468,8 @@ 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::array<ui::Color, geomap_rect_width> map_line_buffer;
std::vector<ui::Color> map_line_buffer;
map_line_buffer.resize(r.width());
int16_t zoom_seek_x, zoom_seek_y;
if (!use_osm) {
+7 -13
View File
@@ -42,8 +42,6 @@ namespace ui {
#define INVALID_ANGLE 400
#define GEOMAP_BANNER_HEIGHT (3 * 16)
#define GEOMAP_RECT_WIDTH 240
#define GEOMAP_RECT_HEIGHT (320 - 16 - GEOMAP_BANNER_HEIGHT)
#define TILE_SIZE 256
@@ -115,31 +113,31 @@ class GeoPos : public View {
spd_unit speed_unit_{};
Labels labels_position{
{{1 * 8, 0 * 16}, "Alt:", Theme::getInstance()->fg_light->foreground},
{{1 * 8, UI_POS_Y(0)}, "Alt:", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 1 * 16}, "Lat: \xB0 ' \"", Theme::getInstance()->fg_light->foreground}, // 0xB0 is degree ° symbol in our 8x16 font
{{1 * 8, 2 * 16}, "Lon: \xB0 ' \"", Theme::getInstance()->fg_light->foreground},
};
Labels label_spd_position{
{{15 * 8, 0 * 16}, "Spd:", Theme::getInstance()->fg_light->foreground},
{{15 * 8, UI_POS_Y(0)}, "Spd:", Theme::getInstance()->fg_light->foreground},
};
NumberField field_altitude{
{6 * 8, 0 * 16},
{6 * 8, UI_POS_Y(0)},
5,
{-1000, 50000},
250,
' '};
NumberField field_speed{
{19 * 8, 0 * 16},
{19 * 8, UI_POS_Y(0)},
4,
{0, 5000},
1,
' '};
Text text_alt_unit{
{12 * 8, 0 * 16, 2 * 8, 16},
{12 * 8, UI_POS_Y(0), 2 * 8, 16},
""};
Text text_speed_unit{
{25 * 8, 0 * 16, 5 * 8, 16},
{25 * 8, UI_POS_Y(0), 5 * 8, 16},
""};
NumberField field_lat_degrees{
@@ -243,10 +241,6 @@ class GeoMap : public Widget {
void clear_markers();
MapMarkerStored store_marker(GeoMarker& marker);
static const Dim banner_height = GEOMAP_BANNER_HEIGHT;
static const Dim geomap_rect_width = GEOMAP_RECT_WIDTH;
static const Dim geomap_rect_height = GEOMAP_RECT_HEIGHT;
private:
void draw_scale(Painter& painter);
ui::Point item_rect_pixel(GeoMarker& item);
@@ -370,7 +364,7 @@ class GeoMapView : public View {
speed_unit_};
GeoMap geomap{
{0, GeoMap::banner_height, GeoMap::geomap_rect_width, GeoMap::geomap_rect_height}};
{0, GEOMAP_BANNER_HEIGHT, screen_width, screen_height - 16 - GEOMAP_BANNER_HEIGHT}};
Button button_ok{
{screen_width - 15 * 8, 0, 15 * 8, 1 * 16},
+3 -3
View File
@@ -55,7 +55,7 @@ QRCodeImage& QRCodeImage::operator=(const QRCodeImage& Image) {
void QRCodeImage::paint(Painter& painter) {
(void)painter;
auto r = screen_rect();
// The structure to manage the QR code
QRCode qrcode;
@@ -66,12 +66,12 @@ void QRCodeImage::paint(Painter& painter) {
qrcode_initText(&qrcode, qrcodeBytes, qr_version, ECC_HIGH, qr_text_);
display.fill_rectangle(Rect(57, 65, 126, 127), Color::white());
display.fill_rectangle(Rect(r.left(), r.top(), 126, 127), Color::white()); // 57,63
for (uint8_t y = 0; y < qrcode.size; y++) {
for (uint8_t x = 0; x < qrcode.size; x++) {
if (qrcode_getModule(&qrcode, x, y)) {
display.fill_rectangle(Rect(63 + (x * 2), 71 + (y * 2), 2, 2), Color::black());
display.fill_rectangle(Rect((r.left() + 6) + (x * 2), (r.top() + 8) + (y * 2), 2, 2), Color::black());
}
}
}
+2 -2
View File
@@ -69,7 +69,7 @@ class QRCodeView : public View {
std::function<void(void)> on_close_{nullptr};
QRCodeImage qr_code{
{50, 100, 100, 100}};
{UI_POS_X_CENTER(16), 63, 100, 100}};
// Text text_qr {
// { 0 * 8, 10 * 16, 32 * 8, 1 * 8 },
@@ -77,7 +77,7 @@ class QRCodeView : public View {
// };
Button button_close{
{9 * 8, 31 * 8, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12), 31 * 8, 12 * 8, 3 * 16},
"Back"};
};
+1
View File
@@ -256,6 +256,7 @@ bool FrequencyKeypadView::on_encoder(const EncoderEvent delta) {
FrequencyKeypadView::FrequencyKeypadView(
NavigationView& nav,
const rf::Frequency value) {
button_w = screen_width / 3;
add_child(&text_value);
const auto button_fn = [this](Button& button) {
+17 -17
View File
@@ -222,7 +222,7 @@ class FrequencyKeypadView : public View {
private:
int16_t focused_button = 0;
static constexpr int button_w = 240 / 3;
int button_w = 240 / 3;
static constexpr int button_h = 48;
static constexpr int mhz_digits = 4;
@@ -232,33 +232,33 @@ class FrequencyKeypadView : public View {
static constexpr int text_digits = mhz_digits + 1 + submhz_digits;
Text text_value{
{0, 4, screen_width, 16}};
{UI_POS_X(0), 4, screen_width, 16}};
std::array<Button, 12> buttons{};
Button button_save_ghz{
{0, 14 * 16, 80 / 2 - 1, 2 * 16},
{UI_POS_X(0), 14 * 16, UI_POS_WIDTH_PERCENT(16) + 1, UI_POS_HEIGHT(2)},
"GHz"};
Button button_save_khz{
{0 + 40 - 1, 14 * 16, 80 / 2, 2 * 16},
{UI_POS_X(0) + UI_POS_WIDTH_PERCENT(16) + 1, 14 * 16, UI_POS_WIDTH_PERCENT(16) + 2, UI_POS_HEIGHT(2)},
"kHz"};
Button button_save_mhz{
{0, 16 * 16, 80 - 1, 3 * 16},
{UI_POS_X(0), 16 * 16, UI_POS_WIDTH_PERCENT(33), UI_POS_HEIGHT(3)},
"Save MHz"};
Button button_load{
{80 + 1, 14 * 16, 80 - 2, 40},
{UI_POS_WIDTH_PERCENT(33) + 1, 14 * 16, UI_POS_WIDTH_PERCENT(33), 40},
"Load"};
Button button_clear{
{80 + 1, 264, 80 - 2, 40},
{UI_POS_WIDTH_PERCENT(33) + 1, 264, UI_POS_WIDTH_PERCENT(33), 40},
"Clear"};
Button button_done_ghz{
{160 + 1, 14 * 16, 80 / 2 - 1, 2 * 16},
{UI_POS_WIDTH_PERCENT(66) + 1, 14 * 16, UI_POS_WIDTH_PERCENT(16) + 1, UI_POS_HEIGHT(2)},
"GHz"};
Button button_done_khz{
{160 + 40, 14 * 16, 80 / 2 - 1, 2 * 16},
{UI_POS_WIDTH_PERCENT(82) + 2, 14 * 16, UI_POS_WIDTH_PERCENT(16) + 2, UI_POS_HEIGHT(2)},
"kHz"};
Button button_done_mhz{
{160 + 1, 16 * 16, 80 - 2, 3 * 16},
{UI_POS_WIDTH_PERCENT(66) + 1, 16 * 16, UI_POS_WIDTH_PERCENT(33), UI_POS_HEIGHT(3)},
"Done MHz"};
/* TODO: Template arg required in enum?! */
@@ -312,29 +312,29 @@ class FrequencyOptionsView : public View {
private:
Text text_step{
{0 * 8, 0 * 16, 4 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), 4 * 8, 1 * 16},
"Step"};
FrequencyStepView field_step{
{5 * 8, 0 * 16},
{5 * 8, UI_POS_Y(0)},
};
void on_step_changed(rf::Frequency v);
void on_reference_ppm_correction_changed(int32_t v);
NumberField field_ppm{
{23 * 8, 0 * 16},
{23 * 8, UI_POS_Y(0)},
3,
{-99, 99},
1,
'0',
};
Text text_ext{
{23 * 8, 0 * 16, 3 * 8, 1 * 16},
{23 * 8, UI_POS_Y(0), 3 * 8, 1 * 16},
"EXT",
};
Text text_ppm{
{27 * 8, 0 * 16, 3 * 8, 16},
{27 * 8, UI_POS_Y(0), 3 * 8, 16},
"PPM",
};
};
@@ -350,11 +350,11 @@ class RadioGainOptionsView : public View {
private:
Text label_rf_amp{
{0 * 8, 0 * 16, 3 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), 3 * 8, 1 * 16},
"Amp"};
RFAmpField field_rf_amp{
{4 * 8, 0 * 16},
{4 * 8, UI_POS_Y(0)},
};
};
+17 -17
View File
@@ -61,7 +61,7 @@ void AudioSpectrumView::paint(Painter& painter) {
// Cursor
const Rect r_cursor{
field_frequency.value() / (48000 / 240), r.bottom() - 32 - cursor_band_height,
field_frequency.value() / (48000 / screen_width), r.bottom() - 32 - cursor_band_height,
1, cursor_band_height};
painter.fill_rectangle(
r_cursor,
@@ -104,8 +104,8 @@ void FrequencyScale::set_channel_filter(
void FrequencyScale::set_cursor_position(const int32_t position) {
cursor_position = position;
cursor_position = std::min<int32_t>(cursor_position, 119);
cursor_position = std::max<int32_t>(cursor_position, -120);
cursor_position = std::min<int32_t>(cursor_position, screen_width / 2 - 1);
cursor_position = std::max<int32_t>(cursor_position, -1 * screen_width / 2);
set_dirty();
}
@@ -124,7 +124,7 @@ void FrequencyScale::paint(Painter& painter) {
draw_frequency_ticks(painter, r);
const Rect r_cursor{
118 + cursor_position, r.bottom() - filter_band_height,
(screen_width / 2 - 2) + cursor_position, r.bottom() - filter_band_height,
5, filter_band_height};
painter.fill_rectangle(
r_cursor,
@@ -222,8 +222,8 @@ void FrequencyScale::on_blur() {
bool FrequencyScale::on_encoder(const EncoderEvent delta) {
cursor_position += delta;
cursor_position = std::min<int32_t>(cursor_position, 119);
cursor_position = std::max<int32_t>(cursor_position, -120);
cursor_position = std::min<int32_t>(cursor_position, screen_width / 2 - 1);
cursor_position = std::max<int32_t>(cursor_position, -1 * screen_width / 2);
set_dirty();
@@ -233,7 +233,7 @@ bool FrequencyScale::on_encoder(const EncoderEvent delta) {
bool FrequencyScale::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (on_select) {
on_select((cursor_position * spectrum_sampling_rate) / 240);
on_select((cursor_position * spectrum_sampling_rate) / screen_width);
cursor_position = 0;
set_dirty();
return true;
@@ -246,7 +246,7 @@ bool FrequencyScale::on_key(const KeyEvent key) {
bool FrequencyScale::on_touch(const TouchEvent touch) {
if (touch.type == TouchEvent::Type::Start) {
if (on_select) {
on_select((touch.point.x() * spectrum_sampling_rate) / 240);
on_select((touch.point.x() * spectrum_sampling_rate) / screen_width);
}
}
return true;
@@ -261,6 +261,8 @@ void WaterfallWidget::on_show() {
const auto screen_r = screen_rect();
display.scroll_set_area(screen_r.top(), screen_r.bottom());
clear();
}
void WaterfallWidget::on_hide() {
@@ -268,27 +270,25 @@ void WaterfallWidget::on_hide() {
* position?
*/
display.scroll_disable();
clear();
}
void WaterfallWidget::on_channel_spectrum(
const ChannelSpectrum& spectrum) {
/* TODO: static_assert that message.spectrum.db.size() >= pixel_row.size() */
std::array<Color, 240> pixel_row;
for (size_t i = 0; i < 120; i++) {
const auto pixel_color = gradient.lut[spectrum.db[256 - 120 + i]];
std::vector<Color> pixel_row(screen_width);
for (size_t i = 0; i < screen_width / 2; i++) {
const auto pixel_color = gradient.lut[spectrum.db[256 - screen_width / 2 + i]];
pixel_row[i] = pixel_color;
}
for (size_t i = 120; i < 240; i++) {
const auto pixel_color = gradient.lut[spectrum.db[i - 120]];
for (size_t i = screen_width / 2; i < screen_width; i++) {
const auto pixel_color = gradient.lut[spectrum.db[i - screen_width / 2]];
pixel_row[i] = pixel_color;
}
const auto draw_y = display.scroll(1);
display.draw_pixels(
{{0, draw_y}, {pixel_row.size(), 1}},
{{0, draw_y}, {(int)pixel_row.size(), 1}},
pixel_row);
}
+3 -3
View File
@@ -51,10 +51,10 @@ class AudioSpectrumView : public View {
int16_t audio_spectrum[128]{0};
Labels labels{
{{6 * 8, 0 * 16}, "Hz", Theme::getInstance()->fg_light->foreground}};
{{6 * 8, UI_POS_Y(0)}, "Hz", Theme::getInstance()->fg_light->foreground}};
NumberField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
5,
{0, 48000},
48000 / 240,
@@ -150,7 +150,7 @@ class WaterfallView : public View {
private:
void update_widgets_rect();
const Rect audio_spectrum_view_rect{0 * 8, 0 * 16, screen_width, 2 * 16 + 20};
const Rect audio_spectrum_view_rect{UI_POS_X(0), UI_POS_Y(0), UI_POS_MAXWIDTH, 2 * 16 + 20};
static constexpr Dim audio_spectrum_height = 16 * 2 + 20;
static constexpr Dim scale_height = 20;
+16 -12
View File
@@ -56,17 +56,21 @@ static const Style* get_style_for_gain(uint8_t tot_gain) {
void TransmitterView::paint(Painter& painter) {
size_t c;
Point pos = {0, screen_pos().y()};
for (c = 0; c < 20; c++) {
Point pos2 = {0, screen_pos().y() + 32 + 8};
// 24*8 pxstripes
for (c = 0; c < (size_t)(screen_width / 24 + 1); c++) {
painter.draw_bitmap(
pos,
bitmap_stripes,
Theme::getInstance()->fg_yellow->foreground,
Theme::getInstance()->fg_yellow->background);
if (c != 9)
pos += {24, 0};
else
pos = {0, screen_pos().y() + 32 + 8};
painter.draw_bitmap(
pos2,
bitmap_stripes,
Theme::getInstance()->fg_yellow->foreground,
Theme::getInstance()->fg_yellow->background);
pos += {24, 0};
pos2 += {24, 0};
}
}
@@ -225,16 +229,16 @@ TransmitterView2::TransmitterView2(Point pos, bool short_ui) {
text_labels.set(short_ui ? "G: A:" : "Gain: Amp:");
text_labels.set_parent_rect(
short_ui
? Rect{0 * 8, 0 * 16, 7 * 8, 1 * 16}
: Rect{0 * 8, 0 * 16, 12 * 8, 1 * 16});
? Rect{UI_POS_X(0), UI_POS_Y(0), 7 * 8, 1 * 16}
: Rect{UI_POS_X(0), UI_POS_Y(0), 12 * 8, 1 * 16});
field_gain.set_parent_rect(
short_ui
? Rect{2 * 8, 0 * 16, 2 * 8, 1 * 16}
: Rect{5 * 8, 0 * 16, 2 * 8, 1 * 16});
? Rect{2 * 8, UI_POS_Y(0), 2 * 8, 1 * 16}
: Rect{5 * 8, UI_POS_Y(0), 2 * 8, 1 * 16});
field_amp.set_parent_rect(
short_ui
? Rect{7 * 8, 0 * 16, 2 * 8, 1 * 16}
: Rect{12 * 8, 0 * 16, 2 * 8, 1 * 16});
? Rect{7 * 8, UI_POS_Y(0), 2 * 8, 1 * 16}
: Rect{12 * 8, UI_POS_Y(0), 2 * 8, 1 * 16});
field_gain.set_value(transmitter_model.tx_gain());
field_gain.on_change = [this](uint32_t tx_gain) {
+1 -1
View File
@@ -115,7 +115,7 @@ class TransmitterView : public View {
' '};
Button button_start{
{21 * 8, 1 * 8, 9 * 8, 32},
{UI_POS_X_RIGHT(9), 1 * 8, 9 * 8, 32},
"START"};
FrequencyStepView field_frequency_step{
+4 -4
View File
@@ -50,11 +50,11 @@ class TimeScopeView : public View {
int16_t audio_spectrum[128]{0};
/*Labels labels {
{ { 6 * 8, 0 * 16 }, "Hz", Theme::getInstance()->fg_light->foreground }
{ { 6 * 8,UI_POS_Y(0) }, "Hz", Theme::getInstance()->fg_light->foreground }
};*/
/*
NumberField field_frequency {
{ 0 * 8, 0 * 16 },
{ 0 * 8,UI_POS_Y(0) },
5,
{ 0, 48000 },
48000 / 240,
@@ -108,7 +108,7 @@ class TVWidget : public View {
void paint(Painter& painter) override;
NumberField field_xcorr{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
5,
{0, 128},
1,
@@ -117,7 +117,7 @@ class TVWidget : public View {
private:
void update_widgets_rect();
const Rect audio_spectrum_view_rect{0 * 8, 0 * 16, screen_width, 2 * 16 + 20};
const Rect audio_spectrum_view_rect{UI_POS_X(0), UI_POS_Y(0), screen_width, 2 * 16 + 20};
static constexpr Dim audio_spectrum_height = 16 * 2 + 20;
static constexpr Dim scale_height = 20;