Files
dayz-dma/external/lumin/framework/gui.cpp
T

598 lines
22 KiB
C++

#include "headers/includes.h"
#include <Windows.h>
#include <array>
#include <cstdlib>
#include <string>
#include "Overlay/MenuBridge.h" // project header (src/ is on the include path)
// -------------------------------------------------------------------------
// DayZ overlay integration
//
// This is the original Lumin demo `gui.cpp`, stripped of the login screen and
// the placeholder "visual" feature panels. The window/sidebar/feature
// scaffolding is preserved verbatim; only the tab list and per-tab content
// were swapped to drive this project's real ESP state via `g_menu`
// (see src/Overlay/MenuBridge.h). https://discord.gg/jxqVWub6Dk
// -------------------------------------------------------------------------
static void sync_layout_preferences()
{
elements->padding = var->gui.compact_layout ? c_vec2(8, 8) : c_vec2(10, 10);
elements->window.rounding = var->gui.window_rounding;
}
static constexpr float visual_window_width = 900.f;
static constexpr float visual_window_height = 527.f;
static constexpr float visual_sidebar_width = 162.5f;
static constexpr float visual_outer_padding = 10.f;
static constexpr float visual_column_gap = 14.f;
static constexpr float visual_panel_scale = 1.0f;
static constexpr float visual_section_width_scale = 0.98f;
static constexpr float visual_feature_padding_x = 0.f;
static constexpr float visual_feature_padding_y = 0.f;
struct visual_panel_density_state
{
float dpi;
float font_scale;
};
static float unscale_visual(float value)
{
return value / ImMax(var->gui.dpi, 0.01f);
}
static float visual_section_height(int section_count)
{
const float content_height = unscale_visual(gui->content_avail().y);
const float gaps = visual_column_gap * static_cast<float>(ImMax(section_count - 1, 0));
return ImMax(220.f, (content_height - gaps) / static_cast<float>(ImMax(section_count, 1)));
}
extern c_vec4 g_pill_selected_rect;
extern c_vec4 g_sidebar_selected_rect;
static int g_panel_number = 0;
// ---- search filtering -----------------------------------------------------
static char visual_search_lower(char value)
{
return value >= 'A' && value <= 'Z' ? static_cast<char>(value - 'A' + 'a') : value;
}
static std::string visual_search_query()
{
std::string query = var->gui.feature_search;
size_t first = 0;
while (first < query.size() && (query[first] == ' ' || query[first] == '\t'))
first++;
size_t last = query.size();
while (last > first && (query[last - 1] == ' ' || query[last - 1] == '\t'))
last--;
return query.substr(first, last - first);
}
static bool visual_contains_ci(std::string_view text, std::string_view query)
{
if (query.empty())
return true;
if (query.size() > text.size())
return false;
for (size_t i = 0; i <= text.size() - query.size(); i++)
{
size_t j = 0;
while (j < query.size() && visual_search_lower(text[i + j]) == visual_search_lower(query[j]))
j++;
if (j == query.size())
return true;
}
return false;
}
static bool visual_search_matches(std::string_view name, std::string_view description = {})
{
const std::string query = visual_search_query();
if (query.empty())
return true;
return visual_contains_ci(name, query) || visual_contains_ci(description, query);
}
struct visual_widget_filter
{
bool checkbox(std::string name, std::string description, bool* callback, title_status_icon status = title_status_none) const
{
if (!visual_search_matches(name, description))
return false;
return ::widgets->checkbox(name, description, callback, status);
}
bool slider(std::string name, std::string description, float* callback, float vmin, float vmax, std::string format = "%.1f") const
{
if (!visual_search_matches(name, description))
return false;
return ::widgets->slider(name, description, callback, vmin, vmax, format);
}
};
// ---- section helpers ------------------------------------------------------
static void begin_visual_section(std::string_view name, float height)
{
const int panel_number = g_panel_number + 1;
const float section_width = elements->child_width * visual_section_width_scale;
const float content_padding_x = ImMax(0.f, section_width * 0.05f);
gui->begin_content(name, c_vec2(section_width, s_(height)), c_vec2(content_padding_x, s_(5)), s_(0, 0), window_flags_no_move | window_flags_no_scrollbar, child_flags_none);
c_window* window = gui->get_window();
draw->rect_filled(window->DrawList, window->Rect().Min, window->Rect().Max, draw->get_clr(clr->child), s_(14));
g_panel_number = panel_number;
}
static void end_visual_section()
{
gui->end_content();
}
static visual_panel_density_state begin_visual_panel_density()
{
c_window* window = gui->get_window();
visual_panel_density_state state{ var->gui.dpi, window->FontWindowScale };
var->gui.dpi = ImMax(0.01f, state.dpi * visual_panel_scale);
ImGui::SetWindowFontScale(state.font_scale * visual_panel_scale);
return state;
}
static void end_visual_panel_density(visual_panel_density_state state)
{
ImGui::SetWindowFontScale(state.font_scale);
var->gui.dpi = state.dpi;
}
static void draw_visual_sidebar_glass(const c_rect& rect)
{
ImDrawList* draw_list = gui->get_window()->DrawList;
const float rounding = s_(14);
draw->rect_filled(draw_list, rect.Min, rect.Max, draw->get_clr(clr->child), rounding);
}
// Full-width single-column panel (used by Info / Radar / Settings).
static void begin_full_section(std::string_view name, float height, bool scroll = false)
{
const float section_width = gui->content_avail().x;
const float content_padding_x = ImMax(0.f, section_width * 0.035f);
const window_flags flags = window_flags_no_move | (scroll ? 0 : window_flags_no_scrollbar);
gui->begin_content(name, c_vec2(section_width, s_(height)), c_vec2(content_padding_x, s_(10)), s_(0, 6), flags, child_flags_none);
c_window* window = gui->get_window();
draw->rect_filled(window->DrawList, window->Rect().Min, window->Rect().Max, draw->get_clr(clr->child), s_(14));
}
// Accent section heading.
static void lumin_heading(const char* text)
{
c_window* w = gui->get_window();
const c_vec2 pos = w->DC.CursorPos;
const c_vec2 size = c_vec2(gui->content_avail().x, s_(22));
const c_rect rect(pos, pos + size);
gui->item_size(rect);
if (!gui->item_add(rect, w->GetID(text)))
return;
draw->text_clipped(w->DrawList, font->get(inter_semibold, 13), rect.Min, rect.Max, draw->get_clr(clr->accent), text, 0, 0, { 0.f, 0.5f });
}
// Muted single-line note.
static void lumin_note(const char* text)
{
c_window* w = gui->get_window();
const c_vec2 pos = w->DC.CursorPos;
const c_vec2 size = c_vec2(gui->content_avail().x, s_(16));
const c_rect rect(pos, pos + size);
gui->item_size(rect);
if (!gui->item_add(rect, w->GetID(text)))
return;
draw->text_clipped(w->DrawList, font->get(inter_medium, 11), rect.Min, rect.Max, draw->get_clr(clr->text), text, 0, 0, { 0.f, 0.5f });
}
// Read-only "label .... value" row on a rounded widget background.
static void lumin_kv(const char* label, const std::string& value, const c_vec4& value_clr)
{
c_window* w = gui->get_window();
const c_vec2 pos = w->DC.CursorPos;
const c_vec2 size = c_vec2(gui->content_avail().x, s_(34));
const c_rect rect(pos, pos + size);
gui->item_size(rect);
if (!gui->item_add(rect, w->GetID(label)))
return;
draw->rect_filled(w->DrawList, rect.Min, rect.Max, draw->get_clr(clr->widget), s_(8));
draw->text_clipped(w->DrawList, font->get(inter_semibold, 12), rect.Min + s_(12, 0), rect.Max, draw->get_clr(clr->white), label, 0, 0, { 0.f, 0.5f });
draw->text_clipped(w->DrawList, font->get(inter_medium, 11), rect.Min, rect.Max - s_(12, 0), draw->get_clr(value_clr), value.data(), 0, 0, { 1.f, 0.5f });
}
// ---- ESP / Items content --------------------------------------------------
static void render_esp_tab(const visual_widget_filter& w, float section_height)
{
if (!g_menu)
return;
gui->begin_group();
{
begin_visual_section("Entities", section_height);
w.checkbox("Players", "Show player ESP", g_menu->showPlayers, title_status_safe);
w.checkbox("Animals", "Show animal ESP", g_menu->showAnimals);
w.checkbox("Zombies", "Show infected ESP", g_menu->showZombies);
w.checkbox("Items", "Show loot ESP", g_menu->showItems);
w.checkbox("Corpses", "Show dead bodies", g_menu->showCorpses);
w.checkbox("Bounding Box", "Draw entity box", g_menu->showBox);
w.checkbox("Skeleton", "Draw bone skeleton", g_menu->showSkeleton);
w.checkbox("Head Circle", "Draw head highlight", g_menu->showHeadDot);
w.checkbox("Weapon In Hand", "Show held weapon name", g_menu->showWeapon);
w.checkbox("Health Bar", "Draw player health bar", g_menu->showHealthBar);
w.checkbox("Health Number", "Draw numeric health", g_menu->showHealthNumber);
w.checkbox("Skeleton Debug", "Label every named bone", g_menu->debugSkeleton, title_status_warning);
end_visual_section();
}
gui->end_group();
gui->sameline();
gui->begin_group();
{
begin_visual_section("Draw Distance", section_height);
w.slider("Players", "Max player draw distance", g_menu->playerMaxDist, 50.f, 1000.f, "%.0f m");
w.slider("Animals", "Max animal draw distance", g_menu->animalMaxDist, 50.f, 1000.f, "%.0f m");
w.slider("Zombies", "Max zombie draw distance", g_menu->zombieMaxDist, 50.f, 500.f, "%.0f m");
w.slider("Items", "Max loot draw distance", g_menu->itemMaxDist, 20.f, 200.f, "%.0f m");
end_visual_section();
}
gui->end_group();
}
static void render_items_tab(const visual_widget_filter& w, float section_height)
{
if (!g_menu || !g_menu->itemCategories)
return;
struct cat_t { const char* key; const char* label; };
static const std::array<cat_t, 17> kCats = { {
{ "isWeapon", "Weapons" },
{ "isAmmo", "Ammo" },
{ "isMelee", "Melee" },
{ "isExplosives", "Explosives" },
{ "isMedical", "Medical" },
{ "isFood", "Food" },
{ "isConsumables", "Consumables" },
{ "isClothing", "Clothing" },
{ "isBackpack", "Backpacks" },
{ "isOptics", "Optics" },
{ "isWeaponAttachments", "Weapon Attachments" },
{ "isTool", "Tools" },
{ "isCrafting", "Crafting" },
{ "isVehiclePart", "Vehicle Parts" },
{ "isForBuilding", "Building" },
{ "isHouse", "House / Terrain" },
{ "isOtherLoot", "Other" },
} };
auto& cats = *g_menu->itemCategories;
auto draw_category = [&](const cat_t& c)
{
// Missing key defaults to enabled; widget mutates the map slot directly.
auto it = cats.find(c.key);
if (it == cats.end())
it = cats.emplace(c.key, true).first;
w.checkbox(c.label, "Toggle loot category", &it->second);
};
const size_t split = (kCats.size() + 1) / 2;
gui->begin_group();
{
begin_visual_section("Loot Categories", section_height);
for (size_t i = 0; i < split; i++)
draw_category(kCats[i]);
end_visual_section();
}
gui->end_group();
gui->sameline();
gui->begin_group();
{
begin_visual_section("Loot Categories##2", section_height);
for (size_t i = split; i < kCats.size(); i++)
draw_category(kCats[i]);
end_visual_section();
}
gui->end_group();
}
static void render_info_tab(float section_height)
{
const c_vec4 green = c_col(75, 225, 145).Value;
const c_vec4 orange = c_col(255, 175, 75).Value;
begin_full_section("Info", section_height);
{
lumin_heading("Server");
if (g_menu && g_menu->connected)
{
lumin_kv("Status", "Connected", green);
if (!g_menu->serverName.empty()) lumin_kv("Server", g_menu->serverName, clr->white.Value);
if (!g_menu->mapName.empty()) lumin_kv("Map", g_menu->mapName, clr->white.Value);
if (g_menu->hasPos)
{
char pos[64];
ImFormatString(pos, IM_ARRAYSIZE(pos), "%.1f / %.1f / %.1f", g_menu->px, g_menu->py, g_menu->pz);
lumin_kv("Position", pos, clr->accent.Value);
}
gui->dummy(c_vec2(0, s_(6)));
lumin_heading("Entity Counts");
lumin_kv("Players", std::to_string(g_menu->nPlayers), clr->accent.Value);
lumin_kv("Animals", std::to_string(g_menu->nAnimals), clr->accent.Value);
lumin_kv("Zombies", std::to_string(g_menu->nZombies), clr->accent.Value);
lumin_kv("Vehicles", std::to_string(g_menu->nVehicles), clr->accent.Value);
lumin_kv("Items", std::to_string(g_menu->nItems), clr->accent.Value);
lumin_kv("Bullets", std::to_string(g_menu->nBullets), clr->accent.Value);
}
else
{
lumin_kv("Status", (g_menu && !g_menu->status.empty()) ? g_menu->status : "Offline", orange);
}
}
end_visual_section();
}
static constexpr const char* kRadarDomain = "radar.charliecharliekirky.christmas";
static void render_radar_tab(float section_height)
{
begin_full_section("Radar", section_height, true);
{
lumin_heading("Web Radar");
lumin_note("Open this address in a browser:");
gui->dummy(c_vec2(0, s_(4)));
if (widgets->action_button(kRadarDomain, "copy"))
ImGui::SetClipboardText(kRadarDomain);
if (g_menu)
{
gui->dummy(c_vec2(0, s_(6)));
lumin_heading("Connection");
lumin_kv("Port", std::to_string(g_menu->webPort), clr->accent.Value);
lumin_kv("Password", "Set in config.cfg", clr->text.Value);
}
}
end_visual_section();
}
static void render_exit_tab(float section_height)
{
begin_full_section("Exit", section_height);
{
lumin_heading("Exit Application");
lumin_note("This will close the overlay and exit the program.");
gui->dummy(c_vec2(0, s_(10)));
const c_col saved_accent = clr->accent;
clr->accent = c_col(225, 70, 70);
if (widgets->primary_button("Exit", "back") && g_menu && g_menu->onExit)
g_menu->onExit();
clr->accent = saved_accent;
}
end_visual_section();
}
static void render_settings_tab(float section_height)
{
if (!g_menu)
return;
// Persistent text buffers backing the four resolution fields. Seeded once
// from the live values, then treated as the source of truth (parsed back
// into the int pointers every frame).
static char ovr_w[8], ovr_h[8], rnd_w[8], rnd_h[8];
static bool buffers_ready = false;
if (!buffers_ready)
{
ImFormatString(ovr_w, IM_ARRAYSIZE(ovr_w), "%d", *g_menu->pendingW);
ImFormatString(ovr_h, IM_ARRAYSIZE(ovr_h), "%d", *g_menu->pendingH);
ImFormatString(rnd_w, IM_ARRAYSIZE(rnd_w), "%d", *g_menu->pendingRW);
ImFormatString(rnd_h, IM_ARRAYSIZE(rnd_h), "%d", *g_menu->pendingRH);
buffers_ready = true;
}
begin_full_section("Settings", section_height, true);
{
lumin_heading("Overlay (Monitor) Resolution");
lumin_note("Set to your MONITOR size. 0 x 0 = auto-detect.");
gui->push_id("ovr_res");
widgets->text_field("Width", ovr_w, IM_ARRAYSIZE(ovr_w));
widgets->text_field("Height", ovr_h, IM_ARRAYSIZE(ovr_h));
gui->pop_id();
*g_menu->pendingW = ImMax(0, atoi(ovr_w));
*g_menu->pendingH = ImMax(0, atoi(ovr_h));
if (widgets->primary_button("Apply Monitor Resolution") && g_menu->onApplyDisplayRes)
g_menu->onApplyDisplayRes();
gui->dummy(c_vec2(0, s_(8)));
lumin_heading("Game Render Resolution");
lumin_note("Set to the in-game resolution when it differs from the");
lumin_note("monitor. 0 x 0 = no stretched-res correction.");
gui->push_id("rnd_res");
widgets->text_field("Width", rnd_w, IM_ARRAYSIZE(rnd_w));
widgets->text_field("Height", rnd_h, IM_ARRAYSIZE(rnd_h));
gui->pop_id();
*g_menu->pendingRW = ImMax(0, atoi(rnd_w));
*g_menu->pendingRH = ImMax(0, atoi(rnd_h));
widgets->checkbox("Stretch to fill", "GPU stretches game to fill the monitor", g_menu->stretchToFill);
if (widgets->primary_button("Apply Render Resolution") && g_menu->onApplyRenderRes)
g_menu->onApplyRenderRes();
gui->dummy(c_vec2(0, s_(10)));
lumin_heading("General");
lumin_note("Press INSERT to toggle this menu.");
gui->dummy(c_vec2(0, s_(4)));
if (widgets->primary_button("Save Config", "active") && g_menu->onSaveConfig)
g_menu->onSaveConfig();
}
end_visual_section();
}
// ---------------------------------------------------------------------------
void c_gui::render()
{
gui->initialize();
sync_layout_preferences();
gui->easing(var->gui.tab_alpha, var->gui.tab != var->gui.tab_stored ? 0.f : 1.f, 7.f, static_easing);
if (var->gui.tab_alpha == 0)
var->gui.tab = var->gui.tab_stored;
const c_vec2 target(s_(visual_window_width), s_(visual_window_height));
gui->easing(elements->window.size.x, target.x, 24.f, dynamic_easing);
gui->easing(elements->window.size.y, target.y, 24.f, dynamic_easing);
const ImVec2 disp = ImGui::GetIO().DisplaySize;
gui->set_next_window_size(elements->window.size);
gui->set_next_window_pos(c_vec2(ImMax(0.f, (disp.x - elements->window.size.x) * 0.5f),
ImMax(0.f, (disp.y - elements->window.size.y) * 0.5f)));
gui->begin(elements->window.name, nullptr, window_flags_no_scrollbar | window_flags_no_scroll_with_mouse | window_flags_no_bring_to_front_on_focus | window_flags_no_focus_on_appearing | window_flags_no_background | window_flags_no_decoration | window_flags_no_move);
{
gui->set_style();
gui->draw_decorations();
const float top_row_y = visual_outer_padding;
const float top_row_height = 50.f;
const float top_row_gap = 2.f;
const float bottom_row_y = top_row_y + top_row_height + top_row_gap;
const float bottom_row_height = visual_window_height - bottom_row_y - visual_outer_padding;
const float sidebar_tabs_y = top_row_y + top_row_height;
const float sidebar_tabs_height = visual_window_height - sidebar_tabs_y - visual_outer_padding;
draw_visual_sidebar_glass(c_rect(c_vec2(s_(visual_outer_padding), s_(top_row_y)), c_vec2(s_(visual_outer_padding + visual_sidebar_width), s_(visual_window_height - visual_outer_padding))));
gui->set_pos(c_vec2(s_(visual_outer_padding), s_(top_row_y)), pos_all);
gui->begin_content("TopBrand", c_vec2(s_(visual_sidebar_width), s_(top_row_height)), s_(0, 0), s_(0, 0), window_flags_no_scrollbar | window_flags_no_background, child_flags_none);
{
widgets->brand_header("KarachiHook", var->gui.profile_name[0] != '\0' ? var->gui.profile_name : "DayZ Overlay");
}
gui->end_content();
gui->set_pos(c_vec2(s_(visual_outer_padding), s_(sidebar_tabs_y)), pos_all);
gui->begin_content("Tabs", c_vec2(s_(visual_sidebar_width), s_(sidebar_tabs_height)), s_(10, 10), s_(0, 4), window_flags_no_scrollbar | window_flags_no_background, child_flags_none);
{
var->gui.sidebar_glass = true;
{
c_window* tabs_inner_bg = gui->get_window();
static c_vec4 sidebar_overlay = c_vec4(0, 0, 0, 0);
gui->easing(sidebar_overlay, g_sidebar_selected_rect, 18.f, dynamic_easing);
if (sidebar_overlay.z > sidebar_overlay.x + 1.f)
{
draw->rect_filled(tabs_inner_bg->DrawList,
c_vec2(sidebar_overlay.x, sidebar_overlay.y), c_vec2(sidebar_overlay.z, sidebar_overlay.w),
draw->get_clr(clr->widget), s_(9.1f));
}
}
widgets->tab_button("ESP", "visuals", 1);
widgets->tab_button("Items", "loot", 2);
widgets->tab_button("Radar", "world", 3);
widgets->tab_button("Settings", "polish", 4);
widgets->tab_button("Exit", "back", 5);
widgets->tab_button("Info", "stats", 6);
{
static c_vec4 sidebar_indicator = c_vec4(0, 0, 0, 0);
gui->easing(sidebar_indicator, g_sidebar_selected_rect, 18.f, dynamic_easing);
if (sidebar_indicator.w > sidebar_indicator.y + 1.f)
{
c_window* tabs_inner = gui->get_window();
const float bar_w = s_(2);
const float bar_inset_y = s_(6);
const float bar_x = sidebar_indicator.x + s_(2);
const c_vec2 bar_min = c_vec2(bar_x, sidebar_indicator.y + bar_inset_y);
const c_vec2 bar_max = c_vec2(bar_x + bar_w, sidebar_indicator.w - bar_inset_y);
draw->rect_filled(tabs_inner->DrawList, bar_min, bar_max,
draw->get_clr(clr->accent), bar_w * 0.5f);
}
}
gui->easing(elements->tab_window_width, gui->get_window()->Size.x, 24.f, dynamic_easing);
var->gui.sidebar_glass = false;
}
gui->end_content();
gui->push_var(style_var_alpha, var->gui.tab_alpha);
const float feature_x = visual_outer_padding + visual_sidebar_width + visual_outer_padding;
const float feature_width = visual_window_width - feature_x - visual_outer_padding;
const float feature_header_height = top_row_height * 0.8f;
const float feature_header_y = top_row_y;
gui->set_pos(c_vec2(s_(feature_x), s_(feature_header_y) + s_(10) * (1.f - var->gui.tab_alpha)), pos_all);
{
const c_vec2 pill_pos = gui->get_window()->DC.CursorPos;
draw->rect_filled(gui->get_window()->DrawList, pill_pos, pill_pos + c_vec2(s_(feature_width), s_(feature_header_height)), draw->get_clr(clr->child), s_(14));
gui->begin_content("FeatureHeader", c_vec2(s_(feature_width), s_(feature_header_height)), s_(8, 4), s_(8, 0), window_flags_no_scrollbar | window_flags_no_background, child_flags_none);
{
const float search_width = s_(178.f);
widgets->search_field("Search", var->gui.feature_search, IM_ARRAYSIZE(var->gui.feature_search), c_vec2(search_width, s_(32.f)));
}
gui->end_content();
}
gui->set_pos(c_vec2(s_(feature_x), s_(bottom_row_y) + s_(10) * (1.f - var->gui.tab_alpha)), pos_all);
gui->begin_content("Features", c_vec2(s_(feature_width), s_(bottom_row_height)), s_(visual_feature_padding_x, visual_feature_padding_y), c_vec2(s_(visual_column_gap), 0.f), window_flags_no_scrollbar);
{
const visual_panel_density_state density_state = begin_visual_panel_density();
g_panel_number = 3;
gui->easing(elements->child_width, (gui->content_avail().x - s_(visual_column_gap)) / 2, 24.f, dynamic_easing);
const float section_height = visual_section_height(1);
const visual_widget_filter visual_widgets;
switch (var->gui.tab)
{
case 1: render_esp_tab(visual_widgets, section_height); break;
case 2: render_items_tab(visual_widgets, section_height); break;
case 3: render_radar_tab(section_height); break;
case 4: render_settings_tab(section_height); break;
case 5: render_exit_tab(section_height); break;
case 6: render_info_tab(section_height); break;
default: break;
}
end_visual_panel_density(density_state);
}
gui->end_content();
gui->pop_var();
}
gui->end();
}
// Shim called by GameOverlay (declared in src/Overlay/MenuBridge.h).
void RenderLuminMenu()
{
gui->render();
}