diff --git a/examples/companion_radio/ui-new/InfoKit.h b/examples/companion_radio/ui-new/InfoKit.h index a145c443..89014d85 100644 --- a/examples/companion_radio/ui-new/InfoKit.h +++ b/examples/companion_radio/ui-new/InfoKit.h @@ -60,16 +60,23 @@ inline void section(DisplayDriver& d, int y, const char* label, int reserve = 0) rule(d, lx + lw + gap, ry, right - (lx + lw + gap)); } -// A battery glyph the size of a mini icon (7 x 5 at 1x), filled to frac. -inline int batteryW(DisplayDriver& d) { return 7 * miniIconScale(d); } +// A battery glyph the size of a mini icon (7 x 5 at 1x, 9 x 7 in the large +// set), its corners softly rounded, filled to frac. +static constexpr int BATT_BODY_W = MINI_ICONS_LARGE ? 8 : 6, BATT_BODY_H = MINI_ICONS_LARGE ? 7 : 5; +inline int batteryW(DisplayDriver& d) { return (BATT_BODY_W + 1) * miniIconScale(d); } +inline int batteryH(DisplayDriver& d) { return BATT_BODY_H * miniIconScale(d); } inline void battery(DisplayDriver& d, int x, int y, float frac) { - const int s = miniIconScale(d); + const int s = miniIconScale(d), w = BATT_BODY_W * s, h = BATT_BODY_H * s; if (frac < 0) frac = 0; if (frac > 1) frac = 1; - d.drawRect(x, y, 6 * s, 5 * s); - d.fillRect(x + 6 * s, y + s, s, 3 * s); - const int fw = (int)(4 * s * frac + 0.5f); - if (fw > 0) d.fillRect(x + s, y + s, fw, 3 * s); + d.fillRect(x + 1, y, w - 2, 1); // the outline without its corner pixels + d.fillRect(x + 1, y + h - 1, w - 2, 1); + d.fillRect(x, y + 1, 1, h - 2); + d.fillRect(x + w - 1, y + 1, 1, h - 2); + d.fillRect(x + w, y + s, s, h - 2 * s); + const int in = MINI_ICONS_LARGE ? 2 * s : s; // the large one keeps a gap inside its outline + const int fw = (int)((w - 2 * in) * frac + 0.5f); + if (fw > 0) d.fillRect(x + in, y + in, fw, h - 2 * in); } // A soft-cornered meter, filled to frac (0..1). diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index af849b68..3f366276 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -335,25 +335,10 @@ class HomeScreen : public UIScreen { uint8_t _slide_from = 0; unsigned long _slide_t0 = 0; - // A wide screen (landscape e-ink) sets the page icons on the header line, - // between the time and the status icons, instead of on a row of their own. - // Measured for every page at full spacing, so it doesn't flip as pages are - // shown or hidden. - static int stripHalfW(DisplayDriver& d) { - const int s = miniIconScale(d); - return (10 * s * ((int)Count - 1) + 5 * s + 8) / 2; - } - static bool stripOnHeader(DisplayDriver& d) { - const int mid = d.width() / 2, half = stripHalfW(d); - // Left: the time and a gap. Right: the battery icon and two status icons. - const int right_need = 2 * d.getLineHeight() + 6 + 2 * (d.getCharWidth() + 3); - return mid - half >= 6 * d.getCharWidth() && mid + half <= d.width() - right_need; - } // First row under the header and the page-icon row (see render()). static int contentTop(DisplayDriver& d) { - const int pg_half = (5 * miniIconScale(d) + 1) / 2, lh = d.getLineHeight(); - if (stripOnHeader(d)) return (lh > 2 * pg_half + 2 ? lh : 2 * pg_half + 2) + 3; - return lh + 2 * pg_half + 4; + const int pg_half = (PAGE_ICON_PX * miniIconScale(d) + 1) / 2; + return d.getLineHeight() + 2 * pg_half + 4; } // 0..1 eased progress of the running slide, or -1 when none. float slideProgress() { @@ -759,7 +744,11 @@ class HomeScreen : public UIScreen { const int iconW = lh * 2; const int bm = display.pixelScale() > 1 ? 3 : 2; // inner margin: 3px on a doubled panel, else 2px battLeftX = display.width() - iconW - 3; - display.drawRect(battLeftX, 0, iconW, iconH); + // The outline with its corner pixels left out, softly rounded. + display.fillRect(battLeftX + 1, 0, iconW - 2, 1); + display.fillRect(battLeftX + 1, iconH - 1, iconW - 2, 1); + display.fillRect(battLeftX, 1, 1, iconH - 2); + display.fillRect(battLeftX + iconW - 1, 1, 1, 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), @@ -1296,7 +1285,7 @@ public: auto tile = [&](int col, int row, const MiniIcon* ic, float batt, const char* head, const char* detail) { const int x = col ? mid_x + 3 : 1, y = row ? mid_y + 2 : top + 1; const int w = (col ? W - x : mid_x - 2 - x); - const int iy = y + (lh - 5 * s) / 2 - s; + const int iy = y + (lh - info::batteryH(d)) / 2 - s; int tx = x; if (ic) { miniIconDraw(d, x, iy, *ic); tx = x + ic->w * s + 2 * s; } else if (batt >= 0) { info::battery(d, x, iy, batt); tx = x + info::batteryW(d) + 2 * s; } @@ -1371,15 +1360,12 @@ public: display.setTextSize(1); const int lh = display.getLineHeight(); // line height at sz1 const int step = display.lineStep(); // lh + 2 - // Page-indicator row: small (5px) page icons replace the old dots. Centre and + // Page-indicator row: small page icons (PAGE_ICON_PX) replace the old dots. Centre and // gap scale with the font so the band clears the header above and content // below (identical to the old lh+4 / +6 dots layout at 1x). - const int pg_half = (5 * miniIconScale(display) + 1) / 2; - const bool one_row = stripOnHeader(display); // the icons on the header line - const int dots_y = one_row ? lh / 2 : lh + pg_half + 1; // icon-row centre + const int pg_half = (PAGE_ICON_PX * miniIconScale(display) + 1) / 2; + const int dots_y = lh + pg_half + 1; // icon-row centre, below the header const int content_y = contentTop(display); // first content row, below the icons - const int strip_l = display.width() / 2 - stripHalfW(display); - const int strip_r = display.width() / 2 + stripHalfW(display); const float slide = slideProgress(); // -1, or how far a page turn has got // Title bar displaying node name (except on lock screen), status icons and battery. @@ -1388,9 +1374,8 @@ public: display.setColor(DisplayDriver::LIGHT); // The lock page has nothing on the left of the bar (its clock sits // below it), so the status icons may use the whole row. - const int lock_reserve = _page == LOCK ? 0 : one_row ? strip_r + 2 : -1; + const int lock_reserve = _page == LOCK ? 0 : -1; int rightEdge = renderBatteryIndicator(display, _task->getBattMilliVolts(), lock_reserve); - if (one_row && _page != LOCK) rightEdge = strip_l; // the time stops at the icons display.setColor(DisplayDriver::LIGHT); if (_page != LOCK) { @@ -1435,7 +1420,7 @@ public: int curr_vis = 0; for (int i = 0; i < n; i++) if (order[i] == _page) { curr_vis = i; break; } const int s = miniIconScale(display); - const int icon_w = 5 * s; + const int icon_w = PAGE_ICON_PX * s; int pitch = icon_w + 5 * s; // comfortable spacing if (n > 1) { // shrink to fit if many pages int fit = (display.width() - icon_w) / (n - 1); diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index 6c22b9e0..e247035b 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -41,6 +41,15 @@ struct MiniIcon { uint8_t w, h; const uint8_t* rows; }; static constexpr MiniIcon name = { (uint8_t)(width), \ (uint8_t)sizeof(name##_rows), name##_rows } +// The landscape e-ink build writes in the 8x13, whose digits stand 9 px: +// there the status, page and check glyphs come in a 7 px set instead of 5. +#if defined(EINK_LARGE_FONT) && EINK_LARGE_FONT + #define MINI_ICONS_LARGE 1 +#else + #define MINI_ICONS_LARGE 0 +#endif +static constexpr int PAGE_ICON_PX = MINI_ICONS_LARGE ? 7 : 5; // the page glyphs' box + // Pixel scale from the font: 1× on an 8px OLED line, 2× on a 16px landscape // e-ink line, etc. Bitmaps are authored on the 1× grid. inline int miniIconScale(DisplayDriver& d) { @@ -104,6 +113,20 @@ inline void miniIconDotRow(DisplayDriver& d, int x, int top_y, int count) { } // Mini-icon bitmaps (authored on the 1× grid as ASCII-art; see packRow above). +#if MINI_ICONS_LARGE +MINI_ICON(ICON_CHECK, 7, // ✓ + packRow("......#"), + packRow(".....#."), + packRow("#...#.."), + packRow(".#.#..."), + packRow("..#....")); +MINI_ICON(ICON_CROSS, 5, // ✗ + packRow("#...#"), + packRow(".#.#."), + packRow("..#.."), + packRow(".#.#."), + packRow("#...#")); +#else MINI_ICON(ICON_CHECK, 5, // ✓ packRow("....#"), packRow("...#."), @@ -114,6 +137,7 @@ MINI_ICON(ICON_CROSS, 4, // ✗ packRow(".##."), packRow(".##."), packRow("#..#")); +#endif // Tiny 3×5 digits — for a small count that needs to sit in an icon-sized slot // (e.g. next to ICON_CHECK) where the normal font is too tall to fit. See @@ -206,6 +230,81 @@ inline void miniIconDrawNumber(DisplayDriver& d, int x, int top_y, int n) { } // Top-bar status glyphs (replace the single-letter M / B / A indicators). +#if MINI_ICONS_LARGE +MINI_ICON(ICON_MUTE, 8, // speaker + cross (sound off) + packRow("...#...."), + packRow("..##...."), + packRow("####.#.#"), + packRow("####..#."), + packRow("####.#.#"), + packRow("..##...."), + packRow("...#....")); +MINI_ICON(ICON_BLUETOOTH, 5, // ᛒ bluetooth rune + packRow("..#.."), + packRow("..##."), + packRow("#.#.#"), + packRow(".###."), + packRow("#.#.#"), + packRow("..##."), + packRow("..#..")); +MINI_ICON(ICON_ADVERT, 7, // ((•)) advert + packRow(".#...#."), + packRow("#.....#"), + packRow("#.###.#"), + packRow("#.###.#"), + packRow("#.###.#"), + packRow("#.....#"), + packRow(".#...#.")); +MINI_ICON(ICON_ALARM, 7, // bell — an alarm is armed + packRow("...#..."), + packRow("..###.."), + packRow(".#####."), + packRow(".#####."), + packRow(".#####."), + packRow("#######"), + packRow("...#...")); +MINI_ICON(ICON_TRAIL, 7, // map pin (GPS trail logging) + packRow("..###.."), + packRow(".#####."), + packRow("##...##"), + packRow("##...##"), + packRow(".#####."), + packRow("..###.."), + packRow("...#...")); +MINI_ICON(ICON_REPEATER, 8, // » relaying (repeater active) + packRow("#...#..."), + packRow(".#...#.."), + packRow("..#...#."), + packRow("...#...#"), + packRow("..#...#."), + packRow(".#...#.."), + packRow("#...#...")); +MINI_ICON(ICON_GPS, 7, // reticle with its dot: GPS has a fix + packRow("..###.."), + packRow(".#...#."), + packRow("#..#..#"), + packRow("#.###.#"), + packRow("#..#..#"), + packRow(".#...#."), + packRow("..###..")); +MINI_ICON(ICON_GPS_SEARCH, 7, // the reticle broken: still searching + packRow("..#.#.."), + packRow(".#...#."), + packRow("#.....#"), + packRow("......."), + packRow("#.....#"), + packRow(".#...#."), + packRow("..#.#..")); + +MINI_ICON(ICON_CHART, 7, // ascending bars — Home › Status + packRow("......#"), + packRow("....#.#"), + packRow("....#.#"), + packRow("..#.#.#"), + packRow("..#.#.#"), + packRow("#.#.#.#"), + packRow("#######")); +#else MINI_ICON(ICON_MUTE, 6, // speaker + cross (sound off) packRow("..#..."), packRow(".##..."), @@ -273,10 +372,93 @@ MINI_ICON(ICON_CHART, 5, // ascending bars — Home › Status packRow("#.#.#"), packRow("#.#.#"), packRow("#####")); +#endif // Home-carousel page glyphs — a uniform 5x5 set, deliberately smaller than the // menu/status icons above, used in place of the page-indicator dots. One per // HomePage; see UITask HomeScreen::pageIcon(). +#if MINI_ICONS_LARGE +MINI_ICON(ICON_PG_CLOCK, 7, // clock face + hands + packRow("..###.."), + packRow(".#.#.#."), + packRow("#..#..#"), + packRow("#..##.#"), + packRow("#.....#"), + packRow(".#...#."), + packRow("..###..")); +MINI_ICON(ICON_PG_STAR, 7, // favourites + packRow("...#..."), + packRow("...#..."), + packRow("#######"), + packRow(".#####."), + packRow("..###.."), + packRow(".##.##."), + packRow(".#...#.")); +MINI_ICON(ICON_PG_RADIO, 7, // antenna with waves — radio + packRow(".#...#."), + packRow("#..#..#"), + packRow("#.###.#"), + packRow(".#.#.#."), + packRow("...#..."), + packRow("..#.#.."), + packRow(".#...#.")); +MINI_ICON(ICON_PG_BT, 5, // bluetooth + packRow("..#.."), + packRow("..##."), + packRow("#.#.#"), + packRow(".###."), + packRow("#.#.#"), + packRow("..##."), + packRow("..#..")); +MINI_ICON(ICON_PG_ADVERT, 7, // advert page: the same waves + packRow(".#...#."), + packRow("#.....#"), + packRow("#.###.#"), + packRow("#.###.#"), + packRow("#.###.#"), + packRow("#.....#"), + packRow(".#...#.")); +MINI_ICON(ICON_PG_SETTINGS, 7, // cog with a hub hole + packRow("..#.#.."), + packRow(".#####."), + packRow("##...##"), + packRow(".#...#."), + packRow("##...##"), + packRow(".#####."), + packRow("..#.#..")); +MINI_ICON(ICON_PG_MAP, 7, // folded map + packRow("#######"), + packRow("#..#..#"), + packRow("#..#..#"), + packRow("#..#..#"), + packRow("#..#..#"), + packRow("#..#..#"), + packRow("#######")); +MINI_ICON(ICON_PG_TOOLS, 7, // wrench, open jaw top right + packRow("....#.#"), + packRow("....#.#"), + packRow("....###"), + packRow("...##.."), + packRow("..##..."), + packRow(".##...."), + packRow("##.....")); +MINI_ICON(ICON_PG_MSG, 7, // speech bubble with three dots + packRow("#######"), + packRow("#.....#"), + packRow("#.#.#.#"), + packRow("#.....#"), + packRow("#######"), + packRow("##....."), + packRow("#......")); +MINI_ICON(ICON_PG_POWER, 7, // power symbol + packRow("...#..."), + packRow(".#.#.#."), + packRow("#..#..#"), + packRow("#.....#"), + packRow("#.....#"), + packRow(".#...#."), + packRow("..###..")); +#else MINI_ICON(ICON_PG_CLOCK, 5, // clock face + hands packRow(".###."), packRow("#.#.#"), @@ -336,6 +518,7 @@ MINI_ICON(ICON_PG_POWER, 5, // power symbol — shutdown packRow("#...#"), packRow("#...#"), packRow(".###.")); +#endif // Trail-map markers — centred on a point (see miniIconDrawCentered) rather // than anchored to a text line. @@ -347,12 +530,23 @@ MINI_ICON(ICON_MAP_RING, 3, // ○ hollow ring — marks a new trail segme packRow("###"), packRow("#.#"), packRow("###")); +#if MINI_ICONS_LARGE // the markers that also head rows of text +MINI_ICON(ICON_MAP_WAYPOINT, 7, // ◇ hollow diamond — saved waypoint + packRow("...#..."), + packRow("..#.#.."), + packRow(".#...#."), + packRow("#.....#"), + packRow(".#...#."), + packRow("..#.#.."), + packRow("...#...")); +#else MINI_ICON(ICON_MAP_WAYPOINT, 5, // ◇ hollow diamond — saved waypoint packRow("..#.."), packRow(".#.#."), packRow("#...#"), packRow(".#.#."), packRow("..#..")); +#endif MINI_ICON(ICON_MAP_START, 5, // + trail start marker packRow("..#.."), packRow("..#.."), @@ -365,12 +559,23 @@ MINI_ICON(ICON_MAP_CURRENT, 5, // ✕ live position / last trail point packRow("..#.."), packRow(".#.#."), packRow("#...#")); +#if MINI_ICONS_LARGE +MINI_ICON(ICON_MAP_CONTACT, 7, // ◆ filled diamond — a live-tracked contact + packRow("...#..."), + packRow("..###.."), + packRow(".#####."), + packRow("#######"), + packRow(".#####."), + packRow("..###.."), + packRow("...#...")); +#else MINI_ICON(ICON_MAP_CONTACT, 5, // ◆ filled diamond — a live-tracked contact ([LOC] share) packRow("..#.."), packRow(".###."), packRow("#####"), packRow(".###."), packRow("..#..")); +#endif MINI_ICON(ICON_MAP_NORTH, 5, // "N" with a peaked roof — compass north marker packRow("..#.."), packRow(".###."),