feat: port weather to MTK toolkit

This commit is contained in:
2026-04-18 17:50:25 +02:00
parent 3b89ab8a75
commit ad68f99b74
+62 -44
View File
@@ -13,6 +13,7 @@
#include <gui/standalone.hpp>
#include <gui/svg.hpp>
#include <gui/truetype.hpp>
#include <gui/mtk.hpp>
#include <tls/tls.hpp>
extern "C" {
@@ -304,47 +305,63 @@ static void do_fetch() {
// Theme colors
// ============================================================================
static constexpr Color CONTENT_BG = Color::from_rgb(0xFF, 0xFF, 0xFF); // white
static constexpr Color FOOTER_BG = Color::from_rgb(0xF5, 0xF5, 0xF5); // light gray
static constexpr Color DIVIDER = Color::from_rgb(0xCC, 0xCC, 0xCC); // subtle border
static constexpr Color DARK_TEXT = Color::from_rgb(0x33, 0x33, 0x33);
static constexpr Color MID_TEXT = Color::from_rgb(0x88, 0x88, 0x88);
static constexpr Color HINT_TEXT = Color::from_rgb(0x99, 0x99, 0x99);
static constexpr Color ERR_TEXT = Color::from_rgb(0xCC, 0x22, 0x22);
static constexpr Color BTN_BG = Color::from_rgb(0x36, 0x7B, 0xF0); // accent blue
static constexpr Color WHITE_TEXT = Color::from_rgb(0xFF, 0xFF, 0xFF);
static mtk::Theme weather_theme() {
mtk::Theme theme = mtk::make_theme();
theme.window_bg = WHITE;
theme.surface = Color::from_rgb(0xF5, 0xF5, 0xF5);
theme.surface_alt = Color::from_rgb(0xF8, 0xF8, 0xF8);
theme.surface_hover = mtk::mix(theme.surface, theme.accent, 18);
theme.border = Color::from_rgb(0xCC, 0xCC, 0xCC);
theme.text = Color::from_rgb(0x33, 0x33, 0x33);
theme.text_muted = Color::from_rgb(0x88, 0x88, 0x88);
theme.text_subtle = Color::from_rgb(0x99, 0x99, 0x99);
return theme;
}
// ============================================================================
// Rendering
// ============================================================================
static constexpr int BTN_W = 110;
static constexpr int BTN_H = 28;
static constexpr int WEATHER_BTN_RADIUS = 6;
static constexpr int BTN_W = 110;
static constexpr int BTN_H = 30;
static int g_mouse_x = -1;
static int g_mouse_y = -1;
static Rect refresh_button_rect() {
int btn_x = (g_win_w - BTN_W) / 2;
int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
return {btn_x, btn_y, BTN_W, BTN_H};
}
static void render(Canvas& canvas) {
mtk::Theme theme = weather_theme();
Rect btn = refresh_button_rect();
// ── Background ───────────────────────────────────────────────────────────
canvas.fill_rect(0, 0, g_win_w, g_win_h - FOOTER_H, CONTENT_BG);
canvas.fill_rect(0, g_win_h - FOOTER_H, g_win_w, FOOTER_H, FOOTER_BG);
canvas.fill_rect(0, 0, g_win_w, g_win_h - FOOTER_H, theme.window_bg);
canvas.fill_rect(0, g_win_h - FOOTER_H, g_win_w, FOOTER_H, theme.surface);
// Divider between content and location strip
canvas.hline(0, HEADER_H, g_win_w, DIVIDER);
canvas.hline(0, HEADER_H, g_win_w, theme.border);
// Divider above footer
canvas.hline(0, g_win_h - FOOTER_H, g_win_w, DIVIDER);
canvas.hline(0, g_win_h - FOOTER_H, g_win_w, theme.border);
if (!g_font) return;
// ── Main content area (y=0..HEADER_H) ────────────────────────────────────
if (g_phase == AppPhase::LOADING) {
draw_text(canvas, g_font, 20, HEADER_H / 2 - 9,
"Fetching weather data...", HINT_TEXT, 18);
"Fetching weather data...", theme.text_subtle, 18);
} else if (g_phase == AppPhase::ERR) {
draw_text(canvas, g_font, 20, 20, g_status, ERR_TEXT, 15);
} else if (g_phase == AppPhase::IDLE) {
draw_text(canvas, g_font, 20, HEADER_H / 2 - 9,
"Click Refresh to check weather.", HINT_TEXT, 18);
"Click Refresh to check weather.", theme.text_subtle, 18);
} else { // DONE
// Weather icon
@@ -352,31 +369,26 @@ static void render(Canvas& canvas) {
// Temperature (large bold)
TrueTypeFont* temp_font = g_font_bold ? g_font_bold : g_font;
draw_text(canvas, temp_font, INFO_X, TEMP_Y, g_temp, DARK_TEXT, TEMP_SIZE);
draw_text(canvas, temp_font, INFO_X, TEMP_Y, g_temp, theme.text, TEMP_SIZE);
// Weather description
draw_text(canvas, g_font, INFO_X, DESC_Y, g_desc, DARK_TEXT, DESC_SIZE);
draw_text(canvas, g_font, INFO_X, DESC_Y, g_desc, theme.text, DESC_SIZE);
// Feels like
draw_text(canvas, g_font, INFO_X, FEELS_Y, g_feels, MID_TEXT, LABEL_SIZE);
draw_text(canvas, g_font, INFO_X, FEELS_Y, g_feels, theme.text_muted, LABEL_SIZE);
}
// ── Location strip (y=HEADER_H..g_win_h-FOOTER_H) ────────────────────────
if (g_phase == AppPhase::DONE) {
draw_text(canvas, g_font, 20, HEADER_H + 14, g_location, DARK_TEXT, LABEL_SIZE);
draw_text(canvas, g_font, 20, HEADER_H + 14, g_location, theme.text, LABEL_SIZE);
}
// ── Refresh button (rounded, in footer) ───────────────────────────────────
int btn_x = (g_win_w - BTN_W) / 2;
int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
if (g_phase == AppPhase::LOADING) {
draw_button(canvas, g_font, btn_x, btn_y, BTN_W, BTN_H,
"Loading...", BTN_BG, WHITE_TEXT, WEATHER_BTN_RADIUS, 14);
} else {
draw_button(canvas, g_font, btn_x, btn_y, BTN_W, BTN_H,
"Refresh", BTN_BG, WHITE_TEXT, WEATHER_BTN_RADIUS, 15);
}
bool hovered = btn.contains(g_mouse_x, g_mouse_y);
bool loading = (g_phase == AppPhase::LOADING);
mtk::WidgetState state = mtk::widget_state(false, hovered, !loading);
mtk::draw_button(canvas, btn, loading ? "Loading..." : "Refresh",
mtk::BUTTON_PRIMARY, state, theme);
}
// ============================================================================
@@ -384,6 +396,9 @@ static void render(Canvas& canvas) {
// ============================================================================
extern "C" void _start() {
if (!fonts::init())
montauk::exit(1);
// Allocate response buffer from heap
g_resp_buf = (char*)malloc(RESP_MAX + 1);
if (!g_resp_buf) montauk::exit(1);
@@ -406,12 +421,13 @@ extern "C" void _start() {
if (!win.create("Weather", INIT_W, INIT_H))
montauk::exit(1);
Canvas canvas = win.canvas();
mtk::StandaloneHost host(&win);
Canvas canvas = host.canvas();
// Initial fetch on startup
g_phase = AppPhase::LOADING;
render(canvas);
win.present();
host.present();
do_fetch();
// Event loop
@@ -423,9 +439,9 @@ extern "C" void _start() {
if (r == 0) {
montauk::sleep_ms(16);
canvas = win.canvas();
canvas = host.canvas();
render(canvas);
win.present();
host.present();
continue;
}
@@ -438,30 +454,32 @@ extern "C" void _start() {
if (ev.type == 2) {
g_win_w = win.width;
g_win_h = win.height;
canvas = win.canvas();
canvas = host.canvas();
}
if (ev.type == 1) {
// Track mouse position for hover states
g_mouse_x = ev.mouse.x;
g_mouse_y = ev.mouse.y;
// Mouse — check for Refresh button click
bool just_clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
if (just_clicked && g_phase != AppPhase::LOADING) {
int btn_x = (g_win_w - BTN_W) / 2;
int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
Rect btn = refresh_button_rect();
int mx = ev.mouse.x, my = ev.mouse.y;
if (mx >= btn_x && mx < btn_x + BTN_W &&
my >= btn_y && my < btn_y + BTN_H) {
if (btn.contains(mx, my)) {
g_phase = AppPhase::LOADING;
canvas = win.canvas();
canvas = host.canvas();
render(canvas);
win.present();
host.present();
do_fetch();
}
}
}
canvas = win.canvas();
canvas = host.canvas();
render(canvas);
win.present();
host.present();
}
if (g_icon.pixels) svg_free(g_icon);