diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-27 18:02:10 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-27 18:02:10 +0100 |
| commit | 0e9f3422e37d0b2d1344efec5ceb07c2246bfcd7 (patch) | |
| tree | a8aa0b5a4718f0e7d407a31135823523a77744b0 | |
| parent | Speedgraph: lower the graph (diff) | |
| download | hl2h-0e9f3422e37d0b2d1344efec5ceb07c2246bfcd7.tar.gz hl2h-0e9f3422e37d0b2d1344efec5ceb07c2246bfcd7.zip | |
GUI: add F2 preview shell with dummy controls
First vertical slice for the custom ISurface GUI:
- window/sidebar/content shell with Movement, Combat, Visuals, Weapons and
System categories;
- cards with dummy Active/Bind, collapsible settings, toggles, sliders,
buttons, bind capture, scrolling and tooltips;
- native OS cursor by releasing SDL relative mode; reasserted while open;
- F2 toggles the preview; Insert still opens the old text tree;
- while the GUI is open, do_move freezes movement/buttons/impulses/view and
skips combat features; ESP and the speed graph keep drawing.
Dummy controls do not touch the real config yet. The text menu is removed
after feature parity in the next pass.
Diffstat (limited to '')
| -rw-r--r-- | CONTEXT.md | 28 | ||||
| -rwxr-xr-x | build.sh | 2 | ||||
| -rw-r--r-- | docs/adr/0001-custom-isurface-gui.md | 58 | ||||
| -rw-r--r-- | src/gui.cpp | 1199 | ||||
| -rw-r--r-- | src/hl2h.cpp | 19 | ||||
| -rw-r--r-- | src/hl2h.h | 5 | ||||
| -rw-r--r-- | src/menu.cpp | 19 |
7 files changed, 1326 insertions, 4 deletions
| @@ -99,6 +99,10 @@ Starts fully collapsed on first open. `Insert` toggles; `Up`/`Down` navigate; | |||
| 99 | Bind rows enter bind-select mode: press a key/button to assign, `Escape` sets | 99 | Bind rows enter bind-select mode: press a key/button to assign, `Escape` sets |
| 100 | None (always-on), `Enter` cancels. | 100 | None (always-on), `Enter` cancels. |
| 101 | 101 | ||
| 102 | > The text menu below is being replaced by the GUI described in the next | ||
| 103 | > section. The old tree is kept only until the GUI reaches parity. The current | ||
| 104 | > preview shell opens with F2; Insert still opens the text tree. | ||
| 105 | |||
| 102 | ``` | 106 | ``` |
| 103 | HL2hake | 107 | HL2hake |
| 104 | Bunnyhop | 108 | Bunnyhop |
| @@ -136,6 +140,30 @@ HL2hake | |||
| 136 | 140 | ||
| 137 | Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604. | 141 | Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604. |
| 138 | 142 | ||
| 143 | ## GUI vocabulary / decisions | ||
| 144 | |||
| 145 | - **GUI**: custom immediate-mode settings surface drawn through the existing | ||
| 146 | `ISurface` paint path. No Dear ImGui, no OpenGL hook, no new runtime | ||
| 147 | dependency. | ||
| 148 | - **Category**: sidebar group. Movement, Combat, Visuals, Weapons, System. | ||
| 149 | - **Card**: per-feature panel. Header always shows Active + Bind; body is a | ||
| 150 | collapsible set of settings. | ||
| 151 | - **Freeze**: while the GUI is open, the usercmd movement/buttons/impulses/view | ||
| 152 | are held and combat features are skipped; the game keeps simulating and | ||
| 153 | ESP/FOV circle/speed graph keep drawing. No invulnerability. | ||
| 154 | - **Native cursor**: on open, leave SDL relative-mouse mode and show the OS | ||
| 155 | cursor; restore on close. Absolute mouse coordinates are used. | ||
| 156 | - **Bind capture**: click Bind, press a key/button; Esc or clicking elsewhere | ||
| 157 | cancels; Clear sets None; Insert is reserved for the GUI. | ||
| 158 | - **UI scale**: `clamp(min(screen_w/1920, screen_h/1080), 0.75, 1.5)`; base | ||
| 159 | window 960x620, body/title fonts 15/20 px at scale 1. | ||
| 160 | - Every feature gets Active + Bind; bind None means always-on. | ||
| 161 | - System page: config path/build info, Reload config, and a two-click Reset | ||
| 162 | that restores feature settings/binds but keeps GUI layout. | ||
| 163 | - Window: fixed-size, draggable by title bar, clamped to screen, position, | ||
| 164 | category and expanded cards persisted under `[gui]`. | ||
| 165 | |||
| 166 | |||
| 139 | ## Bones / ESP | 167 | ## Bones / ESP |
| 140 | 168 | ||
| 141 | - `SetupBones(BONE_USED_BY_ANYTHING)` with engine `Time()`; studio header via | 169 | - `SetupBones(BONE_USED_BY_ANYTHING)` with engine `Time()`; studio header via |
| @@ -3,6 +3,6 @@ set -e | |||
| 3 | cd "$(dirname "$0")" | 3 | cd "$(dirname "$0")" |
| 4 | g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden -fno-omit-frame-pointer \ | 4 | g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden -fno-omit-frame-pointer \ |
| 5 | -o libhl2hake.so \ | 5 | -o libhl2hake.so \ |
| 6 | src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/esp.cpp src/server.cpp src/bones.cpp src/vischeck.cpp \ | 6 | src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/gui.cpp src/esp.cpp src/server.cpp src/bones.cpp src/vischeck.cpp \ |
| 7 | -ldl | 7 | -ldl |
| 8 | file libhl2hake.so | 8 | file libhl2hake.so |
diff --git a/docs/adr/0001-custom-isurface-gui.md b/docs/adr/0001-custom-isurface-gui.md new file mode 100644 index 0000000..9da3c0c --- /dev/null +++ b/docs/adr/0001-custom-isurface-gui.md | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | # 0001 — Custom immediate-mode GUI on ISurface | ||
| 2 | |||
| 3 | - Status: Accepted | ||
| 4 | - Date: 2026-10-27 | ||
| 5 | |||
| 6 | ## Context | ||
| 7 | |||
| 8 | The text-only menu is becoming cumbersome as features accumulate. The cheat | ||
| 9 | already has a proven overlay draw path through `ISurface` (`PaintTraverseEx`), | ||
| 10 | native text with the bundled Liberation Sans font, text metrics, filled/outlined | ||
| 11 | rects, lines and textured polygons. The game runs Source on Linux with a | ||
| 12 | 32-bit process; the cheat is a single `LD_PRELOAD` shared object. | ||
| 13 | |||
| 14 | The main alternatives were: | ||
| 15 | |||
| 16 | 1. Dear ImGui with its standard OpenGL renderer, hooked into the game's | ||
| 17 | swap/present path. | ||
| 18 | 2. Dear ImGui rendered through `ISurface`. | ||
| 19 | 3. A custom immediate-mode GUI on `ISurface`. | ||
| 20 | 4. Keep the text menu and add categories/pages. | ||
| 21 | |||
| 22 | `ISurface::Vertex_t` only carries position and UV, not per-vertex color, so a | ||
| 23 | standard ImGui draw-list renderer would lose per-widget text colors unless | ||
| 24 | draws were split or text was rendered through a separate path. An OpenGL ImGui | ||
| 25 | backend would require locating and hooking the swap path, saving/restoring GL | ||
| 26 | state, uploading the font atlas, and integrating SDL input events — an | ||
| 27 | unverified risk in this build. The existing ISurface path already draws in-game | ||
| 28 | and in menus without a new graphics context. | ||
| 29 | |||
| 30 | ## Decision | ||
| 31 | |||
| 32 | Build a custom immediate-mode GUI directly on the existing `ISurface` draw | ||
| 33 | path. | ||
| 34 | |||
| 35 | - Fixed dark theme with automatic scale: | ||
| 36 | `clamp(min(screen_w/1920, screen_h/1080), 0.75, 1.5)`. | ||
| 37 | - Base window 960x620, body/title fonts 15/20 px at scale 1. | ||
| 38 | - Left sidebar categories (Movement, Combat, Visuals, Weapons, System) and a | ||
| 39 | content pane of feature cards with collapsible settings. | ||
| 40 | - Native OS cursor by releasing SDL relative-mouse mode while the GUI is open. | ||
| 41 | - While open, freeze usercmd input/view and skip combat features; keep the | ||
| 42 | game simulating and keep ESP/FOV circle/speed graph drawing. | ||
| 43 | - Replace the text menu with the GUI. No permanent fallback menu; git history | ||
| 44 | retains the old implementation. | ||
| 45 | |||
| 46 | ## Consequences | ||
| 47 | |||
| 48 | - We own the widget set (scrollbar, sliders, toggles, keybind capture, tooltips) | ||
| 49 | and the layout/clipping code. Text clipping and wheel events need explicit | ||
| 50 | handling because ISurface has no clip-rect API and SDL does not expose wheel | ||
| 51 | state by polling. | ||
| 52 | - No new runtime dependency or GL context management. | ||
| 53 | - The GUI remains inside the engine's VGUI paint path, so it inherits the | ||
| 54 | existing font registration and in-game overlay behavior. | ||
| 55 | - The old text menu is removed once the GUI reaches feature parity. During | ||
| 56 | development the GUI is built alongside the text menu and opened with a | ||
| 57 | temporary F2 key; the replacement is complete only when the old tree is | ||
| 58 | deleted. | ||
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; } | ||
diff --git a/src/hl2h.cpp b/src/hl2h.cpp index c4f8aad..c0c27af 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp | |||
| @@ -540,6 +540,25 @@ static void do_move(int seq, float frametime) { | |||
| 540 | bool inwater = (flags & FL_INWATER) != 0; | 540 | bool inwater = (flags & FL_INWATER) != 0; |
| 541 | const float *vel = (const float *)(e + g_net.velocity); | 541 | const float *vel = (const float *)(e + g_net.velocity); |
| 542 | 542 | ||
| 543 | if (gui_is_open()) { | ||
| 544 | QAngle f = gui_frozen_view(); | ||
| 545 | g_cam_view = Vector{f.x, f.y, f.z}; | ||
| 546 | cmd->viewangles[0] = f.x; | ||
| 547 | cmd->viewangles[1] = f.y; | ||
| 548 | cmd->viewangles[2] = f.z; | ||
| 549 | cmd->forwardmove = 0.0f; | ||
| 550 | cmd->sidemove = 0.0f; | ||
| 551 | cmd->upmove = 0.0f; | ||
| 552 | cmd->buttons = 0; | ||
| 553 | cmd->impulse = 0; | ||
| 554 | cmd->weaponselect = 0; | ||
| 555 | cmd->weaponsubtype = 0; | ||
| 556 | cmd->mousedx = 0; | ||
| 557 | cmd->mousedy = 0; | ||
| 558 | note_modified(cmd, flags, vel); | ||
| 559 | return; | ||
| 560 | } | ||
| 561 | |||
| 543 | bool bhop_on = feature_active(FEAT_BHOP); | 562 | bool bhop_on = feature_active(FEAT_BHOP); |
| 544 | bool abh_on = feature_active(FEAT_ABH); | 563 | bool abh_on = feature_active(FEAT_ABH); |
| 545 | 564 | ||
| @@ -230,6 +230,11 @@ bool head_position(int idx, Vector &out); | |||
| 230 | int aim_points(int idx, unsigned group_mask, Vector *out, int max); | 230 | int aim_points(int idx, unsigned group_mask, Vector *out, int max); |
| 231 | 231 | ||
| 232 | void outer_draw(); | 232 | void outer_draw(); |
| 233 | void menu_force_close(); | ||
| 234 | void gui_handle_input(); | ||
| 235 | void gui_draw(); | ||
| 236 | bool gui_is_open(); | ||
| 237 | QAngle gui_frozen_view(); | ||
| 233 | // ISurface pointer (defined in menu.cpp, used for drawing tests) | 238 | // ISurface pointer (defined in menu.cpp, used for drawing tests) |
| 234 | struct ISurface; | 239 | struct ISurface; |
| 235 | extern ISurface *g_surface; | 240 | extern ISurface *g_surface; |
diff --git a/src/menu.cpp b/src/menu.cpp index 9dffc9e..56fe6fa 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -625,9 +625,15 @@ int silentaim_fov() { return g_silent_fov; } | |||
| 625 | 625 | ||
| 626 | bool aimbot_fov_circle() { return g_aim_fov_circle; } | 626 | bool aimbot_fov_circle() { return g_aim_fov_circle; } |
| 627 | 627 | ||
| 628 | void menu_force_close() { | ||
| 629 | g_menu_open = false; | ||
| 630 | g_bind_mode = false; | ||
| 631 | g_insert_latch = false; | ||
| 632 | g_menu_draw_logged = false; | ||
| 633 | } | ||
| 634 | |||
| 628 | static void menu_handle_input() { | 635 | static void menu_handle_input() { |
| 629 | poll_keyboard(); | 636 | poll_keyboard(); |
| 630 | |||
| 631 | // SDL state can be sampled several times per rendered frame (both ISurface | 637 | // SDL state can be sampled several times per rendered frame (both ISurface |
| 632 | // hooks call outer_draw). Use a physical-key latch instead of the one-poll | 638 | // hooks call outer_draw). Use a physical-key latch instead of the one-poll |
| 633 | // edge so one press cannot open and close the menu in the same frame. | 639 | // edge so one press cannot open and close the menu in the same frame. |
| @@ -838,11 +844,13 @@ void draw_text_col(int x, int y, const char *text, int r, int g, int b) { | |||
| 838 | // (see hkdDrawPrintText hook in hl2h.cpp): we wrap our draws in | 844 | // (see hkdDrawPrintText hook in hl2h.cpp): we wrap our draws in |
| 839 | // PushMakeCurrent/PopMakeCurrent so they are submitted with a clean clip. | 845 | // PushMakeCurrent/PopMakeCurrent so they are submitted with a clean clip. |
| 840 | void outer_draw() { | 846 | void outer_draw() { |
| 841 | menu_handle_input(); | 847 | gui_handle_input(); |
| 848 | if (!gui_is_open()) | ||
| 849 | menu_handle_input(); | ||
| 842 | // HL2H_DRAW_TEST=1 draws a probe rect without needing the menu open | 850 | // HL2H_DRAW_TEST=1 draws a probe rect without needing the menu open |
| 843 | static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; | 851 | static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; |
| 844 | bool esp_on = feature_active(FEAT_ESP); | 852 | bool esp_on = feature_active(FEAT_ESP); |
| 845 | if (!g_surface || (!g_menu_open && !draw_test && !esp_on)) | 853 | if (!g_surface || (!g_menu_open && !gui_is_open() && !draw_test && !esp_on)) |
| 846 | return; | 854 | return; |
| 847 | 855 | ||
| 848 | // Level/menu -> game transitions can invalidate the VGUI font handle. | 856 | // Level/menu -> game transitions can invalidate the VGUI font handle. |
| @@ -896,6 +904,11 @@ void outer_draw() { | |||
| 896 | surf_draw_set_color(255, 255, 255, 255); | 904 | surf_draw_set_color(255, 255, 255, 255); |
| 897 | draw_text(28, 36, "HL2H DRAW TEST"); | 905 | draw_text(28, 36, "HL2H DRAW TEST"); |
| 898 | } | 906 | } |
| 907 | if (gui_is_open()) { | ||
| 908 | gui_draw(); | ||
| 909 | surf_pop_make_current(panel); | ||
| 910 | return; | ||
| 911 | } | ||
| 899 | if (!g_menu_open) { | 912 | if (!g_menu_open) { |
| 900 | surf_pop_make_current(panel); | 913 | surf_pop_make_current(panel); |
| 901 | return; | 914 | return; |
