diff options
Diffstat (limited to 'src/gui.cpp')
| -rw-r--r-- | src/gui.cpp | 259 |
1 files changed, 156 insertions, 103 deletions
diff --git a/src/gui.cpp b/src/gui.cpp index 454e2cb..8a71ecb 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -19,20 +19,8 @@ | |||
| 19 | // ISurface drawing (same binary slots as menu.cpp / esp.cpp) | 19 | // ISurface drawing (same binary slots as menu.cpp / esp.cpp) |
| 20 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
| 21 | 21 | ||
| 22 | enum { | 22 | // ISurface access lives in overlay.cpp. These thin wrappers keep the GUI code |
| 23 | GS_DrawSetColor = 10, | 23 | // readable without repeating the vtable indices in this file. |
| 24 | GS_DrawFilledRect = 12, | ||
| 25 | GS_DrawOutlinedRect = 14, | ||
| 26 | GS_DrawLine = 15, | ||
| 27 | GS_DrawSetTextFont = 17, | ||
| 28 | GS_DrawSetTextColor = 18, | ||
| 29 | GS_DrawSetTextPos = 20, | ||
| 30 | GS_DrawPrintText = 22, | ||
| 31 | GS_GetScreenSize = 38, | ||
| 32 | GS_CreateFont = 66, | ||
| 33 | GS_AddGlyphSet = 67, | ||
| 34 | GS_GetTextSize = 75 | ||
| 35 | }; | ||
| 36 | 24 | ||
| 37 | static unsigned long g_font_body = 0; | 25 | static unsigned long g_font_body = 0; |
| 38 | static unsigned long g_font_title = 0; | 26 | static unsigned long g_font_title = 0; |
| @@ -43,57 +31,27 @@ static void *g_font_local = (void *)-1; | |||
| 43 | static bool g_font_fail_logged = false; | 31 | static bool g_font_fail_logged = false; |
| 44 | static int g_scr_w = 0, g_scr_h = 0; | 32 | static int g_scr_w = 0, g_scr_h = 0; |
| 45 | 33 | ||
| 46 | static void s_color(int r, int g, int b, int a) { | 34 | static void s_color(int r, int g, int b, int a) { overlay_color(r, g, b, a); } |
| 47 | using Fn = void (*)(void *, int, int, int, int); | ||
| 48 | vfunc<Fn>(g_surface, GS_DrawSetColor)(g_surface, r, g, b, a); | ||
| 49 | } | ||
| 50 | static void s_fill(int x0, int y0, int x1, int y1) { | 35 | static void s_fill(int x0, int y0, int x1, int y1) { |
| 51 | using Fn = void (*)(void *, int, int, int, int); | 36 | overlay_fill(x0, y0, x1, y1); |
| 52 | vfunc<Fn>(g_surface, GS_DrawFilledRect)(g_surface, x0, y0, x1, y1); | ||
| 53 | } | ||
| 54 | static void s_outline(int x0, int y0, int x1, int y1) { | ||
| 55 | using Fn = void (*)(void *, int, int, int, int); | ||
| 56 | vfunc<Fn>(g_surface, GS_DrawOutlinedRect)(g_surface, x0, y0, x1, y1); | ||
| 57 | } | 37 | } |
| 58 | static void s_line(int x0, int y0, int x1, int y1) { | 38 | static void s_line(int x0, int y0, int x1, int y1) { |
| 59 | using Fn = void (*)(void *, int, int, int, int); | 39 | overlay_line(x0, y0, x1, y1); |
| 60 | vfunc<Fn>(g_surface, GS_DrawLine)(g_surface, x0, y0, x1, y1); | ||
| 61 | } | ||
| 62 | static void s_text_font(unsigned long f) { | ||
| 63 | using Fn = void (*)(void *, unsigned long); | ||
| 64 | vfunc<Fn>(g_surface, GS_DrawSetTextFont)(g_surface, f); | ||
| 65 | } | 40 | } |
| 41 | static void s_text_font(HFont f) { overlay_text_font(f); } | ||
| 66 | static void s_text_color(int r, int g, int b, int a) { | 42 | static void s_text_color(int r, int g, int b, int a) { |
| 67 | using Fn = void (*)(void *, int, int, int, int); | 43 | overlay_text_color(r, g, b, a); |
| 68 | vfunc<Fn>(g_surface, GS_DrawSetTextColor)(g_surface, r, g, b, a); | ||
| 69 | } | ||
| 70 | static void s_text_pos(int x, int y) { | ||
| 71 | using Fn = void (*)(void *, int, int); | ||
| 72 | vfunc<Fn>(g_surface, GS_DrawSetTextPos)(g_surface, x, y); | ||
| 73 | } | 44 | } |
| 74 | static void s_print(const wchar_t *text, int n) { | 45 | static void s_text_pos(int x, int y) { overlay_text_pos(x, y); } |
| 75 | using Fn = void (*)(void *, const wchar_t *, int, int); | 46 | static void s_print(const wchar_t *text, int n) { overlay_print_text(text, n); } |
| 76 | vfunc<Fn>(g_surface, GS_DrawPrintText)(g_surface, text, n, 0); | 47 | static HFont s_create_font() { return overlay_create_font(); } |
| 77 | } | 48 | static bool s_set_font(HFont font, const char *name, int tall, int weight, |
| 78 | static unsigned long s_create_font() { | ||
| 79 | using Fn = unsigned long (*)(void *); | ||
| 80 | return vfunc<Fn>(g_surface, GS_CreateFont)(g_surface); | ||
| 81 | } | ||
| 82 | static bool s_set_font(unsigned long font, const char *name, int tall, int weight, | ||
| 83 | int flags, int lo, int hi) { | 49 | int flags, int lo, int hi) { |
| 84 | using Fn = bool (*)(void *, unsigned long, const char *, int, int, int, int, | 50 | return overlay_add_glyph_set(font, name, tall, weight, 0, 0, flags, lo, hi); |
| 85 | int, int, int); | ||
| 86 | return vfunc<Fn>(g_surface, GS_AddGlyphSet)(g_surface, font, name, tall, | ||
| 87 | weight, 0, 0, flags, lo, hi); | ||
| 88 | } | ||
| 89 | static void s_screen(int &w, int &h) { | ||
| 90 | using Fn = void (*)(void *, int &, int &); | ||
| 91 | vfunc<Fn>(g_surface, GS_GetScreenSize)(g_surface, w, h); | ||
| 92 | } | 51 | } |
| 93 | static void s_text_size(unsigned long font, const wchar_t *text, int &w, | 52 | static void s_screen(int &w, int &h) { overlay_screen_size(w, h); } |
| 94 | int &h) { | 53 | static void s_text_size(HFont font, const wchar_t *text, int &w, int &h) { |
| 95 | using Fn = void (*)(void *, unsigned long, const wchar_t *, int &, int &); | 54 | overlay_text_size(font, text, w, h); |
| 96 | vfunc<Fn>(g_surface, GS_GetTextSize)(g_surface, font, text, w, h); | ||
| 97 | } | 55 | } |
| 98 | 56 | ||
| 99 | // --------------------------------------------------------------------------- | 57 | // --------------------------------------------------------------------------- |
| @@ -137,9 +95,12 @@ static void gui_init_sdl() { | |||
| 137 | g_sdl_init_tried = true; | 95 | g_sdl_init_tried = true; |
| 138 | sd_get_keys = (GetKeysFn)dlsym(RTLD_DEFAULT, "SDL_GetKeyboardState"); | 96 | sd_get_keys = (GetKeysFn)dlsym(RTLD_DEFAULT, "SDL_GetKeyboardState"); |
| 139 | sd_get_mouse = (GetMouseFn)dlsym(RTLD_DEFAULT, "SDL_GetMouseState"); | 97 | sd_get_mouse = (GetMouseFn)dlsym(RTLD_DEFAULT, "SDL_GetMouseState"); |
| 140 | sd_show_cursor = (ShowCursorFn)dlsym(RTLD_DEFAULT, "SDL_ShowCursor"); | 98 | // RTLD_NEXT, not RTLD_DEFAULT: this object is LD_PRELOADed first, so |
| 141 | sd_set_relative = (SetRelativeFn)dlsym(RTLD_DEFAULT, "SDL_SetRelativeMouseMode"); | 99 | // RTLD_DEFAULT resolves to our own exported wrapper and the "real" pointer |
| 142 | sd_get_relative = (GetRelativeFn)dlsym(RTLD_DEFAULT, "SDL_GetRelativeMouseMode"); | 100 | // would point back into this file. |
| 101 | sd_show_cursor = (ShowCursorFn)dlsym(RTLD_NEXT, "SDL_ShowCursor"); | ||
| 102 | sd_set_relative = (SetRelativeFn)dlsym(RTLD_NEXT, "SDL_SetRelativeMouseMode"); | ||
| 103 | sd_get_relative = (GetRelativeFn)dlsym(RTLD_NEXT, "SDL_GetRelativeMouseMode"); | ||
| 143 | sd_add_watch = (AddWatchFn)dlsym(RTLD_DEFAULT, "SDL_AddEventWatch"); | 104 | sd_add_watch = (AddWatchFn)dlsym(RTLD_DEFAULT, "SDL_AddEventWatch"); |
| 144 | sd_get_ticks = (GetTicksFn)dlsym(RTLD_DEFAULT, "SDL_GetTicks"); | 105 | sd_get_ticks = (GetTicksFn)dlsym(RTLD_DEFAULT, "SDL_GetTicks"); |
| 145 | g_sdl_ready = sd_get_keys && sd_get_mouse; | 106 | g_sdl_ready = sd_get_keys && sd_get_mouse; |
| @@ -607,6 +568,10 @@ static int g_mouse_press = 0; // 1..5 on the frame a button goes down | |||
| 607 | static unsigned char g_prev_keys[512] = {}; | 568 | static unsigned char g_prev_keys[512] = {}; |
| 608 | static bool g_prev_cursor = true; | 569 | static bool g_prev_cursor = true; |
| 609 | static bool g_prev_relative = true; | 570 | static bool g_prev_relative = true; |
| 571 | // Last state the game asked for while the GUI held the mouse; restored on | ||
| 572 | // close instead of the (possibly stale) pre-open snapshot. | ||
| 573 | static bool g_game_wants_cursor = true; | ||
| 574 | static bool g_game_wants_relative = false; | ||
| 610 | static double g_hover_since = 0.0; | 575 | static double g_hover_since = 0.0; |
| 611 | static const char *g_hover_desc = nullptr; | 576 | static const char *g_hover_desc = nullptr; |
| 612 | static const char *g_hover_prev = nullptr; | 577 | static const char *g_hover_prev = nullptr; |
| @@ -688,10 +653,12 @@ static unsigned long create_gui_font(int tall) { | |||
| 688 | static const char *families[] = {"Liberation Sans", "DejaVu Sans", | 653 | static const char *families[] = {"Liberation Sans", "DejaVu Sans", |
| 689 | "Noto Sans", "Tahoma", "Verdana", "Arial", | 654 | "Noto Sans", "Tahoma", "Verdana", "Arial", |
| 690 | nullptr}; | 655 | nullptr}; |
| 656 | // Create one handle and add glyph sets to it. Creating a handle per family | ||
| 657 | // leaked one HFont per failed family, and ISurface has no DestroyFont. | ||
| 658 | unsigned long f = s_create_font(); | ||
| 659 | if (!f) | ||
| 660 | return 0; | ||
| 691 | for (int i = 0; families[i]; i++) { | 661 | for (int i = 0; families[i]; i++) { |
| 692 | unsigned long f = s_create_font(); | ||
| 693 | if (!f) | ||
| 694 | break; | ||
| 695 | if (s_set_font(f, families[i], tall, 400, 0x400, 0x20, 0xFF) && | 662 | if (s_set_font(f, families[i], tall, 400, 0x400, 0x20, 0xFF) && |
| 696 | font_valid(f)) { | 663 | font_valid(f)) { |
| 697 | logd("gui: font %dpx family %s", tall, families[i]); | 664 | logd("gui: font %dpx family %s", tall, families[i]); |
| @@ -710,8 +677,13 @@ static void ensure_fonts() { | |||
| 710 | } | 677 | } |
| 711 | if (le != g_font_local) { | 678 | if (le != g_font_local) { |
| 712 | g_font_local = le; | 679 | g_font_local = le; |
| 713 | gui_reset_fonts(); | 680 | // VGUI fonts are global; a level change alone does not invalidate them. |
| 714 | logd("gui: fonts reset (local entity change)"); | 681 | // Dropping valid handles here leaked a fresh pair on every death, respawn |
| 682 | // and map load. Only reset when the manager actually lost them. | ||
| 683 | if (!font_valid(g_font_body) || !font_valid(g_font_title)) { | ||
| 684 | gui_reset_fonts(); | ||
| 685 | logd("gui: fonts reset (invalid after local entity change)"); | ||
| 686 | } | ||
| 715 | } | 687 | } |
| 716 | 688 | ||
| 717 | bool scale_changed = fabsf(g_font_scale - g_scale) >= 0.001f; | 689 | bool scale_changed = fabsf(g_font_scale - g_scale) >= 0.001f; |
| @@ -871,20 +843,30 @@ static void draw_text_raw(int x, int y, const char *s, unsigned long font, | |||
| 871 | 843 | ||
| 872 | static void text_in(const Rect &clip, int x, int y, const char *s, | 844 | static void text_in(const Rect &clip, int x, int y, const char *s, |
| 873 | unsigned long font, int tall, int max_w, const Col &c) { | 845 | unsigned long font, int tall, int max_w, const Col &c) { |
| 874 | (void)max_w; // callers must size the layout; text is never truncated | ||
| 875 | if (!s || !font) | 846 | if (!s || !font) |
| 876 | return; | 847 | return; |
| 877 | if (y < clip.y || y + tall > clip.y + clip.h) | 848 | if (y < clip.y || y + tall > clip.y + clip.h) |
| 878 | return; | 849 | return; |
| 879 | if (x >= clip.x + clip.w) | 850 | if (x >= clip.x + clip.w) |
| 880 | return; | 851 | return; |
| 881 | int w = text_w(s, tall); | 852 | // Available width: the clip edge, further limited by the layout's column |
| 882 | if (x + w > clip.x + clip.w) | 853 | // width. Overflowing text used to be shifted left over its neighbour; now it |
| 883 | x = clip.x + clip.w - w; | 854 | // is truncated with an ellipsis instead. |
| 884 | if (x < clip.x) | 855 | int avail = clip.x + clip.w - x; |
| 885 | x = clip.x; | 856 | if (max_w > 0 && max_w < avail) |
| 857 | avail = max_w; | ||
| 858 | if (avail <= 0) | ||
| 859 | return; | ||
| 860 | std::string out = s; | ||
| 861 | if (text_w(out.c_str(), tall) > avail) { | ||
| 862 | while (!out.empty() && text_w((out + "...").c_str(), tall) > avail) | ||
| 863 | out.pop_back(); | ||
| 864 | if (out.empty()) | ||
| 865 | return; | ||
| 866 | out += "..."; | ||
| 867 | } | ||
| 886 | s_text_color(c.r, c.g, c.b, c.a); | 868 | s_text_color(c.r, c.g, c.b, c.a); |
| 887 | draw_text_raw(x, y, s, font, tall); | 869 | draw_text_raw(x, y, out.c_str(), font, tall); |
| 888 | } | 870 | } |
| 889 | 871 | ||
| 890 | static Rect clip_rect(const Rect &r, const Rect &clip) { | 872 | static Rect clip_rect(const Rect &r, const Rect &clip) { |
| @@ -911,13 +893,49 @@ static void fill(const Rect &r, const Col &c, const Rect *clip = nullptr) { | |||
| 911 | static void outline(const Rect &r, const Col &c, const Rect *clip = nullptr) { | 893 | static void outline(const Rect &r, const Col &c, const Rect *clip = nullptr) { |
| 912 | if (r.w <= 0 || r.h <= 0) | 894 | if (r.w <= 0 || r.h <= 0) |
| 913 | return; | 895 | return; |
| 914 | Rect q = clip ? clip_rect(r, *clip) : r; | 896 | // Draw the four edges as thin filled rects. Outlining the *clamped* rect used |
| 915 | if (q.w <= 0 || q.h <= 0) | 897 | // to paint a false border along the clip edge whenever a card was partially |
| 916 | return; | 898 | // scrolled out of the content pane. |
| 917 | s_color(c.r, c.g, c.b, c.a); | 899 | int t = S(1.0f); |
| 918 | s_outline(q.x, q.y, q.x + q.w, q.y + q.h); | 900 | if (t < 1) t = 1; |
| 919 | } | 901 | fill(Rect{r.x, r.y, r.w, t}, c, clip); |
| 920 | static void line(int x0, int y0, int x1, int y1, const Col &c) { | 902 | fill(Rect{r.x, r.y + r.h - t, r.w, t}, c, clip); |
| 903 | fill(Rect{r.x, r.y, t, r.h}, c, clip); | ||
| 904 | fill(Rect{r.x + r.w - t, r.y, t, r.h}, c, clip); | ||
| 905 | } | ||
| 906 | // Liang–Barsky line clip. `clip` is optional so the existing call sites that | ||
| 907 | // draw inside an already-safe area keep working unchanged. | ||
| 908 | static void line(int x0, int y0, int x1, int y1, const Col &c, | ||
| 909 | const Rect *clip = nullptr) { | ||
| 910 | if (clip) { | ||
| 911 | float dx = (float)(x1 - x0), dy = (float)(y1 - y0); | ||
| 912 | float t0 = 0.0f, t1 = 1.0f; | ||
| 913 | float p[4] = {-dx, dx, -dy, dy}; | ||
| 914 | float q[4] = {(float)(x0 - clip->x), | ||
| 915 | (float)(clip->x + clip->w - x0), | ||
| 916 | (float)(y0 - clip->y), | ||
| 917 | (float)(clip->y + clip->h - y0)}; | ||
| 918 | for (int i = 0; i < 4; i++) { | ||
| 919 | if (fabsf(p[i]) < 1e-6f) { | ||
| 920 | if (q[i] < 0.0f) | ||
| 921 | return; // parallel and outside | ||
| 922 | continue; | ||
| 923 | } | ||
| 924 | float t = q[i] / p[i]; | ||
| 925 | if (p[i] < 0.0f) { | ||
| 926 | if (t > t1) return; | ||
| 927 | if (t > t0) t0 = t; | ||
| 928 | } else { | ||
| 929 | if (t < t0) return; | ||
| 930 | if (t < t1) t1 = t; | ||
| 931 | } | ||
| 932 | } | ||
| 933 | int nx0 = (int)((float)x0 + t0 * dx + 0.5f); | ||
| 934 | int ny0 = (int)((float)y0 + t0 * dy + 0.5f); | ||
| 935 | int nx1 = (int)((float)x0 + t1 * dx + 0.5f); | ||
| 936 | int ny1 = (int)((float)y0 + t1 * dy + 0.5f); | ||
| 937 | x0 = nx0; y0 = ny0; x1 = nx1; y1 = ny1; | ||
| 938 | } | ||
| 921 | s_color(c.r, c.g, c.b, c.a); | 939 | s_color(c.r, c.g, c.b, c.a); |
| 922 | s_line(x0, y0, x1, y1); | 940 | s_line(x0, y0, x1, y1); |
| 923 | } | 941 | } |
| @@ -1298,7 +1316,7 @@ static void draw_picker(const Rect &clip, const Rect &cr, const Ctrl &ct, | |||
| 1298 | outline(p.alpha, Col{0, 0, 0, 255}, &clip); | 1316 | outline(p.alpha, Col{0, 0, 0, 255}, &clip); |
| 1299 | int amx = p.alpha.x + (int)((float)pa / 255.0f * (float)p.alpha.w); | 1317 | int amx = p.alpha.x + (int)((float)pa / 255.0f * (float)p.alpha.w); |
| 1300 | line(amx, p.alpha.y - S(2.0f), amx, p.alpha.y + p.alpha.h + S(2.0f), | 1318 | line(amx, p.alpha.y - S(2.0f), amx, p.alpha.y + p.alpha.h + S(2.0f), |
| 1301 | Col{255, 255, 255, 255}); | 1319 | Col{255, 255, 255, 255}, &clip); |
| 1302 | } | 1320 | } |
| 1303 | 1321 | ||
| 1304 | // Selection markers. | 1322 | // Selection markers. |
| @@ -1311,7 +1329,7 @@ static void draw_picker(const Rect &clip, const Rect &cr, const Ctrl &ct, | |||
| 1311 | 1329 | ||
| 1312 | int hxm = p.hue.x + (int)((ct.hue / 360.0f) * (float)p.hue.w); | 1330 | int hxm = p.hue.x + (int)((ct.hue / 360.0f) * (float)p.hue.w); |
| 1313 | line(hxm, p.hue.y - S(2.0f), hxm, p.hue.y + p.hue.h + S(2.0f), | 1331 | line(hxm, p.hue.y - S(2.0f), hxm, p.hue.y + p.hue.h + S(2.0f), |
| 1314 | Col{255, 255, 255, 255}); | 1332 | Col{255, 255, 255, 255}, &clip); |
| 1315 | 1333 | ||
| 1316 | // Preview swatch and hex value. | 1334 | // Preview swatch and hex value. |
| 1317 | draw_checker(p.preview, &clip); | 1335 | draw_checker(p.preview, &clip); |
| @@ -1351,6 +1369,23 @@ static Rect card_rect(int index, int y) { | |||
| 1351 | return Rect{x, y, w, card_h(c)}; | 1369 | return Rect{x, y, w, card_h(c)}; |
| 1352 | } | 1370 | } |
| 1353 | 1371 | ||
| 1372 | // Slider track inside a card: the single source of truth for drawing, | ||
| 1373 | // hit-testing and dragging. The drag path used to recompute this by hand and | ||
| 1374 | // omitted the card's +S(10) left margin, so the value jumped on mouse-down. | ||
| 1375 | static Rect slider_track(const Rect &cr, int y, int rh) { | ||
| 1376 | int w = cr.w - S(16.0f) - S(230.0f) - S(64.0f); | ||
| 1377 | if (w < S(30.0f)) w = S(30.0f); | ||
| 1378 | return Rect{cr.x + S(16.0f) + S(230.0f), y + (rh - S(8.0f)) / 2, w, | ||
| 1379 | S(8.0f)}; | ||
| 1380 | } | ||
| 1381 | |||
| 1382 | static int card_for_ctrl(int ci) { | ||
| 1383 | for (int i = 0; i < g_ncards; i++) | ||
| 1384 | if (ci >= g_cards[i].c0 && ci < g_cards[i].c0 + g_cards[i].n) | ||
| 1385 | return i; | ||
| 1386 | return -1; | ||
| 1387 | } | ||
| 1388 | |||
| 1354 | static bool card_in_category(int i) { return g_cards[i].cat == g_cat; } | 1389 | static bool card_in_category(int i) { return g_cards[i].cat == g_cat; } |
| 1355 | 1390 | ||
| 1356 | static int content_height() { | 1391 | static int content_height() { |
| @@ -1379,10 +1414,13 @@ static void close_gui() { | |||
| 1379 | g_reset_armed = false; | 1414 | g_reset_armed = false; |
| 1380 | input_clear_mouse_states(); | 1415 | input_clear_mouse_states(); |
| 1381 | if (g_sdl_ready) { | 1416 | if (g_sdl_ready) { |
| 1417 | // Restore what the game actually wants now, not the pre-open snapshot: the | ||
| 1418 | // game keeps requesting relative mode/cursor state while the GUI is open | ||
| 1419 | // (menu -> map transition), and those requests were being dropped. | ||
| 1382 | if (sd_set_relative) | 1420 | if (sd_set_relative) |
| 1383 | sd_set_relative(g_prev_relative ? 1 : 0); | 1421 | sd_set_relative(g_game_wants_relative ? 1 : 0); |
| 1384 | if (sd_show_cursor) | 1422 | if (sd_show_cursor) |
| 1385 | sd_show_cursor(g_prev_cursor ? 1 : 0); | 1423 | sd_show_cursor(g_game_wants_cursor ? 1 : 0); |
| 1386 | } | 1424 | } |
| 1387 | config_save(); | 1425 | config_save(); |
| 1388 | logf("gui: closed"); | 1426 | logf("gui: closed"); |
| @@ -1410,6 +1448,9 @@ static void open_gui() { | |||
| 1410 | g_prev_relative = sd_get_relative() != 0; | 1448 | g_prev_relative = sd_get_relative() != 0; |
| 1411 | if (sd_show_cursor) | 1449 | if (sd_show_cursor) |
| 1412 | g_prev_cursor = sd_show_cursor(-1) != 0; | 1450 | g_prev_cursor = sd_show_cursor(-1) != 0; |
| 1451 | // Until the game asks for something else, restore what it had before. | ||
| 1452 | g_game_wants_relative = g_prev_relative; | ||
| 1453 | g_game_wants_cursor = g_prev_cursor; | ||
| 1413 | if (sd_set_relative) | 1454 | if (sd_set_relative) |
| 1414 | sd_set_relative(0); | 1455 | sd_set_relative(0); |
| 1415 | if (sd_show_cursor) | 1456 | if (sd_show_cursor) |
| @@ -1519,6 +1560,10 @@ static void handle_mouse_click() { | |||
| 1519 | } | 1560 | } |
| 1520 | } | 1561 | } |
| 1521 | g_dropdown_ctrl = -1; | 1562 | g_dropdown_ctrl = -1; |
| 1563 | // Consume the dismissal click: it must not also activate whatever control | ||
| 1564 | // happened to be underneath the popup. | ||
| 1565 | g_mouse_click = false; | ||
| 1566 | return; | ||
| 1522 | } | 1567 | } |
| 1523 | 1568 | ||
| 1524 | // title bar: drag | 1569 | // title bar: drag |
| @@ -1624,9 +1669,7 @@ static void handle_mouse_click() { | |||
| 1624 | return; | 1669 | return; |
| 1625 | } | 1670 | } |
| 1626 | if (ct.kind == CK_SLIDER) { | 1671 | if (ct.kind == CK_SLIDER) { |
| 1627 | Rect track{cr.x + S(16.0f) + S(230.0f), | 1672 | Rect track = slider_track(cr, by, rh); |
| 1628 | by + (rh - S(8.0f)) / 2, | ||
| 1629 | cr.w - S(16.0f) - S(230.0f) - S(64.0f), S(8.0f)}; | ||
| 1630 | if (mx >= track.x && mx < track.x + track.w) { | 1673 | if (mx >= track.x && mx < track.x + track.w) { |
| 1631 | float t = (float)(mx - track.x) / (float)track.w; | 1674 | float t = (float)(mx - track.x) / (float)track.w; |
| 1632 | int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); | 1675 | int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); |
| @@ -1717,6 +1760,11 @@ static void handle_mouse_click() { | |||
| 1717 | static void handle_hover() { | 1760 | static void handle_hover() { |
| 1718 | g_hover_desc = nullptr; | 1761 | g_hover_desc = nullptr; |
| 1719 | g_hover_slider = -1; | 1762 | g_hover_slider = -1; |
| 1763 | // An open popup/picker or an active drag owns the cursor; arming hover on the | ||
| 1764 | // controls underneath let Left/Right arrows adjust a hidden slider. | ||
| 1765 | if (g_dropdown_ctrl >= 0 || g_picker_ctrl >= 0 || g_picker_drag || | ||
| 1766 | g_slider_drag >= 0 || g_dragging || g_scroll_drag) | ||
| 1767 | return; | ||
| 1720 | int mx = g_mouse_x, my = g_mouse_y; | 1768 | int mx = g_mouse_x, my = g_mouse_y; |
| 1721 | Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), | 1769 | Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), |
| 1722 | g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; | 1770 | g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; |
| @@ -1849,14 +1897,15 @@ void gui_handle_input() { | |||
| 1849 | config_save(); | 1897 | config_save(); |
| 1850 | } else { | 1898 | } else { |
| 1851 | Ctrl &ct = g_ctrls[g_slider_drag]; | 1899 | Ctrl &ct = g_ctrls[g_slider_drag]; |
| 1852 | Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), | 1900 | int ci = card_for_ctrl(g_slider_drag); |
| 1853 | g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; | 1901 | if (ci >= 0) { |
| 1854 | int x0 = g_win_x + S(190.0f) + S(16.0f) + S(230.0f); | 1902 | Rect track = slider_track(card_rect(ci, 0), 0, 0); |
| 1855 | int x1 = g_win_x + g_win_w - S(28.0f) - S(64.0f); | 1903 | float t = (float)(g_mouse_x - track.x) / (float)track.w; |
| 1856 | (void)content; | 1904 | if (t < 0.0f) t = 0.0f; |
| 1857 | float t = (float)(g_mouse_x - x0) / (float)(x1 - x0); | 1905 | if (t > 1.0f) t = 1.0f; |
| 1858 | int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); | 1906 | int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); |
| 1859 | ctrl_set(ct, v); | 1907 | ctrl_set(ct, v); |
| 1908 | } | ||
| 1860 | } | 1909 | } |
| 1861 | } | 1910 | } |
| 1862 | 1911 | ||
| @@ -1938,18 +1987,15 @@ static void draw_checkbox(int x, int y, bool on, | |||
| 1938 | fill(box, on ? C_ACCENT : Col{30, 34, 42, 255}, clip); | 1987 | fill(box, on ? C_ACCENT : Col{30, 34, 42, 255}, clip); |
| 1939 | outline(box, C_BORDER, clip); | 1988 | outline(box, C_BORDER, clip); |
| 1940 | if (on) { | 1989 | if (on) { |
| 1941 | line(x + S(3.0f), y + S(8.0f), x + S(7.0f), y + S(12.0f), C_BG); | 1990 | line(x + S(3.0f), y + S(8.0f), x + S(7.0f), y + S(12.0f), C_BG, clip); |
| 1942 | line(x + S(7.0f), y + S(12.0f), x + S(13.0f), y + S(4.0f), C_BG); | 1991 | line(x + S(7.0f), y + S(12.0f), x + S(13.0f), y + S(4.0f), C_BG, clip); |
| 1943 | } | 1992 | } |
| 1944 | } | 1993 | } |
| 1945 | 1994 | ||
| 1946 | static void draw_slider(const Rect &clip, const Rect &cr, int y, int rh, | 1995 | static void draw_slider(const Rect &clip, const Rect &cr, int y, int rh, |
| 1947 | const Ctrl &ct) { | 1996 | const Ctrl &ct) { |
| 1948 | int track_x = cr.x + S(16.0f) + S(230.0f); | 1997 | Rect track = slider_track(cr, y, rh); |
| 1949 | int track_w = cr.w - S(16.0f) - S(230.0f) - S(64.0f); | 1998 | int track_w = track.w; |
| 1950 | if (track_w < S(30.0f)) track_w = S(30.0f); | ||
| 1951 | int track_y = y + (rh - S(8.0f)) / 2; | ||
| 1952 | Rect track{track_x, track_y, track_w, S(8.0f)}; | ||
| 1953 | fill(track, Col{38, 42, 50, 255}, &clip); | 1999 | fill(track, Col{38, 42, 50, 255}, &clip); |
| 1954 | int value = ctrl_val(ct); | 2000 | int value = ctrl_val(ct); |
| 1955 | float t = (ct.max > ct.min) | 2001 | float t = (ct.max > ct.min) |
| @@ -2416,6 +2462,8 @@ extern "C" __attribute__((visibility("default"))) int SDL_ShowCursor(int toggle) | |||
| 2416 | if (!real_show_cursor) | 2462 | if (!real_show_cursor) |
| 2417 | real_show_cursor = (int (*)(int))dlsym(RTLD_NEXT, "SDL_ShowCursor"); | 2463 | real_show_cursor = (int (*)(int))dlsym(RTLD_NEXT, "SDL_ShowCursor"); |
| 2418 | if (g_open && toggle != -1) { | 2464 | if (g_open && toggle != -1) { |
| 2465 | // Remember what the game asked for so close_gui() can restore it. | ||
| 2466 | g_game_wants_cursor = toggle != 0; | ||
| 2419 | if (real_show_cursor) | 2467 | if (real_show_cursor) |
| 2420 | real_show_cursor(1); | 2468 | real_show_cursor(1); |
| 2421 | return 1; | 2469 | return 1; |
| @@ -2429,6 +2477,7 @@ SDL_SetRelativeMouseMode(int enabled) { | |||
| 2429 | real_set_relative = | 2477 | real_set_relative = |
| 2430 | (int (*)(int))dlsym(RTLD_NEXT, "SDL_SetRelativeMouseMode"); | 2478 | (int (*)(int))dlsym(RTLD_NEXT, "SDL_SetRelativeMouseMode"); |
| 2431 | if (g_open) { | 2479 | if (g_open) { |
| 2480 | g_game_wants_relative = enabled != 0; | ||
| 2432 | if (real_set_relative) | 2481 | if (real_set_relative) |
| 2433 | real_set_relative(0); | 2482 | real_set_relative(0); |
| 2434 | return 0; | 2483 | return 0; |
| @@ -2520,7 +2569,11 @@ SDL_PollEvent(void *event) { | |||
| 2520 | unsigned int type = *(unsigned int *)event; | 2569 | unsigned int type = *(unsigned int *)event; |
| 2521 | if (type != 0x400) // SDL_MOUSEMOTION | 2570 | if (type != 0x400) // SDL_MOUSEMOTION |
| 2522 | return r; | 2571 | return r; |
| 2523 | // Drop motion while the GUI is open: the game must not accumulate look | 2572 | // Keep the event (VGUI hover and SDL's internal cursor state need it) but |
| 2524 | // deltas. The GUI uses absolute SDL_GetMouseState polling. | 2573 | // zero the relative deltas so the game does not accumulate look movement. |
| 2574 | // SDL_MouseMotionEvent: xrel at +28, yrel at +32. | ||
| 2575 | *(int *)((char *)event + 28) = 0; | ||
| 2576 | *(int *)((char *)event + 32) = 0; | ||
| 2577 | return r; | ||
| 2525 | } | 2578 | } |
| 2526 | } | 2579 | } |
