summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--CONTEXT.md4
-rw-r--r--src/ISURFACE_STATUS.md1
-rw-r--r--src/gui.cpp419
3 files changed, 250 insertions, 174 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index eccf7e2..da7138d 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -108,6 +108,10 @@ vocabulary / decisions.
108- **Category**: sidebar group. Movement, Combat, Visuals, Weapons, System. 108- **Category**: sidebar group. Movement, Combat, Visuals, Weapons, System.
109- **Card**: per-feature panel. Header always shows Active + Bind; body is a 109- **Card**: per-feature panel. Header always shows Active + Bind; body is a
110 collapsible set of settings. 110 collapsible set of settings.
111- **Text layout**: measured with `ISurface::GetTextSize` and wrapped to the
112 available width. Card headers, control rows, info rows, tooltips and toasts
113 grow/wrap instead of truncating; there is no ellipsis truncation. The config
114 path is shown on the System page (not duplicated in the footer).
111- **Freeze**: while the GUI is open, look/attack/use/impulse/weapon selection 115- **Freeze**: while the GUI is open, look/attack/use/impulse/weapon selection
112 are blocked and combat features are skipped, but normal WASD/jump/duck/walk 116 are blocked and combat features are skipped, but normal WASD/jump/duck/walk
113 movement is allowed. Mouse deltas are discarded before `CInput::CreateMove` 117 movement is allowed. Mouse deltas are discarded before `CInput::CreateMove`
diff --git a/src/ISURFACE_STATUS.md b/src/ISURFACE_STATUS.md
index d553af0..53be3b0 100644
--- a/src/ISURFACE_STATUS.md
+++ b/src/ISURFACE_STATUS.md
@@ -26,6 +26,7 @@ slots 6/10/12/15/17/18/20/22/66/67.
26| 22 | `DrawPrintText(wchar_t*, int, drawType)` | reads font `0x290`, pos `0x2c/0x30` | 26| 22 | `DrawPrintText(wchar_t*, int, drawType)` | reads font `0x290`, pos `0x2c/0x30` |
27| 66 | `CreateFont()` | SDK header 62; empirical (379) | 27| 66 | `CreateFont()` | SDK header 62; empirical (379) |
28| 67 | `AddGlyphSetToFont(font,name,tall,weight,blur,scanlines,flags,rangeMin,rangeMax)` | SDK header 62+4; disassembly (slot 67 = 0x6a300, passes 9 args to FontManager) | 28| 67 | `AddGlyphSetToFont(font,name,tall,weight,blur,scanlines,flags,rangeMin,rangeMax)` | SDK header 62+4; disassembly (slot 67 = 0x6a300, passes 9 args to FontManager) |
29| 75 | `GetTextSize(HFont, const wchar_t*, int& wide, int& tall)` | SDK header 71+4; used for accurate GUI wrapping |
29| 87 | `SolveTraverse` | InternalSchemeSettingsTraverse profiling strings @ 0x73240 | 30| 87 | `SolveTraverse` | InternalSchemeSettingsTraverse profiling strings @ 0x73240 |
30| 88 | `PaintTraverse(VPANEL)` | tail-calls `ipanel->vt+0x1c8(panel,false)` @ 0x697e0 | 31| 88 | `PaintTraverse(VPANEL)` | tail-calls `ipanel->vt+0x1c8(panel,false)` @ 0x697e0 |
31| 114 | `PaintTraverseEx(VPANEL, bool paintPopups)` | IsVisible check `ipanel+0x40`, popup array `this+0x250/0x25c`, "Too many popups!" assert @ 0x6dd46 | 32| 114 | `PaintTraverseEx(VPANEL, bool paintPopups)` | IsVisible check `ipanel+0x40`, popup array `this+0x250/0x25c`, "Too many popups!" assert @ 0x6dd46 |
diff --git a/src/gui.cpp b/src/gui.cpp
index fc95d1f..f4ba009 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -30,7 +30,8 @@ enum {
30 GS_DrawPrintText = 22, 30 GS_DrawPrintText = 22,
31 GS_GetScreenSize = 38, 31 GS_GetScreenSize = 38,
32 GS_CreateFont = 66, 32 GS_CreateFont = 66,
33 GS_AddGlyphSet = 67 33 GS_AddGlyphSet = 67,
34 GS_GetTextSize = 75
34}; 35};
35 36
36static unsigned long g_font_body = 0; 37static unsigned long g_font_body = 0;
@@ -87,6 +88,11 @@ static void s_screen(int &w, int &h) {
87 using Fn = void (*)(void *, int &, int &); 88 using Fn = void (*)(void *, int &, int &);
88 vfunc<Fn>(g_surface, GS_GetScreenSize)(g_surface, w, h); 89 vfunc<Fn>(g_surface, GS_GetScreenSize)(g_surface, w, h);
89} 90}
91static void s_text_size(unsigned long font, const wchar_t *text, int &w,
92 int &h) {
93 using Fn = void (*)(void *, unsigned long, const wchar_t *, int &, int &);
94 vfunc<Fn>(g_surface, GS_GetTextSize)(g_surface, font, text, w, h);
95}
90 96
91// --------------------------------------------------------------------------- 97// ---------------------------------------------------------------------------
92// SDL polling 98// SDL polling
@@ -511,17 +517,98 @@ static void ensure_fonts() {
511} 517}
512 518
513static int text_w(const char *s, int tall) { 519static int text_w(const char *s, int tall) {
514 if (!s) return 0; 520 if (!s)
521 return 0;
522 unsigned long font =
523 (g_font_title && tall == g_title_h) ? g_font_title : g_font_body;
524 if (g_surface && font) {
525 wchar_t wbuf[1024];
526 size_t n = mbstowcs(wbuf, s, 1023);
527 if (n != (size_t)-1) {
528 wbuf[n] = 0;
529 int w = 0, h = 0;
530 s_text_size(font, wbuf, w, h);
531 if (w > 0)
532 return w;
533 }
534 }
535 // Fallback when the surface/font is not ready yet.
515 return (int)(strlen(s) * tall * 0.52f + 0.5f); 536 return (int)(strlen(s) * tall * 0.52f + 0.5f);
516} 537}
517 538
518static void draw_text_raw(int x, int y, const char *s, unsigned long font, 539static void draw_text_raw(int x, int y, const char *s, unsigned long font,
540 int tall);
541
542// Break text into lines that fit max_w. Words that are too wide (paths, long
543// identifiers) are split by character so nothing is ever truncated.
544static void wrap_text(const char *text, int max_w, int tall,
545 std::vector<std::string> &out) {
546 out.clear();
547 if (!text || !*text || max_w <= 0)
548 return;
549 const char *p = text;
550 std::string line;
551 while (*p) {
552 while (*p == ' ')
553 p++;
554 if (!*p)
555 break;
556 const char *start = p;
557 while (*p && *p != ' ')
558 p++;
559 std::string word(start, (size_t)(p - start));
560 if (!line.empty()) {
561 std::string cand = line + " " + word;
562 if (text_w(cand.c_str(), tall) <= max_w) {
563 line = cand;
564 continue;
565 }
566 out.push_back(line);
567 line.clear();
568 }
569 while (text_w(word.c_str(), tall) > max_w && word.size() > 1) {
570 size_t n = word.size();
571 while (n > 1 && text_w(word.substr(0, n).c_str(), tall) > max_w)
572 n--;
573 out.push_back(word.substr(0, n));
574 word = word.substr(n);
575 }
576 line = word;
577 }
578 if (!line.empty())
579 out.push_back(line);
580}
581
582static int wrap_count(const char *text, int max_w, int tall) {
583 std::vector<std::string> lines;
584 wrap_text(text, max_w, tall, lines);
585 return (int)lines.size();
586}
587
588static void draw_wrapped(const Rect &clip, int x, int y, const char *text,
589 unsigned long font, int tall, int max_w,
590 const Col &c, int line_gap = 2) {
591 if (!text || !font)
592 return;
593 std::vector<std::string> lines;
594 wrap_text(text, max_w, tall, lines);
595 s_text_color(c.r, c.g, c.b, c.a);
596 int lh = tall + line_gap;
597 for (size_t i = 0; i < lines.size(); i++) {
598 int ly = y + (int)i * lh;
599 if (ly < clip.y || ly + tall > clip.y + clip.h)
600 continue;
601 draw_text_raw(x, ly, lines[i].c_str(), font, tall);
602 }
603}
604
605static void draw_text_raw(int x, int y, const char *s, unsigned long font,
519 int tall) { 606 int tall) {
520 (void)tall; 607 (void)tall;
521 if (!s || !font) 608 if (!s || !font)
522 return; 609 return;
523 wchar_t w[256]; 610 wchar_t w[1024];
524 size_t n = mbstowcs(w, s, 255); 611 size_t n = mbstowcs(w, s, 1023);
525 if (n == (size_t)-1) 612 if (n == (size_t)-1)
526 return; 613 return;
527 w[n] = 0; 614 w[n] = 0;
@@ -532,46 +619,20 @@ static void draw_text_raw(int x, int y, const char *s, unsigned long font,
532 619
533static void text_in(const Rect &clip, int x, int y, const char *s, 620static void text_in(const Rect &clip, int x, int y, const char *s,
534 unsigned long font, int tall, int max_w, const Col &c) { 621 unsigned long font, int tall, int max_w, const Col &c) {
622 (void)max_w; // callers must size the layout; text is never truncated
535 if (!s || !font) 623 if (!s || !font)
536 return; 624 return;
537 if (y < clip.y || y + tall > clip.y + clip.h) 625 if (y < clip.y || y + tall > clip.y + clip.h)
538 return; 626 return;
539 if (x >= clip.x + clip.w) 627 if (x >= clip.x + clip.w)
540 return; 628 return;
541
542 // Available space from x to the clip edge, optionally capped by max_w.
543 // Truncate first, then clamp: shifting before truncating used to draw the
544 // text outside the tooltip/button box.
545 int avail = clip.x + clip.w - x;
546 if (max_w > 0 && max_w < avail)
547 avail = max_w;
548 if (avail <= 0)
549 return;
550
551 char buf[256];
552 const char *draw = s;
553 int w = text_w(s, tall); 629 int w = text_w(s, tall);
554 if (w > avail) {
555 snprintf(buf, sizeof(buf), "%s", s);
556 int n = (int)strlen(buf);
557 int dots = text_w("...", tall);
558 while (n > 0 && text_w(buf, tall) + dots > avail)
559 buf[--n] = 0;
560 if (n > 0 && n + 3 < (int)sizeof(buf)) {
561 buf[n++] = '.';
562 buf[n++] = '.';
563 buf[n++] = '.';
564 buf[n] = 0;
565 }
566 draw = buf;
567 w = text_w(draw, tall);
568 }
569 if (x + w > clip.x + clip.w) 630 if (x + w > clip.x + clip.w)
570 x = clip.x + clip.w - w; 631 x = clip.x + clip.w - w;
571 if (x < clip.x) 632 if (x < clip.x)
572 x = clip.x; 633 x = clip.x;
573 s_text_color(c.r, c.g, c.b, c.a); 634 s_text_color(c.r, c.g, c.b, c.a);
574 draw_text_raw(x, y, draw, font, tall); 635 draw_text_raw(x, y, s, font, tall);
575} 636}
576 637
577static Rect clip_rect(const Rect &r, const Rect &clip) { 638static Rect clip_rect(const Rect &r, const Rect &clip) {
@@ -640,13 +701,48 @@ static void layout() {
640 if (g_win_y + g_win_h > h) g_win_y = h - g_win_h; 701 if (g_win_y + g_win_h > h) g_win_y = h - g_win_h;
641} 702}
642 703
704static int card_content_w() { return g_win_w - S(190.0f) - S(28.0f); }
705
706static int title_max_w() { return card_content_w() - S(210.0f); }
707
708static int header_h(const Card &c) {
709 int n = wrap_count(c.title, title_max_w(), g_title_h);
710 if (n < 1)
711 n = 1;
712 int h = n * (g_title_h + S(2.0f)) + S(12.0f);
713 return h < S(36.0f) ? S(36.0f) : h;
714}
715
716static int label_max_w(const Ctrl &c) {
717 if (c.kind == CK_SLIDER)
718 return S(214.0f);
719 return card_content_w() - S(16.0f) - S(110.0f);
720}
721
722static int button_w(const char *label) {
723 int bw = text_w(label, g_body_h) + S(20.0f);
724 if (bw < S(160.0f))
725 bw = S(160.0f);
726 int maxw = card_content_w() - S(24.0f);
727 if (bw > maxw)
728 bw = maxw;
729 return bw;
730}
731
643static int row_h(const Ctrl &c) { 732static int row_h(const Ctrl &c) {
644 switch (c.kind) { 733 if (c.kind == CK_BUTTON)
645 case CK_SECTION: return S(24.0f); 734 return S(32.0f);
646 case CK_INFO: return S(20.0f); 735 int maxw = (c.kind == CK_INFO || c.kind == CK_SECTION)
647 case CK_BUTTON: return S(32.0f); 736 ? card_content_w() - S(24.0f)
648 default: return S(27.0f); 737 : label_max_w(c);
649 } 738 int n = wrap_count(c.label, maxw, g_body_h);
739 if (n < 1)
740 n = 1;
741 int h = n * (g_body_h + S(2.0f)) + S(8.0f);
742 int minh = (c.kind == CK_INFO) ? S(20.0f)
743 : (c.kind == CK_SECTION) ? S(24.0f)
744 : S(27.0f);
745 return h < minh ? minh : h;
650} 746}
651 747
652static int body_h(const Card &c) { 748static int body_h(const Card &c) {
@@ -658,7 +754,7 @@ static int body_h(const Card &c) {
658 return h + S(6.0f); 754 return h + S(6.0f);
659} 755}
660 756
661static int card_h(const Card &c) { return S(36.0f) + body_h(c) + S(8.0f); } 757static int card_h(const Card &c) { return header_h(c) + body_h(c) + S(8.0f); }
662 758
663static Rect card_rect(int index, int y) { 759static Rect card_rect(int index, int y) {
664 Card &c = g_cards[index]; 760 Card &c = g_cards[index];
@@ -866,14 +962,15 @@ static void handle_mouse_click() {
866 Card &c = g_cards[i]; 962 Card &c = g_cards[i];
867 Rect cr = card_rect(i, y); 963 Rect cr = card_rect(i, y);
868 int ch = card_h(c); 964 int ch = card_h(c);
965 int hh = header_h(c);
869 if (my >= cr.y && my < cr.y + ch) { 966 if (my >= cr.y && my < cr.y + ch) {
870 // header 967 // header
871 if (my < cr.y + S(36.0f)) { 968 if (my < cr.y + hh) {
872 if (c.feat) { 969 if (c.feat) {
873 Rect active{cr.x + cr.w - S(170.0f), cr.y + S(9.0f), S(18.0f), 970 Rect active{cr.x + cr.w - S(170.0f),
874 S(18.0f)}; 971 cr.y + (hh - S(18.0f)) / 2, S(18.0f), S(18.0f)};
875 Rect bind{cr.x + cr.w - S(112.0f), cr.y + S(7.0f), S(100.0f), 972 Rect bind{cr.x + cr.w - S(112.0f),
876 S(22.0f)}; 973 cr.y + (hh - S(22.0f)) / 2, S(100.0f), S(22.0f)};
877 if (active.has(mx, my)) { 974 if (active.has(mx, my)) {
878 c.feat->active = !c.feat->active; 975 c.feat->active = !c.feat->active;
879 config_save(); 976 config_save();
@@ -895,7 +992,7 @@ static void handle_mouse_click() {
895 return; 992 return;
896 } 993 }
897 // body 994 // body
898 int by = cr.y + S(36.0f) + S(6.0f); 995 int by = cr.y + hh + S(6.0f);
899 for (int j = 0; j < c.n; j++) { 996 for (int j = 0; j < c.n; j++) {
900 Ctrl &ct = g_ctrls[c.c0 + j]; 997 Ctrl &ct = g_ctrls[c.c0 + j];
901 int rh = row_h(ct); 998 int rh = row_h(ct);
@@ -908,7 +1005,8 @@ static void handle_mouse_click() {
908 return; 1005 return;
909 } 1006 }
910 if (ct.kind == CK_SLIDER) { 1007 if (ct.kind == CK_SLIDER) {
911 Rect track{cr.x + S(16.0f) + S(230.0f), by + S(9.0f), 1008 Rect track{cr.x + S(16.0f) + S(230.0f),
1009 by + (rh - S(8.0f)) / 2,
912 cr.w - S(16.0f) - S(230.0f) - S(64.0f), S(8.0f)}; 1010 cr.w - S(16.0f) - S(230.0f) - S(64.0f), S(8.0f)};
913 if (mx >= track.x && mx < track.x + track.w) { 1011 if (mx >= track.x && mx < track.x + track.w) {
914 float t = (float)(mx - track.x) / (float)track.w; 1012 float t = (float)(mx - track.x) / (float)track.w;
@@ -921,8 +1019,9 @@ static void handle_mouse_click() {
921 return; 1019 return;
922 } 1020 }
923 if (ct.kind == CK_BUTTON) { 1021 if (ct.kind == CK_BUTTON) {
924 Rect b{cr.x + cr.w - S(176.0f), by + S(3.0f), S(160.0f), 1022 int bw = button_w(ct.label);
925 S(26.0f)}; 1023 Rect b{cr.x + cr.w - S(16.0f) - bw,
1024 by + (rh - S(26.0f)) / 2, bw, S(26.0f)};
926 if (b.has(mx, my)) { 1025 if (b.has(mx, my)) {
927 if (ct.action == ACT_RELOAD) { 1026 if (ct.action == ACT_RELOAD) {
928 config_reload(); 1027 config_reload();
@@ -964,13 +1063,14 @@ static void handle_hover() {
964 Card &c = g_cards[i]; 1063 Card &c = g_cards[i];
965 Rect cr = card_rect(i, y); 1064 Rect cr = card_rect(i, y);
966 int ch = card_h(c); 1065 int ch = card_h(c);
1066 int hh = header_h(c);
967 if (my >= cr.y && my < cr.y + ch) { 1067 if (my >= cr.y && my < cr.y + ch) {
968 if (my < cr.y + S(36.0f)) { 1068 if (my < cr.y + hh) {
969 if (c.feat) { 1069 if (c.feat) {
970 Rect active{cr.x + cr.w - S(170.0f), cr.y + S(9.0f), S(18.0f), 1070 Rect active{cr.x + cr.w - S(170.0f),
971 S(18.0f)}; 1071 cr.y + (hh - S(18.0f)) / 2, S(18.0f), S(18.0f)};
972 Rect bind{cr.x + cr.w - S(112.0f), cr.y + S(7.0f), S(100.0f), 1072 Rect bind{cr.x + cr.w - S(112.0f),
973 S(22.0f)}; 1073 cr.y + (hh - S(22.0f)) / 2, S(100.0f), S(22.0f)};
974 if (active.has(mx, my)) 1074 if (active.has(mx, my))
975 g_hover_desc = "Enable this feature. A bind, when set, must be " 1075 g_hover_desc = "Enable this feature. A bind, when set, must be "
976 "held for it to run."; 1076 "held for it to run.";
@@ -987,7 +1087,7 @@ static void handle_hover() {
987 } 1087 }
988 return; 1088 return;
989 } 1089 }
990 int by = cr.y + S(36.0f) + S(6.0f); 1090 int by = cr.y + hh + S(6.0f);
991 for (int j = 0; j < c.n; j++) { 1091 for (int j = 0; j < c.n; j++) {
992 Ctrl &ct = g_ctrls[c.c0 + j]; 1092 Ctrl &ct = g_ctrls[c.c0 + j];
993 int rh = row_h(ct); 1093 int rh = row_h(ct);
@@ -1165,11 +1265,13 @@ static void draw_checkbox(int x, int y, bool on) {
1165 } 1265 }
1166} 1266}
1167 1267
1168static void draw_slider(const Rect &clip, const Rect &cr, int y, const Ctrl &ct) { 1268static void draw_slider(const Rect &clip, const Rect &cr, int y, int rh,
1269 const Ctrl &ct) {
1169 int track_x = cr.x + S(16.0f) + S(230.0f); 1270 int track_x = cr.x + S(16.0f) + S(230.0f);
1170 int track_w = cr.w - S(16.0f) - S(230.0f) - S(64.0f); 1271 int track_w = cr.w - S(16.0f) - S(230.0f) - S(64.0f);
1171 if (track_w < S(30.0f)) track_w = S(30.0f); 1272 if (track_w < S(30.0f)) track_w = S(30.0f);
1172 Rect track{track_x, y + S(9.0f), track_w, S(8.0f)}; 1273 int track_y = y + (rh - S(8.0f)) / 2;
1274 Rect track{track_x, track_y, track_w, S(8.0f)};
1173 fill(track, Col{38, 42, 50, 255}, &clip); 1275 fill(track, Col{38, 42, 50, 255}, &clip);
1174 int value = ctrl_val(ct); 1276 int value = ctrl_val(ct);
1175 float t = (ct.max > ct.min) 1277 float t = (ct.max > ct.min)
@@ -1181,8 +1283,8 @@ static void draw_slider(const Rect &clip, const Rect &cr, int y, const Ctrl &ct)
1181 fill(Rect{kx, track.y - S(4.0f), S(8.0f), S(16.0f)}, C_ACCENT, &clip); 1283 fill(Rect{kx, track.y - S(4.0f), S(8.0f), S(16.0f)}, C_ACCENT, &clip);
1182 char val[32]; 1284 char val[32];
1183 snprintf(val, sizeof(val), "%d", value); 1285 snprintf(val, sizeof(val), "%d", value);
1184 text_in(clip, cr.x + cr.w - S(52.0f), y + S(4.0f), val, g_font_body, g_body_h, 1286 text_in(clip, cr.x + cr.w - S(52.0f), y + (rh - g_body_h) / 2, val,
1185 S(44.0f), C_TEXT); 1287 g_font_body, g_body_h, 0, C_TEXT);
1186} 1288}
1187 1289
1188static void draw_card(const Rect &clip, int index, int y) { 1290static void draw_card(const Rect &clip, int index, int y) {
@@ -1193,10 +1295,12 @@ static void draw_card(const Rect &clip, int index, int y) {
1193 return; 1295 return;
1194 fill(cr, C_PANEL, &clip); 1296 fill(cr, C_PANEL, &clip);
1195 outline(cr, Col{52, 57, 67, 255}, &clip); 1297 outline(cr, Col{52, 57, 67, 255}, &clip);
1196 Rect head{cr.x, cr.y, cr.w, S(36.0f)}; 1298 int hh = header_h(c);
1299 Rect head{cr.x, cr.y, cr.w, hh};
1197 fill(head, C_HEADER, &clip); 1300 fill(head, C_HEADER, &clip);
1301
1198 // expand arrow 1302 // expand arrow
1199 int ax = cr.x + S(10.0f), ay = cr.y + S(13.0f); 1303 int ax = cr.x + S(10.0f), ay = cr.y + (hh - S(8.0f)) / 2;
1200 if (c.expanded) { 1304 if (c.expanded) {
1201 line(ax, ay, ax + S(4.0f), ay + S(5.0f), C_DIM); 1305 line(ax, ay, ax + S(4.0f), ay + S(5.0f), C_DIM);
1202 line(ax + S(4.0f), ay + S(5.0f), ax + S(8.0f), ay, C_DIM); 1306 line(ax + S(4.0f), ay + S(5.0f), ax + S(8.0f), ay, C_DIM);
@@ -1204,40 +1308,53 @@ static void draw_card(const Rect &clip, int index, int y) {
1204 line(ax, ay, ax + S(5.0f), ay + S(4.0f), C_DIM); 1308 line(ax, ay, ax + S(5.0f), ay + S(4.0f), C_DIM);
1205 line(ax + S(5.0f), ay + S(4.0f), ax, ay + S(8.0f), C_DIM); 1309 line(ax + S(5.0f), ay + S(4.0f), ax, ay + S(8.0f), C_DIM);
1206 } 1310 }
1207 text_in(clip, cr.x + S(24.0f), cr.y + S(8.0f), c.title, g_font_title, 1311
1208 g_title_h, cr.w - S(180.0f), C_TEXT); 1312 // title (wraps instead of truncating)
1313 std::vector<std::string> title_lines;
1314 wrap_text(c.title, title_max_w(), g_title_h, title_lines);
1315 int tlh = g_title_h + S(2.0f);
1316 int title_h = (int)title_lines.size() * tlh - S(2.0f);
1317 int ty = cr.y + (hh - title_h) / 2;
1318 draw_wrapped(clip, cr.x + S(24.0f), ty, c.title, g_font_title, g_title_h,
1319 title_max_w(), C_TEXT, S(2.0f));
1320
1209 if (c.feat) { 1321 if (c.feat) {
1210 bool active = c.feat->active; 1322 bool active = c.feat->active;
1211 draw_checkbox(cr.x + cr.w - S(170.0f), cr.y + S(10.0f), active); 1323 int active_y = cr.y + (hh - S(18.0f)) / 2;
1212 text_in(clip, cr.x + cr.w - S(148.0f), cr.y + S(9.0f), 1324 draw_checkbox(cr.x + cr.w - S(170.0f), active_y, active);
1213 active ? "On" : "Off", g_font_body, g_body_h, S(32.0f), 1325 text_in(clip, cr.x + cr.w - S(148.0f),
1214 active ? C_GOOD : C_DIM); 1326 active_y + (S(18.0f) - g_body_h) / 2, active ? "On" : "Off",
1215 Rect bind{cr.x + cr.w - S(112.0f), cr.y + S(7.0f), S(100.0f), S(22.0f)}; 1327 g_font_body, g_body_h, 0, active ? C_GOOD : C_DIM);
1328 Rect bind{cr.x + cr.w - S(112.0f), cr.y + (hh - S(22.0f)) / 2, S(100.0f),
1329 S(22.0f)};
1216 fill(bind, Col{45, 50, 60, 255}, &clip); 1330 fill(bind, Col{45, 50, 60, 255}, &clip);
1217 outline(bind, C_BORDER, &clip); 1331 outline(bind, C_BORDER, &clip);
1218 const char *bl = (g_capture_card == index) 1332 const char *bl = (g_capture_card == index)
1219 ? "Press..." 1333 ? "Press..."
1220 : scancode_name(c.feat->bind); 1334 : scancode_name(c.feat->bind);
1221 text_in(clip, bind.x + S(7.0f), bind.y + S(3.0f), bl, g_font_body, g_body_h, 1335 text_in(clip, bind.x + S(7.0f), bind.y + (bind.h - g_body_h) / 2, bl,
1222 bind.w - S(22.0f), C_TEXT); 1336 g_font_body, g_body_h, 0, C_TEXT);
1223 text_in(clip, bind.x + bind.w - S(16.0f), bind.y + S(3.0f), "x", 1337 text_in(clip, bind.x + bind.w - S(16.0f),
1224 g_font_body, g_body_h, S(10.0f), C_DIM); 1338 bind.y + (bind.h - g_body_h) / 2, "x", g_font_body, g_body_h, 0,
1339 C_DIM);
1225 } 1340 }
1226 if (!c.expanded) 1341 if (!c.expanded)
1227 return; 1342 return;
1228 1343
1229 int by = cr.y + S(36.0f) + S(6.0f); 1344 int by = cr.y + hh + S(6.0f);
1230 for (int j = 0; j < c.n; j++) { 1345 for (int j = 0; j < c.n; j++) {
1231 Ctrl &ct = g_ctrls[c.c0 + j]; 1346 Ctrl &ct = g_ctrls[c.c0 + j];
1232 int rh = row_h(ct); 1347 int rh = row_h(ct);
1233 if (ct.kind == CK_SECTION) { 1348 if (ct.kind == CK_SECTION) {
1234 text_in(clip, cr.x + S(16.0f), by + S(3.0f), ct.label, g_font_body, 1349 draw_wrapped(clip, cr.x + S(16.0f), by + S(3.0f), ct.label, g_font_body,
1235 g_body_h, cr.w - S(24.0f), C_ACCENT); 1350 g_body_h, card_content_w() - S(24.0f), C_ACCENT, S(2.0f));
1236 } else if (ct.kind == CK_INFO) { 1351 } else if (ct.kind == CK_INFO) {
1237 text_in(clip, cr.x + S(16.0f), by + S(1.0f), ct.label, g_font_body, 1352 draw_wrapped(clip, cr.x + S(16.0f), by + S(3.0f), ct.label, g_font_body,
1238 g_body_h, cr.w - S(24.0f), C_DIM); 1353 g_body_h, card_content_w() - S(24.0f), C_DIM, S(2.0f));
1239 } else if (ct.kind == CK_BUTTON) { 1354 } else if (ct.kind == CK_BUTTON) {
1240 Rect b{cr.x + cr.w - S(176.0f), by + S(3.0f), S(160.0f), S(26.0f)}; 1355 int bw = button_w(ct.label);
1356 Rect b{cr.x + cr.w - S(16.0f) - bw, by + (rh - S(26.0f)) / 2, bw,
1357 S(26.0f)};
1241 fill(b, Col{45, 50, 60, 255}, &clip); 1358 fill(b, Col{45, 50, 60, 255}, &clip);
1242 outline(b, C_BORDER, &clip); 1359 outline(b, C_BORDER, &clip);
1243 const char *label = ct.label; 1360 const char *label = ct.label;
@@ -1247,19 +1364,25 @@ static void draw_card(const Rect &clip, int index, int y) {
1247 else 1364 else
1248 label = "Confirm reset"; 1365 label = "Confirm reset";
1249 } 1366 }
1250 text_in(clip, b.x + S(10.0f), b.y + S(4.0f), label, g_font_body, 1367 text_in(clip, b.x + S(10.0f), b.y + (b.h - g_body_h) / 2, label,
1251 g_body_h, b.w - S(16.0f), C_TEXT); 1368 g_font_body, g_body_h, 0, C_TEXT);
1252 } else { 1369 } else {
1253 text_in(clip, cr.x + S(16.0f), by + S(4.0f), ct.label, g_font_body, 1370 int maxw = label_max_w(ct);
1254 g_body_h, S(220.0f), C_TEXT); 1371 std::vector<std::string> lines;
1372 wrap_text(ct.label, maxw, g_body_h, lines);
1373 int lh = g_body_h + S(2.0f);
1374 int text_h = (int)lines.size() * lh - S(2.0f);
1375 int ly = by + (rh - text_h) / 2;
1376 draw_wrapped(clip, cr.x + S(16.0f), ly, ct.label, g_font_body, g_body_h,
1377 maxw, C_TEXT, S(2.0f));
1255 if (ct.kind == CK_TOGGLE) { 1378 if (ct.kind == CK_TOGGLE) {
1256 bool on = ctrl_val(ct) != 0; 1379 bool on = ctrl_val(ct) != 0;
1257 draw_checkbox(cr.x + cr.w - S(31.0f), by + S(5.0f), on); 1380 draw_checkbox(cr.x + cr.w - S(31.0f), by + (rh - S(15.0f)) / 2, on);
1258 text_in(clip, cr.x + cr.w - S(58.0f), by + S(3.0f), 1381 text_in(clip, cr.x + cr.w - S(58.0f), by + (rh - g_body_h) / 2,
1259 on ? "On" : "Off", g_font_body, g_body_h, S(26.0f), 1382 on ? "On" : "Off", g_font_body, g_body_h, 0,
1260 on ? C_GOOD : C_DIM); 1383 on ? C_GOOD : C_DIM);
1261 } else if (ct.kind == CK_SLIDER) { 1384 } else if (ct.kind == CK_SLIDER) {
1262 draw_slider(clip, cr, by, ct); 1385 draw_slider(clip, cr, by, rh, ct);
1263 } 1386 }
1264 } 1387 }
1265 by += rh; 1388 by += rh;
@@ -1272,94 +1395,46 @@ static void draw_tooltip() {
1272 float d = now_sec() - (float)g_hover_since; 1395 float d = now_sec() - (float)g_hover_since;
1273 if (d < 0.4f) 1396 if (d < 0.4f)
1274 return; 1397 return;
1275 int tw = text_w(g_hover_desc, g_body_h) + S(16.0f);
1276 int maxw = S(380.0f); 1398 int maxw = S(380.0f);
1277 if (tw > maxw) tw = maxw; 1399 if (maxw > g_scr_w - S(32.0f))
1278 int th = g_body_h + S(14.0f); 1400 maxw = g_scr_w - S(32.0f);
1401 std::vector<std::string> lines;
1402 wrap_text(g_hover_desc, maxw, g_body_h, lines);
1403 if (lines.empty())
1404 return;
1405 int line_w = 0;
1406 for (auto &line : lines) {
1407 int w = text_w(line.c_str(), g_body_h);
1408 if (w > line_w)
1409 line_w = w;
1410 }
1411 int line_h = g_body_h + S(3.0f);
1412 int tw = line_w + S(16.0f);
1413 int th = (int)lines.size() * line_h + S(12.0f) - S(3.0f);
1279 int x = g_hover_x + S(14.0f); 1414 int x = g_hover_x + S(14.0f);
1280 int y = g_hover_y + S(16.0f); 1415 int y = g_hover_y + S(16.0f);
1281 if (x + tw > g_scr_w - S(4.0f)) x = g_hover_x - tw - S(10.0f); 1416 if (x + tw > g_scr_w - S(4.0f))
1282 if (y + th > g_scr_h - S(4.0f)) y = g_hover_y - th - S(10.0f); 1417 x = g_hover_x - tw - S(10.0f);
1418 if (y + th > g_scr_h - S(4.0f))
1419 y = g_hover_y - th - S(10.0f);
1420 if (x < S(4.0f))
1421 x = S(4.0f);
1422 if (y < S(4.0f))
1423 y = S(4.0f);
1283 Rect r{x, y, tw, th}; 1424 Rect r{x, y, tw, th};
1284 fill(r, Col{12, 14, 18, 248}); 1425 fill(r, Col{12, 14, 18, 248});
1285 outline(r, C_ACCENT_DIM); 1426 outline(r, C_ACCENT_DIM);
1286 Rect inner{x + S(7.0f), y + S(6.0f), tw - S(14.0f), g_body_h}; 1427 s_text_font(g_font_body);
1287 text_in(inner, inner.x, inner.y, g_hover_desc, g_font_body, g_body_h, 1428 s_text_color(C_TEXT.r, C_TEXT.g, C_TEXT.b, C_TEXT.a);
1288 inner.w, C_TEXT); 1429 for (size_t i = 0; i < lines.size(); i++)
1430 draw_text_raw(x + S(7.0f), y + S(6.0f) + (int)i * line_h,
1431 lines[i].c_str(), g_font_body, g_body_h);
1289} 1432}
1290 1433
1291// --------------------------------------------------------------------------- 1434// ---------------------------------------------------------------------------
1292// Toast drawing 1435// Toast drawing
1293// --------------------------------------------------------------------------- 1436// ---------------------------------------------------------------------------
1294 1437
1295static int wrap_toast(const char *text, int max_w, int tall,
1296 char lines[][256], int max_lines) {
1297 int n = 0;
1298 char cur[256] = {};
1299 bool truncated = false;
1300 const char *p = text;
1301 while (*p) {
1302 while (*p == ' ')
1303 p++;
1304 if (!*p)
1305 break;
1306 const char *start = p;
1307 while (*p && *p != ' ')
1308 p++;
1309 size_t wl = (size_t)(p - start);
1310 if (wl > 120)
1311 wl = 120;
1312 char word[128];
1313 memcpy(word, start, wl);
1314 word[wl] = 0;
1315
1316 char cand[256];
1317 if (cur[0])
1318 snprintf(cand, sizeof(cand), "%s %s", cur, word);
1319 else
1320 snprintf(cand, sizeof(cand), "%s", word);
1321
1322 if (text_w(cand, tall) <= max_w || !cur[0]) {
1323 snprintf(cur, sizeof(cur), "%s", cand);
1324 if (text_w(cur, tall) > max_w) {
1325 // A single word longer than the panel: hard-truncate it.
1326 int len = (int)strlen(cur);
1327 while (len > 3 && text_w(cur, tall) > max_w)
1328 cur[--len] = 0;
1329 if (len > 3) {
1330 cur[len - 3] = '.';
1331 cur[len - 2] = '.';
1332 cur[len - 1] = '.';
1333 }
1334 truncated = true;
1335 }
1336 } else {
1337 if (n < max_lines)
1338 snprintf(lines[n++], 256, "%s", cur);
1339 else
1340 truncated = true;
1341 snprintf(cur, sizeof(cur), "%s", word);
1342 }
1343 }
1344 if (cur[0]) {
1345 if (n < max_lines)
1346 snprintf(lines[n++], 256, "%s", cur);
1347 else
1348 truncated = true;
1349 }
1350 if (truncated && n > 0) {
1351 char *last = lines[n - 1];
1352 int len = (int)strlen(last);
1353 if (len + 3 < 256) {
1354 last[len++] = '.';
1355 last[len++] = '.';
1356 last[len++] = '.';
1357 last[len] = 0;
1358 }
1359 }
1360 return n;
1361}
1362
1363void gui_draw_toasts() { 1438void gui_draw_toasts() {
1364 if (!g_surface) 1439 if (!g_surface)
1365 return; 1440 return;
@@ -1401,13 +1476,14 @@ void gui_draw_toasts() {
1401 if (a > 1.0f) a = 1.0f; 1476 if (a > 1.0f) a = 1.0f;
1402 int alpha = (int)(a * 255.0f); 1477 int alpha = (int)(a * 255.0f);
1403 1478
1404 char lines[4][256]; 1479 std::vector<std::string> lines;
1405 int nl = wrap_toast(t.text, max_w, g_body_h, lines, 4); 1480 wrap_text(t.text, max_w, g_body_h, lines);
1481 int nl = (int)lines.size();
1406 if (nl == 0) 1482 if (nl == 0)
1407 continue; 1483 continue;
1408 int line_w = 0; 1484 int line_w = 0;
1409 for (int j = 0; j < nl; j++) { 1485 for (int j = 0; j < nl; j++) {
1410 int w = text_w(lines[j], g_body_h); 1486 int w = text_w(lines[j].c_str(), g_body_h);
1411 if (w > line_w) 1487 if (w > line_w)
1412 line_w = w; 1488 line_w = w;
1413 } 1489 }
@@ -1432,7 +1508,7 @@ void gui_draw_toasts() {
1432 for (int j = 0; j < nl; j++) { 1508 for (int j = 0; j < nl; j++) {
1433 s_text_pos(x + pad + S(6.0f), y + pad + j * line_h); 1509 s_text_pos(x + pad + S(6.0f), y + pad + j * line_h);
1434 wchar_t w[256]; 1510 wchar_t w[256];
1435 size_t wn = mbstowcs(w, lines[j], 255); 1511 size_t wn = mbstowcs(w, lines[j].c_str(), 255);
1436 if (wn == (size_t)-1) 1512 if (wn == (size_t)-1)
1437 continue; 1513 continue;
1438 w[wn] = 0; 1514 w[wn] = 0;
@@ -1515,15 +1591,10 @@ void gui_draw() {
1515 g_capture_card >= 0 ? "press a key / Esc" : "INS close"; 1591 g_capture_card >= 0 ? "press a key / Esc" : "INS close";
1516 int hint_w = text_w(hint, g_body_h); 1592 int hint_w = text_w(hint, g_body_h);
1517 int hint_x = foot.x + foot.w - hint_w - S(12.0f); 1593 int hint_x = foot.x + foot.w - hint_w - S(12.0f);
1518 int left_x = foot.x + S(12.0f); 1594 if (hint_x < foot.x + S(12.0f))
1519 if (hint_x < left_x) 1595 hint_x = foot.x + S(12.0f);
1520 hint_x = left_x; 1596 text_in(foot, hint_x, foot.y + S(6.0f), hint, g_font_body, g_body_h, 0,
1521 int left_max = hint_x - left_x - S(12.0f); 1597 C_DIM);
1522 if (left_max > 0)
1523 text_in(foot, left_x, foot.y + S(6.0f), g_config_label, g_font_body,
1524 g_body_h, left_max, C_DIM);
1525 text_in(foot, hint_x, foot.y + S(6.0f), hint, g_font_body, g_body_h,
1526 hint_w + S(4.0f), C_DIM);
1527 1598
1528 draw_tooltip(); 1599 draw_tooltip();
1529} 1600}