Files

170 lines
7.2 KiB
C++
Raw Permalink Normal View History

#pragma once
// Settings › Power › Battery curve: the cell voltage at 0, 10, ... 100 %
// (NodePrefs::batt_curve_mv, ui-core/Battery.h) as a chart. A finger takes
// the point nearest it along the % axis and drags it up or down in 10 mV
// steps, kept between its neighbours; the first drag copies the built-in
// curve in, Reset goes back to it. A dashed line marks the cell now, so a
// point can be set against what the battery reads. Included by UITask.cpp.
namespace battview {
static lv_obj_t* s_chart = nullptr;
static lv_obj_t* s_now = nullptr; // "Now 3912 mV 76 %"
static lv_obj_t* s_reset = nullptr; // header button's label
static int s_drag = -1; // point under the finger
static const int PTS = battery::CURVE_PTS;
static const int MV_LO = 3000, MV_HI = 4400; // the chart's voltage range
static const int PAD_L = 44, PAD_R = 12, PAD_T = 10, PAD_B = 22;
static int ptX(const lv_area_t& a, int i) { return a.x1 + PAD_L + i * (lv_area_get_width(&a) - PAD_L - PAD_R) / (PTS - 1); }
static int mvY(const lv_area_t& a, int mv) {
const int h = lv_area_get_height(&a) - PAD_T - PAD_B;
return a.y2 - PAD_B - (constrain(mv, MV_LO, MV_HI) - MV_LO) * h / (MV_HI - MV_LO);
}
static int yMv(const lv_area_t& a, int y) {
const int h = lv_area_get_height(&a) - PAD_T - PAD_B;
return MV_LO + (a.y2 - PAD_B - y) * (MV_HI - MV_LO) / h;
}
static void text(lv_layer_t* layer, const char* t, int x, int y, int w, lv_text_align_t align, uint32_t col) {
lv_draw_label_dsc_t d;
lv_draw_label_dsc_init(&d);
d.font = THEME_FONT_SMALL;
d.color = lv_color_hex(col);
d.align = align;
d.text = t;
d.text_local = 1;
lv_area_t r = { x, y, x + w - 1, y + 16 };
lv_draw_label(layer, &d, &r);
}
static void line(lv_layer_t* layer, int x1, int y1, int x2, int y2, uint32_t col, int w, int dash = 0) {
lv_draw_line_dsc_t d;
lv_draw_line_dsc_init(&d);
d.color = lv_color_hex(col);
d.width = w;
d.dash_width = dash;
d.dash_gap = dash;
d.p1.x = x1; d.p1.y = y1; d.p2.x = x2; d.p2.y = y2;
lv_draw_line(layer, &d);
}
static void onDraw(lv_event_t* e) {
lv_obj_t* obj = lv_event_get_current_target_obj(e);
lv_layer_t* layer = lv_event_get_layer(e);
lv_area_t a;
lv_obj_get_coords(obj, &a);
char t[16];
for (int mv = 3200; mv <= MV_HI; mv += 400) { // voltage grid
const int y = mvY(a, mv);
line(layer, a.x1 + PAD_L, y, a.x2 - PAD_R, y, theme::SURFACE_2, 1);
snprintf(t, sizeof(t), "%d.%d V", mv / 1000, mv % 1000 / 100);
text(layer, t, a.x1, y - 8, PAD_L - 6, LV_TEXT_ALIGN_RIGHT, theme::TEXT_MUTED);
}
for (int i = 0; i < PTS; i += 5) { // 0, 50, 100 %
snprintf(t, sizeof(t), "%d%%", i * 10);
const int x = i == PTS - 1 ? a.x2 - 40 : ptX(a, i) - 20; // the last one ends at the edge
text(layer, t, x, a.y2 - PAD_B + 4, 40, i == PTS - 1 ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_CENTER, theme::TEXT_MUTED);
}
const int now = s_ui ? s_ui->battMv() : 0;
if (now > 0) line(layer, a.x1 + PAD_L, mvY(a, now), a.x2 - PAD_R, mvY(a, now), theme::OK, 1, 3);
for (int i = 1; i < PTS; i++)
line(layer, ptX(a, i - 1), mvY(a, battery::curveMvAt(i - 1)), ptX(a, i), mvY(a, battery::curveMvAt(i)), theme::ACCENT, 2);
for (int i = 0; i < PTS; i++) {
lv_draw_rect_dsc_t r;
lv_draw_rect_dsc_init(&r);
r.radius = LV_RADIUS_CIRCLE;
r.bg_color = lv_color_hex(i == s_drag ? theme::ACCENT : theme::BG);
r.border_color = lv_color_hex(theme::ACCENT);
r.border_width = 2;
const int d = i == s_drag ? 7 : 4, x = ptX(a, i), y = mvY(a, battery::curveMvAt(i));
lv_area_t c = { x - d, y - d, x + d, y + d };
lv_draw_rect(layer, &r, &c);
}
if (s_drag >= 0) { // the point being moved, above it
const int x = ptX(a, s_drag), y = mvY(a, battery::curveMvAt(s_drag));
snprintf(t, sizeof(t), "%d%% %d mV", s_drag * 10, battery::curveMvAt(s_drag));
const int lx = constrain(x - 50, (int)a.x1, (int)a.x2 - 100);
lv_draw_rect_dsc_t bg; // on a pill, clear of the grid and the "now" line
lv_draw_rect_dsc_init(&bg);
bg.radius = theme::RADIUS_SM;
bg.bg_color = lv_color_hex(theme::SURFACE_2);
lv_area_t ba = { lx, y - 30, lx + 99, y - 12 };
lv_draw_rect(layer, &bg, &ba);
text(layer, t, lx, y - 29, 100, LV_TEXT_ALIGN_CENTER, theme::TEXT);
}
}
static void onTouch(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t* obj = lv_event_get_current_target_obj(e);
lv_indev_t* in = lv_indev_active();
lv_point_t p = { 0, 0 };
if (in) lv_indev_get_point(in, &p);
lv_area_t a;
lv_obj_get_coords(obj, &a);
if (code == LV_EVENT_PRESSED) { // the point nearest along the % axis
int best = 0;
for (int i = 1; i < PTS; i++) if (abs(ptX(a, i) - p.x) < abs(ptX(a, best) - p.x)) best = i;
s_drag = best;
}
NodePrefs* np = s_ui->prefsMut();
if ((code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING) && s_drag >= 0 && np) {
uint16_t* c = np->batt_curve_mv;
if (!battery::validCurve(c))
for (int k = 0; k < PTS; k++) c[k] = (uint16_t)battery::builtinMvAt(k);
const int lo = s_drag > 0 ? c[s_drag - 1] + 10 : battery::CURVE_MIN_MV;
const int hi = s_drag < PTS - 1 ? c[s_drag + 1] - 10 : battery::CURVE_MAX_MV;
const int mv = constrain((yMv(a, p.y) + 5) / 10 * 10, lo, hi);
if (mv != c[s_drag]) { c[s_drag] = (uint16_t)mv; lv_obj_invalidate(obj); s_ui->refreshBattCurve(); }
}
if (code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
s_drag = -1;
lv_obj_invalidate(obj);
prefsSave();
}
}
static void onReset(lv_event_t* e) {
(void)e;
NodePrefs* np = s_ui->prefsMut();
if (!np || !battery::validCurve(np->batt_curve_mv)) return;
memset(np->batt_curve_mv, 0, sizeof(np->batt_curve_mv));
prefsSave();
if (s_chart) lv_obj_invalidate(s_chart);
s_ui->refreshBattCurve();
}
} // namespace battview
void UITask::showBattCurve() {
using namespace battview;
_screen = SCR_BATT;
lv_obj_t* body = newScreen("Battery curve", true);
headerButton(_header, LV_SYMBOL_REFRESH " Reset", onReset, 8, &s_reset);
lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE);
s_chart = lv_obj_create(body);
styleSurface(s_chart, theme::BG);
lv_obj_remove_flag(s_chart, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_width(s_chart, LV_PCT(100));
lv_obj_set_flex_grow(s_chart, 1);
lv_obj_add_flag(s_chart, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_flag(s_chart, LV_OBJ_FLAG_PRESS_LOCK); // still its drag when the finger strays off it
lv_obj_add_event_cb(s_chart, onDraw, LV_EVENT_DRAW_MAIN_END, NULL);
lv_obj_add_event_cb(s_chart, onTouch, LV_EVENT_ALL, NULL);
s_now = noteLabel(body, "");
s_drag = -1;
refreshBattCurve();
}
// Once a second from loop(), and after a change: the cell now, the reset's state.
void UITask::refreshBattCurve() {
using namespace battview;
if (_screen != SCR_BATT || !s_now) return;
const int mv = battMv();
const bool custom = _prefs && battery::validCurve(_prefs->batt_curve_mv);
char t[64];
if (mv > 0) snprintf(t, sizeof(t), "Now %d mV - %d %% - %s curve", mv, battery::rawPercent(mv), custom ? "your" : "LiPo");
else snprintf(t, sizeof(t), "%s curve - drag a point up or down", custom ? "Your" : "LiPo");
lv_label_set_text(s_now, t);
if (s_reset) lv_obj_set_style_text_opa(s_reset, custom ? LV_OPA_COVER : LV_OPA_40, 0);
if (s_chart && s_drag < 0) lv_obj_invalidate(s_chart); // the "now" line moves with the cell
}