diff options
Diffstat (limited to '')
| -rw-r--r-- | src/ISURFACE_STATUS.md | 1 | ||||
| -rw-r--r-- | src/gui.cpp | 419 |
2 files changed, 246 insertions, 174 deletions
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 | ||
| 36 | static unsigned long g_font_body = 0; | 37 | static 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 | } |
| 91 | static 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 | ||
| 513 | static int text_w(const char *s, int tall) { | 519 | static 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 | ||
| 518 | static void draw_text_raw(int x, int y, const char *s, unsigned long font, | 539 | static 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. | ||
| 544 | static 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 | |||
| 582 | static 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 | |||
| 588 | static 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 | |||
| 605 | static 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 | ||
| 533 | static void text_in(const Rect &clip, int x, int y, const char *s, | 620 | static 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 | ||
| 577 | static Rect clip_rect(const Rect &r, const Rect &clip) { | 638 | static 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 | ||
| 704 | static int card_content_w() { return g_win_w - S(190.0f) - S(28.0f); } | ||
| 705 | |||
| 706 | static int title_max_w() { return card_content_w() - S(210.0f); } | ||
| 707 | |||
| 708 | static 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 | |||
| 716 | static 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 | |||
| 722 | static 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 | |||
| 643 | static int row_h(const Ctrl &c) { | 732 | static 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 | ||
| 652 | static int body_h(const Card &c) { | 748 | static 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 | ||
| 661 | static int card_h(const Card &c) { return S(36.0f) + body_h(c) + S(8.0f); } | 757 | static int card_h(const Card &c) { return header_h(c) + body_h(c) + S(8.0f); } |
| 662 | 758 | ||
| 663 | static Rect card_rect(int index, int y) { | 759 | static 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 | ||
| 1168 | static void draw_slider(const Rect &clip, const Rect &cr, int y, const Ctrl &ct) { | 1268 | static 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 | ||
| 1188 | static void draw_card(const Rect &clip, int index, int y) { | 1290 | static 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 | ||
| 1295 | static 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 | |||
| 1363 | void gui_draw_toasts() { | 1438 | void 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 | } |
