From 0e9f3422e37d0b2d1344efec5ceb07c2246bfcd7 Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 27 Oct 2026 18:02:10 +0100 Subject: 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. --- src/gui.cpp | 1199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/hl2h.cpp | 19 + src/hl2h.h | 5 + src/menu.cpp | 19 +- 4 files changed, 1239 insertions(+), 3 deletions(-) create mode 100644 src/gui.cpp (limited to 'src') 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 @@ +// gui.cpp — custom immediate-mode GUI shell (temporary F2 preview) +// +// This is the first vertical slice: the visual shell, categories, cards, +// dummy controls, cursor handling and input freeze. It does not read or write +// the real feature settings yet; the next pass replaces the dummy state and +// removes the text menu. + +#include "hl2h.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// ISurface drawing (same binary slots as menu.cpp / esp.cpp) +// --------------------------------------------------------------------------- + +enum { + GS_DrawSetColor = 10, + GS_DrawFilledRect = 12, + GS_DrawOutlinedRect = 14, + GS_DrawLine = 15, + GS_DrawSetTextFont = 17, + GS_DrawSetTextColor = 18, + GS_DrawSetTextPos = 20, + GS_DrawPrintText = 22, + GS_GetScreenSize = 38, + GS_CreateFont = 66, + GS_AddGlyphSet = 67 +}; + +static unsigned long g_font_body = 0; +static unsigned long g_font_title = 0; +static int g_body_h = 15; +static int g_title_h = 20; +static float g_font_scale = 0.0f; +static int g_scr_w = 0, g_scr_h = 0; + +static void s_color(int r, int g, int b, int a) { + using Fn = void (*)(void *, int, int, int, int); + vfunc(g_surface, GS_DrawSetColor)(g_surface, r, g, b, a); +} +static void s_fill(int x0, int y0, int x1, int y1) { + using Fn = void (*)(void *, int, int, int, int); + vfunc(g_surface, GS_DrawFilledRect)(g_surface, x0, y0, x1, y1); +} +static void s_outline(int x0, int y0, int x1, int y1) { + using Fn = void (*)(void *, int, int, int, int); + vfunc(g_surface, GS_DrawOutlinedRect)(g_surface, x0, y0, x1, y1); +} +static void s_line(int x0, int y0, int x1, int y1) { + using Fn = void (*)(void *, int, int, int, int); + vfunc(g_surface, GS_DrawLine)(g_surface, x0, y0, x1, y1); +} +static void s_text_font(unsigned long f) { + using Fn = void (*)(void *, unsigned long); + vfunc(g_surface, GS_DrawSetTextFont)(g_surface, f); +} +static void s_text_color(int r, int g, int b, int a) { + using Fn = void (*)(void *, int, int, int, int); + vfunc(g_surface, GS_DrawSetTextColor)(g_surface, r, g, b, a); +} +static void s_text_pos(int x, int y) { + using Fn = void (*)(void *, int, int); + vfunc(g_surface, GS_DrawSetTextPos)(g_surface, x, y); +} +static void s_print(const wchar_t *text, int n) { + using Fn = void (*)(void *, const wchar_t *, int, int); + vfunc(g_surface, GS_DrawPrintText)(g_surface, text, n, 0); +} +static unsigned long s_create_font() { + using Fn = unsigned long (*)(void *); + return vfunc(g_surface, GS_CreateFont)(g_surface); +} +static bool s_set_font(unsigned long font, const char *name, int tall, int weight, + int flags, int lo, int hi) { + using Fn = bool (*)(void *, unsigned long, const char *, int, int, int, int, + int, int, int); + return vfunc(g_surface, GS_AddGlyphSet)(g_surface, font, name, tall, + weight, 0, 0, flags, lo, hi); +} +static void s_screen(int &w, int &h) { + using Fn = void (*)(void *, int &, int &); + vfunc(g_surface, GS_GetScreenSize)(g_surface, w, h); +} + +// --------------------------------------------------------------------------- +// SDL polling +// --------------------------------------------------------------------------- + +using GetKeysFn = const unsigned char *(*)(int *); +using GetMouseFn = unsigned int (*)(int *, int *); +using ShowCursorFn = int (*)(int); +using SetRelativeFn = int (*)(int); +using GetRelativeFn = int (*)(void); +using AddWatchFn = int (*)(int (*)(void *, void *), void *); +using GetTicksFn = unsigned int (*)(void); + +static GetKeysFn sd_get_keys = nullptr; +static GetMouseFn sd_get_mouse = nullptr; +static ShowCursorFn sd_show_cursor = nullptr; +static SetRelativeFn sd_set_relative = nullptr; +static GetRelativeFn sd_get_relative = nullptr; +static AddWatchFn sd_add_watch = nullptr; +static GetTicksFn sd_get_ticks = nullptr; +static bool g_sdl_ready = false; +static bool g_sdl_init_tried = false; + +static std::atomic g_wheel{0}; +static int sdl_event_watch(void *, void *event) { + if (!event) + return 0; + const unsigned char *p = (const unsigned char *)event; + unsigned int type = *(const unsigned int *)p; + if (type == 0x403) { // SDL_MOUSEWHEEL + int y = *(const int *)(p + 20); + g_wheel.fetch_add(y); + } + return 0; +} + +static void gui_init_sdl() { + if (g_sdl_init_tried) + return; + g_sdl_init_tried = true; + sd_get_keys = (GetKeysFn)dlsym(RTLD_DEFAULT, "SDL_GetKeyboardState"); + sd_get_mouse = (GetMouseFn)dlsym(RTLD_DEFAULT, "SDL_GetMouseState"); + sd_show_cursor = (ShowCursorFn)dlsym(RTLD_DEFAULT, "SDL_ShowCursor"); + sd_set_relative = (SetRelativeFn)dlsym(RTLD_DEFAULT, "SDL_SetRelativeMouseMode"); + sd_get_relative = (GetRelativeFn)dlsym(RTLD_DEFAULT, "SDL_GetRelativeMouseMode"); + sd_add_watch = (AddWatchFn)dlsym(RTLD_DEFAULT, "SDL_AddEventWatch"); + sd_get_ticks = (GetTicksFn)dlsym(RTLD_DEFAULT, "SDL_GetTicks"); + g_sdl_ready = sd_get_keys && sd_get_mouse; + if (!g_sdl_ready) { + logf("gui: SDL polling unavailable"); + return; + } + if (sd_add_watch) + sd_add_watch(&sdl_event_watch, nullptr); +} + +// --------------------------------------------------------------------------- +// Layout data (dummy shell) +// --------------------------------------------------------------------------- + +struct Rect { + int x, y, w, h; + bool has(int px, int py) const { + return px >= x && py >= y && px < x + w && py < y + h; + } +}; +struct Col { + int r, g, b, a; +}; +static const Col C_BG{17, 19, 23, 248}; +static const Col C_PANEL{27, 30, 36, 248}; +static const Col C_SIDEBAR{21, 23, 28, 248}; +static const Col C_HEADER{36, 40, 48, 255}; +static const Col C_BORDER{70, 76, 88, 255}; +static const Col C_TEXT{229, 232, 238, 255}; +static const Col C_DIM{150, 156, 168, 255}; +static const Col C_ACCENT{255, 145, 64, 255}; +static const Col C_ACCENT_DIM{120, 70, 32, 255}; +static const Col C_GOOD{86, 210, 135, 255}; + +enum CtrlKind { + CK_TOGGLE, + CK_SLIDER, + CK_BIND, + CK_SECTION, + CK_INFO, + CK_BUTTON +}; + +struct Ctrl { + int kind; + const char *label; + const char *desc; + int *value; + int min, max; +}; + +struct Card { + int cat; + const char *title; + const char *desc; + bool expanded; + bool active; + int bind; + int c0, n; +}; + +static Ctrl g_ctrls[192]; +static int g_nctrl = 0; +static Card g_cards[40]; +static int g_ncards = 0; +static int g_vals[192]; +static bool g_demo_built = false; + +static const char *g_cats[] = {"Movement", "Combat", "Visuals", "Weapons", + "System"}; +static constexpr int CAT_COUNT = 5; + +static int add_card(int cat, const char *title, const char *desc, + bool expanded) { + Card &c = g_cards[g_ncards]; + c.cat = cat; + c.title = title; + c.desc = desc; + c.expanded = expanded; + c.active = true; + c.bind = 0; + c.c0 = g_nctrl; + c.n = 0; + return g_ncards++; +} +static void add_ctrl(int card, int kind, const char *label, const char *desc, + int min, int max, int init) { + int i = g_nctrl++; + g_vals[i] = init; + g_ctrls[i].kind = kind; + g_ctrls[i].label = label; + g_ctrls[i].desc = desc; + g_ctrls[i].value = &g_vals[i]; + g_ctrls[i].min = min; + g_ctrls[i].max = max; + g_cards[card].n++; +} +static void add_toggle(int card, const char *label, const char *desc, + int init) { + add_ctrl(card, CK_TOGGLE, label, desc, 0, 1, init); +} +static void add_slider(int card, const char *label, const char *desc, int min, + int max, int init) { + add_ctrl(card, CK_SLIDER, label, desc, min, max, init); +} +static void add_section(int card, const char *label) { + add_ctrl(card, CK_SECTION, label, nullptr, 0, 0, 0); +} +static void add_info(int card, const char *label) { + add_ctrl(card, CK_INFO, label, nullptr, 0, 0, 0); +} +static void add_button(int card, const char *label, const char *desc) { + add_ctrl(card, CK_BUTTON, label, desc, 0, 0, 0); +} + +static void build_demo() { + if (g_demo_built) + return; + g_demo_built = true; + + int bhop = add_card(0, "Bunnyhop", "Automatic jump on landing.", true); + add_toggle(bhop, "Uni-directional auto-strafe", + "Rotate airborne movement into the velocity.", 1); + + int abh = add_card(0, "ABH", "Accelerate on the jump tick by facing away " + "from the velocity.", true); + (void)abh; + + int jb = add_card(0, "Jumpbug", "Duck+jump when landing would hurt.", true); + add_toggle(jb, "Assist timing", + "Nudge the final command into the unduck window.", 0); + + int aim = add_card(1, "Aimbot", "Smoothly snap to a hostile target.", true); + g_cards[aim].bind = 604; + add_toggle(aim, "Vischeck", "Require world line of sight.", 1); + add_slider(aim, "FOV", "Maximum angular distance from the crosshair.", 1, 180, + 60); + add_slider(aim, "Smoothness", "Maximum degrees per tick.", 1, 45, 5); + add_toggle(aim, "Show FOV circle", "Draw the acquisition cone on screen.", 1); + add_section(aim, "Bones"); + add_toggle(aim, "Head", "Aim at the head hitbox.", 1); + add_toggle(aim, "Torso", "Aim at the chest/stomach.", 0); + add_toggle(aim, "Left arm", "Include the left arm.", 0); + add_toggle(aim, "Right arm", "Include the right arm.", 0); + add_toggle(aim, "Left leg", "Include the left leg.", 0); + add_toggle(aim, "Right leg", "Include the right leg.", 0); + + int sil = add_card(1, "Silent aim", "Fire at the target without turning " + "the rendered view.", true); + g_cards[sil].bind = 602; + add_slider(sil, "FOV", "Maximum angular distance from the crosshair.", 1, 180, + 60); + + int tb = add_card(1, "Triggerbot", "Fire when the crosshair is on a hostile.", + true); + g_cards[tb].bind = 603; + + int melee = add_card(1, "Melee killbot", "360-degree crowbar silent aim.", + true); + + int esp = add_card(2, "ESP", "Draw hostile NPC information.", true); + add_toggle(esp, "Show neutral", "Include neutral wildlife.", 1); + add_toggle(esp, "Show healthbar", "Draw a health bar.", 1); + add_toggle(esp, "Show healthnum", "Draw the numeric health value.", 1); + add_toggle(esp, "Show name", "Draw the model name.", 1); + add_toggle(esp, "Show skeleton", "Draw the animated bone links.", 1); + add_toggle(esp, "Show box", "Draw a bounding box.", 1); + add_toggle(esp, "Show speed graph", "Draw the lower-center speed graph.", 1); + + int spread = add_card(3, "Spread control", + "Choose a server shot seed that tightens the cone.", + true); + add_toggle(spread, "No spread", + "Override the FireBullets seed with the best offset.", 1); + + int sys = add_card(4, "System", "Configuration and maintenance.", true); + add_info(sys, "Config: ~/.config/hl2hake/config"); + add_info(sys, "Build: GUI shell preview"); + add_button(sys, "Reload config", "Re-read the INI without restarting."); + add_button(sys, "Reset defaults", "Restore feature defaults; GUI layout kept."); + + (void)g_cards[melee]; +} + +// --------------------------------------------------------------------------- +// Window state / input +// --------------------------------------------------------------------------- + +static bool g_open = false; +static bool g_was_open = false; +static bool g_dragging = false; +static int g_drag_dx = 0, g_drag_dy = 0; +static int g_win_x = 0, g_win_y = 0, g_win_w = 0, g_win_h = 0; +static float g_scale = 1.0f; +static bool g_positioned = false; +static int g_cat = 0; +static float g_scroll = 0.0f; +static float g_scroll_max = 0.0f; +static bool g_scroll_drag = false; +static int g_scroll_drag_dy = 0; +static float g_scroll_drag_start = 0.0f; +static int g_hover_slider = -1; +static int g_slider_drag = -1; +static int g_capture_card = -1; +static QAngle g_frozen{0, 0, 0}; +static int g_mouse_x = 0, g_mouse_y = 0; +static bool g_mouse_click = false; +static bool g_mouse_down = false; +static bool g_mouse_rclick = false; +static unsigned int g_prev_mask = 0; +static unsigned char g_prev_keys[512] = {}; +static bool g_prev_cursor = true; +static bool g_prev_relative = true; +static double g_hover_since = 0.0; +static const char *g_hover_desc = nullptr; +static const char *g_hover_prev = nullptr; +static int g_hover_x = 0, g_hover_y = 0; +static int g_hover_last_mx = -1, g_hover_last_my = -1; + +static int S(float v) { return (int)(v * g_scale + 0.5f); } +static float now_sec() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (float)ts.tv_sec + (float)ts.tv_nsec * 1e-9f; +} + +static void ensure_fonts() { + if (g_font_body && fabsf(g_font_scale - g_scale) < 0.001f) + return; + g_font_scale = g_scale; + g_body_h = (int)(15.0f * g_scale + 0.5f); + g_title_h = (int)(20.0f * g_scale + 0.5f); + if (g_body_h < 11) g_body_h = 11; + if (g_title_h < 15) g_title_h = 15; + static const char *families[] = {"Liberation Sans", "DejaVu Sans", + "Noto Sans", "Tahoma", "Verdana", "Arial", + nullptr}; + if (!g_font_body) { + for (int i = 0; families[i] && !g_font_body; i++) { + unsigned long f = s_create_font(); + if (!f) + break; + if (s_set_font(f, families[i], g_body_h, 400, 0x400, 0x20, 0xFF)) + g_font_body = f; + } + } + if (!g_font_title) { + for (int i = 0; families[i] && !g_font_title; i++) { + unsigned long f = s_create_font(); + if (!f) + break; + if (s_set_font(f, families[i], g_title_h, 400, 0x400, 0x20, 0xFF)) + g_font_title = f; + } + } + if (!g_font_title) + g_font_title = g_font_body; + if (!g_font_body) + logf("gui: no usable body font"); +} + +static int text_w(const char *s, int tall) { + if (!s) return 0; + return (int)(strlen(s) * tall * 0.52f + 0.5f); +} + +static void draw_text_raw(int x, int y, const char *s, unsigned long font, + int tall) { + (void)tall; + if (!s || !font) + return; + wchar_t w[256]; + size_t n = mbstowcs(w, s, 255); + if (n == (size_t)-1) + return; + w[n] = 0; + s_text_font(font); + s_text_pos(x, y); + s_print(w, (int)n); +} + +static void text_in(const Rect &clip, int x, int y, const char *s, + unsigned long font, int tall, int max_w, const Col &c) { + if (!s || !font) + return; + if (y < clip.y || y + tall > clip.y + clip.h) + return; + if (x >= clip.x + clip.w) + return; + if (x + text_w(s, tall) > clip.x + clip.w) + x = clip.x + clip.w - text_w(s, tall); + if (max_w > 0 && text_w(s, tall) > max_w) { + char buf[256]; + snprintf(buf, sizeof(buf), "%s", s); + int n = (int)strlen(buf); + while (n > 3 && text_w(buf, tall) > max_w) + buf[--n] = 0; + if (n > 3 && n < (int)sizeof(buf) - 3) { + buf[n++] = '.'; + buf[n++] = '.'; + buf[n++] = '.'; + buf[n] = 0; + } + s_text_color(c.r, c.g, c.b, c.a); + draw_text_raw(x, y, buf, font, tall); + return; + } + s_text_color(c.r, c.g, c.b, c.a); + draw_text_raw(x, y, s, font, tall); +} + +static Rect clip_rect(const Rect &r, const Rect &clip) { + Rect o = r; + int x0 = r.x > clip.x ? r.x : clip.x; + int y0 = r.y > clip.y ? r.y : clip.y; + int x1 = (r.x + r.w) < (clip.x + clip.w) ? (r.x + r.w) : (clip.x + clip.w); + int y1 = (r.y + r.h) < (clip.y + clip.h) ? (r.y + r.h) : (clip.y + clip.h); + o.x = x0; + o.y = y0; + o.w = x1 - x0; + o.h = y1 - y0; + return o; +} +static void fill(const Rect &r, const Col &c, const Rect *clip = nullptr) { + if (r.w <= 0 || r.h <= 0) + return; + Rect q = clip ? clip_rect(r, *clip) : r; + if (q.w <= 0 || q.h <= 0) + return; + s_color(c.r, c.g, c.b, c.a); + s_fill(q.x, q.y, q.x + q.w, q.y + q.h); +} +static void outline(const Rect &r, const Col &c, const Rect *clip = nullptr) { + if (r.w <= 0 || r.h <= 0) + return; + Rect q = clip ? clip_rect(r, *clip) : r; + if (q.w <= 0 || q.h <= 0) + return; + s_color(c.r, c.g, c.b, c.a); + s_outline(q.x, q.y, q.x + q.w, q.y + q.h); +} +static void line(int x0, int y0, int x1, int y1, const Col &c) { + s_color(c.r, c.g, c.b, c.a); + s_line(x0, y0, x1, y1); +} + +static const char *bind_label(int b) { + switch (b) { + case 0: return "None"; + case 600: return "MouseL"; + case 601: return "MouseR"; + case 602: return "MouseM"; + case 603: return "Mouse4"; + case 604: return "Mouse5"; + case 41: return "Esc"; + case 73: return "Insert"; + case 59: return "F2"; + default: return "Key"; + } +} + +static void layout() { + int w = g_scr_w, h = g_scr_h; + if (g_surface) { + s_screen(w, h); + if (w > 0) g_scr_w = w; + if (h > 0) g_scr_h = h; + } + w = g_scr_w; + h = g_scr_h; + if (w <= 0 || h <= 0) + return; + float sc = fminf((float)w / 1920.0f, (float)h / 1080.0f); + if (sc < 0.75f) sc = 0.75f; + if (sc > 1.5f) sc = 1.5f; + g_scale = sc; + int nw = S(960.0f), nh = S(620.0f); + if (!g_positioned || nw != g_win_w || nh != g_win_h) { + if (!g_positioned) { + g_win_x = (w - nw) / 2; + g_win_y = (h - nh) / 2; + g_positioned = true; + } + g_win_w = nw; + g_win_h = nh; + } + if (g_win_x < 0) g_win_x = 0; + if (g_win_y < 0) g_win_y = 0; + if (g_win_x + g_win_w > w) g_win_x = w - g_win_w; + if (g_win_y + g_win_h > h) g_win_y = h - g_win_h; +} + +static int row_h(const Ctrl &c) { + switch (c.kind) { + case CK_SECTION: return S(24.0f); + case CK_INFO: return S(20.0f); + case CK_BUTTON: return S(32.0f); + default: return S(27.0f); + } +} + +static int body_h(const Card &c) { + if (!c.expanded) + return 0; + int h = S(6.0f); + for (int i = 0; i < c.n; i++) + h += row_h(g_ctrls[c.c0 + i]); + return h + S(6.0f); +} + +static int card_h(const Card &c) { return S(36.0f) + body_h(c) + S(8.0f); } + +static Rect card_rect(int index, int y) { + Card &c = g_cards[index]; + int x = g_win_x + S(190.0f) + S(10.0f); + int w = g_win_w - S(190.0f) - S(28.0f); + return Rect{x, y, w, card_h(c)}; +} + +static bool card_in_category(int i) { return g_cards[i].cat == g_cat; } + +static int content_height() { + int h = S(8.0f); + for (int i = 0; i < g_ncards; i++) + if (card_in_category(i)) + h += card_h(g_cards[i]); + return h; +} + +// --------------------------------------------------------------------------- +// Input +// --------------------------------------------------------------------------- + +static void close_gui() { + if (!g_open) + return; + g_open = false; + g_capture_card = -1; + g_dragging = false; + g_scroll_drag = false; + g_slider_drag = -1; + if (g_sdl_ready) { + if (sd_set_relative) + sd_set_relative(g_prev_relative ? 1 : 0); + if (sd_show_cursor) + sd_show_cursor(g_prev_cursor ? 1 : 0); + } + logf("gui: closed"); +} + +static void open_gui() { + if (g_open) + return; + int w = 0, h = 0; + if (g_surface) { + s_screen(w, h); + if (w > 0) g_scr_w = w; + if (h > 0) g_scr_h = h; + } + if (!g_iface.engine) { + g_frozen = QAngle{0, 0, 0}; + } else { + using GetViewFn = void (*)(void *, QAngle *); + QAngle v{0, 0, 0}; + vfunc(g_iface.engine, 19)(g_iface.engine, &v); + g_frozen = v; + } + if (g_sdl_ready) { + if (sd_get_relative) + g_prev_relative = sd_get_relative() != 0; + if (sd_show_cursor) + g_prev_cursor = sd_show_cursor(-1) != 0; + if (sd_set_relative) + sd_set_relative(0); + if (sd_show_cursor) + sd_show_cursor(1); + } + g_open = true; + g_capture_card = -1; + g_wheel.exchange(0); + g_scroll = 0.0f; + g_cat = 0; + menu_force_close(); + logf("gui: opened"); +} + +static void update_mouse() { + if (!g_sdl_ready) + return; + int mx = 0, my = 0; + unsigned int mask = sd_get_mouse(&mx, &my); + g_mouse_x = mx; + g_mouse_y = my; + g_mouse_down = (mask & 1u) != 0; + g_mouse_click = g_mouse_down && !(g_prev_mask & 1u); + g_mouse_rclick = ((mask & 2u) != 0) && !(g_prev_mask & 2u); + if (g_mouse_x < 0) g_mouse_x = 0; + if (g_mouse_y < 0) g_mouse_y = 0; + g_prev_mask = mask; +} + +static void handle_capture(const unsigned char *keys) { + if (g_capture_card < 0) + return; + if (!g_open) { + g_capture_card = -1; + return; + } + if (keys[41]) { // Esc cancels + g_capture_card = -1; + return; + } + if (g_mouse_click) { + g_cards[g_capture_card].bind = 600; + g_capture_card = -1; + return; + } + if (g_mouse_rclick) { + g_cards[g_capture_card].bind = 601; + g_capture_card = -1; + return; + } + for (int k = 0; k < 512; k++) { + if (!keys[k] || g_prev_keys[k]) + continue; + if (k == 73 || k == 59) // Insert reserved; F2 closes the preview + continue; + g_cards[g_capture_card].bind = k; + g_capture_card = -1; + return; + } +} + +static void handle_mouse_click() { + if (!g_mouse_click) + return; + int mx = g_mouse_x, my = g_mouse_y; + + // title bar: drag or close + Rect title{g_win_x, g_win_y, g_win_w, S(38.0f)}; + Rect close{g_win_x + g_win_w - S(38.0f), g_win_y, S(38.0f), S(38.0f)}; + if (title.has(mx, my)) { + if (close.has(mx, my)) { + close_gui(); + return; + } + g_dragging = true; + g_drag_dx = mx - g_win_x; + g_drag_dy = my - g_win_y; + return; + } + + // sidebar + int side_x = g_win_x; + int side_y = g_win_y + S(38.0f); + int side_w = S(190.0f); + int cat_h = S(34.0f); + for (int i = 0; i < CAT_COUNT; i++) { + Rect r{side_x, side_y + i * cat_h, side_w, cat_h}; + if (r.has(mx, my)) { + g_cat = i; + g_scroll = 0.0f; + return; + } + } + + // content + Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), + g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; + if (!content.has(mx, my)) + return; + + // scrollbar + int sb_x = content.x + content.w - S(8.0f); + if (mx >= sb_x) { + if (g_scroll_max > 0.5f) { + int thumb_h = (int)((float)content.h * (float)content.h / + (float)(content.h + (int)g_scroll_max)); + if (thumb_h < S(24.0f)) thumb_h = S(24.0f); + int travel = content.h - thumb_h; + int thumb_y = content.y + (int)((g_scroll_max > 0.0f) + ? (g_scroll / g_scroll_max) * travel + : 0); + if (my >= thumb_y && my < thumb_y + thumb_h) { + g_scroll_drag = true; + g_scroll_drag_dy = my - thumb_y; + g_scroll_drag_start = g_scroll; + } else { + float t = (float)(my - content.y) / (float)content.h; + g_scroll = t * g_scroll_max; + } + } + return; + } + + int y = content.y - (int)g_scroll + S(8.0f); + for (int i = 0; i < g_ncards; i++) { + if (!card_in_category(i)) + continue; + Card &c = g_cards[i]; + Rect cr = card_rect(i, y); + int ch = card_h(c); + if (my >= cr.y && my < cr.y + ch) { + // header + if (my < cr.y + S(36.0f)) { + if (c.cat != 4) { + Rect active{cr.x + cr.w - S(150.0f), cr.y + S(9.0f), S(18.0f), + S(18.0f)}; + Rect bind{cr.x + cr.w - S(112.0f), cr.y + S(7.0f), S(100.0f), + S(22.0f)}; + if (active.has(mx, my)) { + c.active = !c.active; + return; + } + if (bind.has(mx, my)) { + if (mx >= bind.x + bind.w - S(18.0f)) { + c.bind = 0; + return; + } + g_capture_card = i; + return; + } + } + c.expanded = !c.expanded; + return; + } + // body + int by = cr.y + S(36.0f) + S(6.0f); + for (int j = 0; j < c.n; j++) { + Ctrl &ct = g_ctrls[c.c0 + j]; + int rh = row_h(ct); + if (my < by || my >= by + rh) { + by += rh; + continue; + } + if (ct.kind == CK_TOGGLE) { + *ct.value = !*ct.value; + return; + } + if (ct.kind == CK_SLIDER) { + Rect track{cr.x + S(16.0f) + S(230.0f), by + S(9.0f), + cr.w - S(16.0f) - S(230.0f) - S(64.0f), S(8.0f)}; + if (mx >= track.x && mx < track.x + track.w) { + float t = (float)(mx - track.x) / (float)track.w; + int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); + if (v < ct.min) v = ct.min; + if (v > ct.max) v = ct.max; + *ct.value = v; + g_slider_drag = c.c0 + j; + } + return; + } + if (ct.kind == CK_BUTTON) { + Rect b{cr.x + cr.w - S(130.0f), by + S(3.0f), S(114.0f), + S(26.0f)}; + if (b.has(mx, my)) { + logf("gui: dummy action %s", ct.label ? ct.label : "?"); + return; + } + } + return; + } + } + y += ch; + } +} + +static void handle_hover() { + g_hover_desc = nullptr; + g_hover_slider = -1; + int mx = g_mouse_x, my = g_mouse_y; + Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), + g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; + if (!content.has(mx, my)) + return; + int y = content.y - (int)g_scroll + S(8.0f); + for (int i = 0; i < g_ncards; i++) { + if (!card_in_category(i)) + continue; + Card &c = g_cards[i]; + Rect cr = card_rect(i, y); + int ch = card_h(c); + if (my >= cr.y && my < cr.y + ch) { + if (my < cr.y + S(36.0f)) { + g_hover_desc = c.desc; + return; + } + int by = cr.y + S(36.0f) + S(6.0f); + for (int j = 0; j < c.n; j++) { + Ctrl &ct = g_ctrls[c.c0 + j]; + int rh = row_h(ct); + if (my >= by && my < by + rh) { + g_hover_desc = ct.desc; + if (ct.kind == CK_SLIDER) + g_hover_slider = c.c0 + j; + return; + } + by += rh; + } + return; + } + y += ch; + } +} + +void gui_handle_input() { + gui_init_sdl(); + if (!g_surface) + return; + build_demo(); + layout(); + if (g_scr_w <= 0 || g_scr_h <= 0) + return; + ensure_fonts(); + if (!g_sdl_ready) + return; + + int nk = 0; + const unsigned char *keys = sd_get_keys(&nk); + if (!keys) + return; + if (nk > 512) nk = 512; + update_mouse(); + + bool f2 = (nk > 59) && keys[59]; + bool f2_edge = f2 && !g_prev_keys[59]; + bool esc = (nk > 41) && keys[41]; + bool esc_edge = esc && !g_prev_keys[41]; + bool left = (nk > 80) && keys[80]; + bool right = (nk > 79) && keys[79]; + bool left_edge = left && !g_prev_keys[80]; + bool right_edge = right && !g_prev_keys[79]; + + if (f2_edge) { + if (g_open) + close_gui(); + else + open_gui(); + } + if (!g_open) { + memcpy(g_prev_keys, keys, nk); + g_was_open = false; + return; + } + + if (esc_edge) { + if (g_capture_card >= 0) + g_capture_card = -1; + else + close_gui(); + } + + // Keep the native cursor free even if the game re-grabs it. + if (sd_set_relative) + sd_set_relative(0); + if (sd_show_cursor) + sd_show_cursor(1); + + handle_capture(keys); + + if (g_dragging) { + if (!g_mouse_down) { + g_dragging = false; + } else { + g_win_x = g_mouse_x - g_drag_dx; + g_win_y = g_mouse_y - g_drag_dy; + if (g_win_x < 0) g_win_x = 0; + if (g_win_y < 0) g_win_y = 0; + if (g_win_x + g_win_w > g_scr_w) g_win_x = g_scr_w - g_win_w; + if (g_win_y + g_win_h > g_scr_h) g_win_y = g_scr_h - g_win_h; + } + } else { + handle_mouse_click(); + } + + if (g_slider_drag >= 0) { + if (!g_mouse_down) { + g_slider_drag = -1; + } else { + Ctrl &ct = g_ctrls[g_slider_drag]; + Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), + g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; + int x0 = g_win_x + S(190.0f) + S(16.0f) + S(230.0f); + int x1 = g_win_x + g_win_w - S(28.0f) - S(64.0f); + (void)content; + float t = (float)(g_mouse_x - x0) / (float)(x1 - x0); + int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); + if (v < ct.min) v = ct.min; + if (v > ct.max) v = ct.max; + *ct.value = v; + } + } + + if (g_scroll_drag) { + if (!g_mouse_down) { + g_scroll_drag = false; + } else { + Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), + g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; + int thumb_h = (int)((float)content.h * (float)content.h / + (float)(content.h + (int)g_scroll_max)); + if (thumb_h < S(24.0f)) thumb_h = S(24.0f); + int travel = content.h - thumb_h; + if (travel > 0) { + float t = (float)(g_mouse_y - content.y - g_scroll_drag_dy) / + (float)travel; + g_scroll = t * g_scroll_max; + } + } + } + + int wheel = g_wheel.exchange(0); + if (wheel != 0) + g_scroll -= wheel * S(44.0f); + + if (left_edge || right_edge) { + if (g_hover_slider >= 0) { + Ctrl &ct = g_ctrls[g_hover_slider]; + *ct.value += right_edge ? 1 : -1; + if (*ct.value < ct.min) *ct.value = ct.min; + if (*ct.value > ct.max) *ct.value = ct.max; + } + } + + int total = content_height(); + Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), + g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; + g_scroll_max = (float)(total + S(8.0f) - content.h); + if (g_scroll_max < 0.0f) g_scroll_max = 0.0f; + if (g_scroll < 0.0f) g_scroll = 0.0f; + if (g_scroll > g_scroll_max) g_scroll = g_scroll_max; + + handle_hover(); + { + double now = now_sec(); + int dx = abs(g_mouse_x - g_hover_last_mx); + int dy = abs(g_mouse_y - g_hover_last_my); + if (g_hover_desc != g_hover_prev || dx > 2 || dy > 2) { + g_hover_prev = g_hover_desc; + g_hover_last_mx = g_mouse_x; + g_hover_last_my = g_mouse_y; + g_hover_since = now; + g_hover_x = g_mouse_x; + g_hover_y = g_mouse_y; + } + } + + memcpy(g_prev_keys, keys, nk); + g_was_open = true; +} + +// --------------------------------------------------------------------------- +// Draw +// --------------------------------------------------------------------------- + +static void draw_checkbox(int x, int y, bool on) { + Rect box{x, y, S(15.0f), S(15.0f)}; + fill(box, on ? C_ACCENT : Col{30, 34, 42, 255}); + outline(box, C_BORDER); + if (on) { + line(x + S(3.0f), y + S(8.0f), x + S(7.0f), y + S(12.0f), C_BG); + line(x + S(7.0f), y + S(12.0f), x + S(13.0f), y + S(4.0f), C_BG); + } +} + +static void draw_slider(const Rect &clip, const Rect &cr, int y, const Ctrl &ct) { + int track_x = cr.x + S(16.0f) + S(230.0f); + int track_w = cr.w - S(16.0f) - S(230.0f) - S(64.0f); + if (track_w < S(30.0f)) track_w = S(30.0f); + Rect track{track_x, y + S(9.0f), track_w, S(8.0f)}; + fill(track, Col{38, 42, 50, 255}, &clip); + float t = (ct.max > ct.min) + ? (float)(*ct.value - ct.min) / (float)(ct.max - ct.min) + : 0.0f; + int fw = (int)(track_w * t + 0.5f); + fill(Rect{track.x, track.y, fw, track.h}, C_ACCENT_DIM, &clip); + int kx = track.x + fw - S(4.0f); + fill(Rect{kx, track.y - S(4.0f), S(8.0f), S(16.0f)}, C_ACCENT, &clip); + char val[32]; + snprintf(val, sizeof(val), "%d", *ct.value); + text_in(clip, cr.x + cr.w - S(52.0f), y + S(4.0f), val, g_font_body, g_body_h, + S(44.0f), C_TEXT); +} + +static void draw_card(const Rect &clip, int index, int y) { + Card &c = g_cards[index]; + Rect cr = card_rect(index, y); + Rect bg = clip_rect(cr, clip); + if (bg.w <= 0 || bg.h <= 0) + return; + fill(cr, C_PANEL, &clip); + outline(cr, Col{52, 57, 67, 255}, &clip); + Rect head{cr.x, cr.y, cr.w, S(36.0f)}; + fill(head, C_HEADER, &clip); + // expand arrow + int ax = cr.x + S(10.0f), ay = cr.y + S(13.0f); + if (c.expanded) { + line(ax, ay, ax + S(4.0f), ay + S(5.0f), C_DIM); + line(ax + S(4.0f), ay + S(5.0f), ax + S(8.0f), ay, C_DIM); + } else { + line(ax, ay, ax + S(5.0f), ay + S(4.0f), C_DIM); + line(ax + S(5.0f), ay + S(4.0f), ax, ay + S(8.0f), C_DIM); + } + text_in(clip, cr.x + S(24.0f), cr.y + S(8.0f), c.title, g_font_title, + g_title_h, cr.w - S(180.0f), C_TEXT); + if (c.cat != 4) { + draw_checkbox(cr.x + cr.w - S(150.0f), cr.y + S(10.0f), c.active); + text_in(clip, cr.x + cr.w - S(128.0f), cr.y + S(9.0f), + c.active ? "On" : "Off", g_font_body, g_body_h, S(30.0f), + c.active ? C_GOOD : C_DIM); + Rect bind{cr.x + cr.w - S(112.0f), cr.y + S(7.0f), S(100.0f), S(22.0f)}; + fill(bind, Col{45, 50, 60, 255}, &clip); + outline(bind, C_BORDER, &clip); + const char *bl = (g_capture_card == index) ? "Press..." : bind_label(c.bind); + text_in(clip, bind.x + S(7.0f), bind.y + S(3.0f), bl, g_font_body, g_body_h, + bind.w - S(22.0f), C_TEXT); + text_in(clip, bind.x + bind.w - S(16.0f), bind.y + S(3.0f), "x", + g_font_body, g_body_h, S(10.0f), C_DIM); + } + if (!c.expanded) + return; + + int by = cr.y + S(36.0f) + S(6.0f); + for (int j = 0; j < c.n; j++) { + Ctrl &ct = g_ctrls[c.c0 + j]; + int rh = row_h(ct); + if (ct.kind == CK_SECTION) { + text_in(clip, cr.x + S(16.0f), by + S(3.0f), ct.label, g_font_body, + g_body_h, cr.w - S(24.0f), C_ACCENT); + } else if (ct.kind == CK_INFO) { + text_in(clip, cr.x + S(16.0f), by + S(1.0f), ct.label, g_font_body, + g_body_h, cr.w - S(24.0f), C_DIM); + } else if (ct.kind == CK_BUTTON) { + Rect b{cr.x + cr.w - S(130.0f), by + S(3.0f), S(114.0f), S(26.0f)}; + fill(b, Col{45, 50, 60, 255}, &clip); + outline(b, C_BORDER, &clip); + text_in(clip, b.x + S(10.0f), b.y + S(4.0f), ct.label, g_font_body, + g_body_h, b.w - S(16.0f), C_TEXT); + } else { + text_in(clip, cr.x + S(16.0f), by + S(4.0f), ct.label, g_font_body, + g_body_h, S(220.0f), C_TEXT); + if (ct.kind == CK_TOGGLE) { + draw_checkbox(cr.x + cr.w - S(31.0f), by + S(5.0f), *ct.value != 0); + text_in(clip, cr.x + cr.w - S(58.0f), by + S(3.0f), + *ct.value ? "On" : "Off", g_font_body, g_body_h, S(26.0f), + *ct.value ? C_GOOD : C_DIM); + } else if (ct.kind == CK_SLIDER) { + draw_slider(clip, cr, by, ct); + } + } + by += rh; + } +} + +static void draw_tooltip() { + if (!g_hover_desc || !g_hover_desc[0]) + return; + float d = now_sec() - (float)g_hover_since; + if (d < 0.4f) + return; + int tw = text_w(g_hover_desc, g_body_h) + S(16.0f); + int maxw = S(380.0f); + if (tw > maxw) tw = maxw; + int th = g_body_h + S(14.0f); + int x = g_hover_x + S(14.0f); + int y = g_hover_y + S(16.0f); + if (x + tw > g_scr_w - S(4.0f)) x = g_hover_x - tw - S(10.0f); + if (y + th > g_scr_h - S(4.0f)) y = g_hover_y - th - S(10.0f); + Rect r{x, y, tw, th}; + fill(r, Col{12, 14, 18, 248}); + outline(r, C_ACCENT_DIM); + Rect inner{x + S(7.0f), y + S(6.0f), tw - S(14.0f), g_body_h}; + text_in(inner, inner.x, inner.y, g_hover_desc, g_font_body, g_body_h, + inner.w, C_TEXT); +} + +void gui_draw() { + if (!g_open || !g_surface) + return; + layout(); + ensure_fonts(); + int w = g_scr_w, h = g_scr_h; + if (w <= 0 || h <= 0) + return; + Rect win{g_win_x, g_win_y, g_win_w, g_win_h}; + fill(Rect{win.x + S(6.0f), win.y + S(6.0f), win.w, win.h}, + Col{0, 0, 0, 100}); + fill(win, C_BG); + outline(win, C_BORDER); + Rect title{win.x, win.y, win.w, S(38.0f)}; + fill(title, Col{31, 35, 42, 255}); + fill(Rect{win.x, win.y, S(4.0f), win.h}, C_ACCENT); + text_in(win, win.x + S(16.0f), win.y + S(7.0f), "HL2hake", g_font_title, + g_title_h, win.w - S(80.0f), C_TEXT); + text_in(win, win.x + S(110.0f), win.y + S(10.0f), "GUI shell preview", + g_font_body, g_body_h, win.w - S(180.0f), C_DIM); + Rect close{win.x + win.w - S(38.0f), win.y, S(38.0f), S(38.0f)}; + fill(close, Col{45, 48, 56, 255}); + line(close.x + S(13.0f), close.y + S(12.0f), close.x + S(25.0f), + close.y + S(25.0f), C_DIM); + line(close.x + S(25.0f), close.y + S(12.0f), close.x + S(13.0f), + close.y + S(25.0f), C_DIM); + + Rect side{win.x, win.y + S(38.0f), S(190.0f), + win.h - S(38.0f) - S(30.0f)}; + fill(side, C_SIDEBAR); + int cat_h = S(34.0f); + for (int i = 0; i < CAT_COUNT; i++) { + Rect r{side.x, side.y + i * cat_h, side.w, cat_h}; + if (i == g_cat) { + fill(r, Col{42, 45, 54, 255}); + fill(Rect{r.x, r.y, S(3.0f), r.h}, C_ACCENT); + } + text_in(side, r.x + S(18.0f), r.y + S(8.0f), g_cats[i], g_font_body, + g_body_h, r.w - S(24.0f), i == g_cat ? C_TEXT : C_DIM); + } + + Rect content{win.x + S(190.0f), win.y + S(38.0f), + win.w - S(190.0f), win.h - S(38.0f) - S(30.0f)}; + fill(content, Col{20, 22, 27, 255}); + Rect clip{content.x + S(4.0f), content.y + S(4.0f), content.w - S(20.0f), + content.h - S(8.0f)}; + int y = content.y - (int)g_scroll + S(8.0f); + for (int i = 0; i < g_ncards; i++) { + if (!card_in_category(i)) + continue; + Card &c = g_cards[i]; + int ch = card_h(c); + if (y + ch >= clip.y && y <= clip.y + clip.h) + draw_card(clip, i, y); + y += ch; + } + + // scrollbar + if (g_scroll_max > 0.5f) { + Rect track{content.x + content.w - S(8.0f), content.y, S(8.0f), + content.h}; + fill(track, Col{30, 33, 40, 255}); + int thumb_h = (int)((float)content.h * (float)content.h / + (float)(content.h + (int)g_scroll_max)); + if (thumb_h < S(24.0f)) thumb_h = S(24.0f); + int travel = content.h - thumb_h; + int thumb_y = content.y + (int)((g_scroll_max > 0.0f) + ? (g_scroll / g_scroll_max) * travel + : 0); + fill(Rect{track.x, thumb_y, track.w, thumb_h}, C_ACCENT_DIM); + } + + Rect foot{win.x, win.y + win.h - S(30.0f), win.w, S(30.0f)}; + fill(foot, Col{24, 27, 33, 255}); + line(foot.x, foot.y, foot.x + foot.w, foot.y, C_BORDER); + text_in(foot, foot.x + S(12.0f), foot.y + S(6.0f), + "config: ~/.config/hl2hake/config", g_font_body, g_body_h, + foot.w - S(220.0f), C_DIM); + text_in(foot, foot.x + foot.w - S(200.0f), foot.y + S(6.0f), + g_capture_card >= 0 ? "press a key / Esc" : "F2 close | Insert text", + g_font_body, g_body_h, S(190.0f), C_DIM); + + draw_tooltip(); +} + +bool gui_is_open() { return g_open; } +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) { bool inwater = (flags & FL_INWATER) != 0; const float *vel = (const float *)(e + g_net.velocity); + if (gui_is_open()) { + QAngle f = gui_frozen_view(); + g_cam_view = Vector{f.x, f.y, f.z}; + cmd->viewangles[0] = f.x; + cmd->viewangles[1] = f.y; + cmd->viewangles[2] = f.z; + cmd->forwardmove = 0.0f; + cmd->sidemove = 0.0f; + cmd->upmove = 0.0f; + cmd->buttons = 0; + cmd->impulse = 0; + cmd->weaponselect = 0; + cmd->weaponsubtype = 0; + cmd->mousedx = 0; + cmd->mousedy = 0; + note_modified(cmd, flags, vel); + return; + } + bool bhop_on = feature_active(FEAT_BHOP); bool abh_on = feature_active(FEAT_ABH); diff --git a/src/hl2h.h b/src/hl2h.h index 285b591..11b9014 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -230,6 +230,11 @@ bool head_position(int idx, Vector &out); int aim_points(int idx, unsigned group_mask, Vector *out, int max); void outer_draw(); +void menu_force_close(); +void gui_handle_input(); +void gui_draw(); +bool gui_is_open(); +QAngle gui_frozen_view(); // ISurface pointer (defined in menu.cpp, used for drawing tests) struct ISurface; 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; } bool aimbot_fov_circle() { return g_aim_fov_circle; } +void menu_force_close() { + g_menu_open = false; + g_bind_mode = false; + g_insert_latch = false; + g_menu_draw_logged = false; +} + static void menu_handle_input() { poll_keyboard(); - // SDL state can be sampled several times per rendered frame (both ISurface // hooks call outer_draw). Use a physical-key latch instead of the one-poll // 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) { // (see hkdDrawPrintText hook in hl2h.cpp): we wrap our draws in // PushMakeCurrent/PopMakeCurrent so they are submitted with a clean clip. void outer_draw() { - menu_handle_input(); + gui_handle_input(); + if (!gui_is_open()) + menu_handle_input(); // HL2H_DRAW_TEST=1 draws a probe rect without needing the menu open static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; bool esp_on = feature_active(FEAT_ESP); - if (!g_surface || (!g_menu_open && !draw_test && !esp_on)) + if (!g_surface || (!g_menu_open && !gui_is_open() && !draw_test && !esp_on)) return; // Level/menu -> game transitions can invalidate the VGUI font handle. @@ -896,6 +904,11 @@ void outer_draw() { surf_draw_set_color(255, 255, 255, 255); draw_text(28, 36, "HL2H DRAW TEST"); } + if (gui_is_open()) { + gui_draw(); + surf_pop_make_current(panel); + return; + } if (!g_menu_open) { surf_pop_make_current(panel); return; -- cgit v1.2.3