diff options
Diffstat (limited to '')
| -rw-r--r-- | src/gui.cpp | 217 |
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 | |||
| 415 | struct Toast { | ||
| 416 | char text[256]; | ||
| 417 | int level; | ||
| 418 | double born; // 0 = waiting for the first draw | ||
| 419 | double ttl; | ||
| 420 | }; | ||
| 421 | |||
| 422 | static Toast g_toasts[8]; | ||
| 423 | static int g_ntoasts = 0; | ||
| 424 | static pthread_mutex_t g_toast_mu = PTHREAD_MUTEX_INITIALIZER; | ||
| 425 | |||
| 426 | void 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 | |||
| 454 | bool 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 | |||
| 409 | static void ensure_fonts() { | 461 | static 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 | ||
| 444 | static int text_w(const char *s, int tall) { | 498 | static 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 | |||
| 1266 | static 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 | |||
| 1334 | void 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 | |||
| 1201 | void gui_draw() { | 1416 | void gui_draw() { |
| 1202 | if (!g_open || !g_surface) | 1417 | if (!g_open || !g_surface) |
| 1203 | return; | 1418 | return; |
