diff --git a/sketch/obd2_gauge/meter.h b/sketch/obd2_gauge/meter.h index ceb039c..1a5577a 100644 --- a/sketch/obd2_gauge/meter.h +++ b/sketch/obd2_gauge/meter.h @@ -1236,9 +1236,49 @@ void plotNoData(uint8_t cell) { } } +// Draw parameter icon into sprite slice with true alpha channel +inline void drawPidIconToSprite(TFT_eSprite &s, int ix, int iy, const String &pidStr, int slice_h) { + int w = 42, h = 42; + const unsigned short *colors = nullptr; + const unsigned char *alphas = nullptr; + + if (pidStr == "0105") { + w = 38; h = 37; + colors = icon_temp_quad; alphas = icon_temp_quad_alpha; + } else if (pidStr == "010D") { + w = 42; h = 42; + colors = icon_speed_quad; alphas = icon_speed_quad_alpha; + } else if (pidStr == "010C") { + w = 42; h = 43; + colors = icon_rpm_quad; alphas = icon_rpm_quad_alpha; + } else if (pidStr == "0142" || pidStr == "ATRV") { + w = 46; h = 35; + colors = icon_battery_quad; alphas = icon_battery_quad_alpha; + } + if (!colors || !alphas) return; + + int startJ = (iy < 0) ? -iy : 0; + int endJ = (iy + h > slice_h) ? (slice_h - iy) : h; + if (startJ >= endJ) return; + + for (int j = startJ; j < endJ; j++) { + int curY = iy + j; + int rowOffset = j * w; + for (int i = 0; i < w; i++) { + int curX = ix + i; + if (curX < 0 || curX >= 320) continue; + uint8_t a = pgm_read_byte(&alphas[rowOffset + i]); + if (a >= 30) { + uint16_t fg = pgm_read_word(&colors[rowOffset + i]); + s.drawPixel(curX, curY, fg); + } + } + } +} + // Render Page 0 (4 Quadrants) into double-buffered Sprite slice inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) { - uint16_t bgCol = getThemeBgColor(); + checkUpdateThemeGradients(); uint16_t divCol = getThemeDivColor(); uint16_t priCol = getThemePrimaryColor(); @@ -1250,37 +1290,71 @@ inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s if (qx + 160 <= 0 || qx >= 320 || ry + 119 <= 0 || ry >= slice_h) continue; - s.fillRect(rx, ry, 159, 119, bgCol); + // 1. Exact dark vignette gradient matching real arcMeter4 + s.fillRect(rx, ry, 159, 119, TFT_BLACK); + int gradStartY = (ry < 0) ? -ry : 0; + int gradEndY = (ry + 82 > slice_h) ? (slice_h - ry) : 82; + for (int j = gradStartY; j < gradEndY; j++) { + int curY = ry + j; + if (curY >= 0 && curY < slice_h) { + s.drawFastHLine(rx, curY, 159, themeGradQuad[j]); + } + } uint8_t pidIdx = pidInCell[c]; - s.setTextColor(TFT_WHITE, bgCol); - s.drawString(pidConfig[pidIdx][0].c_str(), qx + 8, qy + 6, 2); + // 2. Real high-definition parameter icon (thermometer, speedometer, tachometer, battery) + drawPidIconToSprite(s, qx + 5, qy + 3, pidConfig[pidIdx][2], slice_h); + + // 3. Exact 20 curved segmented gauge blocks along the arc int warn = warningValue[pidIdx].toInt(); int min = pidConfig[pidIdx][4].toInt(); int max = pidConfig[pidIdx][5].toInt(); bool digit = pidConfig[pidIdx][7].toInt(); float val = last_valid_data[c]; - int barY = qy + 28; - s.fillRoundRect(qx + 8, barY, 144, 6, 2, METER_INACTIVE_COLOR); - if (val > -900.0f) { - uint16_t col = getMeterColor(pidIdx, val, warn); - float ratio = (val - (float)min) / (float)(max - min); - if (ratio < 0.0f) ratio = 0.0f; - if (ratio > 1.0f) ratio = 1.0f; - int fillW = (int)(ratio * 144.0f); - if (fillW < 4) fillW = 4; - if (fillW > 144) fillW = 144; - s.fillRoundRect(qx + 8, barY, fillW, 6, 2, col); + int numActive = (val > -900.0f) ? calcMeterBlocks(val, min, max) : 0; + uint16_t activeCol = (val > -900.0f) ? getMeterColor(pidIdx, val, warn) : priCol; + + for (uint8_t i = 0; i < NUM_BAR_BLOCKS; i++) { + int bx = qx + pgm_read_word(&quadBlockMinX[i]); + int by = qy + pgm_read_word(&quadBlockMinY[i]); + int bw = pgm_read_word(&quadBlockMaxX[i]) - pgm_read_word(&quadBlockMinX[i]) + 1; + int bh = pgm_read_word(&quadBlockMaxY[i]) - pgm_read_word(&quadBlockMinY[i]) + 1; + + if (by + bh <= 0 || by >= slice_h || bx + bw <= 0 || bx >= 320) continue; + + uint16_t bCol = (i < numActive) ? activeCol : METER_INACTIVE_COLOR; + s.fillRoundRect(bx, by, bw, bh, 2, bCol); } + // 4. Digital value string String numStr = (val <= -900.0f) ? "---" : String(val, digit); - s.setTextColor(priCol, bgCol); + s.setTextColor(priCol); s.drawRightString(numStr.c_str(), qx + 152, qy + 74, 4); } - // Crosshair dividers + // 5. Eco Badge if active + if (isEcoOptimal) { + int rpmCell = 2; + for (int c = 0; c < 4; c++) { + if (pidConfig[pidInCell[c]][3].toInt() == 3) { + rpmCell = c; + break; + } + } + int bx = cell4_x[rpmCell] + 100 + x_offset; + int by = cell4_y[rpmCell] + 4 - y_slice; + int bw = 48, bh = 18; + if (by + bh > 0 && by < slice_h && bx + bw > 0 && bx < 320) { + s.fillRoundRect(bx, by, bw, bh, 4, 0x0202); + s.drawRoundRect(bx, by, bw, bh, 4, 0x07E0); + s.setTextColor(TFT_WHITE, 0x0202); + s.drawCentreString("ECO", bx + (bw / 2), by + 1, 2); + } + } + + // 6. Crosshair dividers s.fillRect(159 + x_offset, 0, 2, slice_h, divCol); int hLineY = 119 - y_slice; if (hLineY >= 0 && hLineY < slice_h) {