mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
feat(ui): migrate e-ink to misc-fixed font; fix Alarm UX and status-bar icon alignment
- GxEPDDisplay now uses the same single misc-fixed 6x9 font as the OLED driver (Lemon retired there too), with baseline math updated for the new ascent. - Clock Tools' Alarm screen: Repeat and Armed now respond to LEFT/RIGHT like every other multi-value/toggle field in Settings, not Enter-only; Hour/Minute merged into one Time row edited with a hand-rolled HH:MM digit cursor (like the Timer's), replacing the two-row DigitEditor popups; Repeat's "Off" label now matches the codebase-wide ON/OFF casing. - Top status bar: battery icon now shares the same box height as the other status icons (Bluetooth/mute/etc.) instead of standing 2px taller, and its charge nub is properly vertically centred instead of drifting off-centre at non-multiple-of-4 box heights. - Small settings-gear icon glyph tweak; wio-tracker-l1 screenshot build variant now enables DUAL_SERIAL. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -312,7 +312,7 @@ struct NodePrefs { // persisted to file
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
static const char* alarmRepeatLabel(uint8_t idx) {
|
static const char* alarmRepeatLabel(uint8_t idx) {
|
||||||
static const char* L[ALARM_REPEAT_COUNT] = { "Off", "Daily", "Weekdays", "Weekends" };
|
static const char* L[ALARM_REPEAT_COUNT] = { "OFF", "Daily", "Weekdays", "Weekends" };
|
||||||
return L[idx < ALARM_REPEAT_COUNT ? idx : 0];
|
return L[idx < ALARM_REPEAT_COUNT ? idx : 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,14 @@
|
|||||||
// • Stopwatch — purely a display utility with no background action, so its
|
// • Stopwatch — purely a display utility with no background action, so its
|
||||||
// millis() state lives here; it keeps counting while you're elsewhere.
|
// millis() state lives here; it keeps counting while you're elsewhere.
|
||||||
//
|
//
|
||||||
// Numeric fields (alarm hour/minute, timer h/m/s) are edited with the shared
|
// Alarm hour/minute and the Timer's HH:MM:SS are both edited with the same
|
||||||
// framework DigitEditor (digit-by-digit: LEFT/RIGHT cursor, UP/DOWN +/- the
|
// hand-rolled digit cursor (LEFT/RIGHT moves between digits, UP/DOWN steps
|
||||||
// place, min/max enforced) — the same widget Settings/Repeater use for Freq,
|
// the digit under it, wrapping within its place and keeping the whole field
|
||||||
// rather than a hand-rolled wrap.
|
// a valid time) rather than the shared DigitEditor framework Settings/
|
||||||
|
// Repeater use for Freq — DigitEditor edits one flat decimal value, and a
|
||||||
|
// combined HH:MM/HH:MM:SS field needs each place capped independently (hour
|
||||||
|
// tens <= 2, minute/second tens <= 5), which a single flat step size can't
|
||||||
|
// express without landing on invalid times at the boundaries.
|
||||||
//
|
//
|
||||||
// E-INK: the running timer/stopwatch readouts would thrash a slow e-paper panel
|
// E-INK: the running timer/stopwatch readouts would thrash a slow e-paper panel
|
||||||
// if redrawn every second, so on e-ink the live readout refreshes only coarsely
|
// if redrawn every second, so on e-ink the live readout refreshes only coarsely
|
||||||
@@ -25,7 +29,6 @@
|
|||||||
|
|
||||||
#include "../NodePrefs.h"
|
#include "../NodePrefs.h"
|
||||||
#include "icons.h" // drawList / drawRowSelection
|
#include "icons.h" // drawList / drawRowSelection
|
||||||
#include "DigitEditor.h" // shared digit-by-digit numeric editor
|
|
||||||
|
|
||||||
class ClockToolsScreen : public UIScreen {
|
class ClockToolsScreen : public UIScreen {
|
||||||
UITask* _task;
|
UITask* _task;
|
||||||
@@ -45,12 +48,10 @@ class ClockToolsScreen : public UIScreen {
|
|||||||
uint32_t _sw_start_ms = 0;
|
uint32_t _sw_start_ms = 0;
|
||||||
uint32_t _sw_accum_ms = 0;
|
uint32_t _sw_accum_ms = 0;
|
||||||
|
|
||||||
// Shared numeric editor (alarm hour/minute) + which field it targets. The
|
// Alarm time's own digit cursor (same shape as the timer's, just HH:MM —
|
||||||
// timer uses its own continuous digit cursor (per-place caps a single
|
// no seconds field).
|
||||||
// DigitEditor can't express), so it isn't listed here.
|
bool _alarm_editing = false;
|
||||||
enum EditKind : uint8_t { EK_NONE, EK_A_HOUR, EK_A_MIN };
|
uint8_t _alarm_cur = 0; // digit cursor over HH:MM, 0..3 (skips the colon)
|
||||||
DigitEditor _editor;
|
|
||||||
EditKind _edit_kind = EK_NONE;
|
|
||||||
|
|
||||||
// E-ink: coarse refresh for the live readouts (millis underneath is exact).
|
// E-ink: coarse refresh for the live readouts (millis underneath is exact).
|
||||||
static int liveTickMs(DisplayDriver& d, int oled_ms) { return d.isEink() ? 30000 : oled_ms; }
|
static int liveTickMs(DisplayDriver& d, int oled_ms) { return d.isEink() ? 30000 : oled_ms; }
|
||||||
@@ -96,32 +97,49 @@ class ClockToolsScreen : public UIScreen {
|
|||||||
*f = (uint8_t)(t * 10 + u);
|
*f = (uint8_t)(t * 10 + u);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the DigitEditor on a numeric field (2 integer digits, no decimals).
|
// Step the digit under the alarm-time cursor by dir (+1/-1) — same shape as
|
||||||
void editField(EditKind kind, uint8_t value, uint8_t vmax) {
|
// stepTimerDigit, just two fields (hour/minute) instead of three.
|
||||||
_edit_kind = kind;
|
void stepAlarmDigit(int dir) {
|
||||||
_editor.begin((float)value, 0.0f, (float)vmax, 2, 0);
|
uint8_t* f; int tens_max; bool is_hours;
|
||||||
|
if (_alarm_cur < 2) { f = &_prefs->alarm_hour; tens_max = 2; is_hours = true; }
|
||||||
|
else { f = &_prefs->alarm_min; tens_max = 5; is_hours = false; }
|
||||||
|
int t = *f / 10, u = *f % 10;
|
||||||
|
if (_alarm_cur % 2 == 0) { // tens place
|
||||||
|
t = (t + dir + (tens_max + 1)) % (tens_max + 1);
|
||||||
|
if (is_hours && t == 2 && u > 3) u = 3; // 2X hours can't exceed 23
|
||||||
|
} else { // units place
|
||||||
|
int umax = (is_hours && t == 2) ? 3 : 9;
|
||||||
|
u = (u + dir + (umax + 1)) % (umax + 1);
|
||||||
|
}
|
||||||
|
*f = (uint8_t)(t * 10 + u);
|
||||||
|
_alarm_dirty = true;
|
||||||
|
_task->onAlarmChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feed a key to the open editor, write the (live) value back to its field, and
|
// Draw the alarm's "HH:MM" value: while this row is in its digit-cursor
|
||||||
// close the editor when DigitEditor reports DONE/CANCELLED.
|
// sub-mode, the digit under the cursor is shown inverted (same convention as
|
||||||
bool feedEditor(char c) {
|
// DigitEditor::render — assumes the caller is already inside a selected row,
|
||||||
DigitEditor::Result r = _editor.handleInput(c);
|
// ink DARK on a LIGHT selection bar); otherwise just the plain text.
|
||||||
uint8_t v = (uint8_t)(_editor.value + 0.5f);
|
void drawAlarmTime(DisplayDriver& d, int y, int valx, bool editing) {
|
||||||
switch (_edit_kind) {
|
char buf[6];
|
||||||
case EK_A_HOUR: _prefs->alarm_hour = v; _alarm_dirty = true; _task->onAlarmChanged(); break;
|
fmtClock(buf, sizeof(buf), _prefs->alarm_hour, _prefs->alarm_min);
|
||||||
case EK_A_MIN: _prefs->alarm_min = v; _alarm_dirty = true; _task->onAlarmChanged(); break;
|
if (!editing) { d.setCursor(valx, y); d.print(buf); return; }
|
||||||
default: break;
|
const int cw = d.getCharWidth();
|
||||||
|
const int char_idx = _alarm_cur + (_alarm_cur / 2); // skip the colon
|
||||||
|
for (int k = 0; buf[k]; k++) {
|
||||||
|
int cx = valx + k * cw;
|
||||||
|
if (k == char_idx) {
|
||||||
|
d.setColor(DisplayDriver::DARK);
|
||||||
|
d.fillRect(cx, y - 1, cw, d.getLineHeight() + 1);
|
||||||
|
d.setColor(DisplayDriver::LIGHT);
|
||||||
|
} else {
|
||||||
|
d.setColor(DisplayDriver::DARK);
|
||||||
}
|
}
|
||||||
if (r != DigitEditor::NONE) _edit_kind = EK_NONE; // editor closed
|
char one[2] = { buf[k], '\0' };
|
||||||
return true;
|
d.setCursor(cx, y);
|
||||||
|
d.print(one);
|
||||||
}
|
}
|
||||||
|
d.setColor(DisplayDriver::LIGHT);
|
||||||
// Draw a row's value column: the live editor when this row is the one being
|
|
||||||
// edited, otherwise the static text.
|
|
||||||
void drawValue(DisplayDriver& d, int y, int valx, int reserve, bool sel,
|
|
||||||
EditKind mykind, const char* text) {
|
|
||||||
if (sel && _editor.active && _edit_kind == mykind) _editor.render(d, valx, y);
|
|
||||||
else d.drawTextEllipsized(valx, y, d.width() - valx - reserve, text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Sub-renders ───────────────────────────────────────────────────────────
|
// ── Sub-renders ───────────────────────────────────────────────────────────
|
||||||
@@ -145,21 +163,22 @@ class ClockToolsScreen : public UIScreen {
|
|||||||
int renderAlarm(DisplayDriver& d) {
|
int renderAlarm(DisplayDriver& d) {
|
||||||
d.drawCenteredHeader("ALARM");
|
d.drawCenteredHeader("ALARM");
|
||||||
if (!_prefs) return 60000;
|
if (!_prefs) return 60000;
|
||||||
const char* rows[4] = { "Hour", "Minute", "Repeat", "Armed" };
|
const char* rows[3] = { "Time", "Repeat", "Armed" };
|
||||||
if (_sel > 3) _sel = 3;
|
if (_sel > 2) _sel = 2;
|
||||||
const int valx = d.width() / 2 + 6;
|
const int valx = d.width() / 2 + 6;
|
||||||
drawList(d, 4, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
|
drawList(d, 3, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
|
||||||
drawRowSelection(d, y, sel, reserve);
|
drawRowSelection(d, y, sel, reserve);
|
||||||
d.setCursor(4, y); d.print(rows[i]);
|
d.setCursor(4, y); d.print(rows[i]);
|
||||||
char b[10];
|
if (i == 0) {
|
||||||
if (i == 0) snprintf(b, sizeof(b), "%02u", _prefs->alarm_hour);
|
drawAlarmTime(d, y, valx, sel && _alarm_editing);
|
||||||
else if (i == 1) snprintf(b, sizeof(b), "%02u", _prefs->alarm_min);
|
} else if (i == 1) {
|
||||||
else if (i == 2) snprintf(b, sizeof(b), "%s", NodePrefs::alarmRepeatLabel(NodePrefs::alarmRepeatIdxForMask(_prefs->alarm_repeat_mask)));
|
d.drawTextEllipsized(valx, y, d.width() - valx - reserve,
|
||||||
else snprintf(b, sizeof(b), "%s", _prefs->alarm_on ? "ON" : "OFF");
|
NodePrefs::alarmRepeatLabel(NodePrefs::alarmRepeatIdxForMask(_prefs->alarm_repeat_mask)));
|
||||||
EditKind k = (i == 0) ? EK_A_HOUR : (i == 1) ? EK_A_MIN : EK_NONE;
|
} else {
|
||||||
drawValue(d, y, valx, reserve, sel, k, b);
|
d.drawTextEllipsized(valx, y, d.width() - valx - reserve, _prefs->alarm_on ? "ON" : "OFF");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return _editor.active ? 50 : 60000;
|
return _alarm_editing ? 50 : 60000;
|
||||||
}
|
}
|
||||||
|
|
||||||
int renderTimer(DisplayDriver& d) {
|
int renderTimer(DisplayDriver& d) {
|
||||||
@@ -220,25 +239,50 @@ class ClockToolsScreen : public UIScreen {
|
|||||||
|
|
||||||
bool inputAlarm(char c) {
|
bool inputAlarm(char c) {
|
||||||
if (!_prefs) { if (c == KEY_CANCEL) { _view = V_MENU; _sel = 0; } return true; }
|
if (!_prefs) { if (c == KEY_CANCEL) { _view = V_MENU; _sel = 0; } return true; }
|
||||||
if (_editor.active) return feedEditor(c);
|
|
||||||
|
// Time row's digit-cursor sub-mode (same shape as the Timer's HH:MM:SS
|
||||||
|
// cursor): LEFT/RIGHT moves across HH:MM's 4 digits, UP/DOWN steps the
|
||||||
|
// digit under the cursor, Enter/Cancel commits and leaves (there's no
|
||||||
|
// rollback — every step already writes straight to alarm_hour/min and
|
||||||
|
// reschedules, same as the old per-field editor did).
|
||||||
|
if (_alarm_editing) {
|
||||||
|
if (keyIsPrev(c)) { _alarm_cur = (_alarm_cur + 3) % 4; return true; }
|
||||||
|
if (keyIsNext(c)) { _alarm_cur = (_alarm_cur + 1) % 4; return true; }
|
||||||
|
if (c == KEY_UP) { stepAlarmDigit(+1); return true; }
|
||||||
|
if (c == KEY_DOWN) { stepAlarmDigit(-1); return true; }
|
||||||
|
if (c == KEY_ENTER || c == KEY_CANCEL) { _alarm_editing = false; return true; }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (c == KEY_CANCEL) {
|
if (c == KEY_CANCEL) {
|
||||||
_task->savePrefsIfDirty(_alarm_dirty); // persist alarm fields only if edited
|
_task->savePrefsIfDirty(_alarm_dirty); // persist alarm fields only if edited
|
||||||
_task->onAlarmChanged(); // re-schedule from the new time
|
_task->onAlarmChanged(); // re-schedule from the new time
|
||||||
_view = V_MENU; _sel = 0; _scroll = 0;
|
_view = V_MENU; _sel = 0; _scroll = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (c == KEY_UP) { _sel = (_sel > 0) ? _sel - 1 : 3; return true; }
|
if (c == KEY_UP) { _sel = (_sel > 0) ? _sel - 1 : 2; return true; }
|
||||||
if (c == KEY_DOWN) { _sel = (_sel < 3) ? _sel + 1 : 0; return true; }
|
if (c == KEY_DOWN) { _sel = (_sel < 2) ? _sel + 1 : 0; return true; }
|
||||||
if (c == KEY_ENTER) {
|
// Repeat: LEFT/RIGHT steps Off -> Daily -> Weekdays -> Weekends (wrapping),
|
||||||
if (_sel == 0) editField(EK_A_HOUR, _prefs->alarm_hour, 23);
|
// matching the rest of the UI's convention for multi-value fields (e.g.
|
||||||
else if (_sel == 1) editField(EK_A_MIN, _prefs->alarm_min, 59);
|
// Settings' Keyboard Alphabet / Battery display); Enter still advances one
|
||||||
else if (_sel == 2) { // Repeat: cycle Off -> Daily -> Weekdays -> Weekends -> Off
|
// step too, for parity with those same fields.
|
||||||
uint8_t idx = (NodePrefs::alarmRepeatIdxForMask(_prefs->alarm_repeat_mask) + 1) % NodePrefs::ALARM_REPEAT_COUNT;
|
if (_sel == 1 && (keyIsPrev(c) || keyIsNext(c) || c == KEY_ENTER)) {
|
||||||
|
int idx = NodePrefs::alarmRepeatIdxForMask(_prefs->alarm_repeat_mask);
|
||||||
|
idx = keyIsPrev(c) ? (idx + NodePrefs::ALARM_REPEAT_COUNT - 1) % NodePrefs::ALARM_REPEAT_COUNT
|
||||||
|
: (idx + 1) % NodePrefs::ALARM_REPEAT_COUNT;
|
||||||
_prefs->alarm_repeat_mask = NodePrefs::alarmRepeatMaskForIdx(idx);
|
_prefs->alarm_repeat_mask = NodePrefs::alarmRepeatMaskForIdx(idx);
|
||||||
_alarm_dirty = true; _task->onAlarmChanged();
|
_alarm_dirty = true; _task->onAlarmChanged();
|
||||||
} else { _prefs->alarm_on ^= 1; _alarm_dirty = true; _task->onAlarmChanged(); }
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Armed: a plain on/off toggle, but still answers LEFT/RIGHT as well as
|
||||||
|
// Enter (same as every boolean field in Settings — e.g. Auto-lock, Units,
|
||||||
|
// Power save — flips either way rather than reserving direction for
|
||||||
|
// "increase/decrease").
|
||||||
|
if (_sel == 2 && (keyIsPrev(c) || keyIsNext(c) || c == KEY_ENTER)) {
|
||||||
|
_prefs->alarm_on ^= 1; _alarm_dirty = true; _task->onAlarmChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (c == KEY_ENTER && _sel == 0) { _alarm_editing = true; _alarm_cur = 0; return true; }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +322,7 @@ public:
|
|||||||
void onShow() override {
|
void onShow() override {
|
||||||
_view = V_MENU; _sel = 0; _scroll = 0;
|
_view = V_MENU; _sel = 0; _scroll = 0;
|
||||||
_timer_cur = 0;
|
_timer_cur = 0;
|
||||||
_editor.active = false; _edit_kind = EK_NONE;
|
_alarm_editing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int render(DisplayDriver& display) override {
|
int render(DisplayDriver& display) override {
|
||||||
|
|||||||
@@ -459,13 +459,18 @@ class HomeScreen : public UIScreen {
|
|||||||
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
||||||
display.setCursor(battLeftX, 0);
|
display.setCursor(battLeftX, 0);
|
||||||
display.print(buf);
|
display.print(buf);
|
||||||
} else { // icon — scales with lh
|
} else { // icon — scales with lh, same box height as the status icons beside it (ind_h)
|
||||||
const int iconH = lh;
|
const int iconH = ind_h;
|
||||||
const int iconW = lh * 2;
|
const int iconW = lh * 2;
|
||||||
const int bm = display.isLandscape() ? 3 : 2; // inner margin: 3px on landscape e-ink, 2px on OLED/portrait
|
const int bm = display.isLandscape() ? 3 : 2; // inner margin: 3px on landscape e-ink, 2px on OLED/portrait
|
||||||
battLeftX = display.width() - iconW - 3;
|
battLeftX = display.width() - iconW - 3;
|
||||||
display.drawRect(battLeftX, 0, iconW, iconH);
|
display.drawRect(battLeftX, 0, iconW, iconH);
|
||||||
display.fillRect(battLeftX + iconW, iconH / 4, 2, iconH / 2);
|
// Nub height/2, vertically centred by remaining-space/2 rather than a flat
|
||||||
|
// iconH/4 margin — the flat form only centres when iconH is a multiple of
|
||||||
|
// 4 (true for the old built-in font's lh=8, false for misc-fixed's 7/9),
|
||||||
|
// so it visibly drifted off-centre once the box height changed.
|
||||||
|
const int nub_h = iconH / 2;
|
||||||
|
display.fillRect(battLeftX + iconW, (iconH - nub_h) / 2, 2, nub_h);
|
||||||
int fillW = (pct * (iconW - 2 * bm)) / 100;
|
int fillW = (pct * (iconW - 2 * bm)) / 100;
|
||||||
display.fillRect(battLeftX + bm, bm, fillW, iconH - 2 * bm);
|
display.fillRect(battLeftX + bm, bm, fillW, iconH - 2 * bm);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ MINI_ICON(ICON_PG_SENSORS, 5, // thermometer/gauge — sensors
|
|||||||
MINI_ICON(ICON_PG_SETTINGS, 5, // small cog — settings
|
MINI_ICON(ICON_PG_SETTINGS, 5, // small cog — settings
|
||||||
packRow(".#.#."),
|
packRow(".#.#."),
|
||||||
packRow("#####"),
|
packRow("#####"),
|
||||||
packRow("##.##"),
|
packRow(".#.#."),
|
||||||
packRow("#####"),
|
packRow("#####"),
|
||||||
packRow(".#.#."));
|
packRow(".#.#."));
|
||||||
MINI_ICON(ICON_PG_MAP, 5, // folded map — map page
|
MINI_ICON(ICON_PG_MAP, 5, // folded map — map page
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
// the two conventions match.
|
// the two conventions match.
|
||||||
static int fontAscender(int sz, bool use_lemon, int scale) {
|
static int fontAscender(int sz, bool use_lemon, int scale) {
|
||||||
if (sz == 3) return 26; // FreeSans18pt7b: proportional, baseline origin
|
if (sz == 3) return 26; // FreeSans18pt7b: proportional, baseline origin
|
||||||
if (sz == 1 && use_lemon) return 8 * scale; // Lemon GFX font: baseline origin, ascender 8px×scale
|
if (sz == 1 && use_lemon) return 7 * scale; // misc-fixed 6x9 GFX font: baseline origin, ascent 7px×scale
|
||||||
return 0; // GFX built-in font: cursor is top-left of cell
|
return 0; // GFX built-in font: cursor is top-left of cell
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,11 +47,11 @@ int16_t GxEPDDisplay::drawLemonChar(int16_t x, int16_t y, uint32_t cp, int sc) {
|
|||||||
return x + xa * sc;
|
return x + xa * sc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cp < Lemon.first || cp > Lemon.last) {
|
if (cp < MiscFixed.first || cp > MiscFixed.last) {
|
||||||
if (cp >= 0x20) display.fillRect(x + sc, y - 8*sc, 4*sc, 6*sc, _curr_color);
|
if (cp >= 0x20) display.fillRect(x + sc, y - 7*sc, 4*sc, 6*sc, _curr_color);
|
||||||
return x + 6 * sc;
|
return x + 6 * sc;
|
||||||
}
|
}
|
||||||
const GFXglyph* g = &lemonGlyphs[cp - Lemon.first];
|
const GFXglyph* g = &MiscFixedGlyphs[cp - MiscFixed.first];
|
||||||
uint8_t w = pgm_read_byte(&g->width), h = pgm_read_byte(&g->height);
|
uint8_t w = pgm_read_byte(&g->width), h = pgm_read_byte(&g->height);
|
||||||
int8_t xo = (int8_t)pgm_read_byte(&g->xOffset), yo = (int8_t)pgm_read_byte(&g->yOffset);
|
int8_t xo = (int8_t)pgm_read_byte(&g->xOffset), yo = (int8_t)pgm_read_byte(&g->yOffset);
|
||||||
uint8_t xa = pgm_read_byte(&g->xAdvance);
|
uint8_t xa = pgm_read_byte(&g->xAdvance);
|
||||||
@@ -59,7 +59,7 @@ int16_t GxEPDDisplay::drawLemonChar(int16_t x, int16_t y, uint32_t cp, int sc) {
|
|||||||
uint8_t bits = 0, bit = 0;
|
uint8_t bits = 0, bit = 0;
|
||||||
for (uint8_t row = 0; row < h; row++)
|
for (uint8_t row = 0; row < h; row++)
|
||||||
for (uint8_t col = 0; col < w; col++) {
|
for (uint8_t col = 0; col < w; col++) {
|
||||||
if (!bit) { bits = pgm_read_byte(&lemonBitmaps[bo++]); bit = 0x80; }
|
if (!bit) { bits = pgm_read_byte(&MiscFixedBitmaps[bo++]); bit = 0x80; }
|
||||||
if (bits & bit) {
|
if (bits & bit) {
|
||||||
if (sc == 1) display.drawPixel(x + xo + col, y + yo + row, _curr_color);
|
if (sc == 1) display.drawPixel(x + xo + col, y + yo + row, _curr_color);
|
||||||
else display.fillRect(x + xo*sc + col*sc, y + yo*sc + row*sc, sc, sc, _curr_color);
|
else display.fillRect(x + xo*sc + col*sc, y + yo*sc + row*sc, sc, sc, _curr_color);
|
||||||
@@ -71,8 +71,8 @@ int16_t GxEPDDisplay::drawLemonChar(int16_t x, int16_t y, uint32_t cp, int sc) {
|
|||||||
|
|
||||||
uint8_t GxEPDDisplay::lemonXAdvance(uint32_t cp, int sc) {
|
uint8_t GxEPDDisplay::lemonXAdvance(uint32_t cp, int sc) {
|
||||||
uint8_t xa;
|
uint8_t xa;
|
||||||
if (cp < Lemon.first || cp > Lemon.last) xa = 6;
|
if (cp < MiscFixed.first || cp > MiscFixed.last) xa = 6;
|
||||||
else xa = pgm_read_byte(&lemonGlyphs[cp - Lemon.first].xAdvance);
|
else xa = pgm_read_byte(&MiscFixedGlyphs[cp - MiscFixed.first].xAdvance);
|
||||||
return xa * sc;
|
return xa * sc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ void GxEPDDisplay::startFrame(Color bkg) {
|
|||||||
display.setTextColor(_curr_color = GxEPD_BLACK);
|
display.setTextColor(_curr_color = GxEPD_BLACK);
|
||||||
_text_sz = 1;
|
_text_sz = 1;
|
||||||
int sc = scale();
|
int sc = scale();
|
||||||
display.setFont(_use_lemon ? &Lemon : NULL);
|
display.setFont(_use_lemon ? &MiscFixed : NULL);
|
||||||
display.setTextSize(sc);
|
display.setTextSize(sc);
|
||||||
display_crc.reset();
|
display_crc.reset();
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ void GxEPDDisplay::setTextSize(int sz) {
|
|||||||
display.setTextSize(scale() * 2);
|
display.setTextSize(scale() * 2);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
display.setFont(_use_lemon ? &Lemon : NULL);
|
display.setFont(_use_lemon ? &MiscFixed : NULL);
|
||||||
display.setTextSize(sc);
|
display.setTextSize(sc);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ void GxEPDDisplay::setCursor(int x, int y) {
|
|||||||
|
|
||||||
void GxEPDDisplay::print(const char* str) {
|
void GxEPDDisplay::print(const char* str) {
|
||||||
display_crc.update<char>(str, strlen(str));
|
display_crc.update<char>(str, strlen(str));
|
||||||
// Lemon path only for sz=1 — setTextSize(2/3) switches GFX to non-Lemon fonts.
|
// misc-fixed path only for sz=1 — setTextSize(2/3) switches GFX to other fonts.
|
||||||
if (_use_lemon && _text_sz == 1) {
|
if (_use_lemon && _text_sz == 1) {
|
||||||
int16_t cx = display.getCursorX();
|
int16_t cx = display.getCursorX();
|
||||||
int16_t cy = display.getCursorY();
|
int16_t cy = display.getCursorY();
|
||||||
@@ -191,7 +191,7 @@ void GxEPDDisplay::print(const char* str) {
|
|||||||
const uint8_t* p = (const uint8_t*)str;
|
const uint8_t* p = (const uint8_t*)str;
|
||||||
while (*p) {
|
while (*p) {
|
||||||
uint32_t cp = decodeCodepoint(p);
|
uint32_t cp = decodeCodepoint(p);
|
||||||
if (cp == '\n') { cy += Lemon.yAdvance * sc; cx = 0; }
|
if (cp == '\n') { cy += MiscFixed.yAdvance * sc; cx = 0; }
|
||||||
else { cx = drawLemonChar(cx, cy, cp, sc); }
|
else { cx = drawLemonChar(cx, cy, cp, sc); }
|
||||||
}
|
}
|
||||||
display.setCursor(cx, cy);
|
display.setCursor(cx, cy);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#include <CRC32.h>
|
#include <CRC32.h>
|
||||||
|
|
||||||
#include "DisplayDriver.h"
|
#include "DisplayDriver.h"
|
||||||
#include "LemonFont.h"
|
#include "MiscFixedFont.h"
|
||||||
|
|
||||||
#ifndef DISPLAY_ROTATION
|
#ifndef DISPLAY_ROTATION
|
||||||
#define DISPLAY_ROTATION 0
|
#define DISPLAY_ROTATION 0
|
||||||
@@ -57,7 +57,7 @@ class GxEPDDisplay : public DisplayDriver {
|
|||||||
#endif
|
#endif
|
||||||
bool _init = false;
|
bool _init = false;
|
||||||
bool _isOn = false;
|
bool _isOn = false;
|
||||||
bool _use_lemon = false;
|
bool _use_lemon = true; // e-ink is single-font too now (misc-fixed 6x9), matching the OLED driver
|
||||||
uint16_t _curr_color;
|
uint16_t _curr_color;
|
||||||
CRC32 display_crc;
|
CRC32 display_crc;
|
||||||
int last_display_crc_value = 0;
|
int last_display_crc_value = 0;
|
||||||
@@ -103,16 +103,16 @@ public:
|
|||||||
if (_text_sz == 4) return 6 * BIG_TEXT_SCALE;
|
if (_text_sz == 4) return 6 * BIG_TEXT_SCALE;
|
||||||
if (_text_sz == 3) return 17;
|
if (_text_sz == 3) return 17;
|
||||||
if (_text_sz == 2) return 12 * sc;
|
if (_text_sz == 2) return 12 * sc;
|
||||||
return (_use_lemon ? 5 : 6) * sc;
|
return (_use_lemon ? 6 : 6) * sc; // misc-fixed 6x9 is 6px wide
|
||||||
}
|
}
|
||||||
int getLineHeight() const override {
|
int getLineHeight() const override {
|
||||||
int sc = scale();
|
int sc = scale();
|
||||||
if (_text_sz == 4) return 8 * BIG_TEXT_SCALE;
|
if (_text_sz == 4) return 8 * BIG_TEXT_SCALE;
|
||||||
if (_text_sz == 3) return 28;
|
if (_text_sz == 3) return 28;
|
||||||
if (_text_sz == 2) return 16 * sc;
|
if (_text_sz == 2) return 16 * sc;
|
||||||
return (_use_lemon ? 10 : 8) * sc;
|
return (_use_lemon ? 9 : 8) * sc; // misc-fixed 6x9 box height
|
||||||
}
|
}
|
||||||
void setLemonFont(bool enabled) override { _use_lemon = enabled; _vw_dirty = true; }
|
void setLemonFont(bool) override { } // single-font: ignore toggles, stay misc-fixed 6x9
|
||||||
bool isLemonFont() const override { return _use_lemon; }
|
bool isLemonFont() const override { return _use_lemon; }
|
||||||
void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) override {
|
void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) override {
|
||||||
if (_use_lemon) {
|
if (_use_lemon) {
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ build_unflags = -Ofast
|
|||||||
build_flags = ${WioTrackerL1CompanionDual.build_flags}
|
build_flags = ${WioTrackerL1CompanionDual.build_flags}
|
||||||
-D UI_HAS_JOYSTICK_UPDOWN=1
|
-D UI_HAS_JOYSTICK_UPDOWN=1
|
||||||
-D ENABLE_SCREENSHOT
|
-D ENABLE_SCREENSHOT
|
||||||
|
-D DUAL_SERIAL=1
|
||||||
-Os
|
-Os
|
||||||
extra_scripts = post:create-uf2.py
|
extra_scripts = post:create-uf2.py
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user