#include "widgets.h" #include "fonts.h" namespace roro::widgets { using namespace theme; MeasureText bodyMeasure(Canvas& canvas) { return [&canvas](const std::string& s) { canvas.setFont(&fonts::body); return static_cast(canvas.textWidth(s.c_str())); }; } void statusBar(Canvas& c, const StatusInfo& info) { c.fillRect(0, 0, kWidth, kStatusBarHeight, kBar); c.setFont(&fonts::small); c.setTextDatum(top_left); c.setTextColor(kText); c.drawString(info.title.c_str(), 3, 2); // Right side, laid out right to left. int x = kWidth - 3; auto right = [&](const std::string& text, uint16_t color) { c.setTextColor(color); c.setTextDatum(top_right); c.drawString(text.c_str(), x, 2); x -= c.textWidth(text.c_str()) + 6; }; right(info.clock, kText); if (info.unread > 0) right("[" + std::to_string(info.unread) + "]", kMessage); if (info.batteryPercent >= 0) right(std::to_string(info.batteryPercent) + "%", info.batteryPercent <= 15 ? kWarning : kText); if (info.sdPresent) right("SD", info.sdLevel >= 80 ? kWarning : kMuted); switch (info.wifi) { case StatusInfo::Wifi::Connected: { // Three signal bars, then the label. for (int i = 0; i < 3; i++) { int h = 2 + 2 * i; c.fillRect(x - 10 + 4 * i, 9 - h, 3, h, i < info.wifiBars ? kText : kMuted); } x -= 14; right("W", kText); break; } case StatusInfo::Wifi::Searching: right("W?", kMuted); break; case StatusInfo::Wifi::Monitoring: right("MON", kAccent); break; case StatusInfo::Wifi::None: break; } if (info.tracking) right("REC", kWarning); switch (info.gnss) { // Q61: muted while searching, normal with a Fix, the count with a 3D Fix case StatusInfo::Gnss::Searching: right("G", kMuted); break; case StatusInfo::Gnss::TwoD: right("G", kText); break; case StatusInfo::Gnss::ThreeD: right("G" + std::to_string(info.gnssSatellites), kText); break; case StatusInfo::Gnss::None: break; } if (info.compose) right(std::string("opt ") + (info.compose == '*' ? "" : std::string(1, info.compose)), kAccent); c.setTextDatum(top_left); } void list(Canvas& c, const ListModel& model, Rect area, const std::function& label, const std::function& value) { c.setFont(&fonts::body); c.setTextDatum(top_left); int rows = model.visibleRows(); for (int row = 0; row < rows; row++) { int i = model.firstVisible() + row; if (i >= model.count()) break; int y = area.y + row * kLineHeight; bool selected = i == model.selected(); if (selected) c.fillRect(area.x, y, area.w - 4, kLineHeight, kSelection); c.setTextColor(kText); c.drawString(label(i).c_str(), area.x + 4, y + 1); if (value) { c.setTextColor(selected ? kText : kMuted); c.setTextDatum(top_right); c.drawString(value(i).c_str(), area.x + area.w - 8, y + 1); c.setTextDatum(top_left); } } // Scrollbar if (model.count() > rows) { int barH = area.h * rows / model.count(); int barY = area.y + (area.h - barH) * model.firstVisible() / (model.count() - rows); c.fillRect(area.x + area.w - 2, barY, 2, barH, kMuted); } } void textLines(Canvas& c, const std::vector& lines, int firstLine, Rect area) { c.setFont(&fonts::body); c.setTextDatum(top_left); c.setTextColor(kText); int rows = area.h / kLineHeight; for (int row = 0; row < rows && firstLine + row < static_cast(lines.size()); row++) c.drawString(lines[firstLine + row].c_str(), area.x + 4, area.y + row * kLineHeight + 1); int total = static_cast(lines.size()); if (total > rows) { int barH = area.h * rows / total; int barY = area.y + (area.h - barH) * firstLine / (total - rows); c.fillRect(area.x + area.w - 2, barY, 2, barH, kMuted); } } void lineEditor(Canvas& c, const LineEditor& editor, Rect area) { c.setFont(&fonts::body); c.setTextDatum(top_left); c.drawRect(area.x, area.y, area.w, kLineHeight + 4, kAccent); const std::string& text = editor.text(); int cursorX = c.textWidth(text.substr(0, editor.cursor()).c_str()); int visible = area.w - 8; int scroll = cursorX > visible ? cursorX - visible : 0; c.setClipRect(area.x + 2, area.y + 1, area.w - 4, kLineHeight + 2); c.setTextColor(kText); c.drawString(text.c_str(), area.x + 3 - scroll, area.y + 3); c.fillRect(area.x + 3 + cursorX - scroll, area.y + 2, 1, kLineHeight, kAccent); c.clearClipRect(); } void dialog(Canvas& c, const std::string& title, const std::string& message, const DialogModel& model) { Rect box{16, 26, kWidth - 32, 92}; c.fillRect(box.x, box.y, box.w, box.h, kBackground); c.drawRect(box.x, box.y, box.w, box.h, kAccent); c.setTextDatum(top_left); c.setFont(&fonts::bold); c.setTextColor(kText); c.drawString(title.c_str(), box.x + 6, box.y + 4); auto lines = wrapText(message, box.w - 12, bodyMeasure(c)); textLines(c, lines, 0, {box.x + 2, box.y + 20, box.w - 4, 3 * kLineHeight}); // Buttons, right-aligned. c.setFont(&fonts::body); int x = box.x + box.w - 6; const auto& buttons = model.buttons(); for (int i = static_cast(buttons.size()) - 1; i >= 0; i--) { int w = c.textWidth(buttons[i].c_str()) + 12; x -= w; bool selected = i == model.selected(); if (selected) c.fillRect(x, box.y + box.h - 19, w, 15, kAccent); else c.drawRect(x, box.y + box.h - 19, w, 15, kMuted); c.setTextColor(selected ? kBackground : kText); c.drawString(buttons[i].c_str(), x + 6, box.y + box.h - 17); x -= 6; } } void toast(Canvas& c, const Toast& t) { uint16_t color = t.level == NotificationLevel::Warning ? kWarning : t.level == NotificationLevel::Message ? kMessage : kAccent; c.setFont(&fonts::body); int w = c.textWidth(t.text.c_str()) + 16; if (w > kWidth - 8) w = kWidth - 8; int x = (kWidth - w) / 2, y = kHeight - 22; c.fillRect(x, y, w, 18, color); c.setTextColor(kBackground); c.setTextDatum(top_left); c.setClipRect(x + 4, y, w - 8, 18); c.drawString(t.text.c_str(), x + 8, y + 3); c.clearClipRect(); } } // namespace roro::widgets