refactor: modernize Dear ImGui menu UI
This commit is contained in:
parent
9503d22b62
commit
baabc7878e
|
|
@ -143,6 +143,9 @@
|
||||||
<ClCompile Include="Cheat\Aim.cpp" />
|
<ClCompile Include="Cheat\Aim.cpp" />
|
||||||
<ClCompile Include="Cheat\Features.cpp" />
|
<ClCompile Include="Cheat\Features.cpp" />
|
||||||
<ClCompile Include="Cheat\Menu.cpp" />
|
<ClCompile Include="Cheat\Menu.cpp" />
|
||||||
|
<ClCompile Include="Cheat\MenuPages.cpp" />
|
||||||
|
<ClCompile Include="Cheat\UI\MenuTheme.cpp" />
|
||||||
|
<ClCompile Include="Cheat\UI\MenuWidgets.cpp" />
|
||||||
<ClCompile Include="Cheat\Render.cpp" />
|
<ClCompile Include="Cheat\Render.cpp" />
|
||||||
<ClCompile Include="Cheat\SDK\CExfil\CExfil.cpp" />
|
<ClCompile Include="Cheat\SDK\CExfil\CExfil.cpp" />
|
||||||
<ClCompile Include="Cheat\SDK\CItem\CItem.cpp" />
|
<ClCompile Include="Cheat\SDK\CItem\CItem.cpp" />
|
||||||
|
|
@ -169,6 +172,8 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Cheat\Framework.h" />
|
<ClInclude Include="Cheat\Framework.h" />
|
||||||
|
<ClInclude Include="Cheat\UI\MenuTheme.h" />
|
||||||
|
<ClInclude Include="Cheat\UI\MenuWidgets.h" />
|
||||||
<ClInclude Include="Cheat\SnapshotHealth.h" />
|
<ClInclude Include="Cheat\SnapshotHealth.h" />
|
||||||
<ClInclude Include="Cheat\SDK\CExfil\CExfil.h" />
|
<ClInclude Include="Cheat\SDK\CExfil\CExfil.h" />
|
||||||
<ClInclude Include="Cheat\SDK\CItem\CItem.h" />
|
<ClInclude Include="Cheat\SDK\CItem\CItem.h" />
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,13 @@ private:
|
||||||
void AimMemory(const Vector3& target, const Vector3& localPos, uintptr_t localAddress);
|
void AimMemory(const Vector3& target, const Vector3& localPos, uintptr_t localAddress);
|
||||||
void ScanRotationOffset(); // 2阶段扫描: Player->MC 与 MC->Rotation 偏移
|
void ScanRotationOffset(); // 2阶段扫描: Player->MC 与 MC->Rotation 偏移
|
||||||
|
|
||||||
|
// Menu pages are UI-only renderers. They keep the existing Globals/config bindings intact.
|
||||||
|
void RenderAimPage(float columnWidth, float gap);
|
||||||
|
void RenderVisualPage(float columnWidth, float gap);
|
||||||
|
void RenderFeaturesPage(float columnWidth, float gap);
|
||||||
|
void RenderSystemPage(float columnWidth, float gap, const char*& configStatus);
|
||||||
|
void RenderDebugPage(float columnWidth, float gap);
|
||||||
|
|
||||||
// Runtime feature backup state is declared above.
|
// Runtime feature backup state is declared above.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,357 +1,192 @@
|
||||||
#include "Framework.h"
|
#include "Framework.h"
|
||||||
#include "../Framework/Input/Input.h"
|
#include "UI/MenuTheme.h"
|
||||||
|
#include "UI/MenuWidgets.h"
|
||||||
|
#include "../Framework/Config/Config.h"
|
||||||
|
|
||||||
// ImGui::Combo/ImGui::List等で使う文字列群
|
#include <cfloat>
|
||||||
const char* BoxTypeList[] = { "简单", "棱角" };
|
|
||||||
const char* CrosshairList[] = { "十字", "圆圈" };
|
namespace
|
||||||
const char* AimMethodList[] = { "内存", "鼠标" };
|
{
|
||||||
const char* AimBoneList[] = { "头部", "颈部", "身体" };
|
enum class MenuPage
|
||||||
std::vector<const char*> MenuStringList = { "自瞄", "视觉", "功能", "设置" };
|
{
|
||||||
std::vector<const char*> MenuIconList = { ICON_FA_CROSSHAIRS, ICON_FA_EYE, ICON_FA_BARS, ICON_FA_GEAR };
|
Aim,
|
||||||
|
Visual,
|
||||||
|
Features,
|
||||||
|
System,
|
||||||
|
Debug,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void CFramework::RenderMenu()
|
void CFramework::RenderMenu()
|
||||||
{
|
{
|
||||||
// Setup
|
static MenuPage currentPage = MenuPage::Aim;
|
||||||
static int Index = 0;
|
static const char* configStatus = "自动加载;可手动保存/读取";
|
||||||
const auto runtimeSnapshot = GetDynamicSnapshot();
|
|
||||||
const bool runtimeFresh = runtimeSnapshot && IsSnapshotFresh(runtimeSnapshot->capturedAt);
|
|
||||||
const bool runtimeInRaid = runtimeFresh && runtimeSnapshot->inRaid;
|
|
||||||
const uintptr_t runtimeLocalAddress = runtimeFresh ? runtimeSnapshot->local.m_address : 0;
|
|
||||||
const std::size_t runtimeEntityCount = runtimeFresh ? runtimeSnapshot->players.size() : 0;
|
|
||||||
|
|
||||||
ImGui::SetNextWindowBgAlpha(0.975f);
|
const bool debug = g.g_DebugMode;
|
||||||
ImGui::SetNextWindowSize(ImVec2(725.f, 450.f));
|
if (currentPage == MenuPage::Debug && !debug)
|
||||||
ImGui::Begin("塔科夫 PvE [ 外部辅助 ]", &g.ShowMenu, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
currentPage = MenuPage::System;
|
||||||
|
|
||||||
//---// Clild 0 //-----------------------------------//
|
UI::Theme::ApplyTheme();
|
||||||
ImGui::BeginChild("##SelectChild", ImVec2(150.f, ImGui::GetContentRegionAvail().y), false);
|
ImGui::SetNextWindowPos(ImVec2(70.f, 40.f), ImGuiCond_FirstUseEver);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(920.f, 620.f), ImGuiCond_FirstUseEver);
|
||||||
|
ImGui::SetNextWindowSizeConstraints(ImVec2(760.f, 520.f), ImVec2(FLT_MAX, FLT_MAX));
|
||||||
|
ImGui::SetNextWindowBgAlpha(1.f);
|
||||||
|
|
||||||
ImGui::NewLine();
|
const ImGuiWindowFlags windowFlags =
|
||||||
|
ImGuiWindowFlags_NoTitleBar |
|
||||||
|
ImGuiWindowFlags_NoCollapse |
|
||||||
|
ImGuiWindowFlags_NoMove |
|
||||||
|
ImGuiWindowFlags_NoScrollbar |
|
||||||
|
ImGuiWindowFlags_NoSavedSettings;
|
||||||
|
|
||||||
ImGui::PushFont(icon);
|
if (!ImGui::Begin("##CFW_ControlDeck", nullptr, windowFlags))
|
||||||
|
{
|
||||||
for (int i = 0; i < MenuIconList.size(); i++) {
|
ImGui::End();
|
||||||
if (ImGui::CustomButton(MenuIconList[i], MenuStringList[i], ImVec2(ImGui::GetContentRegionAvail().x, 35.f), Index == i ? true : false))
|
return;
|
||||||
Index = i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PopFont();
|
const auto drawHeader = [&]()
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
//---// Clild 0 End //-------------------------------//
|
|
||||||
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
//---// Clild 1 //-----------------------------------//
|
|
||||||
ImGui::BeginChild("##ContextChild", ImVec2(ImGui::GetContentRegionAvail()), false);
|
|
||||||
|
|
||||||
//---// Left //--------------------------------------//
|
|
||||||
ImGui::BeginChild("##LeftChild", ImVec2(ImGui::GetContentRegionAvail().x / 2.f - 16.f, ImGui::GetContentRegionAvail().y), false);
|
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.f));
|
|
||||||
|
|
||||||
switch (Index)
|
|
||||||
{
|
{
|
||||||
case 0: // AimBot
|
const ImVec2 headerPos = ImGui::GetCursorScreenPos();
|
||||||
ImGui::BeginChild("##C000", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 2.f), true);
|
const float headerWidth = ImGui::GetContentRegionAvail().x;
|
||||||
|
const float headerHeight = UI::Theme::HeaderHeight;
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
ImGui::Text("自瞄");
|
drawList->AddRectFilled(
|
||||||
ImGui::Separator();
|
headerPos,
|
||||||
ImGui::Spacing();
|
ImVec2(headerPos.x + headerWidth, headerPos.y + headerHeight),
|
||||||
|
UI::Theme::U32(UI::Theme::CardBackground),
|
||||||
|
UI::Theme::CardRounding);
|
||||||
|
drawList->AddRectFilled(
|
||||||
|
headerPos,
|
||||||
|
ImVec2(headerPos.x + headerWidth, headerPos.y + 2.f),
|
||||||
|
UI::Theme::U32(UI::Theme::Accent),
|
||||||
|
1.f);
|
||||||
|
|
||||||
ImGui::Checkbox("启用", &g.g_Aim);
|
ImGui::SetCursorScreenPos(ImVec2(headerPos.x + 16.f, headerPos.y + 14.f));
|
||||||
ImGui::Combo("方式", &g.g_AimMethod, AimMethodList, IM_ARRAYSIZE(AimMethodList));
|
if (icon)
|
||||||
ImGui::CustomSliderFloat("锁定范围", "##AimFov", &g.g_AimFov, 10.f, 500.f);
|
{
|
||||||
ImGui::CustomSliderFloat("平滑(0-100)", "##AimSmooth", &g.g_AimSmooth, 0.f, 100.f);
|
ImGui::PushFont(icon);
|
||||||
ImGui::CustomSliderFloat("最大距离", "##AimDist", &g.g_AimMaxDist, 50.f, 2000.f);
|
ImGui::TextColored(UI::Theme::Accent, "%s", ICON_FA_SHIELD_HALVED);
|
||||||
ImGui::Combo("瞄准部位", &g.g_AimBone, AimBoneList, IM_ARRAYSIZE(AimBoneList));
|
ImGui::PopFont();
|
||||||
|
ImGui::SameLine(0.f, 9.f);
|
||||||
|
}
|
||||||
|
ImGui::TextColored(UI::Theme::TextPrimary, "PvE 战术控制台");
|
||||||
|
ImGui::SameLine(0.f, 8.f);
|
||||||
|
ImGui::TextColored(UI::Theme::TextSecondary, "SPT-AKI 3.11.3");
|
||||||
|
ImGui::SameLine(0.f, 8.f);
|
||||||
|
ImGui::TextColored(UI::Theme::AccentStrong, "v2.1");
|
||||||
|
|
||||||
ImGui::EndChild();
|
const float statusX = headerPos.x + headerWidth * 0.53f;
|
||||||
ImGui::BeginChild("##C001", ImVec2(ImGui::GetContentRegionAvail()), true);
|
ImGui::SetCursorScreenPos(ImVec2(statusX, headerPos.y + 15.f));
|
||||||
|
UI::StatusBadge("ESP", g.g_ESP);
|
||||||
|
ImGui::SameLine(0.f, 6.f);
|
||||||
|
UI::StatusBadge("AIM", g.g_Aim);
|
||||||
|
ImGui::SameLine(0.f, 6.f);
|
||||||
|
UI::StatusBadge("RECOIL", g.g_Recoil);
|
||||||
|
ImGui::SameLine(0.f, 6.f);
|
||||||
|
UI::StatusBadge("STREAM", g.g_StreamProof);
|
||||||
|
|
||||||
ImGui::Text("鼠标模式");
|
const float hideWidth = 66.f;
|
||||||
ImGui::Separator();
|
ImGui::SetCursorScreenPos(ImVec2(headerPos.x + headerWidth - hideWidth - 12.f, headerPos.y + 12.f));
|
||||||
ImGui::Spacing();
|
if (UI::Button("隐藏", ImVec2(hideWidth, 28.f)))
|
||||||
|
g.ShowMenu = false;
|
||||||
|
|
||||||
ImGui::CustomSliderFloat("灵敏度", "##AimSens", &g.g_AimSens, 0.1f, 5.f);
|
const bool headerHovered = ImGui::IsMouseHoveringRect(
|
||||||
ImGui::CustomSliderFloat("最大速度", "##AimMaxSpeed", &g.g_AimMaxSpeed, 1.f, 300.f);
|
headerPos,
|
||||||
|
ImVec2(headerPos.x + headerWidth, headerPos.y + headerHeight));
|
||||||
|
if (headerHovered && !ImGui::IsItemHovered() && ImGui::IsMouseDragging(ImGuiMouseButton_Left))
|
||||||
|
{
|
||||||
|
const ImVec2 delta = ImGui::GetIO().MouseDelta;
|
||||||
|
const ImVec2 currentWindowPos = ImGui::GetWindowPos();
|
||||||
|
ImGui::SetWindowPos(ImVec2(currentWindowPos.x + delta.x, currentWindowPos.y + delta.y));
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::SetCursorScreenPos(ImVec2(headerPos.x, headerPos.y + headerHeight + 12.f));
|
||||||
ImGui::Spacing();
|
};
|
||||||
ImGui::Text("压枪");
|
drawHeader();
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Checkbox("启用", &g.g_Recoil);
|
if (UI::Tab("自瞄", currentPage == MenuPage::Aim))
|
||||||
ImGui::CustomSliderFloat("力度", "##RecStr", &g.g_RecoilStrength, 0.f, 20.f);
|
currentPage = MenuPage::Aim;
|
||||||
ImGui::CustomSliderInt("间隔(ms)", "##RecInt", &g.g_RecoilInterval, 1, 100);
|
ImGui::SameLine(0.f, 6.f);
|
||||||
|
if (UI::Tab("视觉", currentPage == MenuPage::Visual))
|
||||||
ImGui::EndChild();
|
currentPage = MenuPage::Visual;
|
||||||
break;
|
ImGui::SameLine(0.f, 6.f);
|
||||||
case 1: // Visual
|
if (UI::Tab("功能", currentPage == MenuPage::Features))
|
||||||
ImGui::BeginChild("##C010", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 3.f), true);
|
currentPage = MenuPage::Features;
|
||||||
|
ImGui::SameLine(0.f, 6.f);
|
||||||
ImGui::Text("视觉");
|
if (UI::Tab("系统", currentPage == MenuPage::System))
|
||||||
ImGui::Separator();
|
currentPage = MenuPage::System;
|
||||||
ImGui::Spacing();
|
if (debug)
|
||||||
|
{
|
||||||
ImGui::Checkbox("ESP", &g.g_ESP);
|
ImGui::SameLine(0.f, 6.f);
|
||||||
ImGui::Checkbox("物资ESP", &g.g_ESP_Item);
|
if (UI::Tab("调试", currentPage == MenuPage::Debug))
|
||||||
ImGui::Checkbox("尸体ESP", &g.g_ESP_Corpse);
|
currentPage = MenuPage::Debug;
|
||||||
ImGui::Checkbox("容器ESP", &g.g_ESP_Container);
|
|
||||||
ImGui::Checkbox("撤离点ESP", &g.g_ESP_Exfil);
|
|
||||||
ImGui::Checkbox("手雷ESP", &g.g_ESP_Grenade);
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
ImGui::BeginChild("##C011", ImVec2(ImGui::GetContentRegionAvail()), true);
|
|
||||||
|
|
||||||
ImGui::Text("ESP选项");
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Checkbox("连线", &g.g_ESP_Line);
|
|
||||||
ImGui::Checkbox("方框", &g.g_ESP_Box);
|
|
||||||
ImGui::Checkbox("填充方框", &g.g_ESP_BoxFilled);
|
|
||||||
ImGui::Checkbox("骨骼", &g.g_ESP_Skeleton);
|
|
||||||
ImGui::Checkbox("距离", &g.g_ESP_Distance);
|
|
||||||
ImGui::Checkbox("名称", &g.g_ESP_Name);
|
|
||||||
ImGui::Checkbox("血条", &g.g_ESP_HealthBar);
|
|
||||||
ImGui::CustomSliderFloat("物资最大距离", "##ItemDist", &g.g_ESP_MaxItemDistance, 0.f, 1000.f);
|
|
||||||
ImGui::CustomSliderFloat("容器最大距离", "##ContainerDist", &g.g_ESP_MaxContainerDistance, 0.f, 1000.f);
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
break;
|
|
||||||
case 2: // Misc
|
|
||||||
ImGui::BeginChild("##C020", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 3.f), true);
|
|
||||||
|
|
||||||
ImGui::Text("功能");
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Checkbox("无限耐力", &g.g_InfStamina);
|
|
||||||
ImGui::Checkbox("无跌落伤害", &g.g_NoFallDamage);
|
|
||||||
ImGui::Checkbox("无摇摆", &g.g_NoSway);
|
|
||||||
ImGui::Checkbox("无后座(内存)", &g.g_NoRecoil);
|
|
||||||
ImGui::Checkbox("无故障/卡弹", &g.g_NoMalfunction);
|
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::TextDisabled("只写入 dump 已确认的对象字段");
|
|
||||||
ImGui::TextDisabled("弹道追踪:暂不写入,等待 runtime 验证");
|
|
||||||
ImGui::EndChild();
|
|
||||||
|
|
||||||
ImGui::BeginChild("##C021", ImVec2(ImGui::GetContentRegionAvail()), true);
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
break;
|
|
||||||
case 3: // Setting
|
|
||||||
ImGui::BeginChild("##C030", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 3.f), true);
|
|
||||||
ImGui::Text("系统");
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Checkbox("串流隐藏", &g.g_StreamProof);
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
ImGui::BeginChild("##C031", ImVec2(ImGui::GetContentRegionAvail()), true);
|
|
||||||
|
|
||||||
ImGui::Text("准星");
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Checkbox("准星", &g.g_Crosshair);
|
|
||||||
ImGui::CustomSliderInt("准星大小", "##SizeCH", &g.g_CrosshairSize, 1, 10);
|
|
||||||
ImGui::ColorEdit4("颜色##C", &CrosshairColor.Value.x);
|
|
||||||
ImGui::Combo("类型##C", &g.g_CrosshairType, CrosshairList, IM_ARRAYSIZE(CrosshairList));
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
UI::Divider();
|
||||||
|
|
||||||
ImGui::PopStyleColor();
|
const float footerHeight = UI::Theme::FooterHeight;
|
||||||
|
if (ImGui::BeginChild(
|
||||||
ImGui::EndChild();
|
"##CFW_Content",
|
||||||
//---------------------------------------------------//
|
ImVec2(0.f, -footerHeight),
|
||||||
|
ImGuiChildFlags_None,
|
||||||
ImGui::SameLine();
|
ImGuiWindowFlags_NoBackground))
|
||||||
|
|
||||||
//---// Right //--------------------------------------//
|
|
||||||
ImGui::BeginChild("##RightChild", ImVec2(ImGui::GetContentRegionAvail()), false);
|
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.f));
|
|
||||||
|
|
||||||
switch (Index)
|
|
||||||
{
|
{
|
||||||
case 0: // AimBot
|
const float gap = UI::Theme::ColumnGap;
|
||||||
ImGui::BeginChild("##C100", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 2.f), true);
|
const float columnWidth = UI::ColumnWidth(gap);
|
||||||
|
switch (currentPage)
|
||||||
ImGui::Text("目标过滤");
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Checkbox("PMC", &g.g_AimTargetPMC);
|
|
||||||
ImGui::Checkbox("SCAV", &g.g_AimTargetScav);
|
|
||||||
ImGui::Checkbox("BOSS", &g.g_AimTargetBoss);
|
|
||||||
ImGui::Checkbox("忽略血量", &g.g_AimIgnoreHealth);
|
|
||||||
ImGui::Checkbox("强制开启(免按键)", &g.g_AimForce);
|
|
||||||
ImGui::Checkbox("显示范围圈", &g.g_AimDrawFov);
|
|
||||||
ImGui::Checkbox("显示目标线", &g.g_AimDrawTarget);
|
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
// 数据链路状态: 哪一环断了看这里
|
|
||||||
ImGui::Text("自瞄状态");
|
|
||||||
ImGui::Text("局内 : %s", runtimeInRaid ? "是" : "否");
|
|
||||||
ImGui::Text("本地 : 0x%llX", (unsigned long long)runtimeLocalAddress);
|
|
||||||
ImGui::Text("实体 : %zu", runtimeEntityCount);
|
|
||||||
ImGui::Text("扫描 : %d 实体 / %d 血量 / %d 跳过", m_diagTotal, m_diagHealth, m_diagSkipped);
|
|
||||||
ImGui::Text("目标 : %s (距 %0.fpx)", m_diagBestAddr ? "锁定" : "无", m_diagBestDist);
|
|
||||||
{
|
{
|
||||||
bool k2 = (GetAsyncKeyState(g.g_AimKey2) & 0x8000) != 0;
|
case MenuPage::Aim:
|
||||||
ImGui::Text("按键 : %s (右键/左键/F9)", (IsKeyDown(g.g_AimKey0) || IsKeyDown(g.g_AimKey1) || k2) ? "按住" : "---");
|
RenderAimPage(columnWidth, gap);
|
||||||
|
break;
|
||||||
|
case MenuPage::Visual:
|
||||||
|
RenderVisualPage(columnWidth, gap);
|
||||||
|
break;
|
||||||
|
case MenuPage::Features:
|
||||||
|
RenderFeaturesPage(columnWidth, gap);
|
||||||
|
break;
|
||||||
|
case MenuPage::System:
|
||||||
|
RenderSystemPage(columnWidth, gap, configStatus);
|
||||||
|
break;
|
||||||
|
case MenuPage::Debug:
|
||||||
|
RenderDebugPage(columnWidth, gap);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::Theme::WindowBackground);
|
||||||
ImGui::BeginChild("##C101", ImVec2(ImGui::GetContentRegionAvail()), true);
|
if (ImGui::BeginChild(
|
||||||
|
"##CFW_Footer",
|
||||||
|
ImVec2(0.f, footerHeight),
|
||||||
|
ImGuiChildFlags_AlwaysUseWindowPadding,
|
||||||
|
ImGuiWindowFlags_NoScrollbar))
|
||||||
|
{
|
||||||
|
UI::Divider();
|
||||||
|
const float rowStart = ImGui::GetCursorPosX();
|
||||||
|
const float rightEdge = rowStart + ImGui::GetContentRegionAvail().x;
|
||||||
|
constexpr float buttonWidth = 88.f;
|
||||||
|
constexpr float buttonGap = 8.f;
|
||||||
|
constexpr float actionsWidth = buttonWidth * 3.f + buttonGap * 2.f;
|
||||||
|
|
||||||
ImGui::Text("偏移调试(内存)");
|
ImGui::TextColored(
|
||||||
ImGui::Separator();
|
UI::Theme::TextSecondary,
|
||||||
ImGui::Spacing();
|
"配置 %s · %s",
|
||||||
|
Config::FileName(),
|
||||||
if (runtimeInRaid && runtimeLocalAddress != 0)
|
configStatus);
|
||||||
{
|
const float actionX = (std::max)(ImGui::GetCursorPosX() + 12.f, rightEdge - actionsWidth);
|
||||||
uintptr_t mc = m.Read<uintptr_t>(runtimeLocalAddress + (uintptr_t)g.g_AimMcOffset);
|
ImGui::SameLine(actionX);
|
||||||
if (mc)
|
if (UI::Button("读取配置", ImVec2(buttonWidth, 30.f)))
|
||||||
{
|
configStatus = Config::Load() ? "配置已读取" : "配置读取失败(文件不存在或不可读)";
|
||||||
Vector2 rot = m.Read<Vector2>(mc + (uintptr_t)g.g_AimRotOffset);
|
ImGui::SameLine(0.f, buttonGap);
|
||||||
ImGui::Text("水平 : %.3f", rot.x);
|
if (UI::Button("保存配置", ImVec2(buttonWidth, 30.f), false, true))
|
||||||
ImGui::Text("俯仰 : %.3f", rot.y);
|
configStatus = Config::Save() ? "配置已保存" : "配置保存失败";
|
||||||
ImGui::TextDisabled("移动鼠标验证偏移是否正确");
|
ImGui::SameLine(0.f, buttonGap);
|
||||||
}
|
if (UI::Button("退出", ImVec2(buttonWidth, 30.f), true))
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui::TextDisabled("MC 为空 (偏移错误?)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui::TextDisabled("仅限局内显示");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Text("偏移(十六进制)");
|
|
||||||
ImGui::InputInt("MC", &g.g_AimMcOffset, 8, 8, ImGuiInputTextFlags_CharsHexadecimal);
|
|
||||||
ImGui::InputInt("旋转", &g.g_AimRotOffset, 4, 4, ImGuiInputTextFlags_CharsHexadecimal);
|
|
||||||
|
|
||||||
// dump.txt: MovementContext = Player +0x58, Rotation_1 = MovementContext +0x27C
|
|
||||||
if (ImGui::Button("重置偏移 (0x58/0x27C)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f)))
|
|
||||||
{
|
|
||||||
g.g_AimMcOffset = 0x58;
|
|
||||||
g.g_AimRotOffset = 0x27C;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::Button("扫描旋转偏移(两步)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f)))
|
|
||||||
ScanRotationOffset();
|
|
||||||
|
|
||||||
if (m_scanPhase == 1)
|
|
||||||
ImGui::TextColored(TEXT_COLOR_ATTENTION.Value, "水平转动鼠标后,再点一次扫描");
|
|
||||||
|
|
||||||
if (m_scanResult[0])
|
|
||||||
ImGui::TextWrapped("%s", m_scanResult);
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
break;
|
|
||||||
case 1: // Visual
|
|
||||||
ImGui::BeginChild("##C110", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 2.5f), true);
|
|
||||||
|
|
||||||
ImGui::Text("ESP设置");
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::CustomSliderFloat("玩家", "##Dist", &g.g_ESP_MaxDistance, 100.f, 2000.f);
|
|
||||||
ImGui::CustomSliderFloat("物资", "##ItemDist", &g.g_ESP_MaxItemDistance, 10.f, 1000.f);
|
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Combo("方框样式", &g.g_ESP_BoxStyle, BoxTypeList, IM_ARRAYSIZE(BoxTypeList));
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
ImGui::BeginChild("##C111", ImVec2(ImGui::GetContentRegionAvail()), true);
|
|
||||||
|
|
||||||
ImGui::Text("ESP颜色");
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
if (ImGui::TreeNode("玩家/SCAV"))
|
|
||||||
{
|
|
||||||
ImGui::ColorEdit4("PMC", &Col_ESP_PMC.Value.x);
|
|
||||||
ImGui::ColorEdit4("SCAV", &Col_ESP_Scav.Value.x);
|
|
||||||
ImGui::ColorEdit4("玩家SCAV", &Col_ESP_pScav.Value.x);
|
|
||||||
ImGui::ColorEdit4("精英SCAV", &Col_ESP_SpecialScav.Value.x);
|
|
||||||
ImGui::ColorEdit4("BOSS", &Col_ESP_Boss.Value.x);
|
|
||||||
ImGui::ColorEdit4("填充", &Col_ESP_Filled.Value.x);
|
|
||||||
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
if (ImGui::TreeNode("游戏"))
|
|
||||||
{
|
|
||||||
ImGui::Text("[+] 物资");
|
|
||||||
ImGui::ColorEdit4("稀有", &Col_ESP_RareItem.Value.x);
|
|
||||||
|
|
||||||
ImGui::NewLine();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Text("[+] 撤离点");
|
|
||||||
ImGui::ColorEdit4("开启", &Col_ESP_ExfilOpen.Value.x);
|
|
||||||
ImGui::ColorEdit4("关闭", &Col_ESP_ExfilClose.Value.x);
|
|
||||||
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
break;
|
|
||||||
case 2: // Misc
|
|
||||||
ImGui::BeginChild("##C200", ImVec2(ImGui::GetContentRegionAvail()), true);
|
|
||||||
|
|
||||||
ImGui::Text("功能状态");
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
ImGui::Text("局内:%s", runtimeInRaid ? "是" : "否");
|
|
||||||
ImGui::Text("本地:0x%llX", (unsigned long long)runtimeLocalAddress);
|
|
||||||
ImGui::TextDisabled("关闭开关或离开局内会恢复原值");
|
|
||||||
ImGui::TextDisabled("非法/不完整指针链不会写入");
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
break;
|
|
||||||
case 3: // Setting
|
|
||||||
|
|
||||||
ImGui::BeginChild("##C300", ImVec2(ImGui::GetContentRegionAvail()), true);
|
|
||||||
|
|
||||||
ImGui::Text("退出");
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Spacing();
|
|
||||||
if (ImGui::Button("退出", ImVec2(ImGui::GetContentRegionAvail().x, 30.f)))
|
|
||||||
g.process_active = false;
|
g.process_active = false;
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
ImGui::EndChild();
|
|
||||||
//---------------------------------------------------//
|
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,309 @@
|
||||||
|
#include "Framework.h"
|
||||||
|
#include "UI/MenuWidgets.h"
|
||||||
|
#include "../Framework/Config/Config.h"
|
||||||
|
#include "../Framework/Input/Input.h"
|
||||||
|
#include "../Framework/Memory/Memory.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const char* kBoxTypeList[] = { "简单", "棱角" };
|
||||||
|
const char* kCrosshairList[] = { "十字", "圆圈" };
|
||||||
|
const char* kAimMethodList[] = { "内存", "鼠标" };
|
||||||
|
const char* kAimBoneList[] = { "头部", "颈部", "身体" };
|
||||||
|
|
||||||
|
void EndRow()
|
||||||
|
{
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Spacing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFramework::RenderAimPage(float columnWidth, float gap)
|
||||||
|
{
|
||||||
|
if (UI::BeginCard("##aim_basic", "基础设置", UI::Theme::Accent, columnWidth))
|
||||||
|
{
|
||||||
|
UI::Toggle("自瞄", &g.g_Aim);
|
||||||
|
UI::Combo("方式", &g.g_AimMethod, kAimMethodList, IM_ARRAYSIZE(kAimMethodList));
|
||||||
|
UI::Combo("部位", &g.g_AimBone, kAimBoneList, IM_ARRAYSIZE(kAimBoneList));
|
||||||
|
UI::SliderFloat("锁定范围", &g.g_AimFov, 10.f, 500.f);
|
||||||
|
UI::SliderFloat("平滑", &g.g_AimSmooth, 0.f, 100.f);
|
||||||
|
UI::SliderFloat("最大距离", &g.g_AimMaxDist, 50.f, 2000.f);
|
||||||
|
UI::SliderFloat("灵敏度", &g.g_AimSens, 0.1f, 5.f, "%.2f");
|
||||||
|
UI::SliderFloat("最大速度", &g.g_AimMaxSpeed, 1.f, 300.f);
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##aim_behavior", "行为设置", UI::Theme::AccentStrong, columnWidth))
|
||||||
|
{
|
||||||
|
UI::Toggle("PMC", &g.g_AimTargetPMC);
|
||||||
|
UI::Toggle("SCAV", &g.g_AimTargetScav);
|
||||||
|
UI::Toggle("BOSS", &g.g_AimTargetBoss);
|
||||||
|
UI::Toggle("忽略血量", &g.g_AimIgnoreHealth);
|
||||||
|
UI::Toggle("强制开启(免按键)", &g.g_AimForce);
|
||||||
|
UI::Toggle("显示范围圈", &g.g_AimDrawFov);
|
||||||
|
UI::Toggle("显示目标线", &g.g_AimDrawTarget);
|
||||||
|
ImGui::Spacing();
|
||||||
|
UI::StatusBadge("自瞄", g.g_Aim);
|
||||||
|
ImGui::SameLine(0.f, 8.f);
|
||||||
|
UI::StatusBadge("压枪", g.g_Recoil);
|
||||||
|
UI::Note("强制开启会绕过触发键;范围圈和目标线只影响可视化。 ");
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
|
||||||
|
if (UI::BeginCard("##aim_recoil", "压枪 / 后座", ImVec4(0.95f, 0.62f, 0.31f, 1.f), columnWidth))
|
||||||
|
{
|
||||||
|
UI::Toggle("压枪", &g.g_Recoil);
|
||||||
|
UI::SliderFloat("压枪力度", &g.g_RecoilStrength, 0.f, 20.f);
|
||||||
|
UI::SliderInt("压枪间隔(ms)", &g.g_RecoilInterval, 1, 100);
|
||||||
|
UI::Note("力度和间隔直接绑定现有压枪参数,不改变原有更新路径。 ");
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##aim_hotkeys", "按键绑定", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth))
|
||||||
|
{
|
||||||
|
UI::Hotkey("菜单键", &g.g_MenuKey);
|
||||||
|
UI::Hotkey("触发键 1", &g.g_AimKey0, g.g_MenuKey);
|
||||||
|
UI::Hotkey("触发键 2", &g.g_AimKey1, g.g_MenuKey);
|
||||||
|
UI::Hotkey("备用键", &g.g_AimKey2, g.g_MenuKey);
|
||||||
|
UI::Note("点击右侧按键框后输入新键;显示 Press a key... 时按 Escape 取消。 ");
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFramework::RenderVisualPage(float columnWidth, float gap)
|
||||||
|
{
|
||||||
|
if (UI::BeginCard("##visual_modules", "ESP 模块", UI::Theme::Accent, columnWidth))
|
||||||
|
{
|
||||||
|
UI::Toggle("ESP", &g.g_ESP);
|
||||||
|
UI::Toggle("物资 ESP", &g.g_ESP_Item);
|
||||||
|
UI::Toggle("尸体 ESP", &g.g_ESP_Corpse);
|
||||||
|
UI::Toggle("容器 ESP", &g.g_ESP_Container);
|
||||||
|
UI::Toggle("撤离点 ESP", &g.g_ESP_Exfil);
|
||||||
|
UI::Toggle("手雷 ESP", &g.g_ESP_Grenade);
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##visual_options", "显示选项", UI::Theme::AccentStrong, columnWidth))
|
||||||
|
{
|
||||||
|
UI::Toggle("连线", &g.g_ESP_Line);
|
||||||
|
UI::Toggle("方框", &g.g_ESP_Box);
|
||||||
|
UI::Toggle("填充方框", &g.g_ESP_BoxFilled);
|
||||||
|
UI::Toggle("骨骼", &g.g_ESP_Skeleton);
|
||||||
|
UI::Toggle("距离", &g.g_ESP_Distance);
|
||||||
|
UI::Toggle("名称", &g.g_ESP_Name);
|
||||||
|
UI::Toggle("血条", &g.g_ESP_HealthBar);
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
|
||||||
|
if (UI::BeginCard("##visual_distance", "距离与样式", ImVec4(0.95f, 0.62f, 0.31f, 1.f), columnWidth))
|
||||||
|
{
|
||||||
|
UI::SliderFloat("玩家距离", &g.g_ESP_MaxDistance, 100.f, 2000.f);
|
||||||
|
UI::SliderFloat("物资距离", &g.g_ESP_MaxItemDistance, 0.f, 1000.f);
|
||||||
|
UI::SliderFloat("容器距离", &g.g_ESP_MaxContainerDistance, 0.f, 1000.f);
|
||||||
|
UI::Combo("方框样式", &g.g_ESP_BoxStyle, kBoxTypeList, IM_ARRAYSIZE(kBoxTypeList));
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##visual_crosshair", "准星", ImVec4(0.78f, 0.55f, 0.95f, 1.f), columnWidth))
|
||||||
|
{
|
||||||
|
UI::Toggle("准星", &g.g_Crosshair);
|
||||||
|
UI::SliderInt("准星大小", &g.g_CrosshairSize, 1, 10);
|
||||||
|
UI::ColorEdit("准星颜色", &CrosshairColor.Value);
|
||||||
|
UI::Combo("准星类型", &g.g_CrosshairType, kCrosshairList, IM_ARRAYSIZE(kCrosshairList));
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
|
||||||
|
if (UI::BeginCard("##visual_player_colors", "玩家颜色", UI::Theme::Accent, columnWidth))
|
||||||
|
{
|
||||||
|
UI::ColorEdit("PMC", &Col_ESP_PMC.Value);
|
||||||
|
UI::ColorEdit("SCAV", &Col_ESP_Scav.Value);
|
||||||
|
UI::ColorEdit("玩家 SCAV", &Col_ESP_pScav.Value);
|
||||||
|
UI::ColorEdit("精英 SCAV", &Col_ESP_SpecialScav.Value);
|
||||||
|
UI::ColorEdit("BOSS", &Col_ESP_Boss.Value);
|
||||||
|
UI::ColorEdit("填充", &Col_ESP_Filled.Value);
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##visual_object_colors", "游戏对象颜色", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth))
|
||||||
|
{
|
||||||
|
UI::ColorEdit("稀有物资", &Col_ESP_RareItem.Value);
|
||||||
|
UI::ColorEdit("撤离点开启", &Col_ESP_ExfilOpen.Value);
|
||||||
|
UI::ColorEdit("撤离点关闭", &Col_ESP_ExfilClose.Value);
|
||||||
|
UI::Note("颜色编辑仍写入现有 ImColor 对象,渲染端无需改动。 ");
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFramework::RenderFeaturesPage(float columnWidth, float gap)
|
||||||
|
{
|
||||||
|
if (UI::BeginCard("##features_player", "角色增强", UI::Theme::Accent, columnWidth))
|
||||||
|
{
|
||||||
|
UI::Toggle("无限耐力", &g.g_InfStamina);
|
||||||
|
UI::Toggle("无跌落伤害", &g.g_NoFallDamage);
|
||||||
|
UI::Toggle("无摇摆", &g.g_NoSway);
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##features_weapon", "武器增强", UI::Theme::AccentStrong, columnWidth))
|
||||||
|
{
|
||||||
|
UI::Toggle("无后座(内存)", &g.g_NoRecoil);
|
||||||
|
UI::Toggle("无故障 / 卡弹", &g.g_NoMalfunction);
|
||||||
|
ImGui::Spacing();
|
||||||
|
UI::StatusBadge("无限耐力", g.g_InfStamina);
|
||||||
|
ImGui::SameLine(0.f, 8.f);
|
||||||
|
UI::StatusBadge("无后座", g.g_NoRecoil);
|
||||||
|
ImGui::SameLine(0.f, 8.f);
|
||||||
|
UI::StatusBadge("无故障", g.g_NoMalfunction);
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFramework::RenderSystemPage(float columnWidth, float gap, const char*& configStatus)
|
||||||
|
{
|
||||||
|
if (UI::BeginCard("##system_switches", "系统", UI::Theme::Accent, columnWidth))
|
||||||
|
{
|
||||||
|
UI::Toggle("串流隐藏", &g.g_StreamProof);
|
||||||
|
UI::Toggle("调试模式", &g.g_DebugMode);
|
||||||
|
UI::Toggle("准星", &g.g_Crosshair);
|
||||||
|
UI::Note("调试模式打开后会显示额外的偏移扫描与运行时诊断页。 ");
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##system_config", "配置", ImVec4(0.95f, 0.62f, 0.31f, 1.f), columnWidth))
|
||||||
|
{
|
||||||
|
ImGui::TextColored(UI::Theme::TextPrimary, "文件 %s", Config::FileName());
|
||||||
|
if (UI::Button("读取配置", ImVec2(112.f, 28.f)))
|
||||||
|
configStatus = Config::Load() ? "配置已读取" : "配置读取失败(文件不存在或不可读)";
|
||||||
|
ImGui::SameLine(0.f, 8.f);
|
||||||
|
if (UI::Button("保存配置", ImVec2(112.f, 28.f), false, true))
|
||||||
|
configStatus = Config::Save() ? "配置已保存" : "配置保存失败";
|
||||||
|
UI::Note(configStatus);
|
||||||
|
UI::Note("配置文件保存/读取路径和字段集合保持不变。 ");
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
|
||||||
|
if (UI::BeginCard("##system_hotkeys", "快捷键", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth))
|
||||||
|
{
|
||||||
|
UI::Hotkey("菜单键", &g.g_MenuKey);
|
||||||
|
UI::Hotkey("触发键 1", &g.g_AimKey0, g.g_MenuKey);
|
||||||
|
UI::Hotkey("触发键 2", &g.g_AimKey1, g.g_MenuKey);
|
||||||
|
UI::Hotkey("备用键", &g.g_AimKey2, g.g_MenuKey);
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##system_crosshair", "准星", UI::Theme::AccentStrong, columnWidth))
|
||||||
|
{
|
||||||
|
UI::SliderInt("准星大小", &g.g_CrosshairSize, 1, 10);
|
||||||
|
UI::ColorEdit("准星颜色", &CrosshairColor.Value);
|
||||||
|
UI::Combo("准星类型", &g.g_CrosshairType, kCrosshairList, IM_ARRAYSIZE(kCrosshairList));
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
|
||||||
|
if (UI::BeginCard("##system_about", "关于", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth))
|
||||||
|
{
|
||||||
|
ImGui::TextColored(UI::Theme::TextPrimary, "PvE 战术控制台 v2.1");
|
||||||
|
UI::Note("SPT-AKI 3.11.3 / 只读快照渲染");
|
||||||
|
UI::Note("调试页签仅在调试模式开启时出现。 ");
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##system_status", "运行状态", UI::Theme::Accent, columnWidth))
|
||||||
|
{
|
||||||
|
UI::StatusBadge("ESP", g.g_ESP);
|
||||||
|
ImGui::SameLine(0.f, 8.f);
|
||||||
|
UI::StatusBadge("串流", g.g_StreamProof);
|
||||||
|
ImGui::SameLine(0.f, 8.f);
|
||||||
|
UI::StatusBadge("调试", g.g_DebugMode);
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFramework::RenderDebugPage(float columnWidth, float gap)
|
||||||
|
{
|
||||||
|
if (UI::BeginCard("##debug_offsets", "偏移调试(内存)", UI::Theme::Danger, columnWidth))
|
||||||
|
{
|
||||||
|
ImGui::TextColored(UI::Theme::TextSecondary, "运行时偏移仅在调试页修改,绑定保持原样。");
|
||||||
|
ImGui::SetNextItemWidth(-1.f);
|
||||||
|
ImGui::InputInt("MC", &g.g_AimMcOffset, 8, 8, ImGuiInputTextFlags_CharsHexadecimal);
|
||||||
|
ImGui::SetNextItemWidth(-1.f);
|
||||||
|
ImGui::InputInt("旋转", &g.g_AimRotOffset, 4, 4, ImGuiInputTextFlags_CharsHexadecimal);
|
||||||
|
if (UI::Button("重置偏移(0x58 / 0x27C)", ImVec2(-1.f, 28.f)))
|
||||||
|
{
|
||||||
|
g.g_AimMcOffset = 0x58;
|
||||||
|
g.g_AimRotOffset = 0x27C;
|
||||||
|
}
|
||||||
|
if (UI::Button("扫描旋转偏移(两步)", ImVec2(-1.f, 28.f)))
|
||||||
|
ScanRotationOffset();
|
||||||
|
if (m_scanPhase == 1)
|
||||||
|
ImGui::TextColored(TEXT_COLOR_ATTENTION.Value, "水平转动鼠标后,再点一次扫描");
|
||||||
|
if (m_scanResult[0])
|
||||||
|
ImGui::TextWrapped("%s", m_scanResult);
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
UI::NextColumn(gap);
|
||||||
|
|
||||||
|
if (UI::BeginCard("##debug_status", "自瞄状态", UI::Theme::Danger, columnWidth))
|
||||||
|
{
|
||||||
|
const auto runtimeSnapshot = GetDynamicSnapshot();
|
||||||
|
const bool runtimeFresh = runtimeSnapshot && IsSnapshotFresh(runtimeSnapshot->capturedAt);
|
||||||
|
const bool runtimeInRaid = runtimeFresh && runtimeSnapshot->inRaid;
|
||||||
|
const uintptr_t runtimeLocalAddress = runtimeFresh ? runtimeSnapshot->local.m_address : 0;
|
||||||
|
const std::size_t runtimeEntityCount = runtimeFresh ? runtimeSnapshot->players.size() : 0;
|
||||||
|
|
||||||
|
ImGui::Text("局内 : %s", runtimeInRaid ? "是" : "否");
|
||||||
|
ImGui::Text("本地 : 0x%llX", static_cast<unsigned long long>(runtimeLocalAddress));
|
||||||
|
ImGui::Text("实体 : %zu", runtimeEntityCount);
|
||||||
|
ImGui::Text("扫描 : %d 实体 / %d 血量 / %d 跳过", m_diagTotal, m_diagHealth, m_diagSkipped);
|
||||||
|
ImGui::Text("目标 : %s(距 %.0fpx)", m_diagBestAddr ? "锁定" : "无", m_diagBestDist);
|
||||||
|
const bool k2 = (GetAsyncKeyState(g.g_AimKey2) & 0x8000) != 0;
|
||||||
|
ImGui::Text("按键 : %s(当前触发键)",
|
||||||
|
(IsKeyDown(g.g_AimKey0) || IsKeyDown(g.g_AimKey1) || k2) ? "按住" : "---");
|
||||||
|
|
||||||
|
if (runtimeInRaid && runtimeLocalAddress != 0)
|
||||||
|
{
|
||||||
|
const uintptr_t mc = m.Read<uintptr_t>(runtimeLocalAddress + static_cast<uintptr_t>(g.g_AimMcOffset));
|
||||||
|
if (mc)
|
||||||
|
{
|
||||||
|
const Vector2 rot = m.Read<Vector2>(mc + static_cast<uintptr_t>(g.g_AimRotOffset));
|
||||||
|
ImGui::Text("水平 : %.3f", rot.x);
|
||||||
|
ImGui::Text("俯仰 : %.3f", rot.y);
|
||||||
|
UI::Note("移动鼠标验证偏移是否正确。 ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UI::Note("MC 为空(偏移错误?)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UI::Note("仅限局内显示。 ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
EndRow();
|
||||||
|
|
||||||
|
if (UI::BeginCard("##debug_notes", "调试说明", UI::Theme::Danger, columnWidth))
|
||||||
|
{
|
||||||
|
UI::Note("此页仅调试模式可见。 ");
|
||||||
|
UI::Note("偏移调试、扫描与诊断读取都在此页。 ");
|
||||||
|
}
|
||||||
|
UI::EndCard();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
#include "MenuTheme.h"
|
||||||
|
|
||||||
|
namespace UI::Theme
|
||||||
|
{
|
||||||
|
const ImVec4 WindowBackground{ 0.067f, 0.075f, 0.094f, 1.f }; // #111318
|
||||||
|
const ImVec4 CardBackground{ 0.090f, 0.102f, 0.125f, 1.f }; // #171A20
|
||||||
|
const ImVec4 SecondaryBackground{ 0.114f, 0.129f, 0.161f, 1.f }; // #1D2129
|
||||||
|
const ImVec4 SecondaryHover{ 0.150f, 0.173f, 0.216f, 1.f };
|
||||||
|
const ImVec4 TextPrimary{ 0.910f, 0.922f, 0.941f, 1.f }; // #E8EBF0
|
||||||
|
const ImVec4 TextSecondary{ 0.545f, 0.576f, 0.647f, 1.f }; // #8B93A5
|
||||||
|
const ImVec4 Accent{ 0.286f, 0.827f, 0.729f, 1.f }; // cyan teal
|
||||||
|
const ImVec4 AccentStrong{ 0.400f, 0.898f, 0.804f, 1.f };
|
||||||
|
const ImVec4 AccentSoft{ 0.286f, 0.827f, 0.729f, 0.18f };
|
||||||
|
const ImVec4 Border{ 1.f, 1.f, 1.f, 0.07f };
|
||||||
|
const ImVec4 ToggleOff{ 0.235f, 0.255f, 0.306f, 1.f };
|
||||||
|
const ImVec4 Danger{ 0.930f, 0.330f, 0.350f, 1.f };
|
||||||
|
const ImVec4 DangerSoft{ 0.390f, 0.150f, 0.170f, 1.f };
|
||||||
|
|
||||||
|
ImU32 U32(const ImVec4& color)
|
||||||
|
{
|
||||||
|
return ImGui::GetColorU32(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyTheme()
|
||||||
|
{
|
||||||
|
ImGuiStyle& style = ImGui::GetStyle();
|
||||||
|
style.WindowRounding = WindowRounding;
|
||||||
|
style.ChildRounding = CardRounding;
|
||||||
|
style.FrameRounding = FrameRounding;
|
||||||
|
style.PopupRounding = PopupRounding;
|
||||||
|
style.ScrollbarRounding = ScrollbarRounding;
|
||||||
|
style.GrabRounding = GrabRounding;
|
||||||
|
style.WindowPadding = ImVec2(16.f, 14.f);
|
||||||
|
style.FramePadding = ImVec2(10.f, 7.f);
|
||||||
|
style.ItemSpacing = ImVec2(10.f, 8.f);
|
||||||
|
style.ItemInnerSpacing = ImVec2(8.f, 6.f);
|
||||||
|
style.ScrollbarSize = 10.f;
|
||||||
|
style.GrabMinSize = 10.f;
|
||||||
|
style.WindowBorderSize = 0.f;
|
||||||
|
style.ChildBorderSize = 1.f;
|
||||||
|
style.FrameBorderSize = 0.f;
|
||||||
|
style.PopupBorderSize = 1.f;
|
||||||
|
|
||||||
|
style.Colors[ImGuiCol_WindowBg] = WindowBackground;
|
||||||
|
style.Colors[ImGuiCol_ChildBg] = CardBackground;
|
||||||
|
style.Colors[ImGuiCol_PopupBg] = SecondaryBackground;
|
||||||
|
style.Colors[ImGuiCol_Border] = Border;
|
||||||
|
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.f, 0.f, 0.f, 0.f);
|
||||||
|
style.Colors[ImGuiCol_FrameBg] = SecondaryBackground;
|
||||||
|
style.Colors[ImGuiCol_FrameBgHovered] = SecondaryHover;
|
||||||
|
style.Colors[ImGuiCol_FrameBgActive] = AccentSoft;
|
||||||
|
style.Colors[ImGuiCol_TitleBg] = WindowBackground;
|
||||||
|
style.Colors[ImGuiCol_TitleBgActive] = WindowBackground;
|
||||||
|
style.Colors[ImGuiCol_Button] = SecondaryBackground;
|
||||||
|
style.Colors[ImGuiCol_ButtonHovered] = SecondaryHover;
|
||||||
|
style.Colors[ImGuiCol_ButtonActive] = AccentSoft;
|
||||||
|
style.Colors[ImGuiCol_Header] = SecondaryBackground;
|
||||||
|
style.Colors[ImGuiCol_HeaderHovered] = SecondaryHover;
|
||||||
|
style.Colors[ImGuiCol_HeaderActive] = AccentSoft;
|
||||||
|
style.Colors[ImGuiCol_SliderGrab] = Accent;
|
||||||
|
style.Colors[ImGuiCol_SliderGrabActive] = AccentStrong;
|
||||||
|
style.Colors[ImGuiCol_CheckMark] = Accent;
|
||||||
|
style.Colors[ImGuiCol_Separator] = Border;
|
||||||
|
style.Colors[ImGuiCol_SeparatorHovered] = AccentSoft;
|
||||||
|
style.Colors[ImGuiCol_SeparatorActive] = Accent;
|
||||||
|
style.Colors[ImGuiCol_Text] = TextPrimary;
|
||||||
|
style.Colors[ImGuiCol_TextDisabled] = TextSecondary;
|
||||||
|
style.Colors[ImGuiCol_TextSelectedBg] = AccentSoft;
|
||||||
|
style.Colors[ImGuiCol_NavHighlight] = AccentSoft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../Framework/ImGui/imgui.h"
|
||||||
|
|
||||||
|
namespace UI::Theme
|
||||||
|
{
|
||||||
|
extern const ImVec4 WindowBackground;
|
||||||
|
extern const ImVec4 CardBackground;
|
||||||
|
extern const ImVec4 SecondaryBackground;
|
||||||
|
extern const ImVec4 SecondaryHover;
|
||||||
|
extern const ImVec4 TextPrimary;
|
||||||
|
extern const ImVec4 TextSecondary;
|
||||||
|
extern const ImVec4 Accent;
|
||||||
|
extern const ImVec4 AccentStrong;
|
||||||
|
extern const ImVec4 AccentSoft;
|
||||||
|
extern const ImVec4 Border;
|
||||||
|
extern const ImVec4 ToggleOff;
|
||||||
|
extern const ImVec4 Danger;
|
||||||
|
extern const ImVec4 DangerSoft;
|
||||||
|
|
||||||
|
constexpr float WindowRounding = 10.f;
|
||||||
|
constexpr float CardRounding = 8.f;
|
||||||
|
constexpr float FrameRounding = 6.f;
|
||||||
|
constexpr float PopupRounding = 8.f;
|
||||||
|
constexpr float ScrollbarRounding = 6.f;
|
||||||
|
constexpr float GrabRounding = 6.f;
|
||||||
|
constexpr float HeaderHeight = 52.f;
|
||||||
|
constexpr float FooterHeight = 80.f;
|
||||||
|
constexpr float ColumnGap = 14.f;
|
||||||
|
|
||||||
|
ImU32 U32(const ImVec4& color);
|
||||||
|
void ApplyTheme();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,447 @@
|
||||||
|
#include "MenuWidgets.h"
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::unordered_map<ImGuiID, float> g_toggleAnimation;
|
||||||
|
ImGuiID g_captureId = 0;
|
||||||
|
bool g_captureArmed = false;
|
||||||
|
|
||||||
|
ImVec4 Mix(const ImVec4& a, const ImVec4& b, float t)
|
||||||
|
{
|
||||||
|
return ImVec4(
|
||||||
|
a.x + (b.x - a.x) * t,
|
||||||
|
a.y + (b.y - a.y) * t,
|
||||||
|
a.z + (b.z - a.z) * t,
|
||||||
|
a.w + (b.w - a.w) * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnyVirtualKeyDown()
|
||||||
|
{
|
||||||
|
for (int vk = 1; vk < 256; ++vk)
|
||||||
|
{
|
||||||
|
if ((GetAsyncKeyState(vk) & 0x8000) != 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadVirtualKey()
|
||||||
|
{
|
||||||
|
for (int vk = 1; vk < 256; ++vk)
|
||||||
|
{
|
||||||
|
if ((GetAsyncKeyState(vk) & 0x8000) != 0)
|
||||||
|
return vk;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string VirtualKeyName(int vk)
|
||||||
|
{
|
||||||
|
switch (vk)
|
||||||
|
{
|
||||||
|
case VK_LBUTTON: return "LMB";
|
||||||
|
case VK_RBUTTON: return "RMB";
|
||||||
|
case VK_MBUTTON: return "MMB";
|
||||||
|
case VK_XBUTTON1: return "X1";
|
||||||
|
case VK_XBUTTON2: return "X2";
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char keyName[64]{};
|
||||||
|
const UINT scanCode = MapVirtualKeyA(static_cast<UINT>(vk), MAPVK_VK_TO_VSC);
|
||||||
|
LONG keyData = static_cast<LONG>(scanCode << 16);
|
||||||
|
switch (vk)
|
||||||
|
{
|
||||||
|
case VK_LEFT:
|
||||||
|
case VK_UP:
|
||||||
|
case VK_RIGHT:
|
||||||
|
case VK_DOWN:
|
||||||
|
case VK_PRIOR:
|
||||||
|
case VK_NEXT:
|
||||||
|
case VK_END:
|
||||||
|
case VK_HOME:
|
||||||
|
case VK_INSERT:
|
||||||
|
case VK_DELETE:
|
||||||
|
case VK_DIVIDE:
|
||||||
|
case VK_NUMLOCK:
|
||||||
|
keyData |= 1 << 24;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetKeyNameTextA(keyData, keyName, static_cast<int>(sizeof(keyName))) > 0)
|
||||||
|
return keyName;
|
||||||
|
|
||||||
|
char fallback[16]{};
|
||||||
|
sprintf_s(fallback, "VK %d", vk);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawSliderTrack(const char* label, float& fraction, const char* valueText)
|
||||||
|
{
|
||||||
|
ImGui::PushID(label);
|
||||||
|
const float rowStart = ImGui::GetCursorPosX();
|
||||||
|
const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
|
||||||
|
|
||||||
|
ImGui::TextUnformatted(label);
|
||||||
|
const float labelEnd = ImGui::GetCursorPosX();
|
||||||
|
const float valueWidth = 54.f;
|
||||||
|
const float gap = 8.f;
|
||||||
|
ImGui::SameLine(labelEnd + 10.f);
|
||||||
|
const float barStartX = ImGui::GetCursorPosX();
|
||||||
|
const float barWidth = (std::max)(64.f, contentRight - barStartX - valueWidth - gap);
|
||||||
|
const ImVec2 barPos = ImGui::GetCursorScreenPos();
|
||||||
|
ImGui::InvisibleButton("##slider", ImVec2(barWidth, 24.f));
|
||||||
|
|
||||||
|
const bool hovered = ImGui::IsItemHovered();
|
||||||
|
const bool active = ImGui::IsItemActive();
|
||||||
|
bool changed = false;
|
||||||
|
if ((hovered || active) && ImGui::IsMouseDown(ImGuiMouseButton_Left))
|
||||||
|
{
|
||||||
|
const float next = (std::clamp)((ImGui::GetIO().MousePos.x - barPos.x) / barWidth, 0.f, 1.f);
|
||||||
|
changed = next != fraction;
|
||||||
|
fraction = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 trackMin(barPos.x, barPos.y + 9.f);
|
||||||
|
const ImVec2 trackMax(barPos.x + barWidth, barPos.y + 15.f);
|
||||||
|
const ImVec2 fillMax(barPos.x + barWidth * fraction, barPos.y + 15.f);
|
||||||
|
const ImU32 trackColor = UI::Theme::U32(hovered ? UI::Theme::SecondaryHover : UI::Theme::ToggleOff);
|
||||||
|
const ImU32 fillColor = UI::Theme::U32(hovered ? UI::Theme::AccentStrong : UI::Theme::Accent);
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
drawList->AddRectFilled(trackMin, trackMax, trackColor, 3.f);
|
||||||
|
if (fraction > 0.f)
|
||||||
|
drawList->AddRectFilled(trackMin, fillMax, fillColor, 3.f);
|
||||||
|
drawList->AddCircleFilled(ImVec2(fillMax.x, barPos.y + 12.f), hovered || active ? 5.f : 4.f, fillColor, 16);
|
||||||
|
|
||||||
|
ImGui::SameLine(0.f, gap);
|
||||||
|
ImGui::TextColored(UI::Theme::TextPrimary, "%s", valueText);
|
||||||
|
ImGui::PopID();
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace UI
|
||||||
|
{
|
||||||
|
bool BeginCard(const char* id, const char* title, const ImVec4& accent, float width)
|
||||||
|
{
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, Theme::CardBackground);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Border, Theme::Border);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, Theme::CardRounding);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.f);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.f, 12.f));
|
||||||
|
|
||||||
|
const float cardWidth = width > 0.f ? width : ImGui::GetContentRegionAvail().x;
|
||||||
|
const bool visible = ImGui::BeginChild(
|
||||||
|
id,
|
||||||
|
ImVec2(cardWidth, 0.f),
|
||||||
|
ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysUseWindowPadding,
|
||||||
|
ImGuiWindowFlags_NoScrollbar);
|
||||||
|
if (visible)
|
||||||
|
SectionTitle(title, accent);
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndCard()
|
||||||
|
{
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::PopStyleVar(3);
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Toggle(const char* label, bool* value)
|
||||||
|
{
|
||||||
|
if (!value)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ImGui::PushID(label);
|
||||||
|
const float rowStart = ImGui::GetCursorPosX();
|
||||||
|
const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
|
||||||
|
const float switchWidth = 44.f;
|
||||||
|
const float switchHeight = 24.f;
|
||||||
|
|
||||||
|
ImGui::TextColored(Theme::TextPrimary, "%s", label);
|
||||||
|
const float labelEnd = ImGui::GetCursorPosX();
|
||||||
|
ImGui::SameLine((std::max)(labelEnd + 8.f, contentRight - switchWidth));
|
||||||
|
const ImVec2 switchPos = ImGui::GetCursorScreenPos();
|
||||||
|
const bool clicked = ImGui::InvisibleButton("##toggle", ImVec2(switchWidth, switchHeight));
|
||||||
|
if (clicked)
|
||||||
|
*value = !*value;
|
||||||
|
|
||||||
|
const ImGuiID animationId = ImGui::GetID("##toggle");
|
||||||
|
float& transition = g_toggleAnimation[animationId];
|
||||||
|
const float target = *value ? 1.f : 0.f;
|
||||||
|
transition += (target - transition) * (std::min)(1.f, ImGui::GetIO().DeltaTime * 14.f);
|
||||||
|
if (std::abs(target - transition) < 0.001f)
|
||||||
|
transition = target;
|
||||||
|
|
||||||
|
const ImVec4 track = Mix(Theme::ToggleOff, Theme::Accent, transition);
|
||||||
|
const ImVec2 trackMax(switchPos.x + switchWidth, switchPos.y + switchHeight);
|
||||||
|
const float knobX = switchPos.x + 4.f + transition * (switchWidth - switchHeight);
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
drawList->AddRectFilled(switchPos, trackMax, Theme::U32(track), switchHeight * 0.5f);
|
||||||
|
drawList->AddCircleFilled(
|
||||||
|
ImVec2(knobX + (switchHeight - 8.f) * 0.5f, switchPos.y + switchHeight * 0.5f),
|
||||||
|
(switchHeight - 8.f) * 0.5f,
|
||||||
|
Theme::U32(Theme::TextPrimary),
|
||||||
|
16);
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SliderFloat(const char* label, float* value, float vmin, float vmax, const char* format)
|
||||||
|
{
|
||||||
|
if (!value || vmax <= vmin)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char valueText[32]{};
|
||||||
|
sprintf_s(valueText, format, *value);
|
||||||
|
float fraction = (std::clamp)((*value - vmin) / (vmax - vmin), 0.f, 1.f);
|
||||||
|
const bool changed = DrawSliderTrack(label, fraction, valueText);
|
||||||
|
if (changed)
|
||||||
|
*value = vmin + fraction * (vmax - vmin);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SliderInt(const char* label, int* value, int vmin, int vmax, const char* format)
|
||||||
|
{
|
||||||
|
if (!value || vmax <= vmin)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char valueText[32]{};
|
||||||
|
sprintf_s(valueText, format, *value);
|
||||||
|
float fraction = (std::clamp)(static_cast<float>(*value - vmin) / static_cast<float>(vmax - vmin), 0.f, 1.f);
|
||||||
|
const bool changed = DrawSliderTrack(label, fraction, valueText);
|
||||||
|
if (changed)
|
||||||
|
*value = vmin + static_cast<int>(fraction * static_cast<float>(vmax - vmin) + 0.5f);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Combo(const char* label, int* value, const char* const items[], int count)
|
||||||
|
{
|
||||||
|
if (!value || !items || count <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ImGui::PushID(label);
|
||||||
|
const float rowStart = ImGui::GetCursorPosX();
|
||||||
|
const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
|
||||||
|
ImGui::TextColored(Theme::TextPrimary, "%s", label);
|
||||||
|
const float labelEnd = ImGui::GetCursorPosX();
|
||||||
|
const float comboWidth = (std::min)(160.f, (std::max)(92.f, contentRight - labelEnd - 10.f));
|
||||||
|
ImGui::SameLine(contentRight - comboWidth);
|
||||||
|
ImGui::SetNextItemWidth(comboWidth);
|
||||||
|
const bool changed = ImGui::Combo("##combo", value, items, count);
|
||||||
|
ImGui::PopID();
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorEdit(const char* label, ImVec4* color)
|
||||||
|
{
|
||||||
|
if (!color)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ImGui::PushID(label);
|
||||||
|
const float rowStart = ImGui::GetCursorPosX();
|
||||||
|
const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
|
||||||
|
const float swatchWidth = 28.f;
|
||||||
|
ImGui::TextColored(Theme::TextPrimary, "%s", label);
|
||||||
|
const float labelEnd = ImGui::GetCursorPosX();
|
||||||
|
ImGui::SameLine((std::max)(labelEnd + 8.f, contentRight - swatchWidth));
|
||||||
|
const ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||||
|
const bool clicked = ImGui::InvisibleButton("##color", ImVec2(swatchWidth, 24.f));
|
||||||
|
if (clicked)
|
||||||
|
ImGui::OpenPopup("##color_popup");
|
||||||
|
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
drawList->AddRectFilled(pos, ImVec2(pos.x + swatchWidth, pos.y + 24.f), ImGui::GetColorU32(*color), 5.f);
|
||||||
|
drawList->AddRect(pos, ImVec2(pos.x + swatchWidth, pos.y + 24.f), Theme::U32(Theme::Border), 5.f);
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
if (ImGui::BeginPopup("##color_popup"))
|
||||||
|
{
|
||||||
|
changed = ImGui::ColorEdit4("##picker", &color->x, ImGuiColorEditFlags_NoInputs);
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Hotkey(const char* label, int* value, int blockedKey)
|
||||||
|
{
|
||||||
|
if (!value)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ImGui::PushID(label);
|
||||||
|
const ImGuiID id = ImGui::GetID("##hotkey");
|
||||||
|
const float rowStart = ImGui::GetCursorPosX();
|
||||||
|
const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
|
||||||
|
const float controlWidth = 132.f;
|
||||||
|
const float controlHeight = 26.f;
|
||||||
|
ImGui::TextColored(Theme::TextPrimary, "%s", label);
|
||||||
|
const float labelEnd = ImGui::GetCursorPosX();
|
||||||
|
ImGui::SameLine((std::max)(labelEnd + 8.f, contentRight - controlWidth));
|
||||||
|
const ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||||
|
const bool clicked = ImGui::InvisibleButton("##hotkey", ImVec2(controlWidth, controlHeight));
|
||||||
|
if (clicked)
|
||||||
|
{
|
||||||
|
g_captureId = id;
|
||||||
|
g_captureArmed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_captureId == id)
|
||||||
|
{
|
||||||
|
if (!g_captureArmed)
|
||||||
|
{
|
||||||
|
if (!AnyVirtualKeyDown())
|
||||||
|
g_captureArmed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const int captured = ReadVirtualKey();
|
||||||
|
if (captured != 0)
|
||||||
|
{
|
||||||
|
if (captured == VK_ESCAPE)
|
||||||
|
{
|
||||||
|
g_captureId = 0;
|
||||||
|
}
|
||||||
|
else if (captured != blockedKey)
|
||||||
|
{
|
||||||
|
*value = captured;
|
||||||
|
g_captureId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool waiting = g_captureId == id;
|
||||||
|
const std::string text = waiting ? "Press a key..." : VirtualKeyName(*value);
|
||||||
|
const ImU32 fill = waiting ? Theme::U32(Theme::AccentSoft) : Theme::U32(Theme::SecondaryBackground);
|
||||||
|
const ImU32 border = waiting ? Theme::U32(Theme::Accent) : Theme::U32(Theme::Border);
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
drawList->AddRectFilled(pos, ImVec2(pos.x + controlWidth, pos.y + controlHeight), fill, 6.f);
|
||||||
|
drawList->AddRect(pos, ImVec2(pos.x + controlWidth, pos.y + controlHeight), border, 6.f);
|
||||||
|
const ImVec2 textSize = ImGui::CalcTextSize(text.c_str());
|
||||||
|
drawList->AddText(
|
||||||
|
ImVec2(pos.x + (controlWidth - textSize.x) * 0.5f, pos.y + (controlHeight - textSize.y) * 0.5f),
|
||||||
|
Theme::U32(waiting ? Theme::AccentStrong : Theme::TextPrimary),
|
||||||
|
text.c_str());
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tab(const char* label, bool active)
|
||||||
|
{
|
||||||
|
ImGui::PushID(label);
|
||||||
|
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||||
|
const ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||||
|
const ImVec2 size(textSize.x + 28.f, 36.f);
|
||||||
|
const bool clicked = ImGui::InvisibleButton("##tab", size);
|
||||||
|
const bool hovered = ImGui::IsItemHovered();
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
if (active || hovered)
|
||||||
|
{
|
||||||
|
const ImVec4 bg = active ? Theme::AccentSoft : Theme::SecondaryBackground;
|
||||||
|
drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), Theme::U32(bg), 6.f);
|
||||||
|
}
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
drawList->AddRectFilled(
|
||||||
|
ImVec2(pos.x + 8.f, pos.y + size.y - 3.f),
|
||||||
|
ImVec2(pos.x + size.x - 8.f, pos.y + size.y),
|
||||||
|
Theme::U32(Theme::Accent),
|
||||||
|
1.5f);
|
||||||
|
}
|
||||||
|
const ImVec4 textColor = active ? Theme::TextPrimary : (hovered ? Theme::TextPrimary : Theme::TextSecondary);
|
||||||
|
drawList->AddText(
|
||||||
|
ImVec2(pos.x + (size.x - textSize.x) * 0.5f, pos.y + (size.y - textSize.y) * 0.5f),
|
||||||
|
Theme::U32(textColor),
|
||||||
|
label);
|
||||||
|
ImGui::PopID();
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Button(const char* label, const ImVec2& size, bool danger, bool primary)
|
||||||
|
{
|
||||||
|
const ImVec4 normal = danger ? Theme::DangerSoft : (primary ? Theme::Accent : Theme::SecondaryBackground);
|
||||||
|
const ImVec4 hovered = danger ? Theme::Danger : (primary ? Theme::AccentStrong : Theme::SecondaryHover);
|
||||||
|
const ImVec4 active = danger ? Theme::Danger : Theme::AccentStrong;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, normal);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hovered);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, active);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, primary ? Theme::WindowBackground : Theme::TextPrimary);
|
||||||
|
const bool clicked = ImGui::Button(label, size);
|
||||||
|
ImGui::PopStyleColor(4);
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SectionTitle(const char* title, const ImVec4& accent)
|
||||||
|
{
|
||||||
|
const ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||||
|
ImGui::GetWindowDrawList()->AddRectFilled(
|
||||||
|
pos,
|
||||||
|
ImVec2(pos.x + 30.f, pos.y + 3.f),
|
||||||
|
Theme::U32(accent),
|
||||||
|
1.5f);
|
||||||
|
ImGui::Dummy(ImVec2(0.f, 7.f));
|
||||||
|
ImGui::TextColored(Theme::TextPrimary, "%s", title);
|
||||||
|
ImGui::Dummy(ImVec2(0.f, 3.f));
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusBadge(const char* label, bool on)
|
||||||
|
{
|
||||||
|
const char* suffix = on ? "ON" : "OFF";
|
||||||
|
const ImVec2 labelSize = ImGui::CalcTextSize(label);
|
||||||
|
const ImVec2 suffixSize = ImGui::CalcTextSize(suffix);
|
||||||
|
const float width = labelSize.x + suffixSize.x + 31.f;
|
||||||
|
const float height = 22.f;
|
||||||
|
const ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||||
|
ImGui::Dummy(ImVec2(width, height));
|
||||||
|
|
||||||
|
const ImVec4 background = on ? Theme::AccentSoft : Theme::SecondaryBackground;
|
||||||
|
const ImVec4 foreground = on ? Theme::AccentStrong : Theme::TextSecondary;
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
drawList->AddRectFilled(pos, ImVec2(pos.x + width, pos.y + height), Theme::U32(background), 11.f);
|
||||||
|
drawList->AddCircleFilled(ImVec2(pos.x + 10.f, pos.y + height * 0.5f), 3.f, Theme::U32(foreground), 12);
|
||||||
|
drawList->AddText(ImVec2(pos.x + 17.f, pos.y + 3.f), Theme::U32(Theme::TextPrimary), label);
|
||||||
|
drawList->AddText(ImVec2(pos.x + 17.f + labelSize.x + 5.f, pos.y + 3.f), Theme::U32(foreground), suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Note(const char* text)
|
||||||
|
{
|
||||||
|
ImGui::PushTextWrapPos(0.f);
|
||||||
|
ImGui::TextColored(Theme::TextSecondary, "%s", text);
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Divider()
|
||||||
|
{
|
||||||
|
const ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||||
|
const float width = ImGui::GetContentRegionAvail().x;
|
||||||
|
ImGui::GetWindowDrawList()->AddLine(
|
||||||
|
ImVec2(pos.x, pos.y + 4.f),
|
||||||
|
ImVec2(pos.x + width, pos.y + 4.f),
|
||||||
|
Theme::U32(Theme::Border),
|
||||||
|
1.f);
|
||||||
|
ImGui::Dummy(ImVec2(width, 12.f));
|
||||||
|
}
|
||||||
|
|
||||||
|
float ColumnWidth(float gap)
|
||||||
|
{
|
||||||
|
return (std::max)(220.f, (ImGui::GetContentRegionAvail().x - gap) * 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NextColumn(float gap)
|
||||||
|
{
|
||||||
|
ImGui::SameLine(0.f, gap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../Framework/ImGui/imgui.h"
|
||||||
|
#include "MenuTheme.h"
|
||||||
|
|
||||||
|
namespace UI
|
||||||
|
{
|
||||||
|
bool BeginCard(const char* id, const char* title, const ImVec4& accent, float width = 0.f);
|
||||||
|
void EndCard();
|
||||||
|
|
||||||
|
bool Toggle(const char* label, bool* value);
|
||||||
|
bool SliderFloat(const char* label, float* value, float vmin, float vmax, const char* format = "%.0f");
|
||||||
|
bool SliderInt(const char* label, int* value, int vmin, int vmax, const char* format = "%d");
|
||||||
|
bool Combo(const char* label, int* value, const char* const items[], int count);
|
||||||
|
bool ColorEdit(const char* label, ImVec4* color);
|
||||||
|
bool Hotkey(const char* label, int* value, int blockedKey = 0);
|
||||||
|
|
||||||
|
bool Tab(const char* label, bool active);
|
||||||
|
bool Button(const char* label, const ImVec2& size, bool danger = false, bool primary = false);
|
||||||
|
void SectionTitle(const char* title, const ImVec4& accent);
|
||||||
|
void StatusBadge(const char* label, bool on);
|
||||||
|
void Note(const char* text);
|
||||||
|
void Divider();
|
||||||
|
|
||||||
|
float ColumnWidth(float gap = Theme::ColumnGap);
|
||||||
|
void NextColumn(float gap = Theme::ColumnGap);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue