#pragma once
//Widgets.h — classe de base pour tous les widgets (colonne gauche et droite)
#include "Widget.h"
#include "WeatherData.h"
#include <math.h>
// ════════════════════════════════════════════════════════════════════════════
// WidgetMainTemp — colonne gauche : ville, température, stats
// ════════════════════════════════════════════════════════════════════════════
class WidgetMainTemp : public Widget {
public:
WidgetMainTemp(int x, int y, int w, int h) : Widget(x, y, w, h) {}
void draw(TFT_eSPI& tft) override {
// Fond colonne gauche (pas de panel, fond transparent)
tft.fillRect(x, y, w, h, C_BG);
// Séparateur vertical droit
tft.drawFastVLine(x + w, y, h, C_BORDER);
const WeatherData& d = gWeather;
// Ville + heure
tft.setTextSize(1);
tft.setTextColor(C_DIM, C_BG);
tft.setCursor(x + MARGIN, y + 8);
tft.print(d.city);
tft.setCursor(x + MARGIN, y + 20);
tft.print(d.time);
// Icône météo principale centrée
drawWeatherIcon(tft, x + w - 38, y + 32, d.uvIndex > 5 ? 0 : 3);
// Température géante
tft.setTextColor(C_WHITE, C_BG);
tft.setTextSize(7);
tft.setCursor(x + MARGIN - 2, y + 34);
tft.print(d.temp);
tft.setTextSize(2);
tft.setTextColor(C_MUTED, C_BG);
tft.setCursor(x + MARGIN + 50, y + 38);
tft.print("oC");
// Condition
tft.setTextSize(1);
tft.setTextColor(C_MUTED, C_BG);
tft.setCursor(x + MARGIN, y + 98);
tft.print(d.condition);
// Min / Max / Ressenti
tft.setTextColor(C_ACCENT_BLUE, C_BG);
tft.setCursor(x + MARGIN, y + 110);
tft.print("^"); tft.print(d.tempMax); tft.print(" ");
tft.setTextColor(C_DIM, C_BG);
tft.print("v"); tft.print(d.tempMin);
tft.print(" R:"); tft.print(d.feelsLike);
// ── 4 mini stats en grid 2x2 ──────────────────────────────────────
int gx = x + MARGIN;
int gy = y + 130;
int sw = (w - MARGIN*2 - GAP) / 2;
int sh = 46;
drawStat(tft, gx, gy, sw, sh, "HUMIDITE", d.humidity, "%");
drawStat(tft, gx+sw+GAP, gy, sw, sh, "VENT", d.windSpeed, "km/h");
drawStat(tft, gx, gy+sh+GAP, sw, sh, "PRESSION", d.pressure, "hPa");
drawStat(tft, gx+sw+GAP, gy+sh+GAP, sw, sh, "UV", d.uvIndex, uvLabel(d.uvIndex), uvColor(d.uvIndex));
}
private:
void drawStat(TFT_eSPI& tft, int sx, int sy, int sw, int sh,
const char* label, int val, const char* unit,
uint16_t valColor = C_WHITE) {
tft.fillRoundRect(sx, sy, sw, sh, RADIUS, C_PANEL);
tft.drawRoundRect(sx, sy, sw, sh, RADIUS, C_BORDER);
tft.setTextSize(1);
tft.setTextColor(C_DIM, C_PANEL);
tft.setCursor(sx + 5, sy + 5);
tft.print(label);
tft.setTextSize(2);
tft.setTextColor(valColor, C_PANEL);
tft.setCursor(sx + 5, sy + 17);
tft.print(val);
tft.setTextSize(1);
tft.setTextColor(C_MUTED, C_PANEL);
tft.print(" "); tft.print(unit);
}
const char* uvLabel(int uv) {
if (uv <= 2) return "Faible";
if (uv <= 5) return "Modere";
if (uv <= 7) return "Eleve";
return "Danger";
}
uint16_t uvColor(int uv) {
if (uv <= 2) return C_ACCENT_GREEN;
if (uv <= 5) return C_ACCENT_GOLD;
return C_ACCENT_RED;
}
void drawWeatherIcon(TFT_eSPI& tft, int cx, int cy, int type) {
switch(type) {
case 0: // Soleil
tft.fillCircle(cx, cy, 9, C_ACCENT_GOLD);
for(int i = 0; i < 8; i++) {
float a = i * PI / 4;
tft.drawLine(cx+12*cos(a), cy+12*sin(a),
cx+17*cos(a), cy+17*sin(a), C_ACCENT_GOLD);
}
break;
case 1: // Nuages
tft.fillCircle(cx-5, cy+3, 8, C_MUTED);
tft.fillCircle(cx+5, cy+3, 8, C_MUTED);
tft.fillCircle(cx, cy-2, 9, TFT_WHITE);
tft.fillRect(cx-12, cy+3, 24, 8, C_MUTED);
break;
case 2: // Pluie
tft.fillCircle(cx-4, cy, 7, 0x8C71);
tft.fillCircle(cx+4, cy, 7, 0x8C71);
tft.fillCircle(cx, cy-4, 7, 0x8C71);
tft.fillRect(cx-10, cy, 20, 7, 0x8C71);
for(int i = 0; i < 4; i++)
tft.drawLine(cx-6+i*4, cy+11, cx-8+i*4, cy+18, C_ACCENT_BLUE);
break;
case 3: // Soleil + nuage
default:
tft.fillCircle(cx+6, cy-5, 7, C_ACCENT_GOLD);
tft.fillCircle(cx-4, cy+2, 7, C_MUTED);
tft.fillCircle(cx+4, cy+2, 7, C_MUTED);
tft.fillCircle(cx, cy-2, 7, TFT_WHITE);
tft.fillRect(cx-9, cy+2, 18, 6, C_MUTED);
break;
}
}
};
// ════════════════════════════════════════════════════════════════════════════
// WidgetForecast — prévisions 5 jours
// ════════════════════════════════════════════════════════════════════════════
class WidgetForecast : public Widget {
public:
WidgetForecast(int x, int y, int w, int h) : Widget(x, y, w, h) {}
void draw(TFT_eSPI& tft) override {
drawBackground(tft);
drawLabel(tft, 6, 5, "PREVISIONS 5 JOURS");
int cellW = (w - 2) / 5;
for (int i = 0; i < 5; i++) {
int cx = x + 1 + i * cellW;
bool highlight = (i == 1);
if (highlight) {
tft.fillRoundRect(cx+1, y+1, cellW-2, h-2, RADIUS, 0x0C64);
tft.drawRoundRect(cx+1, y+1, cellW-2, h-2, RADIUS, C_ACCENT_BLUE);
}
uint16_t tc = highlight ? C_ACCENT_BLUE : C_MUTED;
tft.setTextSize(1);
tft.setTextColor(tc, highlight ? 0x0C64 : C_PANEL);
tft.setCursor(cx + cellW/2 - 9, y + 16);
tft.print(gForecast[i].label);
drawMiniIcon(tft, cx + cellW/2, y + 36, gForecast[i].iconType);
tft.setTextSize(1);
uint16_t tempCol = (gForecast[i].temp >= 22) ? C_ACCENT_GOLD : C_WHITE;
tft.setTextColor(tempCol, highlight ? 0x0C64 : C_PANEL);
tft.setCursor(cx + cellW/2 - 8, y + 52);
tft.print(gForecast[i].temp);
tft.print("o");
}
}
private:
void drawMiniIcon(TFT_eSPI& tft, int cx, int cy, int type) {
switch(type) {
case 0: tft.fillCircle(cx, cy, 6, C_ACCENT_GOLD); break;
case 1:
tft.fillCircle(cx-3, cy+2, 5, C_MUTED);
tft.fillCircle(cx+3, cy+2, 5, C_MUTED);
tft.fillCircle(cx, cy-1, 5, TFT_WHITE);
tft.fillRect(cx-7, cy+2, 14, 4, C_MUTED);
break;
case 2:
tft.fillCircle(cx, cy-1, 5, 0x8C71);
tft.fillRect(cx-6, cy-1, 12, 4, 0x8C71);
tft.drawLine(cx-2, cy+7, cx-3, cy+12, C_ACCENT_BLUE);
tft.drawLine(cx+2, cy+7, cx+1, cy+12, C_ACCENT_BLUE);
break;
case 3:
tft.fillCircle(cx+4, cy-3, 5, C_ACCENT_GOLD);
tft.fillCircle(cx-2, cy+2, 5, C_MUTED);
tft.fillCircle(cx+3, cy+2, 5, C_MUTED);
tft.fillRect(cx-6, cy+2, 12, 4, C_MUTED);
break;
case 4:
tft.fillCircle(cx, cy, 5, 0xA514);
tft.fillRect(cx-5, cy, 10, 4, 0xA514);
tft.drawLine(cx, cy+8, cx-1, cy+13, C_ACCENT_BLUE);
break;
}
}
};
// ════════════════════════════════════════════════════════════════════════════
// WidgetChart — courbe température 24h (8 points)
// ════════════════════════════════════════════════════════════════════════════
class WidgetChart : public Widget {
public:
WidgetChart(int x, int y, int w, int h) : Widget(x, y, w, h) {}
void draw(TFT_eSPI& tft) override {
drawBackground(tft);
drawLabel(tft, 6, 5, "TEMPERATURE 24H");
int chartX = x + 6;
int chartY = y + 16;
int chartW = w - 12;
int chartH = h - 24;
// Trouver min/max
int tMin = gHourly[0].temp, tMax = gHourly[0].temp;
for (int i = 1; i < 8; i++) {
tMin = min(tMin, gHourly[i].temp);
tMax = max(tMax, gHourly[i].temp);
}
int range = max(tMax - tMin, 1);
// Calculer les points
int px[8], py[8];
for (int i = 0; i < 8; i++) {
px[i] = chartX + (i * chartW) / 7;
py[i] = chartY + chartH - ((gHourly[i].temp - tMin) * (chartH - 14)) / range;
}
// Aire sous la courbe (remplie par lignes horizontales)
for (int yy = chartY + chartH; yy > chartY; yy--) {
float alpha = 1.0f - (float)(chartY + chartH - yy) / chartH;
if (alpha < 0.05f) break;
// Interpoler la courbe à cette hauteur Y
for (int i = 0; i < 7; i++) {
if (yy <= max(py[i], py[i+1])) continue;
// Ligne de fill entre les deux points de la courbe
tft.drawFastHLine(px[i], yy, px[i+1] - px[i],
alpha > 0.3f ? 0x0944 : 0x0822);
}
}
// Courbe
for (int i = 0; i < 7; i++)
tft.drawLine(px[i], py[i], px[i+1], py[i+1], C_ACCENT_BLUE);
// Points et labels
for (int i = 0; i < 8; i++) {
tft.fillCircle(px[i], py[i], 2, C_ACCENT_BLUE);
// Label heure
tft.setTextSize(1);
tft.setTextColor(C_DIM, C_PANEL);
tft.setCursor(px[i] - 6, chartY + chartH + 2);
tft.print(gHourly[i].label);
}
// Point max mis en évidence
int maxIdx = 0;
for (int i = 1; i < 8; i++)
if (gHourly[i].temp > gHourly[maxIdx].temp) maxIdx = i;
tft.fillCircle(px[maxIdx], py[maxIdx], 3, C_ACCENT_GOLD);
tft.setTextSize(1);
tft.setTextColor(C_ACCENT_GOLD, C_PANEL);
tft.setCursor(px[maxIdx] - 6, py[maxIdx] - 10);
tft.print(gHourly[maxIdx].temp); tft.print("o");
}
};
// ════════════════════════════════════════════════════════════════════════════
// WidgetSun — lever/coucher du soleil avec arc
// ════════════════════════════════════════════════════════════════════════════
class WidgetSun : public Widget {
public:
WidgetSun(int x, int y, int w, int h) : Widget(x, y, w, h) {}
void draw(TFT_eSPI& tft) override {
drawBackground(tft);
drawLabel(tft, 5, 5, "SOLEIL");
int mx = x + w/2;
int my = y + h - 8;
int r = min(w, h*2) / 2 - 6;
// Arc du soleil (demi-cercle)
for (float a = PI; a <= 2*PI; a += 0.05f) {
int ax = mx + r * cos(a);
int ay = my + r * sin(a);
tft.drawPixel(ax, ay, C_MUTED);
}
// Position actuelle (exemple : 14h sur une journée 06h-22h)
// Normalise entre 0..1 selon l'heure actuelle
float pos = 0.52f; // 14h ≈ 52% de la journée
float angle = PI + pos * PI;
int sx = mx + r * cos(angle);
int sy = my + r * sin(angle);
tft.fillCircle(sx, sy, 4, C_ACCENT_GOLD);
// Labels
tft.setTextSize(1);
tft.setTextColor(C_MUTED, C_PANEL);
tft.setCursor(x + 4, y + h - 14);
tft.print(gWeather.sunriseTime);
tft.setCursor(x + w - 28, y + h - 14);
tft.print(gWeather.sunsetTime);
}
};
// ════════════════════════════════════════════════════════════════════════════
// WidgetAQI — qualité de l'air
// ════════════════════════════════════════════════════════════════════════════
class WidgetAQI : public Widget {
public:
WidgetAQI(int x, int y, int w, int h) : Widget(x, y, w, h) {}
void draw(TFT_eSPI& tft) override {
drawBackground(tft);
drawLabel(tft, 5, 5, "QUALITE AIR");
int aqi = gWeather.aqi;
uint16_t col = aqiColor(aqi);
const char* lbl = aqiLabel(aqi);
// Valeur AQI
tft.setTextSize(3);
tft.setTextColor(col, C_PANEL);
tft.setCursor(x + 5, y + 16);
tft.print(aqi);
// Label
tft.setTextSize(1);
tft.setTextColor(col, C_PANEL);
tft.setCursor(x + 5, y + 42);
tft.print(lbl);
// Barre
drawBar(tft, 5, h - 8, w - 10, 4, aqi / 200.0f, col);
}
private:
uint16_t aqiColor(int aqi) {
if (aqi <= 50) return C_ACCENT_GREEN;
if (aqi <= 100) return C_ACCENT_GOLD;
return C_ACCENT_RED;
}
const char* aqiLabel(int aqi) {
if (aqi <= 50) return "Bon";
if (aqi <= 100) return "Modere";
return "Mauvais";
}
};