summaryrefslogtreecommitdiffstats
path: root/src/gui.cpp
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-27 18:53:32 +0100
committersalmonik <salmonik@tuta.io>2026-10-27 18:53:32 +0100
commit6b5425ebd35c28e20680f5f28c887b705827ba1c (patch)
tree9b9c4b74c597b4d225e65a04e8812de784d91f01 /src/gui.cpp
parentGUI: fix footer clipping; remove the old text menu (diff)
downloadhl2h-6b5425ebd35c28e20680f5f28c887b705827ba1c.tar.gz
hl2h-6b5425ebd35c28e20680f5f28c887b705827ba1c.zip
GUI: add on-screen toasts and atomic config writes
- Toast queue drawn in the top-right by the overlay, independent of the GUI being open. Info/warn/error colors and 6/9/12 s lifetimes; timers start on the first painted frame so startup messages are not missed. - Queue a load toast after install_menu, plus warnings/errors for missing modules/interfaces/netvars, input hooks, fonts, ISurface, server datamap, and spread seed support. - Toast on config reload, reset, bind/unbind and config save failure. - Config saves now write `config.tmp`, flush, then rename it over the real file; failures leave the previous config intact. - config_load reports whether a file was found so Reload can distinguish a successful reload from a missing file. - Update CONTEXT and ISURFACE_STATUS for the single GUI, toasts and atomic config writes.
Diffstat (limited to 'src/gui.cpp')
-rw-r--r--src/gui.cpp217
1 files changed, 216 insertions, 1 deletions
diff --git a/src/gui.cpp b/src/gui.cpp
index 132068e..0d5b458 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -6,6 +6,7 @@
6#include "hl2h.h" 6#include "hl2h.h"
7 7
8#include <atomic> 8#include <atomic>
9#include <cstdarg>
9#include <cmath> 10#include <cmath>
10#include <cstdio> 11#include <cstdio>
11#include <cstdlib> 12#include <cstdlib>
@@ -136,6 +137,7 @@ static void gui_init_sdl() {
136 g_sdl_ready = sd_get_keys && sd_get_mouse; 137 g_sdl_ready = sd_get_keys && sd_get_mouse;
137 if (!g_sdl_ready) { 138 if (!g_sdl_ready) {
138 logf("gui: SDL polling unavailable"); 139 logf("gui: SDL polling unavailable");
140 toastf(TOAST_ERROR, "GUI input unavailable: SDL not found");
139 return; 141 return;
140 } 142 }
141 if (sd_add_watch) 143 if (sd_add_watch)
@@ -406,6 +408,56 @@ static float now_sec() {
406 return (float)ts.tv_sec + (float)ts.tv_nsec * 1e-9f; 408 return (float)ts.tv_sec + (float)ts.tv_nsec * 1e-9f;
407} 409}
408 410
411// ---------------------------------------------------------------------------
412// Toasts (shown even when the GUI is closed)
413// ---------------------------------------------------------------------------
414
415struct Toast {
416 char text[256];
417 int level;
418 double born; // 0 = waiting for the first draw
419 double ttl;
420};
421
422static Toast g_toasts[8];
423static int g_ntoasts = 0;
424static pthread_mutex_t g_toast_mu = PTHREAD_MUTEX_INITIALIZER;
425
426void toastf(int level, const char *fmt, ...) {
427 char buf[256];
428 va_list ap;
429 va_start(ap, fmt);
430 vsnprintf(buf, sizeof(buf), fmt, ap);
431 va_end(ap);
432
433 pthread_mutex_lock(&g_toast_mu);
434 for (int i = 0; i < g_ntoasts; i++) {
435 if (strcmp(g_toasts[i].text, buf) == 0) {
436 g_toasts[i].level = level;
437 g_toasts[i].born = 0.0;
438 pthread_mutex_unlock(&g_toast_mu);
439 return;
440 }
441 }
442 if (g_ntoasts == 8) {
443 memmove(&g_toasts[0], &g_toasts[1], sizeof(Toast) * 7);
444 g_ntoasts = 7;
445 }
446 Toast &t = g_toasts[g_ntoasts++];
447 snprintf(t.text, sizeof(t.text), "%s", buf);
448 t.level = level;
449 t.born = 0.0;
450 t.ttl = level == TOAST_ERROR ? 12.0 : level == TOAST_WARN ? 9.0 : 6.0;
451 pthread_mutex_unlock(&g_toast_mu);
452}
453
454bool gui_toasts_active() {
455 pthread_mutex_lock(&g_toast_mu);
456 bool active = g_ntoasts > 0;
457 pthread_mutex_unlock(&g_toast_mu);
458 return active;
459}
460
409static void ensure_fonts() { 461static void ensure_fonts() {
410 if (g_font_body && fabsf(g_font_scale - g_scale) < 0.001f) 462 if (g_font_body && fabsf(g_font_scale - g_scale) < 0.001f)
411 return; 463 return;
@@ -437,8 +489,10 @@ static void ensure_fonts() {
437 } 489 }
438 if (!g_font_title) 490 if (!g_font_title)
439 g_font_title = g_font_body; 491 g_font_title = g_font_body;
440 if (!g_font_body) 492 if (!g_font_body) {
441 logf("gui: no usable body font"); 493 logf("gui: no usable body font");
494 toastf(TOAST_ERROR, "No usable GUI font");
495 }
442} 496}
443 497
444static int text_w(const char *s, int tall) { 498static int text_w(const char *s, int tall) {
@@ -698,6 +752,8 @@ static void handle_capture(const unsigned char *keys) {
698 g_mouse_rclick = false; 752 g_mouse_rclick = false;
699 g_capture_card = -1; 753 g_capture_card = -1;
700 config_save(); 754 config_save();
755 toastf(TOAST_INFO, "%s bound to %s", card.title,
756 scancode_name(card.feat->bind));
701 return; 757 return;
702 } 758 }
703 for (int k = 0; k < 512; k++) { 759 for (int k = 0; k < 512; k++) {
@@ -709,6 +765,9 @@ static void handle_capture(const unsigned char *keys) {
709 card.feat->bind = k; 765 card.feat->bind = k;
710 g_capture_card = -1; 766 g_capture_card = -1;
711 config_save(); 767 config_save();
768 if (card.feat)
769 toastf(TOAST_INFO, "%s bound to %s", card.title,
770 scancode_name(card.feat->bind));
712 return; 771 return;
713 } 772 }
714} 773}
@@ -795,6 +854,7 @@ static void handle_mouse_click() {
795 if (mx >= bind.x + bind.w - S(18.0f)) { 854 if (mx >= bind.x + bind.w - S(18.0f)) {
796 c.feat->bind = 0; 855 c.feat->bind = 0;
797 config_save(); 856 config_save();
857 toastf(TOAST_INFO, "%s unbound (always-on)", c.title);
798 return; 858 return;
799 } 859 }
800 g_capture_card = i; 860 g_capture_card = i;
@@ -844,6 +904,7 @@ static void handle_mouse_click() {
844 config_reset_defaults(); 904 config_reset_defaults();
845 g_reset_armed = false; 905 g_reset_armed = false;
846 logf("gui: defaults restored"); 906 logf("gui: defaults restored");
907 toastf(TOAST_INFO, "Feature defaults restored");
847 } else { 908 } else {
848 g_reset_armed = true; 909 g_reset_armed = true;
849 g_reset_armed_at = now; 910 g_reset_armed_at = now;
@@ -1198,6 +1259,160 @@ static void draw_tooltip() {
1198 inner.w, C_TEXT); 1259 inner.w, C_TEXT);
1199} 1260}
1200 1261
1262// ---------------------------------------------------------------------------
1263// Toast drawing
1264// ---------------------------------------------------------------------------
1265
1266static int wrap_toast(const char *text, int max_w, int tall,
1267 char lines[][256], int max_lines) {
1268 int n = 0;
1269 char cur[256] = {};
1270 bool truncated = false;
1271 const char *p = text;
1272 while (*p) {
1273 while (*p == ' ')
1274 p++;
1275 if (!*p)
1276 break;
1277 const char *start = p;
1278 while (*p && *p != ' ')
1279 p++;
1280 size_t wl = (size_t)(p - start);
1281 if (wl > 120)
1282 wl = 120;
1283 char word[128];
1284 memcpy(word, start, wl);
1285 word[wl] = 0;
1286
1287 char cand[256];
1288 if (cur[0])
1289 snprintf(cand, sizeof(cand), "%s %s", cur, word);
1290 else
1291 snprintf(cand, sizeof(cand), "%s", word);
1292
1293 if (text_w(cand, tall) <= max_w || !cur[0]) {
1294 snprintf(cur, sizeof(cur), "%s", cand);
1295 if (text_w(cur, tall) > max_w) {
1296 // A single word longer than the panel: hard-truncate it.
1297 int len = (int)strlen(cur);
1298 while (len > 3 && text_w(cur, tall) > max_w)
1299 cur[--len] = 0;
1300 if (len > 3) {
1301 cur[len - 3] = '.';
1302 cur[len - 2] = '.';
1303 cur[len - 1] = '.';
1304 }
1305 truncated = true;
1306 }
1307 } else {
1308 if (n < max_lines)
1309 snprintf(lines[n++], 256, "%s", cur);
1310 else
1311 truncated = true;
1312 snprintf(cur, sizeof(cur), "%s", word);
1313 }
1314 }
1315 if (cur[0]) {
1316 if (n < max_lines)
1317 snprintf(lines[n++], 256, "%s", cur);
1318 else
1319 truncated = true;
1320 }
1321 if (truncated && n > 0) {
1322 char *last = lines[n - 1];
1323 int len = (int)strlen(last);
1324 if (len + 3 < 256) {
1325 last[len++] = '.';
1326 last[len++] = '.';
1327 last[len++] = '.';
1328 last[len] = 0;
1329 }
1330 }
1331 return n;
1332}
1333
1334void gui_draw_toasts() {
1335 if (!g_surface)
1336 return;
1337 ensure_fonts();
1338 if (!g_font_body || g_scr_w <= 0 || g_scr_h <= 0)
1339 return;
1340
1341 Toast local[8];
1342 int n = 0;
1343 double now = now_sec();
1344 pthread_mutex_lock(&g_toast_mu);
1345 for (int i = 0; i < g_ntoasts; i++) {
1346 Toast &t = g_toasts[i];
1347 if (t.born == 0.0)
1348 t.born = now;
1349 if (now - t.born > t.ttl)
1350 continue;
1351 local[n++] = t;
1352 }
1353 g_ntoasts = 0;
1354 for (int i = 0; i < n; i++)
1355 g_toasts[g_ntoasts++] = local[i];
1356 pthread_mutex_unlock(&g_toast_mu);
1357
1358 int margin = S(16.0f);
1359 int max_w = S(420.0f);
1360 if (max_w > g_scr_w - margin * 2)
1361 max_w = g_scr_w - margin * 2;
1362 int y = margin;
1363 for (int i = 0; i < n; i++) {
1364 const Toast &t = local[i];
1365 float age = (float)(now - t.born);
1366 float a = 1.0f;
1367 if (age < 0.2f)
1368 a = age / 0.2f;
1369 else if (age > t.ttl - 0.5f)
1370 a = (float)((t.ttl - age) / 0.5);
1371 if (a < 0.0f) a = 0.0f;
1372 if (a > 1.0f) a = 1.0f;
1373 int alpha = (int)(a * 255.0f);
1374
1375 char lines[4][256];
1376 int nl = wrap_toast(t.text, max_w, g_body_h, lines, 4);
1377 if (nl == 0)
1378 continue;
1379 int line_w = 0;
1380 for (int j = 0; j < nl; j++) {
1381 int w = text_w(lines[j], g_body_h);
1382 if (w > line_w)
1383 line_w = w;
1384 }
1385 int pad = S(12.0f);
1386 int line_h = g_body_h + S(4.0f);
1387 int panel_w = line_w + pad * 2 + S(6.0f);
1388 int panel_h = nl * line_h + pad * 2 - S(4.0f);
1389 int x = g_scr_w - margin - panel_w;
1390 if (x < margin)
1391 x = margin;
1392
1393 Col accent = t.level == TOAST_ERROR
1394 ? Col{240, 90, 90, 255}
1395 : t.level == TOAST_WARN ? Col{255, 200, 80, 255}
1396 : C_ACCENT;
1397 fill(Rect{x, y, panel_w, panel_h}, Col{16, 18, 22, (int)(235 * a)});
1398 outline(Rect{x, y, panel_w, panel_h}, Col{accent.r, accent.g, accent.b, alpha});
1399 fill(Rect{x, y, S(4.0f), panel_h},
1400 Col{accent.r, accent.g, accent.b, alpha});
1401 s_text_font(g_font_body);
1402 s_text_color(C_TEXT.r, C_TEXT.g, C_TEXT.b, alpha);
1403 for (int j = 0; j < nl; j++) {
1404 s_text_pos(x + pad + S(6.0f), y + pad + j * line_h);
1405 wchar_t w[256];
1406 size_t wn = mbstowcs(w, lines[j], 255);
1407 if (wn == (size_t)-1)
1408 continue;
1409 w[wn] = 0;
1410 s_print(w, (int)wn);
1411 }
1412 y += panel_h + S(8.0f);
1413 }
1414}
1415
1201void gui_draw() { 1416void gui_draw() {
1202 if (!g_open || !g_surface) 1417 if (!g_open || !g_surface)
1203 return; 1418 return;