diff options
Diffstat (limited to '')
| -rw-r--r-- | src/gui.cpp | 1199 |
1 files changed, 1199 insertions, 0 deletions
diff --git a/src/gui.cpp b/src/gui.cpp new file mode 100644 index 0000000..74dd537 --- /dev/null +++ b/src/gui.cpp | |||
| @@ -0,0 +1,1199 @@ | |||
| 1 | // gui.cpp — custom immediate-mode GUI shell (temporary F2 preview) | ||
| 2 | // | ||
| 3 | // This is the first vertical slice: the visual shell, categories, cards, | ||
| 4 | // dummy controls, cursor handling and input freeze. It does not read or write | ||
| 5 | // the real feature settings yet; the next pass replaces the dummy state and | ||
| 6 | // removes the text menu. | ||
| 7 | |||
| 8 | #include "hl2h.h" | ||
| 9 | |||
| 10 | #include <atomic> | ||
| 11 | #include <cmath> | ||
| 12 | #include <cstdio> | ||
| 13 | #include <cstdlib> | ||
| 14 | #include <cstring> | ||
| 15 | #include <cwchar> | ||
| 16 | #include <dlfcn.h> | ||
| 17 | #include <time.h> | ||
| 18 | |||
| 19 | // --------------------------------------------------------------------------- | ||
| 20 | // ISurface drawing (same binary slots as menu.cpp / esp.cpp) | ||
| 21 | // --------------------------------------------------------------------------- | ||
| 22 | |||
| 23 | enum { | ||
| 24 | GS_DrawSetColor = 10, | ||
| 25 | GS_DrawFilledRect = 12, | ||
| 26 | GS_DrawOutlinedRect = 14, | ||
| 27 | GS_DrawLine = 15, | ||
| 28 | GS_DrawSetTextFont = 17, | ||
| 29 | GS_DrawSetTextColor = 18, | ||
| 30 | GS_DrawSetTextPos = 20, | ||
| 31 | GS_DrawPrintText = 22, | ||
| 32 | GS_GetScreenSize = 38, | ||
| 33 | GS_CreateFont = 66, | ||
| 34 | GS_AddGlyphSet = 67 | ||
| 35 | }; | ||
| 36 | |||
| 37 | static unsigned long g_font_body = 0; | ||
| 38 | static unsigned long g_font_title = 0; | ||
| 39 | static int g_body_h = 15; | ||
| 40 | static int g_title_h = 20; | ||
| 41 | static float g_font_scale = 0.0f; | ||
| 42 | static int g_scr_w = 0, g_scr_h = 0; | ||
| 43 | |||
| 44 | static void s_color(int r, int g, int b, int a) { | ||
| 45 | using Fn = void (*)(void *, int, int, int, int); | ||
| 46 | vfunc<Fn>(g_surface, GS_DrawSetColor)(g_surface, r, g, b, a); | ||
| 47 | } | ||
| 48 | static void s_fill(int x0, int y0, int x1, int y1) { | ||
| 49 | using Fn = void (*)(void *, int, int, int, int); | ||
| 50 | vfunc<Fn>(g_surface, GS_DrawFilledRect)(g_surface, x0, y0, x1, y1); | ||
| 51 | } | ||
| 52 | static void s_outline(int x0, int y0, int x1, int y1) { | ||
| 53 | using Fn = void (*)(void *, int, int, int, int); | ||
| 54 | vfunc<Fn>(g_surface, GS_DrawOutlinedRect)(g_surface, x0, y0, x1, y1); | ||
| 55 | } | ||
| 56 | static void s_line(int x0, int y0, int x1, int y1) { | ||
| 57 | using Fn = void (*)(void *, int, int, int, int); | ||
| 58 | vfunc<Fn>(g_surface, GS_DrawLine)(g_surface, x0, y0, x1, y1); | ||
| 59 | } | ||
| 60 | static void s_text_font(unsigned long f) { | ||
| 61 | using Fn = void (*)(void *, unsigned long); | ||
| 62 | vfunc<Fn>(g_surface, GS_DrawSetTextFont)(g_surface, f); | ||
| 63 | } | ||
| 64 | static void s_text_color(int r, int g, int b, int a) { | ||
| 65 | using Fn = void (*)(void *, int, int, int, int); | ||
| 66 | vfunc<Fn>(g_surface, GS_DrawSetTextColor)(g_surface, r, g, b, a); | ||
| 67 | } | ||
| 68 | static void s_text_pos(int x, int y) { | ||
| 69 | using Fn = void (*)(void *, int, int); | ||
| 70 | vfunc<Fn>(g_surface, GS_DrawSetTextPos)(g_surface, x, y); | ||
| 71 | } | ||
| 72 | static void s_print(const wchar_t *text, int n) { | ||
| 73 | using Fn = void (*)(void *, const wchar_t *, int, int); | ||
| 74 | vfunc<Fn>(g_surface, GS_DrawPrintText)(g_surface, text, n, 0); | ||
| 75 | } | ||
| 76 | static unsigned long s_create_font() { | ||
| 77 | using Fn = unsigned long (*)(void *); | ||
| 78 | return vfunc<Fn>(g_surface, GS_CreateFont)(g_surface); | ||
| 79 | } | ||
| 80 | static bool s_set_font(unsigned long font, const char *name, int tall, int weight, | ||
| 81 | int flags, int lo, int hi) { | ||
| 82 | using Fn = bool (*)(void *, unsigned long, const char *, int, int, int, int, | ||
| 83 | int, int, int); | ||
| 84 | return vfunc<Fn>(g_surface, GS_AddGlyphSet)(g_surface, font, name, tall, | ||
| 85 | weight, 0, 0, flags, lo, hi); | ||
| 86 | } | ||
| 87 | static void s_screen(int &w, int &h) { | ||
| 88 | using Fn = void (*)(void *, int &, int &); | ||
| 89 | vfunc<Fn>(g_surface, GS_GetScreenSize)(g_surface, w, h); | ||
| 90 | } | ||
| 91 | |||
| 92 | // --------------------------------------------------------------------------- | ||
| 93 | // SDL polling | ||
| 94 | // --------------------------------------------------------------------------- | ||
| 95 | |||
| 96 | using GetKeysFn = const unsigned char *(*)(int *); | ||
| 97 | using GetMouseFn = unsigned int (*)(int *, int *); | ||
| 98 | using ShowCursorFn = int (*)(int); | ||
| 99 | using SetRelativeFn = int (*)(int); | ||
| 100 | using GetRelativeFn = int (*)(void); | ||
| 101 | using AddWatchFn = int (*)(int (*)(void *, void *), void *); | ||
| 102 | using GetTicksFn = unsigned int (*)(void); | ||
| 103 | |||
| 104 | static GetKeysFn sd_get_keys = nullptr; | ||
| 105 | static GetMouseFn sd_get_mouse = nullptr; | ||
| 106 | static ShowCursorFn sd_show_cursor = nullptr; | ||
| 107 | static SetRelativeFn sd_set_relative = nullptr; | ||
| 108 | static GetRelativeFn sd_get_relative = nullptr; | ||
| 109 | static AddWatchFn sd_add_watch = nullptr; | ||
| 110 | static GetTicksFn sd_get_ticks = nullptr; | ||
| 111 | static bool g_sdl_ready = false; | ||
| 112 | static bool g_sdl_init_tried = false; | ||
| 113 | |||
| 114 | static std::atomic<int> g_wheel{0}; | ||
| 115 | static int sdl_event_watch(void *, void *event) { | ||
| 116 | if (!event) | ||
| 117 | return 0; | ||
| 118 | const unsigned char *p = (const unsigned char *)event; | ||
| 119 | unsigned int type = *(const unsigned int *)p; | ||
| 120 | if (type == 0x403) { // SDL_MOUSEWHEEL | ||
| 121 | int y = *(const int *)(p + 20); | ||
| 122 | g_wheel.fetch_add(y); | ||
| 123 | } | ||
| 124 | return 0; | ||
| 125 | } | ||
| 126 | |||
| 127 | static void gui_init_sdl() { | ||
| 128 | if (g_sdl_init_tried) | ||
| 129 | return; | ||
| 130 | g_sdl_init_tried = true; | ||
| 131 | sd_get_keys = (GetKeysFn)dlsym(RTLD_DEFAULT, "SDL_GetKeyboardState"); | ||
| 132 | sd_get_mouse = (GetMouseFn)dlsym(RTLD_DEFAULT, "SDL_GetMouseState"); | ||
| 133 | sd_show_cursor = (ShowCursorFn)dlsym(RTLD_DEFAULT, "SDL_ShowCursor"); | ||
| 134 | sd_set_relative = (SetRelativeFn)dlsym(RTLD_DEFAULT, "SDL_SetRelativeMouseMode"); | ||
| 135 | sd_get_relative = (GetRelativeFn)dlsym(RTLD_DEFAULT, "SDL_GetRelativeMouseMode"); | ||
| 136 | sd_add_watch = (AddWatchFn)dlsym(RTLD_DEFAULT, "SDL_AddEventWatch"); | ||
| 137 | sd_get_ticks = (GetTicksFn)dlsym(RTLD_DEFAULT, "SDL_GetTicks"); | ||
| 138 | g_sdl_ready = sd_get_keys && sd_get_mouse; | ||
| 139 | if (!g_sdl_ready) { | ||
| 140 | logf("gui: SDL polling unavailable"); | ||
| 141 | return; | ||
| 142 | } | ||
| 143 | if (sd_add_watch) | ||
| 144 | sd_add_watch(&sdl_event_watch, nullptr); | ||
| 145 | } | ||
| 146 | |||
| 147 | // --------------------------------------------------------------------------- | ||
| 148 | // Layout data (dummy shell) | ||
| 149 | // --------------------------------------------------------------------------- | ||
| 150 | |||
| 151 | struct Rect { | ||
| 152 | int x, y, w, h; | ||
| 153 | bool has(int px, int py) const { | ||
| 154 | return px >= x && py >= y && px < x + w && py < y + h; | ||
| 155 | } | ||
| 156 | }; | ||
| 157 | struct Col { | ||
| 158 | int r, g, b, a; | ||
| 159 | }; | ||
| 160 | static const Col C_BG{17, 19, 23, 248}; | ||
| 161 | static const Col C_PANEL{27, 30, 36, 248}; | ||
| 162 | static const Col C_SIDEBAR{21, 23, 28, 248}; | ||
| 163 | static const Col C_HEADER{36, 40, 48, 255}; | ||
| 164 | static const Col C_BORDER{70, 76, 88, 255}; | ||
| 165 | static const Col C_TEXT{229, 232, 238, 255}; | ||
| 166 | static const Col C_DIM{150, 156, 168, 255}; | ||
| 167 | static const Col C_ACCENT{255, 145, 64, 255}; | ||
| 168 | static const Col C_ACCENT_DIM{120, 70, 32, 255}; | ||
| 169 | static const Col C_GOOD{86, 210, 135, 255}; | ||
| 170 | |||
| 171 | enum CtrlKind { | ||
| 172 | CK_TOGGLE, | ||
| 173 | CK_SLIDER, | ||
| 174 | CK_BIND, | ||
| 175 | CK_SECTION, | ||
| 176 | CK_INFO, | ||
| 177 | CK_BUTTON | ||
| 178 | }; | ||
| 179 | |||
| 180 | struct Ctrl { | ||
| 181 | int kind; | ||
| 182 | const char *label; | ||
| 183 | const char *desc; | ||
| 184 | int *value; | ||
| 185 | int min, max; | ||
| 186 | }; | ||
| 187 | |||
| 188 | struct Card { | ||
| 189 | int cat; | ||
| 190 | const char *title; | ||
| 191 | const char *desc; | ||
| 192 | bool expanded; | ||
| 193 | bool active; | ||
| 194 | int bind; | ||
| 195 | int c0, n; | ||
| 196 | }; | ||
| 197 | |||
| 198 | static Ctrl g_ctrls[192]; | ||
| 199 | static int g_nctrl = 0; | ||
| 200 | static Card g_cards[40]; | ||
| 201 | static int g_ncards = 0; | ||
| 202 | static int g_vals[192]; | ||
| 203 | static bool g_demo_built = false; | ||
| 204 | |||
| 205 | static const char *g_cats[] = {"Movement", "Combat", "Visuals", "Weapons", | ||
| 206 | "System"}; | ||
| 207 | static constexpr int CAT_COUNT = 5; | ||
| 208 | |||
| 209 | static int add_card(int cat, const char *title, const char *desc, | ||
| 210 | bool expanded) { | ||
| 211 | Card &c = g_cards[g_ncards]; | ||
| 212 | c.cat = cat; | ||
| 213 | c.title = title; | ||
| 214 | c.desc = desc; | ||
| 215 | c.expanded = expanded; | ||
| 216 | c.active = true; | ||
| 217 | c.bind = 0; | ||
| 218 | c.c0 = g_nctrl; | ||
| 219 | c.n = 0; | ||
| 220 | return g_ncards++; | ||
| 221 | } | ||
| 222 | static void add_ctrl(int card, int kind, const char *label, const char *desc, | ||
| 223 | int min, int max, int init) { | ||
| 224 | int i = g_nctrl++; | ||
| 225 | g_vals[i] = init; | ||
| 226 | g_ctrls[i].kind = kind; | ||
| 227 | g_ctrls[i].label = label; | ||
| 228 | g_ctrls[i].desc = desc; | ||
| 229 | g_ctrls[i].value = &g_vals[i]; | ||
| 230 | g_ctrls[i].min = min; | ||
| 231 | g_ctrls[i].max = max; | ||
| 232 | g_cards[card].n++; | ||
| 233 | } | ||
| 234 | static void add_toggle(int card, const char *label, const char *desc, | ||
| 235 | int init) { | ||
| 236 | add_ctrl(card, CK_TOGGLE, label, desc, 0, 1, init); | ||
| 237 | } | ||
| 238 | static void add_slider(int card, const char *label, const char *desc, int min, | ||
| 239 | int max, int init) { | ||
| 240 | add_ctrl(card, CK_SLIDER, label, desc, min, max, init); | ||
| 241 | } | ||
| 242 | static void add_section(int card, const char *label) { | ||
| 243 | add_ctrl(card, CK_SECTION, label, nullptr, 0, 0, 0); | ||
| 244 | } | ||
| 245 | static void add_info(int card, const char *label) { | ||
| 246 | add_ctrl(card, CK_INFO, label, nullptr, 0, 0, 0); | ||
| 247 | } | ||
| 248 | static void add_button(int card, const char *label, const char *desc) { | ||
| 249 | add_ctrl(card, CK_BUTTON, label, desc, 0, 0, 0); | ||
| 250 | } | ||
| 251 | |||
| 252 | static void build_demo() { | ||
| 253 | if (g_demo_built) | ||
| 254 | return; | ||
| 255 | g_demo_built = true; | ||
| 256 | |||
| 257 | int bhop = add_card(0, "Bunnyhop", "Automatic jump on landing.", true); | ||
| 258 | add_toggle(bhop, "Uni-directional auto-strafe", | ||
| 259 | "Rotate airborne movement into the velocity.", 1); | ||
| 260 | |||
| 261 | int abh = add_card(0, "ABH", "Accelerate on the jump tick by facing away " | ||
| 262 | "from the velocity.", true); | ||
| 263 | (void)abh; | ||
| 264 | |||
| 265 | int jb = add_card(0, "Jumpbug", "Duck+jump when landing would hurt.", true); | ||
| 266 | add_toggle(jb, "Assist timing", | ||
| 267 | "Nudge the final command into the unduck window.", 0); | ||
| 268 | |||
| 269 | int aim = add_card(1, "Aimbot", "Smoothly snap to a hostile target.", true); | ||
| 270 | g_cards[aim].bind = 604; | ||
| 271 | add_toggle(aim, "Vischeck", "Require world line of sight.", 1); | ||
| 272 | add_slider(aim, "FOV", "Maximum angular distance from the crosshair.", 1, 180, | ||
| 273 | 60); | ||
| 274 | add_slider(aim, "Smoothness", "Maximum degrees per tick.", 1, 45, 5); | ||
| 275 | add_toggle(aim, "Show FOV circle", "Draw the acquisition cone on screen.", 1); | ||
| 276 | add_section(aim, "Bones"); | ||
| 277 | add_toggle(aim, "Head", "Aim at the head hitbox.", 1); | ||
| 278 | add_toggle(aim, "Torso", "Aim at the chest/stomach.", 0); | ||
| 279 | add_toggle(aim, "Left arm", "Include the left arm.", 0); | ||
| 280 | add_toggle(aim, "Right arm", "Include the right arm.", 0); | ||
| 281 | add_toggle(aim, "Left leg", "Include the left leg.", 0); | ||
| 282 | add_toggle(aim, "Right leg", "Include the right leg.", 0); | ||
| 283 | |||
| 284 | int sil = add_card(1, "Silent aim", "Fire at the target without turning " | ||
| 285 | "the rendered view.", true); | ||
| 286 | g_cards[sil].bind = 602; | ||
| 287 | add_slider(sil, "FOV", "Maximum angular distance from the crosshair.", 1, 180, | ||
| 288 | 60); | ||
| 289 | |||
| 290 | int tb = add_card(1, "Triggerbot", "Fire when the crosshair is on a hostile.", | ||
| 291 | true); | ||
| 292 | g_cards[tb].bind = 603; | ||
| 293 | |||
| 294 | int melee = add_card(1, "Melee killbot", "360-degree crowbar silent aim.", | ||
| 295 | true); | ||
| 296 | |||
| 297 | int esp = add_card(2, "ESP", "Draw hostile NPC information.", true); | ||
| 298 | add_toggle(esp, "Show neutral", "Include neutral wildlife.", 1); | ||
| 299 | add_toggle(esp, "Show healthbar", "Draw a health bar.", 1); | ||
| 300 | add_toggle(esp, "Show healthnum", "Draw the numeric health value.", 1); | ||
| 301 | add_toggle(esp, "Show name", "Draw the model name.", 1); | ||
| 302 | add_toggle(esp, "Show skeleton", "Draw the animated bone links.", 1); | ||
| 303 | add_toggle(esp, "Show box", "Draw a bounding box.", 1); | ||
| 304 | add_toggle(esp, "Show speed graph", "Draw the lower-center speed graph.", 1); | ||
| 305 | |||
| 306 | int spread = add_card(3, "Spread control", | ||
| 307 | "Choose a server shot seed that tightens the cone.", | ||
| 308 | true); | ||
| 309 | add_toggle(spread, "No spread", | ||
| 310 | "Override the FireBullets seed with the best offset.", 1); | ||
| 311 | |||
| 312 | int sys = add_card(4, "System", "Configuration and maintenance.", true); | ||
| 313 | add_info(sys, "Config: ~/.config/hl2hake/config"); | ||
| 314 | add_info(sys, "Build: GUI shell preview"); | ||
| 315 | add_button(sys, "Reload config", "Re-read the INI without restarting."); | ||
| 316 | add_button(sys, "Reset defaults", "Restore feature defaults; GUI layout kept."); | ||
| 317 | |||
| 318 | (void)g_cards[melee]; | ||
| 319 | } | ||
| 320 | |||
| 321 | // --------------------------------------------------------------------------- | ||
| 322 | // Window state / input | ||
| 323 | // --------------------------------------------------------------------------- | ||
| 324 | |||
| 325 | static bool g_open = false; | ||
| 326 | static bool g_was_open = false; | ||
| 327 | static bool g_dragging = false; | ||
| 328 | static int g_drag_dx = 0, g_drag_dy = 0; | ||
| 329 | static int g_win_x = 0, g_win_y = 0, g_win_w = 0, g_win_h = 0; | ||
| 330 | static float g_scale = 1.0f; | ||
| 331 | static bool g_positioned = false; | ||
| 332 | static int g_cat = 0; | ||
| 333 | static float g_scroll = 0.0f; | ||
| 334 | static float g_scroll_max = 0.0f; | ||
| 335 | static bool g_scroll_drag = false; | ||
| 336 | static int g_scroll_drag_dy = 0; | ||
| 337 | static float g_scroll_drag_start = 0.0f; | ||
| 338 | static int g_hover_slider = -1; | ||
| 339 | static int g_slider_drag = -1; | ||
| 340 | static int g_capture_card = -1; | ||
| 341 | static QAngle g_frozen{0, 0, 0}; | ||
| 342 | static int g_mouse_x = 0, g_mouse_y = 0; | ||
| 343 | static bool g_mouse_click = false; | ||
| 344 | static bool g_mouse_down = false; | ||
| 345 | static bool g_mouse_rclick = false; | ||
| 346 | static unsigned int g_prev_mask = 0; | ||
| 347 | static unsigned char g_prev_keys[512] = {}; | ||
| 348 | static bool g_prev_cursor = true; | ||
| 349 | static bool g_prev_relative = true; | ||
| 350 | static double g_hover_since = 0.0; | ||
| 351 | static const char *g_hover_desc = nullptr; | ||
| 352 | static const char *g_hover_prev = nullptr; | ||
| 353 | static int g_hover_x = 0, g_hover_y = 0; | ||
| 354 | static int g_hover_last_mx = -1, g_hover_last_my = -1; | ||
| 355 | |||
| 356 | static int S(float v) { return (int)(v * g_scale + 0.5f); } | ||
| 357 | static float now_sec() { | ||
| 358 | struct timespec ts; | ||
| 359 | clock_gettime(CLOCK_MONOTONIC, &ts); | ||
| 360 | return (float)ts.tv_sec + (float)ts.tv_nsec * 1e-9f; | ||
| 361 | } | ||
| 362 | |||
| 363 | static void ensure_fonts() { | ||
| 364 | if (g_font_body && fabsf(g_font_scale - g_scale) < 0.001f) | ||
| 365 | return; | ||
| 366 | g_font_scale = g_scale; | ||
| 367 | g_body_h = (int)(15.0f * g_scale + 0.5f); | ||
| 368 | g_title_h = (int)(20.0f * g_scale + 0.5f); | ||
| 369 | if (g_body_h < 11) g_body_h = 11; | ||
| 370 | if (g_title_h < 15) g_title_h = 15; | ||
| 371 | static const char *families[] = {"Liberation Sans", "DejaVu Sans", | ||
| 372 | "Noto Sans", "Tahoma", "Verdana", "Arial", | ||
| 373 | nullptr}; | ||
| 374 | if (!g_font_body) { | ||
| 375 | for (int i = 0; families[i] && !g_font_body; i++) { | ||
| 376 | unsigned long f = s_create_font(); | ||
| 377 | if (!f) | ||
| 378 | break; | ||
| 379 | if (s_set_font(f, families[i], g_body_h, 400, 0x400, 0x20, 0xFF)) | ||
| 380 | g_font_body = f; | ||
| 381 | } | ||
| 382 | } | ||
| 383 | if (!g_font_title) { | ||
| 384 | for (int i = 0; families[i] && !g_font_title; i++) { | ||
| 385 | unsigned long f = s_create_font(); | ||
| 386 | if (!f) | ||
| 387 | break; | ||
| 388 | if (s_set_font(f, families[i], g_title_h, 400, 0x400, 0x20, 0xFF)) | ||
| 389 | g_font_title = f; | ||
| 390 | } | ||
| 391 | } | ||
| 392 | if (!g_font_title) | ||
| 393 | g_font_title = g_font_body; | ||
| 394 | if (!g_font_body) | ||
| 395 | logf("gui: no usable body font"); | ||
| 396 | } | ||
| 397 | |||
| 398 | static int text_w(const char *s, int tall) { | ||
| 399 | if (!s) return 0; | ||
| 400 | return (int)(strlen(s) * tall * 0.52f + 0.5f); | ||
| 401 | } | ||
| 402 | |||
| 403 | static void draw_text_raw(int x, int y, const char *s, unsigned long font, | ||
| 404 | int tall) { | ||
| 405 | (void)tall; | ||
| 406 | if (!s || !font) | ||
| 407 | return; | ||
| 408 | wchar_t w[256]; | ||
| 409 | size_t n = mbstowcs(w, s, 255); | ||
| 410 | if (n == (size_t)-1) | ||
| 411 | return; | ||
| 412 | w[n] = 0; | ||
| 413 | s_text_font(font); | ||
| 414 | s_text_pos(x, y); | ||
| 415 | s_print(w, (int)n); | ||
| 416 | } | ||
| 417 | |||
| 418 | static void text_in(const Rect &clip, int x, int y, const char *s, | ||
| 419 | unsigned long font, int tall, int max_w, const Col &c) { | ||
| 420 | if (!s || !font) | ||
| 421 | return; | ||
| 422 | if (y < clip.y || y + tall > clip.y + clip.h) | ||
| 423 | return; | ||
| 424 | if (x >= clip.x + clip.w) | ||
| 425 | return; | ||
| 426 | if (x + text_w(s, tall) > clip.x + clip.w) | ||
| 427 | x = clip.x + clip.w - text_w(s, tall); | ||
| 428 | if (max_w > 0 && text_w(s, tall) > max_w) { | ||
| 429 | char buf[256]; | ||
| 430 | snprintf(buf, sizeof(buf), "%s", s); | ||
| 431 | int n = (int)strlen(buf); | ||
| 432 | while (n > 3 && text_w(buf, tall) > max_w) | ||
| 433 | buf[--n] = 0; | ||
| 434 | if (n > 3 && n < (int)sizeof(buf) - 3) { | ||
| 435 | buf[n++] = '.'; | ||
| 436 | buf[n++] = '.'; | ||
| 437 | buf[n++] = '.'; | ||
| 438 | buf[n] = 0; | ||
| 439 | } | ||
| 440 | s_text_color(c.r, c.g, c.b, c.a); | ||
| 441 | draw_text_raw(x, y, buf, font, tall); | ||
| 442 | return; | ||
| 443 | } | ||
| 444 | s_text_color(c.r, c.g, c.b, c.a); | ||
| 445 | draw_text_raw(x, y, s, font, tall); | ||
| 446 | } | ||
| 447 | |||
| 448 | static Rect clip_rect(const Rect &r, const Rect &clip) { | ||
| 449 | Rect o = r; | ||
| 450 | int x0 = r.x > clip.x ? r.x : clip.x; | ||
| 451 | int y0 = r.y > clip.y ? r.y : clip.y; | ||
| 452 | int x1 = (r.x + r.w) < (clip.x + clip.w) ? (r.x + r.w) : (clip.x + clip.w); | ||
| 453 | int y1 = (r.y + r.h) < (clip.y + clip.h) ? (r.y + r.h) : (clip.y + clip.h); | ||
| 454 | o.x = x0; | ||
| 455 | o.y = y0; | ||
| 456 | o.w = x1 - x0; | ||
| 457 | o.h = y1 - y0; | ||
| 458 | return o; | ||
| 459 | } | ||
| 460 | static void fill(const Rect &r, const Col &c, const Rect *clip = nullptr) { | ||
| 461 | if (r.w <= 0 || r.h <= 0) | ||
| 462 | return; | ||
| 463 | Rect q = clip ? clip_rect(r, *clip) : r; | ||
| 464 | if (q.w <= 0 || q.h <= 0) | ||
| 465 | return; | ||
| 466 | s_color(c.r, c.g, c.b, c.a); | ||
| 467 | s_fill(q.x, q.y, q.x + q.w, q.y + q.h); | ||
| 468 | } | ||
| 469 | static void outline(const Rect &r, const Col &c, const Rect *clip = nullptr) { | ||
| 470 | if (r.w <= 0 || r.h <= 0) | ||
| 471 | return; | ||
| 472 | Rect q = clip ? clip_rect(r, *clip) : r; | ||
| 473 | if (q.w <= 0 || q.h <= 0) | ||
| 474 | return; | ||
| 475 | s_color(c.r, c.g, c.b, c.a); | ||
| 476 | s_outline(q.x, q.y, q.x + q.w, q.y + q.h); | ||
| 477 | } | ||
| 478 | static void line(int x0, int y0, int x1, int y1, const Col &c) { | ||
| 479 | s_color(c.r, c.g, c.b, c.a); | ||
| 480 | s_line(x0, y0, x1, y1); | ||
| 481 | } | ||
| 482 | |||
| 483 | static const char *bind_label(int b) { | ||
| 484 | switch (b) { | ||
| 485 | case 0: return "None"; | ||
| 486 | case 600: return "MouseL"; | ||
| 487 | case 601: return "MouseR"; | ||
| 488 | case 602: return "MouseM"; | ||
| 489 | case 603: return "Mouse4"; | ||
| 490 | case 604: return "Mouse5"; | ||
| 491 | case 41: return "Esc"; | ||
| 492 | case 73: return "Insert"; | ||
| 493 | case 59: return "F2"; | ||
| 494 | default: return "Key"; | ||
| 495 | } | ||
| 496 | } | ||
| 497 | |||
| 498 | static void layout() { | ||
| 499 | int w = g_scr_w, h = g_scr_h; | ||
| 500 | if (g_surface) { | ||
| 501 | s_screen(w, h); | ||
| 502 | if (w > 0) g_scr_w = w; | ||
| 503 | if (h > 0) g_scr_h = h; | ||
| 504 | } | ||
| 505 | w = g_scr_w; | ||
| 506 | h = g_scr_h; | ||
| 507 | if (w <= 0 || h <= 0) | ||
| 508 | return; | ||
| 509 | float sc = fminf((float)w / 1920.0f, (float)h / 1080.0f); | ||
| 510 | if (sc < 0.75f) sc = 0.75f; | ||
| 511 | if (sc > 1.5f) sc = 1.5f; | ||
| 512 | g_scale = sc; | ||
| 513 | int nw = S(960.0f), nh = S(620.0f); | ||
| 514 | if (!g_positioned || nw != g_win_w || nh != g_win_h) { | ||
| 515 | if (!g_positioned) { | ||
| 516 | g_win_x = (w - nw) / 2; | ||
| 517 | g_win_y = (h - nh) / 2; | ||
| 518 | g_positioned = true; | ||
| 519 | } | ||
| 520 | g_win_w = nw; | ||
| 521 | g_win_h = nh; | ||
| 522 | } | ||
| 523 | if (g_win_x < 0) g_win_x = 0; | ||
| 524 | if (g_win_y < 0) g_win_y = 0; | ||
| 525 | if (g_win_x + g_win_w > w) g_win_x = w - g_win_w; | ||
| 526 | if (g_win_y + g_win_h > h) g_win_y = h - g_win_h; | ||
| 527 | } | ||
| 528 | |||
| 529 | static int row_h(const Ctrl &c) { | ||
| 530 | switch (c.kind) { | ||
| 531 | case CK_SECTION: return S(24.0f); | ||
| 532 | case CK_INFO: return S(20.0f); | ||
| 533 | case CK_BUTTON: return S(32.0f); | ||
| 534 | default: return S(27.0f); | ||
| 535 | } | ||
| 536 | } | ||
| 537 | |||
| 538 | static int body_h(const Card &c) { | ||
| 539 | if (!c.expanded) | ||
| 540 | return 0; | ||
| 541 | int h = S(6.0f); | ||
| 542 | for (int i = 0; i < c.n; i++) | ||
| 543 | h += row_h(g_ctrls[c.c0 + i]); | ||
| 544 | return h + S(6.0f); | ||
| 545 | } | ||
| 546 | |||
| 547 | static int card_h(const Card &c) { return S(36.0f) + body_h(c) + S(8.0f); } | ||
| 548 | |||
| 549 | static Rect card_rect(int index, int y) { | ||
| 550 | Card &c = g_cards[index]; | ||
| 551 | int x = g_win_x + S(190.0f) + S(10.0f); | ||
| 552 | int w = g_win_w - S(190.0f) - S(28.0f); | ||
| 553 | return Rect{x, y, w, card_h(c)}; | ||
| 554 | } | ||
| 555 | |||
| 556 | static bool card_in_category(int i) { return g_cards[i].cat == g_cat; } | ||
| 557 | |||
| 558 | static int content_height() { | ||
| 559 | int h = S(8.0f); | ||
| 560 | for (int i = 0; i < g_ncards; i++) | ||
| 561 | if (card_in_category(i)) | ||
| 562 | h += card_h(g_cards[i]); | ||
| 563 | return h; | ||
| 564 | } | ||
| 565 | |||
| 566 | // --------------------------------------------------------------------------- | ||
| 567 | // Input | ||
| 568 | // --------------------------------------------------------------------------- | ||
| 569 | |||
| 570 | static void close_gui() { | ||
| 571 | if (!g_open) | ||
| 572 | return; | ||
| 573 | g_open = false; | ||
| 574 | g_capture_card = -1; | ||
| 575 | g_dragging = false; | ||
| 576 | g_scroll_drag = false; | ||
| 577 | g_slider_drag = -1; | ||
| 578 | if (g_sdl_ready) { | ||
| 579 | if (sd_set_relative) | ||
| 580 | sd_set_relative(g_prev_relative ? 1 : 0); | ||
| 581 | if (sd_show_cursor) | ||
| 582 | sd_show_cursor(g_prev_cursor ? 1 : 0); | ||
| 583 | } | ||
| 584 | logf("gui: closed"); | ||
| 585 | } | ||
| 586 | |||
| 587 | static void open_gui() { | ||
| 588 | if (g_open) | ||
| 589 | return; | ||
| 590 | int w = 0, h = 0; | ||
| 591 | if (g_surface) { | ||
| 592 | s_screen(w, h); | ||
| 593 | if (w > 0) g_scr_w = w; | ||
| 594 | if (h > 0) g_scr_h = h; | ||
| 595 | } | ||
| 596 | if (!g_iface.engine) { | ||
| 597 | g_frozen = QAngle{0, 0, 0}; | ||
| 598 | } else { | ||
| 599 | using GetViewFn = void (*)(void *, QAngle *); | ||
| 600 | QAngle v{0, 0, 0}; | ||
| 601 | vfunc<GetViewFn>(g_iface.engine, 19)(g_iface.engine, &v); | ||
| 602 | g_frozen = v; | ||
| 603 | } | ||
| 604 | if (g_sdl_ready) { | ||
| 605 | if (sd_get_relative) | ||
| 606 | g_prev_relative = sd_get_relative() != 0; | ||
| 607 | if (sd_show_cursor) | ||
| 608 | g_prev_cursor = sd_show_cursor(-1) != 0; | ||
| 609 | if (sd_set_relative) | ||
| 610 | sd_set_relative(0); | ||
| 611 | if (sd_show_cursor) | ||
| 612 | sd_show_cursor(1); | ||
| 613 | } | ||
| 614 | g_open = true; | ||
| 615 | g_capture_card = -1; | ||
| 616 | g_wheel.exchange(0); | ||
| 617 | g_scroll = 0.0f; | ||
| 618 | g_cat = 0; | ||
| 619 | menu_force_close(); | ||
| 620 | logf("gui: opened"); | ||
| 621 | } | ||
| 622 | |||
| 623 | static void update_mouse() { | ||
| 624 | if (!g_sdl_ready) | ||
| 625 | return; | ||
| 626 | int mx = 0, my = 0; | ||
| 627 | unsigned int mask = sd_get_mouse(&mx, &my); | ||
| 628 | g_mouse_x = mx; | ||
| 629 | g_mouse_y = my; | ||
| 630 | g_mouse_down = (mask & 1u) != 0; | ||
| 631 | g_mouse_click = g_mouse_down && !(g_prev_mask & 1u); | ||
| 632 | g_mouse_rclick = ((mask & 2u) != 0) && !(g_prev_mask & 2u); | ||
| 633 | if (g_mouse_x < 0) g_mouse_x = 0; | ||
| 634 | if (g_mouse_y < 0) g_mouse_y = 0; | ||
| 635 | g_prev_mask = mask; | ||
| 636 | } | ||
| 637 | |||
| 638 | static void handle_capture(const unsigned char *keys) { | ||
| 639 | if (g_capture_card < 0) | ||
| 640 | return; | ||
| 641 | if (!g_open) { | ||
| 642 | g_capture_card = -1; | ||
| 643 | return; | ||
| 644 | } | ||
| 645 | if (keys[41]) { // Esc cancels | ||
| 646 | g_capture_card = -1; | ||
| 647 | return; | ||
| 648 | } | ||
| 649 | if (g_mouse_click) { | ||
| 650 | g_cards[g_capture_card].bind = 600; | ||
| 651 | g_capture_card = -1; | ||
| 652 | return; | ||
| 653 | } | ||
| 654 | if (g_mouse_rclick) { | ||
| 655 | g_cards[g_capture_card].bind = 601; | ||
| 656 | g_capture_card = -1; | ||
| 657 | return; | ||
| 658 | } | ||
| 659 | for (int k = 0; k < 512; k++) { | ||
| 660 | if (!keys[k] || g_prev_keys[k]) | ||
| 661 | continue; | ||
| 662 | if (k == 73 || k == 59) // Insert reserved; F2 closes the preview | ||
| 663 | continue; | ||
| 664 | g_cards[g_capture_card].bind = k; | ||
| 665 | g_capture_card = -1; | ||
| 666 | return; | ||
| 667 | } | ||
| 668 | } | ||
| 669 | |||
| 670 | static void handle_mouse_click() { | ||
| 671 | if (!g_mouse_click) | ||
| 672 | return; | ||
| 673 | int mx = g_mouse_x, my = g_mouse_y; | ||
| 674 | |||
| 675 | // title bar: drag or close | ||
| 676 | Rect title{g_win_x, g_win_y, g_win_w, S(38.0f)}; | ||
| 677 | Rect close{g_win_x + g_win_w - S(38.0f), g_win_y, S(38.0f), S(38.0f)}; | ||
| 678 | if (title.has(mx, my)) { | ||
| 679 | if (close.has(mx, my)) { | ||
| 680 | close_gui(); | ||
| 681 | return; | ||
| 682 | } | ||
| 683 | g_dragging = true; | ||
| 684 | g_drag_dx = mx - g_win_x; | ||
| 685 | g_drag_dy = my - g_win_y; | ||
| 686 | return; | ||
| 687 | } | ||
| 688 | |||
| 689 | // sidebar | ||
| 690 | int side_x = g_win_x; | ||
| 691 | int side_y = g_win_y + S(38.0f); | ||
| 692 | int side_w = S(190.0f); | ||
| 693 | int cat_h = S(34.0f); | ||
| 694 | for (int i = 0; i < CAT_COUNT; i++) { | ||
| 695 | Rect r{side_x, side_y + i * cat_h, side_w, cat_h}; | ||
| 696 | if (r.has(mx, my)) { | ||
| 697 | g_cat = i; | ||
| 698 | g_scroll = 0.0f; | ||
| 699 | return; | ||
| 700 | } | ||
| 701 | } | ||
| 702 | |||
| 703 | // content | ||
| 704 | Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), | ||
| 705 | g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; | ||
| 706 | if (!content.has(mx, my)) | ||
| 707 | return; | ||
| 708 | |||
| 709 | // scrollbar | ||
| 710 | int sb_x = content.x + content.w - S(8.0f); | ||
| 711 | if (mx >= sb_x) { | ||
| 712 | if (g_scroll_max > 0.5f) { | ||
| 713 | int thumb_h = (int)((float)content.h * (float)content.h / | ||
| 714 | (float)(content.h + (int)g_scroll_max)); | ||
| 715 | if (thumb_h < S(24.0f)) thumb_h = S(24.0f); | ||
| 716 | int travel = content.h - thumb_h; | ||
| 717 | int thumb_y = content.y + (int)((g_scroll_max > 0.0f) | ||
| 718 | ? (g_scroll / g_scroll_max) * travel | ||
| 719 | : 0); | ||
| 720 | if (my >= thumb_y && my < thumb_y + thumb_h) { | ||
| 721 | g_scroll_drag = true; | ||
| 722 | g_scroll_drag_dy = my - thumb_y; | ||
| 723 | g_scroll_drag_start = g_scroll; | ||
| 724 | } else { | ||
| 725 | float t = (float)(my - content.y) / (float)content.h; | ||
| 726 | g_scroll = t * g_scroll_max; | ||
| 727 | } | ||
| 728 | } | ||
| 729 | return; | ||
| 730 | } | ||
| 731 | |||
| 732 | int y = content.y - (int)g_scroll + S(8.0f); | ||
| 733 | for (int i = 0; i < g_ncards; i++) { | ||
| 734 | if (!card_in_category(i)) | ||
| 735 | continue; | ||
| 736 | Card &c = g_cards[i]; | ||
| 737 | Rect cr = card_rect(i, y); | ||
| 738 | int ch = card_h(c); | ||
| 739 | if (my >= cr.y && my < cr.y + ch) { | ||
| 740 | // header | ||
| 741 | if (my < cr.y + S(36.0f)) { | ||
| 742 | if (c.cat != 4) { | ||
| 743 | Rect active{cr.x + cr.w - S(150.0f), cr.y + S(9.0f), S(18.0f), | ||
| 744 | S(18.0f)}; | ||
| 745 | Rect bind{cr.x + cr.w - S(112.0f), cr.y + S(7.0f), S(100.0f), | ||
| 746 | S(22.0f)}; | ||
| 747 | if (active.has(mx, my)) { | ||
| 748 | c.active = !c.active; | ||
| 749 | return; | ||
| 750 | } | ||
| 751 | if (bind.has(mx, my)) { | ||
| 752 | if (mx >= bind.x + bind.w - S(18.0f)) { | ||
| 753 | c.bind = 0; | ||
| 754 | return; | ||
| 755 | } | ||
| 756 | g_capture_card = i; | ||
| 757 | return; | ||
| 758 | } | ||
| 759 | } | ||
| 760 | c.expanded = !c.expanded; | ||
| 761 | return; | ||
| 762 | } | ||
| 763 | // body | ||
| 764 | int by = cr.y + S(36.0f) + S(6.0f); | ||
| 765 | for (int j = 0; j < c.n; j++) { | ||
| 766 | Ctrl &ct = g_ctrls[c.c0 + j]; | ||
| 767 | int rh = row_h(ct); | ||
| 768 | if (my < by || my >= by + rh) { | ||
| 769 | by += rh; | ||
| 770 | continue; | ||
| 771 | } | ||
| 772 | if (ct.kind == CK_TOGGLE) { | ||
| 773 | *ct.value = !*ct.value; | ||
| 774 | return; | ||
| 775 | } | ||
| 776 | if (ct.kind == CK_SLIDER) { | ||
| 777 | Rect track{cr.x + S(16.0f) + S(230.0f), by + S(9.0f), | ||
| 778 | cr.w - S(16.0f) - S(230.0f) - S(64.0f), S(8.0f)}; | ||
| 779 | if (mx >= track.x && mx < track.x + track.w) { | ||
| 780 | float t = (float)(mx - track.x) / (float)track.w; | ||
| 781 | int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); | ||
| 782 | if (v < ct.min) v = ct.min; | ||
| 783 | if (v > ct.max) v = ct.max; | ||
| 784 | *ct.value = v; | ||
| 785 | g_slider_drag = c.c0 + j; | ||
| 786 | } | ||
| 787 | return; | ||
| 788 | } | ||
| 789 | if (ct.kind == CK_BUTTON) { | ||
| 790 | Rect b{cr.x + cr.w - S(130.0f), by + S(3.0f), S(114.0f), | ||
| 791 | S(26.0f)}; | ||
| 792 | if (b.has(mx, my)) { | ||
| 793 | logf("gui: dummy action %s", ct.label ? ct.label : "?"); | ||
| 794 | return; | ||
| 795 | } | ||
| 796 | } | ||
| 797 | return; | ||
| 798 | } | ||
| 799 | } | ||
| 800 | y += ch; | ||
| 801 | } | ||
| 802 | } | ||
| 803 | |||
| 804 | static void handle_hover() { | ||
| 805 | g_hover_desc = nullptr; | ||
| 806 | g_hover_slider = -1; | ||
| 807 | int mx = g_mouse_x, my = g_mouse_y; | ||
| 808 | Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), | ||
| 809 | g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; | ||
| 810 | if (!content.has(mx, my)) | ||
| 811 | return; | ||
| 812 | int y = content.y - (int)g_scroll + S(8.0f); | ||
| 813 | for (int i = 0; i < g_ncards; i++) { | ||
| 814 | if (!card_in_category(i)) | ||
| 815 | continue; | ||
| 816 | Card &c = g_cards[i]; | ||
| 817 | Rect cr = card_rect(i, y); | ||
| 818 | int ch = card_h(c); | ||
| 819 | if (my >= cr.y && my < cr.y + ch) { | ||
| 820 | if (my < cr.y + S(36.0f)) { | ||
| 821 | g_hover_desc = c.desc; | ||
| 822 | return; | ||
| 823 | } | ||
| 824 | int by = cr.y + S(36.0f) + S(6.0f); | ||
| 825 | for (int j = 0; j < c.n; j++) { | ||
| 826 | Ctrl &ct = g_ctrls[c.c0 + j]; | ||
| 827 | int rh = row_h(ct); | ||
| 828 | if (my >= by && my < by + rh) { | ||
| 829 | g_hover_desc = ct.desc; | ||
| 830 | if (ct.kind == CK_SLIDER) | ||
| 831 | g_hover_slider = c.c0 + j; | ||
| 832 | return; | ||
| 833 | } | ||
| 834 | by += rh; | ||
| 835 | } | ||
| 836 | return; | ||
| 837 | } | ||
| 838 | y += ch; | ||
| 839 | } | ||
| 840 | } | ||
| 841 | |||
| 842 | void gui_handle_input() { | ||
| 843 | gui_init_sdl(); | ||
| 844 | if (!g_surface) | ||
| 845 | return; | ||
| 846 | build_demo(); | ||
| 847 | layout(); | ||
| 848 | if (g_scr_w <= 0 || g_scr_h <= 0) | ||
| 849 | return; | ||
| 850 | ensure_fonts(); | ||
| 851 | if (!g_sdl_ready) | ||
| 852 | return; | ||
| 853 | |||
| 854 | int nk = 0; | ||
| 855 | const unsigned char *keys = sd_get_keys(&nk); | ||
| 856 | if (!keys) | ||
| 857 | return; | ||
| 858 | if (nk > 512) nk = 512; | ||
| 859 | update_mouse(); | ||
| 860 | |||
| 861 | bool f2 = (nk > 59) && keys[59]; | ||
| 862 | bool f2_edge = f2 && !g_prev_keys[59]; | ||
| 863 | bool esc = (nk > 41) && keys[41]; | ||
| 864 | bool esc_edge = esc && !g_prev_keys[41]; | ||
| 865 | bool left = (nk > 80) && keys[80]; | ||
| 866 | bool right = (nk > 79) && keys[79]; | ||
| 867 | bool left_edge = left && !g_prev_keys[80]; | ||
| 868 | bool right_edge = right && !g_prev_keys[79]; | ||
| 869 | |||
| 870 | if (f2_edge) { | ||
| 871 | if (g_open) | ||
| 872 | close_gui(); | ||
| 873 | else | ||
| 874 | open_gui(); | ||
| 875 | } | ||
| 876 | if (!g_open) { | ||
| 877 | memcpy(g_prev_keys, keys, nk); | ||
| 878 | g_was_open = false; | ||
| 879 | return; | ||
| 880 | } | ||
| 881 | |||
| 882 | if (esc_edge) { | ||
| 883 | if (g_capture_card >= 0) | ||
| 884 | g_capture_card = -1; | ||
| 885 | else | ||
| 886 | close_gui(); | ||
| 887 | } | ||
| 888 | |||
| 889 | // Keep the native cursor free even if the game re-grabs it. | ||
| 890 | if (sd_set_relative) | ||
| 891 | sd_set_relative(0); | ||
| 892 | if (sd_show_cursor) | ||
| 893 | sd_show_cursor(1); | ||
| 894 | |||
| 895 | handle_capture(keys); | ||
| 896 | |||
| 897 | if (g_dragging) { | ||
| 898 | if (!g_mouse_down) { | ||
| 899 | g_dragging = false; | ||
| 900 | } else { | ||
| 901 | g_win_x = g_mouse_x - g_drag_dx; | ||
| 902 | g_win_y = g_mouse_y - g_drag_dy; | ||
| 903 | if (g_win_x < 0) g_win_x = 0; | ||
| 904 | if (g_win_y < 0) g_win_y = 0; | ||
| 905 | if (g_win_x + g_win_w > g_scr_w) g_win_x = g_scr_w - g_win_w; | ||
| 906 | if (g_win_y + g_win_h > g_scr_h) g_win_y = g_scr_h - g_win_h; | ||
| 907 | } | ||
| 908 | } else { | ||
| 909 | handle_mouse_click(); | ||
| 910 | } | ||
| 911 | |||
| 912 | if (g_slider_drag >= 0) { | ||
| 913 | if (!g_mouse_down) { | ||
| 914 | g_slider_drag = -1; | ||
| 915 | } else { | ||
| 916 | Ctrl &ct = g_ctrls[g_slider_drag]; | ||
| 917 | Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), | ||
| 918 | g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; | ||
| 919 | int x0 = g_win_x + S(190.0f) + S(16.0f) + S(230.0f); | ||
| 920 | int x1 = g_win_x + g_win_w - S(28.0f) - S(64.0f); | ||
| 921 | (void)content; | ||
| 922 | float t = (float)(g_mouse_x - x0) / (float)(x1 - x0); | ||
| 923 | int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); | ||
| 924 | if (v < ct.min) v = ct.min; | ||
| 925 | if (v > ct.max) v = ct.max; | ||
| 926 | *ct.value = v; | ||
| 927 | } | ||
| 928 | } | ||
| 929 | |||
| 930 | if (g_scroll_drag) { | ||
| 931 | if (!g_mouse_down) { | ||
| 932 | g_scroll_drag = false; | ||
| 933 | } else { | ||
| 934 | Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), | ||
| 935 | g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; | ||
| 936 | int thumb_h = (int)((float)content.h * (float)content.h / | ||
| 937 | (float)(content.h + (int)g_scroll_max)); | ||
| 938 | if (thumb_h < S(24.0f)) thumb_h = S(24.0f); | ||
| 939 | int travel = content.h - thumb_h; | ||
| 940 | if (travel > 0) { | ||
| 941 | float t = (float)(g_mouse_y - content.y - g_scroll_drag_dy) / | ||
| 942 | (float)travel; | ||
| 943 | g_scroll = t * g_scroll_max; | ||
| 944 | } | ||
| 945 | } | ||
| 946 | } | ||
| 947 | |||
| 948 | int wheel = g_wheel.exchange(0); | ||
| 949 | if (wheel != 0) | ||
| 950 | g_scroll -= wheel * S(44.0f); | ||
| 951 | |||
| 952 | if (left_edge || right_edge) { | ||
| 953 | if (g_hover_slider >= 0) { | ||
| 954 | Ctrl &ct = g_ctrls[g_hover_slider]; | ||
| 955 | *ct.value += right_edge ? 1 : -1; | ||
| 956 | if (*ct.value < ct.min) *ct.value = ct.min; | ||
| 957 | if (*ct.value > ct.max) *ct.value = ct.max; | ||
| 958 | } | ||
| 959 | } | ||
| 960 | |||
| 961 | int total = content_height(); | ||
| 962 | Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), | ||
| 963 | g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; | ||
| 964 | g_scroll_max = (float)(total + S(8.0f) - content.h); | ||
| 965 | if (g_scroll_max < 0.0f) g_scroll_max = 0.0f; | ||
| 966 | if (g_scroll < 0.0f) g_scroll = 0.0f; | ||
| 967 | if (g_scroll > g_scroll_max) g_scroll = g_scroll_max; | ||
| 968 | |||
| 969 | handle_hover(); | ||
| 970 | { | ||
| 971 | double now = now_sec(); | ||
| 972 | int dx = abs(g_mouse_x - g_hover_last_mx); | ||
| 973 | int dy = abs(g_mouse_y - g_hover_last_my); | ||
| 974 | if (g_hover_desc != g_hover_prev || dx > 2 || dy > 2) { | ||
| 975 | g_hover_prev = g_hover_desc; | ||
| 976 | g_hover_last_mx = g_mouse_x; | ||
| 977 | g_hover_last_my = g_mouse_y; | ||
| 978 | g_hover_since = now; | ||
| 979 | g_hover_x = g_mouse_x; | ||
| 980 | g_hover_y = g_mouse_y; | ||
| 981 | } | ||
| 982 | } | ||
| 983 | |||
| 984 | memcpy(g_prev_keys, keys, nk); | ||
| 985 | g_was_open = true; | ||
| 986 | } | ||
| 987 | |||
| 988 | // --------------------------------------------------------------------------- | ||
| 989 | // Draw | ||
| 990 | // --------------------------------------------------------------------------- | ||
| 991 | |||
| 992 | static void draw_checkbox(int x, int y, bool on) { | ||
| 993 | Rect box{x, y, S(15.0f), S(15.0f)}; | ||
| 994 | fill(box, on ? C_ACCENT : Col{30, 34, 42, 255}); | ||
| 995 | outline(box, C_BORDER); | ||
| 996 | if (on) { | ||
| 997 | line(x + S(3.0f), y + S(8.0f), x + S(7.0f), y + S(12.0f), C_BG); | ||
| 998 | line(x + S(7.0f), y + S(12.0f), x + S(13.0f), y + S(4.0f), C_BG); | ||
| 999 | } | ||
| 1000 | } | ||
| 1001 | |||
| 1002 | static void draw_slider(const Rect &clip, const Rect &cr, int y, const Ctrl &ct) { | ||
| 1003 | int track_x = cr.x + S(16.0f) + S(230.0f); | ||
| 1004 | int track_w = cr.w - S(16.0f) - S(230.0f) - S(64.0f); | ||
| 1005 | if (track_w < S(30.0f)) track_w = S(30.0f); | ||
| 1006 | Rect track{track_x, y + S(9.0f), track_w, S(8.0f)}; | ||
| 1007 | fill(track, Col{38, 42, 50, 255}, &clip); | ||
| 1008 | float t = (ct.max > ct.min) | ||
| 1009 | ? (float)(*ct.value - ct.min) / (float)(ct.max - ct.min) | ||
| 1010 | : 0.0f; | ||
| 1011 | int fw = (int)(track_w * t + 0.5f); | ||
| 1012 | fill(Rect{track.x, track.y, fw, track.h}, C_ACCENT_DIM, &clip); | ||
| 1013 | int kx = track.x + fw - S(4.0f); | ||
| 1014 | fill(Rect{kx, track.y - S(4.0f), S(8.0f), S(16.0f)}, C_ACCENT, &clip); | ||
| 1015 | char val[32]; | ||
| 1016 | snprintf(val, sizeof(val), "%d", *ct.value); | ||
| 1017 | text_in(clip, cr.x + cr.w - S(52.0f), y + S(4.0f), val, g_font_body, g_body_h, | ||
| 1018 | S(44.0f), C_TEXT); | ||
| 1019 | } | ||
| 1020 | |||
| 1021 | static void draw_card(const Rect &clip, int index, int y) { | ||
| 1022 | Card &c = g_cards[index]; | ||
| 1023 | Rect cr = card_rect(index, y); | ||
| 1024 | Rect bg = clip_rect(cr, clip); | ||
| 1025 | if (bg.w <= 0 || bg.h <= 0) | ||
| 1026 | return; | ||
| 1027 | fill(cr, C_PANEL, &clip); | ||
| 1028 | outline(cr, Col{52, 57, 67, 255}, &clip); | ||
| 1029 | Rect head{cr.x, cr.y, cr.w, S(36.0f)}; | ||
| 1030 | fill(head, C_HEADER, &clip); | ||
| 1031 | // expand arrow | ||
| 1032 | int ax = cr.x + S(10.0f), ay = cr.y + S(13.0f); | ||
| 1033 | if (c.expanded) { | ||
| 1034 | line(ax, ay, ax + S(4.0f), ay + S(5.0f), C_DIM); | ||
| 1035 | line(ax + S(4.0f), ay + S(5.0f), ax + S(8.0f), ay, C_DIM); | ||
| 1036 | } else { | ||
| 1037 | line(ax, ay, ax + S(5.0f), ay + S(4.0f), C_DIM); | ||
| 1038 | line(ax + S(5.0f), ay + S(4.0f), ax, ay + S(8.0f), C_DIM); | ||
| 1039 | } | ||
| 1040 | text_in(clip, cr.x + S(24.0f), cr.y + S(8.0f), c.title, g_font_title, | ||
| 1041 | g_title_h, cr.w - S(180.0f), C_TEXT); | ||
| 1042 | if (c.cat != 4) { | ||
| 1043 | draw_checkbox(cr.x + cr.w - S(150.0f), cr.y + S(10.0f), c.active); | ||
| 1044 | text_in(clip, cr.x + cr.w - S(128.0f), cr.y + S(9.0f), | ||
| 1045 | c.active ? "On" : "Off", g_font_body, g_body_h, S(30.0f), | ||
| 1046 | c.active ? C_GOOD : C_DIM); | ||
| 1047 | Rect bind{cr.x + cr.w - S(112.0f), cr.y + S(7.0f), S(100.0f), S(22.0f)}; | ||
| 1048 | fill(bind, Col{45, 50, 60, 255}, &clip); | ||
| 1049 | outline(bind, C_BORDER, &clip); | ||
| 1050 | const char *bl = (g_capture_card == index) ? "Press..." : bind_label(c.bind); | ||
| 1051 | text_in(clip, bind.x + S(7.0f), bind.y + S(3.0f), bl, g_font_body, g_body_h, | ||
| 1052 | bind.w - S(22.0f), C_TEXT); | ||
| 1053 | text_in(clip, bind.x + bind.w - S(16.0f), bind.y + S(3.0f), "x", | ||
| 1054 | g_font_body, g_body_h, S(10.0f), C_DIM); | ||
| 1055 | } | ||
| 1056 | if (!c.expanded) | ||
| 1057 | return; | ||
| 1058 | |||
| 1059 | int by = cr.y + S(36.0f) + S(6.0f); | ||
| 1060 | for (int j = 0; j < c.n; j++) { | ||
| 1061 | Ctrl &ct = g_ctrls[c.c0 + j]; | ||
| 1062 | int rh = row_h(ct); | ||
| 1063 | if (ct.kind == CK_SECTION) { | ||
| 1064 | text_in(clip, cr.x + S(16.0f), by + S(3.0f), ct.label, g_font_body, | ||
| 1065 | g_body_h, cr.w - S(24.0f), C_ACCENT); | ||
| 1066 | } else if (ct.kind == CK_INFO) { | ||
| 1067 | text_in(clip, cr.x + S(16.0f), by + S(1.0f), ct.label, g_font_body, | ||
| 1068 | g_body_h, cr.w - S(24.0f), C_DIM); | ||
| 1069 | } else if (ct.kind == CK_BUTTON) { | ||
| 1070 | Rect b{cr.x + cr.w - S(130.0f), by + S(3.0f), S(114.0f), S(26.0f)}; | ||
| 1071 | fill(b, Col{45, 50, 60, 255}, &clip); | ||
| 1072 | outline(b, C_BORDER, &clip); | ||
| 1073 | text_in(clip, b.x + S(10.0f), b.y + S(4.0f), ct.label, g_font_body, | ||
| 1074 | g_body_h, b.w - S(16.0f), C_TEXT); | ||
| 1075 | } else { | ||
| 1076 | text_in(clip, cr.x + S(16.0f), by + S(4.0f), ct.label, g_font_body, | ||
| 1077 | g_body_h, S(220.0f), C_TEXT); | ||
| 1078 | if (ct.kind == CK_TOGGLE) { | ||
| 1079 | draw_checkbox(cr.x + cr.w - S(31.0f), by + S(5.0f), *ct.value != 0); | ||
| 1080 | text_in(clip, cr.x + cr.w - S(58.0f), by + S(3.0f), | ||
| 1081 | *ct.value ? "On" : "Off", g_font_body, g_body_h, S(26.0f), | ||
| 1082 | *ct.value ? C_GOOD : C_DIM); | ||
| 1083 | } else if (ct.kind == CK_SLIDER) { | ||
| 1084 | draw_slider(clip, cr, by, ct); | ||
| 1085 | } | ||
| 1086 | } | ||
| 1087 | by += rh; | ||
| 1088 | } | ||
| 1089 | } | ||
| 1090 | |||
| 1091 | static void draw_tooltip() { | ||
| 1092 | if (!g_hover_desc || !g_hover_desc[0]) | ||
| 1093 | return; | ||
| 1094 | float d = now_sec() - (float)g_hover_since; | ||
| 1095 | if (d < 0.4f) | ||
| 1096 | return; | ||
| 1097 | int tw = text_w(g_hover_desc, g_body_h) + S(16.0f); | ||
| 1098 | int maxw = S(380.0f); | ||
| 1099 | if (tw > maxw) tw = maxw; | ||
| 1100 | int th = g_body_h + S(14.0f); | ||
| 1101 | int x = g_hover_x + S(14.0f); | ||
| 1102 | int y = g_hover_y + S(16.0f); | ||
| 1103 | if (x + tw > g_scr_w - S(4.0f)) x = g_hover_x - tw - S(10.0f); | ||
| 1104 | if (y + th > g_scr_h - S(4.0f)) y = g_hover_y - th - S(10.0f); | ||
| 1105 | Rect r{x, y, tw, th}; | ||
| 1106 | fill(r, Col{12, 14, 18, 248}); | ||
| 1107 | outline(r, C_ACCENT_DIM); | ||
| 1108 | Rect inner{x + S(7.0f), y + S(6.0f), tw - S(14.0f), g_body_h}; | ||
| 1109 | text_in(inner, inner.x, inner.y, g_hover_desc, g_font_body, g_body_h, | ||
| 1110 | inner.w, C_TEXT); | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | void gui_draw() { | ||
| 1114 | if (!g_open || !g_surface) | ||
| 1115 | return; | ||
| 1116 | layout(); | ||
| 1117 | ensure_fonts(); | ||
| 1118 | int w = g_scr_w, h = g_scr_h; | ||
| 1119 | if (w <= 0 || h <= 0) | ||
| 1120 | return; | ||
| 1121 | Rect win{g_win_x, g_win_y, g_win_w, g_win_h}; | ||
| 1122 | fill(Rect{win.x + S(6.0f), win.y + S(6.0f), win.w, win.h}, | ||
| 1123 | Col{0, 0, 0, 100}); | ||
| 1124 | fill(win, C_BG); | ||
| 1125 | outline(win, C_BORDER); | ||
| 1126 | Rect title{win.x, win.y, win.w, S(38.0f)}; | ||
| 1127 | fill(title, Col{31, 35, 42, 255}); | ||
| 1128 | fill(Rect{win.x, win.y, S(4.0f), win.h}, C_ACCENT); | ||
| 1129 | text_in(win, win.x + S(16.0f), win.y + S(7.0f), "HL2hake", g_font_title, | ||
| 1130 | g_title_h, win.w - S(80.0f), C_TEXT); | ||
| 1131 | text_in(win, win.x + S(110.0f), win.y + S(10.0f), "GUI shell preview", | ||
| 1132 | g_font_body, g_body_h, win.w - S(180.0f), C_DIM); | ||
| 1133 | Rect close{win.x + win.w - S(38.0f), win.y, S(38.0f), S(38.0f)}; | ||
| 1134 | fill(close, Col{45, 48, 56, 255}); | ||
| 1135 | line(close.x + S(13.0f), close.y + S(12.0f), close.x + S(25.0f), | ||
| 1136 | close.y + S(25.0f), C_DIM); | ||
| 1137 | line(close.x + S(25.0f), close.y + S(12.0f), close.x + S(13.0f), | ||
| 1138 | close.y + S(25.0f), C_DIM); | ||
| 1139 | |||
| 1140 | Rect side{win.x, win.y + S(38.0f), S(190.0f), | ||
| 1141 | win.h - S(38.0f) - S(30.0f)}; | ||
| 1142 | fill(side, C_SIDEBAR); | ||
| 1143 | int cat_h = S(34.0f); | ||
| 1144 | for (int i = 0; i < CAT_COUNT; i++) { | ||
| 1145 | Rect r{side.x, side.y + i * cat_h, side.w, cat_h}; | ||
| 1146 | if (i == g_cat) { | ||
| 1147 | fill(r, Col{42, 45, 54, 255}); | ||
| 1148 | fill(Rect{r.x, r.y, S(3.0f), r.h}, C_ACCENT); | ||
| 1149 | } | ||
| 1150 | text_in(side, r.x + S(18.0f), r.y + S(8.0f), g_cats[i], g_font_body, | ||
| 1151 | g_body_h, r.w - S(24.0f), i == g_cat ? C_TEXT : C_DIM); | ||
| 1152 | } | ||
| 1153 | |||
| 1154 | Rect content{win.x + S(190.0f), win.y + S(38.0f), | ||
| 1155 | win.w - S(190.0f), win.h - S(38.0f) - S(30.0f)}; | ||
| 1156 | fill(content, Col{20, 22, 27, 255}); | ||
| 1157 | Rect clip{content.x + S(4.0f), content.y + S(4.0f), content.w - S(20.0f), | ||
| 1158 | content.h - S(8.0f)}; | ||
| 1159 | int y = content.y - (int)g_scroll + S(8.0f); | ||
| 1160 | for (int i = 0; i < g_ncards; i++) { | ||
| 1161 | if (!card_in_category(i)) | ||
| 1162 | continue; | ||
| 1163 | Card &c = g_cards[i]; | ||
| 1164 | int ch = card_h(c); | ||
| 1165 | if (y + ch >= clip.y && y <= clip.y + clip.h) | ||
| 1166 | draw_card(clip, i, y); | ||
| 1167 | y += ch; | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | // scrollbar | ||
| 1171 | if (g_scroll_max > 0.5f) { | ||
| 1172 | Rect track{content.x + content.w - S(8.0f), content.y, S(8.0f), | ||
| 1173 | content.h}; | ||
| 1174 | fill(track, Col{30, 33, 40, 255}); | ||
| 1175 | int thumb_h = (int)((float)content.h * (float)content.h / | ||
| 1176 | (float)(content.h + (int)g_scroll_max)); | ||
| 1177 | if (thumb_h < S(24.0f)) thumb_h = S(24.0f); | ||
| 1178 | int travel = content.h - thumb_h; | ||
| 1179 | int thumb_y = content.y + (int)((g_scroll_max > 0.0f) | ||
| 1180 | ? (g_scroll / g_scroll_max) * travel | ||
| 1181 | : 0); | ||
| 1182 | fill(Rect{track.x, thumb_y, track.w, thumb_h}, C_ACCENT_DIM); | ||
| 1183 | } | ||
| 1184 | |||
| 1185 | Rect foot{win.x, win.y + win.h - S(30.0f), win.w, S(30.0f)}; | ||
| 1186 | fill(foot, Col{24, 27, 33, 255}); | ||
| 1187 | line(foot.x, foot.y, foot.x + foot.w, foot.y, C_BORDER); | ||
| 1188 | text_in(foot, foot.x + S(12.0f), foot.y + S(6.0f), | ||
| 1189 | "config: ~/.config/hl2hake/config", g_font_body, g_body_h, | ||
| 1190 | foot.w - S(220.0f), C_DIM); | ||
| 1191 | text_in(foot, foot.x + foot.w - S(200.0f), foot.y + S(6.0f), | ||
| 1192 | g_capture_card >= 0 ? "press a key / Esc" : "F2 close | Insert text", | ||
| 1193 | g_font_body, g_body_h, S(190.0f), C_DIM); | ||
| 1194 | |||
| 1195 | draw_tooltip(); | ||
| 1196 | } | ||
| 1197 | |||
| 1198 | bool gui_is_open() { return g_open; } | ||
| 1199 | QAngle gui_frozen_view() { return g_frozen; } | ||
