diff --git a/programs/src/weather/main.cpp b/programs/src/weather/main.cpp index cb0fd73..9957754 100644 --- a/programs/src/weather/main.cpp +++ b/programs/src/weather/main.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include 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);