Initial port

This commit is contained in:
maybegreat48
2025-03-13 16:59:02 -04:00
parent c2f19fe029
commit aa3746462b
120 changed files with 11235 additions and 7012 deletions
-1
View File
@@ -19,7 +19,6 @@ AlwaysBreakTemplateDeclarations: 'Yes'
BinPackArguments: 'false'
BinPackParameters: 'true'
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: 'false'
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
+9 -2
View File
@@ -7,7 +7,6 @@ include(cmake/async-logger.cmake)
include(cmake/imgui.cmake)
include(cmake/json.cmake)
include(cmake/minhook.cmake)
include(cmake/gtav-classes.cmake)
# source
set(SRC_DIR "${PROJECT_SOURCE_DIR}/src")
@@ -21,13 +20,21 @@ set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 23)
target_precompile_headers(${PROJECT_NAME} PUBLIC "${SRC_DIR}/common.hpp")
add_compile_definitions(
_CRT_SECURE_NO_WARNINGS
WIN32_LEAN_AND_MEAN
NOMINMAX
IMGUI_DEFINE_MATH_OPERATORS
)
message(STATUS "Setting up include directories.")
target_include_directories(${PROJECT_NAME} PRIVATE
"${SRC_DIR}"
"${imgui_SOURCE_DIR}"
"${minhook_SOURCE_DIR}/include"
"${minhook_SOURCE_DIR}/src/hde"
"${gtav_classes_SOURCE_DIR}"
)
message(STATUS "Setting up linked libraries")
target_link_libraries(${PROJECT_NAME} PRIVATE AsyncLogger imgui minhook nlohmann_json::nlohmann_json)
target_link_libraries(${PROJECT_NAME} PRIVATE AsyncLogger imgui minhook nlohmann_json::nlohmann_json dbghelp)
+13 -14
View File
@@ -1,18 +1,7 @@
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
},
{
"name": "x64-Release",
"name": "x64-Release-MSVC",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"buildRoot": "${projectDir}\\out\\build\\${name}",
@@ -20,8 +9,18 @@
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x64_x64" ],
"variables": []
"inheritEnvironments": [ "msvc_x64_x64" ]
},
{
"name": "x64-Release-Clang",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64_x64" ]
}
]
}
+2 -2
View File
@@ -1,6 +1,6 @@
# Template Hell Base
# YimMenuV2
This base is honestly a joke and a learning opporunity for myself to learn some more stuff about C++20. I went through the basics of what makes a mod menu base and tried to template them to hell and back.
Experimental menu for GTA 5: Enhanced
## Structure
+2 -2
View File
@@ -1,10 +1,10 @@
include(FetchContent)
add_compile_definitions(CXX_FORMAT_SUPPORT)
message(STATUS "Setting up AsyncLogger")
FetchContent_Declare(
AsyncLogger
GIT_REPOSITORY https://github.com/Yimura/AsyncLogger.git
GIT_TAG v0.0.6
GIT_TAG 6fcfd90b3f4ca4dae09c4a96e9a506e6aea06472
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(AsyncLogger)
-14
View File
@@ -1,14 +0,0 @@
include(FetchContent)
FetchContent_Declare(
gtav_classes
GIT_REPOSITORY https://github.com/maybegreat48/GTAV-Classes.git
GIT_TAG fb7ec7fe0a8ac08823ab7a0a2e68c08b027fce2f
GIT_PROGRESS TRUE
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
)
message("GTAV-Classes")
if(NOT gtav_classes_POPULATED)
FetchContent_Populate(gtav_classes)
endif()
+3 -3
View File
@@ -5,8 +5,8 @@ message(STATUS "Setting up ${LIB_NAME}")
FetchContent_Declare(
${LIB_NAME}
GIT_REPOSITORY https://github.com/Spyral-Org/imgui.git
GIT_TAG 44d98bfd6d12caee43bea49fda71c8313c3e57e7
GIT_REPOSITORY https://github.com/ocornut/imgui
GIT_TAG 6e30c4210181aebb6137f6c10fdfc572ea1202f2
GIT_PROGRESS TRUE
)
@@ -19,7 +19,7 @@ if(NOT imgui_POPULATED)
"${imgui_SOURCE_DIR}/*.cpp"
"${imgui_SOURCE_DIR}/*.h"
"${imgui_SOURCE_DIR}/backends/imgui_impl_win32.*"
"${imgui_SOURCE_DIR}/backends/imgui_impl_dx11.*"
"${imgui_SOURCE_DIR}/backends/imgui_impl_dx12.*"
"${imgui_SOURCE_DIR}/misc/cpp/imgui_stdlib.*"
)
+5
View File
@@ -18,15 +18,20 @@
#include <string_view>
#include <thread>
#include <vector>
#include <stack>
#include <unordered_set>
#include <nlohmann/json.hpp>
using namespace al;
#include "core/logger/LogHelper.hpp"
#undef Yield
namespace YimMenu
{
using namespace std::chrono_literals;
using namespace std::string_view_literals;
extern std::atomic<bool> g_Running;
extern HINSTANCE g_DllInstance;
+51
View File
@@ -0,0 +1,51 @@
#include "FiberPool.hpp"
#include "ScriptMgr.hpp"
namespace YimMenu
{
void FiberPool::InitImpl(int num_fibers)
{
for (std::size_t i = 0; i < num_fibers; ++i)
{
ScriptMgr::AddScript(std::make_unique<Script>(&ScriptEntry));
}
}
void FiberPool::DestroyImpl()
{
std::lock_guard lock(m_Mutex);
while (!m_Jobs.empty())
m_Jobs.pop();
}
void FiberPool::PushImpl(std::function<void()> callback)
{
std::lock_guard lock(m_Mutex);
m_Jobs.push(std::move(callback));
}
void FiberPool::Tick()
{
std::unique_lock lock(m_Mutex);
if (!m_Jobs.empty())
{
auto job = std::move(m_Jobs.top());
m_Jobs.pop();
lock.unlock();
std::invoke(std::move(job));
}
}
void FiberPool::ScriptEntry()
{
while (true)
{
FiberPool::GetInstance().Tick();
ScriptMgr::Yield();
}
}
}
+47
View File
@@ -0,0 +1,47 @@
#pragma once
namespace YimMenu
{
class FiberPool
{
FiberPool() = default;
public:
FiberPool(const FiberPool&) = delete;
FiberPool(FiberPool&&) noexcept = delete;
FiberPool& operator=(const FiberPool&) = delete;
FiberPool& operator=(FiberPool&&) noexcept = delete;
virtual ~FiberPool() = default;
static void Init(int num_fibers)
{
GetInstance().InitImpl(num_fibers);
}
static void Destroy()
{
GetInstance().DestroyImpl();
}
static void Push(std::function<void()> callback)
{
GetInstance().PushImpl(callback);
}
private:
std::recursive_mutex m_Mutex{};
std::stack<std::function<void()>> m_Jobs{};
void InitImpl(int num_fibers);
void DestroyImpl();
void PushImpl(std::function<void()> callback);
void Tick();
static void ScriptEntry();
static FiberPool& GetInstance()
{
static FiberPool i{};
return i;
}
};
}
+53
View File
@@ -0,0 +1,53 @@
#include "PatternCache.hpp"
#include "core/filemgr/FileMgr.hpp"
namespace YimMenu
{
std::optional<int> PatternCache::GetCachedOffsetImpl(PatternHash hash)
{
if (auto it = m_Data.find(hash.GetHash()); it != m_Data.end())
return it->second;
return std::nullopt;
}
void PatternCache::UpdateCachedOffsetImpl(PatternHash hash, int offset)
{
m_Data[hash.GetHash()] = offset;
}
void PatternCache::InitImpl()
{
auto file = FileMgr::GetProjectFile("./pattern_cache.bin");
if (file.Exists())
{
std::ifstream stream(file.Path(), std::ios_base::binary);
while (!stream.eof())
{
std::uint64_t hash;
int offset;
stream.read(reinterpret_cast<char*>(&hash), sizeof(hash));
stream.read(reinterpret_cast<char*>(&offset), sizeof(offset));
m_Data.emplace(hash, offset);
}
}
m_Initialized = true;
}
void PatternCache::UpdateImpl()
{
auto file = FileMgr::GetProjectFile("./pattern_cache.bin");
std::ofstream stream(file.Path(), std::ios_base::binary);
for (auto& [h, offset] : m_Data)
{
auto hash = h;
stream.write(reinterpret_cast<char*>(&hash), sizeof(hash));
stream.write(reinterpret_cast<char*>(&offset), sizeof(offset));
}
}
}
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include "core/memory/PatternHash.hpp"
namespace YimMenu
{
class PatternCache
{
bool m_Initialized;
std::unordered_map<std::uint64_t, int> m_Data;
public:
PatternCache() :
m_Initialized(false)
{
}
static void Init()
{
GetInstance().InitImpl();
}
static void Update()
{
GetInstance().UpdateImpl();
}
static std::optional<int> GetCachedOffset(PatternHash hash)
{
return GetInstance().GetCachedOffsetImpl(hash);
}
static void UpdateCachedOffset(PatternHash hash, int offset)
{
GetInstance().UpdateCachedOffsetImpl(hash, offset);
}
static bool IsInitialized()
{
return GetInstance().m_Initialized;
}
private:
static PatternCache& GetInstance()
{
static PatternCache Instance;
return Instance;
}
void InitImpl();
void UpdateImpl();
std::optional<int> GetCachedOffsetImpl(PatternHash hash);
void UpdateCachedOffsetImpl(PatternHash hash, int offset);
};
}
+79
View File
@@ -0,0 +1,79 @@
#include "ScriptMgr.hpp"
namespace YimMenu
{
Script::Script(std::function<void()> callback) :
m_Callback(callback),
m_Done(false),
m_ChildFiber(0),
m_MainFiber(0),
m_WakeTime(std::nullopt)
{
m_ChildFiber = CreateFiber(
0,
[](void* param) {
auto this_script = static_cast<Script*>(param);
this_script->m_Callback();
this_script->m_Done = true;
SwitchToFiber(this_script->m_MainFiber);
},
this);
}
Script::~Script()
{
if (m_ChildFiber)
DeleteFiber(m_ChildFiber);
}
void Script::Tick()
{
m_MainFiber = GetCurrentFiber();
if ((!m_WakeTime.has_value() || m_WakeTime.value() <= std::chrono::high_resolution_clock::now()) && !m_Done)
{
SwitchToFiber(m_ChildFiber);
}
}
void Script::Yield(std::optional<std::chrono::high_resolution_clock::duration> time)
{
if (time.has_value())
m_WakeTime = std::chrono::high_resolution_clock::now() + time.value();
else
m_WakeTime = std::nullopt;
SwitchToFiber(m_MainFiber);
}
void ScriptMgr::InitImpl()
{
// none required
}
void ScriptMgr::DestroyImpl()
{
std::lock_guard lock(m_Mutex);
m_Scripts.clear();
}
void ScriptMgr::TickImpl()
{
std::lock_guard lock(m_Mutex);
static bool ensure_main_fiber = (ConvertThreadToFiber(nullptr), true);
for (const auto& script : m_Scripts)
script->Tick();
}
void ScriptMgr::YieldImpl(std::optional<std::chrono::high_resolution_clock::duration> time)
{
if (auto script = static_cast<Script*>(GetFiberData()))
script->Yield(time);
}
void ScriptMgr::AddScriptImpl(std::unique_ptr<Script> script)
{
std::lock_guard lock(m_Mutex);
m_Scripts.push_back(std::move(script));
}
}
+79
View File
@@ -0,0 +1,79 @@
#pragma once
namespace YimMenu
{
class Script
{
public:
std::function<void()> m_Callback;
bool m_Done;
HANDLE m_ChildFiber;
HANDLE m_MainFiber;
std::optional<std::chrono::high_resolution_clock::time_point> m_WakeTime;
public:
explicit Script(std::function<void()> callback);
~Script();
void Tick();
void Yield(std::optional<std::chrono::high_resolution_clock::duration> time = std::nullopt);
};
class ScriptMgr
{
private:
ScriptMgr() {};
public:
virtual ~ScriptMgr()
{
DestroyImpl();
};
ScriptMgr(const ScriptMgr&) = delete;
ScriptMgr(ScriptMgr&&) noexcept = delete;
ScriptMgr& operator=(const ScriptMgr&) = delete;
ScriptMgr& operator=(ScriptMgr&&) noexcept = delete;
static void Init()
{
GetInstance().InitImpl();
}
static void Destroy()
{
GetInstance().DestroyImpl();
}
static void Tick()
{
GetInstance().TickImpl();
}
static void Yield(std::optional<std::chrono::high_resolution_clock::duration> time = std::nullopt)
{
GetInstance().YieldImpl(time);
}
static void AddScript(std::unique_ptr<Script> script)
{
GetInstance().AddScriptImpl(std::move(script));
}
private:
std::mutex m_Mutex;
std::vector<std::unique_ptr<Script>> m_Scripts;
void InitImpl();
void DestroyImpl();
void TickImpl();
void YieldImpl(std::optional<std::chrono::high_resolution_clock::duration> time = std::nullopt);
void AddScriptImpl(std::unique_ptr<Script> script);
static ScriptMgr& GetInstance()
{
static ScriptMgr i{};
return i;
}
};
}
+58
View File
@@ -0,0 +1,58 @@
#include "BoolCommand.hpp"
#include "core/backend/FiberPool.hpp"
#include "Commands.hpp"
namespace YimMenu
{
BoolCommand::BoolCommand(std::string name, std::string label, std::string description, bool def_value) :
Command(name, label, description, 0),
m_State(def_value)
{
Commands::AddBoolCommand(this);
}
void BoolCommand::OnCall()
{
SetState(!m_State);
}
void BoolCommand::SaveState(nlohmann::json& value)
{
value = m_State;
}
void BoolCommand::LoadState(nlohmann::json& value)
{
m_State = value;
}
bool BoolCommand::GetState()
{
return m_State;
}
void BoolCommand::SetState(bool state)
{
if (state && !m_State)
FiberPool::Push([this] {
OnEnable();
});
else if (!state && m_State)
FiberPool::Push([this] {
OnDisable();
});
m_State = state;
MarkDirty();
}
void BoolCommand::Initialize()
{
OnEnable();
}
void BoolCommand::Shutdown()
{
OnDisable();
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "Command.hpp"
namespace YimMenu
{
class BoolCommand : public Command
{
protected:
virtual void OnEnable() {};
virtual void OnDisable() {};
virtual void OnCall() override;
virtual void SaveState(nlohmann::json& value) override;
virtual void LoadState(nlohmann::json& value) override;
bool m_State = false;
public:
BoolCommand(std::string name, std::string label, std::string description, bool def_value = false);
bool GetState();
void SetState(bool state);
void Initialize();
void Shutdown();
};
}
+68
View File
@@ -0,0 +1,68 @@
#include "ColorCommand.hpp"
#include "core/backend/FiberPool.hpp"
namespace
{
bool AreColorsEqual(ImVec4 col1, ImVec4 col2)
{
return col1.x == col2.x && col1.y == col2.y && col1.z == col2.z && col1.w == col2.w;
}
}
namespace YimMenu
{
void ColorCommand::OnCall()
{
}
void ColorCommand::SaveState(nlohmann::json& value)
{
value = nlohmann::json::object();
value["r"] = m_State.x;
value["g"] = m_State.y;
value["b"] = m_State.z;
value["a"] = m_State.w;
}
void ColorCommand::LoadState(nlohmann::json& value)
{
if (value.is_object())
{
m_State.x = value["r"];
m_State.y = value["g"];
m_State.z = value["b"];
m_State.w = value["a"];
}
else if (value.is_array())
{
auto arr = value.get<std::array<float, 4>>();
m_State.x = arr[0];
m_State.y = arr[1];
m_State.z = arr[2];
m_State.w = arr[3];
}
}
ColorCommand::ColorCommand(std::string name, std::string label, std::string description, ImVec4 color) :
Command(name, label, description, 0),
m_State(color)
{
}
ImVec4 ColorCommand::GetState()
{
return m_State;
}
void ColorCommand::SetState(ImVec4 state)
{
if (!AreColorsEqual(m_State, state))
{
FiberPool::Push([this] {
OnChange();
});
m_State = state;
MarkDirty();
}
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "Command.hpp"
#include "imgui.h"
namespace YimMenu
{
class ColorCommand : public Command
{
protected:
virtual void OnChange(){};
virtual void OnCall() override;
virtual void SaveState(nlohmann::json& value) override;
virtual void LoadState(nlohmann::json& value) override;
ImVec4 m_State = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
public:
ColorCommand(std::string name, std::string label, std::string description, ImVec4 color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImVec4 GetState();
void SetState(ImVec4 state);
};
}
+26
View File
@@ -0,0 +1,26 @@
#include "core/util/Joaat.hpp"
#include "Command.hpp"
#include "Commands.hpp"
namespace YimMenu
{
Command::Command(std::string name, std::string label, std::string description, int num_args) :
m_Name(name),
m_Label(label),
m_Description(description),
m_NumArgs(num_args),
m_Hash(Joaat(name))
{
Commands::AddCommand(this);
}
void Command::Call()
{
OnCall();
}
void Command::MarkDirty()
{
Commands::MarkDirty();
}
}
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include "core/util/Joaat.hpp"
#include <nlohmann/json.hpp>
namespace YimMenu
{
class Command
{
private:
std::string m_Name;
std::string m_Label;
std::string m_Description;
joaat_t m_Hash;
int m_NumArgs = 0; // TODO: currently unused
protected:
virtual void OnCall() = 0;
void MarkDirty();
public:
Command(std::string name, std::string label, std::string description, int num_args = 0);
void Call();
virtual void SaveState(nlohmann::json& value){};
virtual void LoadState(nlohmann::json& value){};
const std::string& GetName()
{
return m_Name;
}
const std::string& GetLabel()
{
return m_Label;
}
const std::string& GetDescription()
{
return m_Description;
}
joaat_t GetHash()
{
return m_Hash;
}
};
}
+83
View File
@@ -0,0 +1,83 @@
#include "Commands.hpp"
#include "Command.hpp"
#include "LoopedCommand.hpp"
namespace YimMenu
{
Commands::Commands() :
IStateSerializer("commands")
{
}
void Commands::RunScript()
{
Commands::EnableBoolCommands();
while (g_Running)
{
Commands::RunLoopedCommands();
}
}
void Commands::AddCommandImpl(Command* command)
{
m_Commands.insert({command->GetHash(), command});
}
void Commands::AddBoolCommandImpl(BoolCommand* command)
{
m_BoolCommands.push_back(command);
}
void Commands::AddLoopedCommandImpl(LoopedCommand* command)
{
m_LoopedCommands.push_back(command);
}
void Commands::EnableBoolCommandsImpl()
{
for (auto& command : m_BoolCommands)
if (command->GetState())
command->Initialize();
}
void Commands::RunLoopedCommandsImpl()
{
for (auto& command : m_LoopedCommands)
if (command->GetState())
command->Tick();
}
Command* Commands::GetCommandImpl(joaat_t hash)
{
if (auto it = m_Commands.find(hash); it != m_Commands.end())
return it->second;
return nullptr;
}
void Commands::SaveStateImpl(nlohmann::json& state)
{
for (auto& command : m_Commands)
{
if (!state.contains(command.second->GetName()))
state[command.second->GetName()] = nlohmann::json::object();
command.second->SaveState(state[command.second->GetName()]);
}
}
void Commands::LoadStateImpl(nlohmann::json& state)
{
for (auto& command : m_Commands)
{
if (state.contains(command.second->GetName()))
command.second->LoadState(state[command.second->GetName()]);
}
}
void Commands::ShutdownImpl()
{
for (auto& command : m_LoopedCommands)
if (command->GetState())
command->Shutdown();
}
}
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include "core/util/Joaat.hpp"
#include "core/settings/IStateSerializer.hpp"
namespace YimMenu
{
class Command;
class LoopedCommand;
class BoolCommand;
class Commands :
private IStateSerializer
{
private:
std::unordered_map<joaat_t, Command*> m_Commands;
std::vector<LoopedCommand*> m_LoopedCommands;
std::vector<BoolCommand*> m_BoolCommands;
Commands();
public:
static void AddCommand(Command* command)
{
GetInstance().AddCommandImpl(command);
}
static void AddBoolCommand(BoolCommand* command)
{
GetInstance().AddBoolCommandImpl(command);
}
static void AddLoopedCommand(LoopedCommand* command)
{
GetInstance().AddLoopedCommandImpl(command);
}
static void RunLoopedCommands()
{
GetInstance().RunLoopedCommandsImpl();
}
static void EnableBoolCommands()
{
GetInstance().EnableBoolCommandsImpl();
}
template<typename T = Command>
static T* GetCommand(joaat_t hash)
{
return reinterpret_cast<T*>(GetInstance().GetCommandImpl(hash));
}
static std::unordered_map<joaat_t, Command*>& GetCommands()
{
return GetInstance().m_Commands;
};
static std::vector<LoopedCommand*> GetLoopedCommands()
{
return GetInstance().m_LoopedCommands;
}
static void MarkDirty()
{
GetInstance().MarkStateDirty();
}
static void Shutdown()
{
GetInstance().ShutdownImpl();
}
static void RunScript();
private:
void AddCommandImpl(Command* command);
void AddBoolCommandImpl(BoolCommand* command);
void AddLoopedCommandImpl(LoopedCommand* command);
void EnableBoolCommandsImpl();
void RunLoopedCommandsImpl();
Command* GetCommandImpl(joaat_t hash);
virtual void SaveStateImpl(nlohmann::json& state) override;
virtual void LoadStateImpl(nlohmann::json& state) override;
void ShutdownImpl();
static Commands& GetInstance()
{
static Commands instance{};
return instance;
}
};
}
+51
View File
@@ -0,0 +1,51 @@
#include "FloatCommand.hpp"
#include "core/backend/FiberPool.hpp"
namespace YimMenu
{
void FloatCommand::OnCall()
{
}
void FloatCommand::SaveState(nlohmann::json& value)
{
value = m_State;
}
void FloatCommand::LoadState(nlohmann::json& value)
{
m_State = value;
}
FloatCommand::FloatCommand(std::string name, std::string label, std::string description, std::optional<float> min, std::optional<float> max, float def_val) :
Command(name, label, description, 0),
m_Min(min),
m_Max(max),
m_State(def_val)
{
}
float FloatCommand::GetState()
{
return m_State;
}
void FloatCommand::SetState(float state)
{
FiberPool::Push([this] {
OnChange();
});
m_State = state;
MarkDirty();
}
std::optional<float> FloatCommand::GetMinimum()
{
return m_Min;
}
std::optional<float> FloatCommand::GetMaximum()
{
return m_Max;
}
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "Command.hpp"
namespace YimMenu
{
class FloatCommand : public Command
{
protected:
virtual void OnChange(){};
virtual void OnCall() override;
virtual void SaveState(nlohmann::json& value) override;
virtual void LoadState(nlohmann::json& value) override;
float m_State = 0;
std::optional<float> m_Min;
std::optional<float> m_Max;
public:
FloatCommand(std::string name, std::string label, std::string description, std::optional<float> min = std::nullopt, std::optional<float> max = std::nullopt, float def_val = 0.0f);
float GetState();
void SetState(float state);
std::optional<float> GetMinimum();
std::optional<float> GetMaximum();
};
}
+147
View File
@@ -0,0 +1,147 @@
#include "HotkeySystem.hpp"
#include "core/backend/FiberPool.hpp"
#include "core/backend/ScriptMgr.hpp"
#include "Commands.hpp"
#include "LoopedCommand.hpp"
#include "core/util/Joaat.hpp"
// TODO: serialization isn't stable
namespace YimMenu
{
HotkeySystem::HotkeySystem() :
IStateSerializer("hotkeys")
{
}
void HotkeySystem::RegisterCommands()
{
auto& cmds = Commands::GetCommands();
for (auto& [hash, cmd] : cmds)
{
CommandLink link;
m_CommandHotkeys.insert(std::make_pair(hash, link));
}
LOG(INFO) << "Registered " << m_CommandHotkeys.size() << " commands";
}
bool HotkeySystem::ListenAndApply(int& Hotkey, std::vector<int> Blacklist)
{
static auto IsKeyBlacklisted = [Blacklist](int Key) -> bool {
for (auto Key_ : Blacklist)
if (Key_ == Key)
return true;
return false;
};
// VK_OEM_CLEAR Is about the limit in terms of virtual key codes
for (int i = 0; i < VK_OEM_CLEAR; i++)
{
if ((GetKeyState(i) & 0x8000) && i != 1 && !IsKeyBlacklisted(i))
{
Hotkey = i;
return true;
}
}
return false;
}
// Will return the keycode if there are no labels
std::string HotkeySystem::GetHotkeyLabel(int HotkeyModifier)
{
char KeyName[32];
GetKeyNameTextA(MapVirtualKey(HotkeyModifier, MAPVK_VK_TO_VSC) << 16, KeyName, 32);
if (std::string(KeyName).empty())
strcpy(KeyName, std::to_string(HotkeyModifier).data());
return KeyName;
}
// Meant to be called in a loop
void HotkeySystem::CreateHotkey(std::vector<int>& chain)
{
static auto is_key_unique = [this](int Key, std::vector<int> List) -> bool {
for (auto& Key_ : List)
if (GetHotkeyLabel(Key_) == GetHotkeyLabel(Key))
return false;
return true;
};
int pressed_key = 0;
ListenAndApply(pressed_key, chain);
if (pressed_key > 1)
{
if (is_key_unique(pressed_key, chain))
{
chain.push_back(pressed_key);
}
}
MarkStateDirty();
}
void HotkeySystem::Update()
{
for (auto& [hash, link] : m_CommandHotkeys)
{
if (link.m_Chain.empty() || link.m_BeingModified)
continue;
bool all_keys_pressed = true;
for (auto modifier : link.m_Chain)
{
if (!(GetAsyncKeyState(modifier) & 0x8000))
{
all_keys_pressed = false;
}
}
if (all_keys_pressed && std::chrono::system_clock::now() - m_LastHotkeyTriggerTime > 100ms)
{
auto command = Commands::GetCommand(hash);
if (command)
{
// TODO: this is the only way I can prevent chat from blocking the main loop while keeping everything else fast
if (hash != "chathelper"_J)
command->Call();
else
{
FiberPool::Push([command] {
command->Call();
});
}
}
m_LastHotkeyTriggerTime = std::chrono::system_clock::now();
}
}
}
void HotkeySystem::SaveStateImpl(nlohmann::json& state)
{
for (auto& hotkey : m_CommandHotkeys)
{
if (!hotkey.second.m_Chain.empty())
{
state[std::to_string(hotkey.first).data()] = hotkey.second.m_Chain;
}
}
}
void HotkeySystem::LoadStateImpl(nlohmann::json& state)
{
for (auto& [key, value] : state.items())
{
if (m_CommandHotkeys.contains(std::atoi(key.data())))
m_CommandHotkeys[std::atoi(key.data())].m_Chain = value.get<std::vector<int>>();
}
}
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "core/settings/IStateSerializer.hpp"
namespace YimMenu
{
struct CommandLink
{
public:
std::vector<int> m_Chain{};
bool m_BeingModified = false;
CommandLink(){};
};
class HotkeySystem :
private IStateSerializer
{
std::chrono::system_clock::time_point m_LastHotkeyTriggerTime;
public:
HotkeySystem();
std::map<uint32_t, CommandLink> m_CommandHotkeys;
void RegisterCommands();
bool ListenAndApply(int& Hotkey, std::vector<int> blacklist = {0});
std::string GetHotkeyLabel(int hotkey_modifiers);
void CreateHotkey(std::vector<int>& Hotkey);
void Update();
virtual void SaveStateImpl(nlohmann::json& state) override;
virtual void LoadStateImpl(nlohmann::json& state) override;
};
inline HotkeySystem g_HotkeySystem;
}
+51
View File
@@ -0,0 +1,51 @@
#include "IntCommand.hpp"
#include "core/backend/FiberPool.hpp"
namespace YimMenu
{
void IntCommand::OnCall()
{
}
void IntCommand::SaveState(nlohmann::json& value)
{
value = m_State;
}
void IntCommand::LoadState(nlohmann::json& value)
{
m_State = value;
}
IntCommand::IntCommand(std::string name, std::string label, std::string description, std::optional<int> min, std::optional<int> max, int def_val) :
Command(name, label, description, 0),
m_Min(min),
m_Max(max),
m_State(def_val)
{
}
int IntCommand::GetState()
{
return m_State;
}
void IntCommand::SetState(int state)
{
FiberPool::Push([this] {
OnChange();
});
m_State = state;
MarkDirty();
}
std::optional<int> IntCommand::GetMinimum()
{
return m_Min;
}
std::optional<int> IntCommand::GetMaximum()
{
return m_Max;
}
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "Command.hpp"
namespace YimMenu
{
class IntCommand : public Command
{
protected:
virtual void OnChange(){};
virtual void OnCall() override;
virtual void SaveState(nlohmann::json& value) override;
virtual void LoadState(nlohmann::json& value) override;
int m_State = 0;
std::optional<int> m_Min;
std::optional<int> m_Max;
public:
IntCommand(std::string name, std::string label, std::string description, std::optional<int> min = std::nullopt, std::optional<int> max = std::nullopt, int def_val = 0);
int GetState();
void SetState(int state);
std::optional<int> GetMinimum();
std::optional<int> GetMaximum();
};
}
+45
View File
@@ -0,0 +1,45 @@
#include "ListCommand.hpp"
#include "core/backend/FiberPool.hpp"
namespace YimMenu
{
void ListCommand::OnCall()
{
}
void ListCommand::SaveState(nlohmann::json& value)
{
value = m_State;
}
void ListCommand::LoadState(nlohmann::json& value)
{
m_State = value;
}
ListCommand::ListCommand(std::string name, std::string label, std::string description, std::vector<std::pair<int, const char*>> list, int def_val) :
Command(name, label, description, 0),
m_List(list),
m_State(def_val)
{
}
int ListCommand::GetState()
{
return m_State;
}
void ListCommand::SetState(int state)
{
FiberPool::Push([this] {
OnChange();
});
m_State = state;
MarkDirty();
}
std::vector<std::pair<int, const char*>>& ListCommand::GetList()
{
return m_List;
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "Command.hpp"
namespace YimMenu
{
class ListCommand : public Command
{
protected:
virtual void OnChange(){};
virtual void OnCall() override;
virtual void SaveState(nlohmann::json& value) override;
virtual void LoadState(nlohmann::json& value) override;
int m_State = 0;
std::vector<std::pair<int, const char*>> m_List; // not very efficient but keeps things sorted
public:
ListCommand(std::string name, std::string label, std::string description, std::vector<std::pair<int, const char*>> list, int def_val = 0);
int GetState();
void SetState(int state);
std::vector<std::pair<int, const char*>>& GetList();
};
}
+16
View File
@@ -0,0 +1,16 @@
#include "LoopedCommand.hpp"
#include "Commands.hpp"
namespace YimMenu
{
LoopedCommand::LoopedCommand(std::string name, std::string label, std::string description) :
BoolCommand(name, label, description)
{
Commands::AddLoopedCommand(this);
}
void LoopedCommand::Tick()
{
OnTick();
}
}
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include "BoolCommand.hpp"
namespace YimMenu
{
class LoopedCommand : public BoolCommand
{
protected:
virtual void OnTick() = 0;
public:
LoopedCommand(std::string name, std::string label, std::string description);
void Tick();
};
}
+36
View File
@@ -0,0 +1,36 @@
#include "StringCommand.hpp"
namespace YimMenu
{
void StringCommand::OnCall()
{
}
void StringCommand::SaveState(nlohmann::json& value)
{
value = m_StringValue;
}
void StringCommand::LoadState(nlohmann::json& value)
{
m_StringValue = value.get<std::string>();
}
StringCommand::StringCommand(std::string name, std::string label, std::string description, std::string defaultValue) :
Command(name, label, description, 0),
m_StringValue(defaultValue)
{
}
std::string StringCommand::GetString() const
{
return m_StringValue;
}
void StringCommand::SetStringValue(const std::string& value)
{
m_StringValue = value;
OnChange();
MarkDirty();
}
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "Command.hpp"
namespace YimMenu
{
class StringCommand : public Command
{
protected:
std::string m_StringValue;
virtual void OnChange()
{
}
virtual void OnCall() override;
virtual void SaveState(nlohmann::json& value) override;
virtual void LoadState(nlohmann::json& value) override;
public:
StringCommand(std::string name, std::string label, std::string description, std::string defaultValue = "");
std::string GetString() const;
void SetStringValue(const std::string& value);
};
}
+47
View File
@@ -0,0 +1,47 @@
#include "Vector3Command.hpp"
#include "core/backend/FiberPool.hpp"
namespace YimMenu
{
void Vector3Command::OnCall()
{
}
void Vector3Command::SaveState(nlohmann::json& value)
{
value = nlohmann::json::object();
value["x"] = m_State.x;
value["y"] = m_State.y;
value["z"] = m_State.z;
}
void Vector3Command::LoadState(nlohmann::json& value)
{
if (value.is_object())
{
m_State.x = value["x"];
m_State.y = value["y"];
m_State.z = value["z"];
}
}
Vector3Command::Vector3Command(std::string name, std::string label, std::string description, rage::fvector3 def_val) :
Command(name, label, description, 0),
m_State(def_val)
{
}
rage::fvector3 Vector3Command::GetState()
{
return m_State;
}
void Vector3Command::SetState(const rage::fvector3& state)
{
FiberPool::Push([this] {
OnChange();
});
m_State = state;
MarkDirty();
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "types/rage/vector.hpp"
#include "Command.hpp"
namespace YimMenu
{
class Vector3Command : public Command
{
protected:
virtual void OnChange(){};
virtual void OnCall() override;
virtual void SaveState(nlohmann::json& value) override;
virtual void LoadState(nlohmann::json& value) override;
rage::fvector3 m_State{};
public:
Vector3Command(std::string name, std::string label, std::string description, rage::fvector3 def_val = {});
rage::fvector3 GetState();
void SetState(const rage::fvector3& state);
};
}
+162
View File
@@ -0,0 +1,162 @@
#include "Notifications.hpp"
#include "core/logger/LogHelper.hpp"
#include "core/backend/FiberPool.hpp"
#include "core/util/Joaat.hpp"
#include <mutex>
namespace YimMenu
{
Notification Notifications::ShowImpl(std::string title, std::string message, NotificationType type, int duration, std::function<void()> context_function, std::string context_function_name)
{
if (title.empty() || message.empty())
return {};
auto message_id = Joaat(title + message);
auto exists = std::find_if(m_Notifications.begin(), m_Notifications.end(), [&](auto& notification) {
return notification.second.m_Identifier == message_id;
});
if (exists != m_Notifications.end())
{
exists->second.m_CreatedOn = std::chrono::system_clock::now();
return {};
}
Notification notification{};
notification.m_Title = title;
notification.m_Message = message;
notification.m_Type = type;
notification.m_CreatedOn = std::chrono::system_clock::now();
notification.m_Duration = duration;
notification.m_Identifier = message_id;
if (context_function)
{
notification.m_ContextFunc = context_function;
notification.m_ContextFuncName = context_function_name.empty() ? "Context Function" : context_function_name;
}
std::lock_guard<std::mutex> lock(m_mutex);
auto result = m_Notifications.insert(std::make_pair(title + message, notification));
return notification;
}
bool Notifications::EraseImpl(Notification notification)
{
std::lock_guard<std::mutex> lock(m_mutex);
for (auto& [id, n] : m_Notifications)
{
if (n.m_Identifier == notification.m_Identifier)
{
n.m_Erasing = true;
return true;
}
}
return false;
}
static void DrawNotification(Notification& notification, int position)
{
float y_pos = position * 100;
float x_pos = 10;
ImVec2 cardSize(m_CardSizeX, m_CardSizeY);
ImGui::SetNextWindowSize(cardSize, ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(x_pos + notification.m_AnimationOffset, y_pos + 10), ImGuiCond_Always);
std::string windowTitle = std::to_string(position);
ImGui::Begin(windowTitle.c_str(), nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoFocusOnAppearing);
auto timeElapsed = (float)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - notification.m_CreatedOn).count();
auto depletionProgress = 1.0f - (timeElapsed / (float)notification.m_Duration);
ImGui::ProgressBar(depletionProgress, ImVec2(-1, 3.5f), "");
// TODO: Add icon for type instead of colored text
if (notification.m_Type == NotificationType::Info)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::Text("%s", notification.m_Title.c_str());
}
else if (notification.m_Type == NotificationType::Success)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 1.0f, 0.0f, 1.0f));
ImGui::Text("%s", notification.m_Title.c_str());
}
else if (notification.m_Type == NotificationType::Warning)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f));
ImGui::Text("%s", notification.m_Title.c_str());
}
else if (notification.m_Type == NotificationType::Error)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
ImGui::Text("%s", notification.m_Title.c_str());
}
ImGui::PopStyleColor();
ImGui::Separator();
ImGui::TextWrapped("%s", notification.m_Message.c_str());
if (notification.m_ContextFunc)
{
ImGui::Spacing();
if (ImGui::Selectable(notification.m_ContextFuncName.c_str()))
FiberPool::Push([notification] {
notification.m_ContextFunc();
});
}
ImGui::End();
}
void Notifications::DrawImpl()
{
std::vector<std::string> keys_to_erase;
{
std::lock_guard<std::mutex> lock(m_mutex);
int position = 0;
for (auto& [id, notification] : m_Notifications)
{
DrawNotification(notification, position);
if (!notification.m_Erasing)
{
if (notification.m_AnimationOffset < 0)
notification.m_AnimationOffset += m_CardAnimationSpeed;
//Need this to account for changes in card size (x dimension), custom increments might result in odd numbers
if (notification.m_AnimationOffset > 0)
notification.m_AnimationOffset = 0.f;
}
else
{
notification.m_AnimationOffset -= m_CardAnimationSpeed;
if (notification.m_AnimationOffset <= -m_CardSizeX)
keys_to_erase.push_back(id);
}
if ((float)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - notification.m_CreatedOn).count() >= notification.m_Duration)
keys_to_erase.push_back(id);
position++;
}
}
std::lock_guard<std::mutex> lock(m_mutex);
for (const auto& key : keys_to_erase)
{
m_Notifications.erase(key);
}
}
}
+65
View File
@@ -0,0 +1,65 @@
#pragma once
namespace YimMenu
{
static inline float m_CardSizeX = 350.f;
static inline float m_CardSizeY = 100.f;
static inline float m_CardAnimationSpeed = 50.f;
enum class NotificationType
{
Info,
Success,
Warning,
Error
};
struct Notification
{
NotificationType m_Type;
std::string m_Title;
std::string m_Message;
std::chrono::time_point<std::chrono::system_clock> m_CreatedOn;
int m_Duration;
std::function<void()> m_ContextFunc;
std::string m_ContextFuncName;
float m_AnimationOffset = -m_CardSizeX;
bool m_Erasing = false;
std::uint32_t m_Identifier;
};
class Notifications
{
private:
std::unordered_map<std::string, Notification> m_Notifications = {};
std::mutex m_mutex;
// duration is in milliseconds
Notification ShowImpl(std::string title, std::string message, NotificationType type, int duration, std::function<void()> context_function, std::string context_function_name);
void DrawImpl();
bool EraseImpl(Notification notification);
static Notifications& GetInstance()
{
static Notifications instance;
return instance;
}
public:
static Notification Show(std::string title, std::string message, NotificationType type = NotificationType::Info, int duration = 5000, std::function<void()> context_function = nullptr, std::string context_function_name = "")
{
return GetInstance().ShowImpl(title, message, type, duration, context_function, context_function_name);
}
static void Draw()
{
GetInstance().DrawImpl();
}
static bool Erase(Notification notification)
{
return GetInstance().EraseImpl(notification);
}
};
}
+19
View File
@@ -0,0 +1,19 @@
#include "Category.hpp"
namespace YimMenu
{
void Category::Draw()
{
for (auto& item : m_Items)
item->Draw();
}
int Category::GetLength()
{
if (m_Length.has_value())
return m_Length.value();
m_Length = std::max(ImGui::CalcTextSize(m_Name.c_str()).x + 25.0f, 75.0f);
return m_Length.value();
}
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "UIItem.hpp"
namespace YimMenu
{
class Category
{
public:
constexpr Category(std::string_view name) :
m_Name(name)
{
}
void AddItem(std::shared_ptr<UIItem>&& item)
{
m_Items.push_back(std::move(item));
}
void PrependItem(std::shared_ptr<UIItem>&& item)
{
m_Items.insert(m_Items.begin(), std::move(item));
}
void Draw();
int GetLength();
private:
std::vector<std::shared_ptr<UIItem>> m_Items;
std::optional<int> m_Length;
public:
std::string m_Name;
};
}
+54
View File
@@ -0,0 +1,54 @@
#include "Submenu.hpp"
namespace YimMenu
{
void Submenu::SetActiveCategory(const std::shared_ptr<Category> category)
{
m_ActiveCategory = category;
}
void Submenu::AddCategory(std::shared_ptr<Category>&& category)
{
if (!m_ActiveCategory)
m_ActiveCategory = category;
m_Categories.push_back(std::move(category));
}
void Submenu::DrawCategorySelectors()
{
for (auto& category : m_Categories)
{
if (category)
{
auto& style = ImGui::GetStyle();
auto color = style.Colors[ImGuiCol_Button];
color.w -= 0.5;
auto active = category == GetActiveCategory();
if (!active)
ImGui::PushStyleColor(ImGuiCol_Button, color);
if (ImGui::Button(category->m_Name.data(), ImVec2(category->GetLength(), 35)))
{
SetActiveCategory(category);
}
if (!active)
ImGui::PopStyleColor();
if (m_Categories.back() != category)
ImGui::SameLine();
}
}
}
void Submenu::Draw()
{
if (m_ActiveCategory)
{
m_ActiveCategory->Draw();
}
}
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include "Category.hpp"
namespace YimMenu
{
class Submenu
{
public:
constexpr Submenu(std::string name, std::string icon = "") :
m_Name(name),
m_Icon(icon)
{
}
std::shared_ptr<Category> GetActiveCategory() const
{
return m_ActiveCategory;
}
void AddCategory(std::shared_ptr<Category>&& category);
void DrawCategorySelectors();
void SetActiveCategory(const std::shared_ptr<Category> category);
void Draw();
private:
std::shared_ptr<Category> m_ActiveCategory;
public:
std::vector<std::shared_ptr<Category>> m_Categories;
std::string m_Name;
std::string m_Icon; // currently unused
};
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "core/util/Joaat.hpp"
namespace YimMenu
{
// Preferably, all items should be saved in a global instance to make UI elements searchable
class UIItem
{
public:
virtual void Draw() = 0;
};
}
+82
View File
@@ -0,0 +1,82 @@
#include "UIManager.hpp"
namespace YimMenu
{
void UIManager::AddSubmenuImpl(const std::shared_ptr<Submenu>&& submenu)
{
if (!m_ActiveSubmenu)
m_ActiveSubmenu = submenu;
m_Submenus.push_back(std::move(submenu));
}
void UIManager::SetActiveSubmenuImpl(const std::shared_ptr<Submenu> Submenu)
{
m_ActiveSubmenu = Submenu;
}
void UIManager::DrawImpl()
{
auto pos = ImGui::GetCursorPos();
if (ImGui::BeginChild("##submenus", ImVec2(120, ImGui::GetContentRegionAvail().y - 20), true))
{
for (auto& submenu : m_Submenus)
{
if (ImGui::Selectable(submenu->m_Name.data(), (submenu == m_ActiveSubmenu)))
{
SetActiveSubmenu(submenu);
}
}
}
ImGui::EndChild();
ImGui::Text("Terminus");
pos.y -= 28;
ImGui::SetCursorPos(ImVec2(pos.x + 130, pos.y));
if (ImGui::BeginChild("##minisubmenus", ImVec2(0, 50), true, ImGuiWindowFlags_NoScrollbar))
{
if (m_ActiveSubmenu)
m_ActiveSubmenu->DrawCategorySelectors();
}
ImGui::EndChild();
ImGui::SetCursorPos(ImVec2(pos.x + 130, pos.y + 60));
if (ImGui::BeginChild("##options", ImVec2(0, 0), true))
{
if (m_OptionsFont)
ImGui::PushFont(m_OptionsFont);
if (m_ActiveSubmenu)
m_ActiveSubmenu->Draw();
if (m_OptionsFont)
ImGui::PopFont();
}
ImGui::EndChild();
}
std::shared_ptr<Submenu> UIManager::GetActiveSubmenuImpl()
{
if (m_ActiveSubmenu)
{
return m_ActiveSubmenu;
}
return nullptr;
}
std::shared_ptr<Category> UIManager::GetActiveCategoryImpl()
{
if (m_ActiveSubmenu)
{
return m_ActiveSubmenu->GetActiveCategory();
}
return nullptr;
}
}
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include "Category.hpp"
#include "Submenu.hpp"
namespace YimMenu
{
class UIManager
{
public:
static void AddSubmenu(const std::shared_ptr<Submenu>&& submenu)
{
GetInstance().AddSubmenuImpl(std::move(submenu));
}
static void SetActiveSubmenu(const std::shared_ptr<Submenu> submenu)
{
GetInstance().SetActiveSubmenuImpl(submenu);
}
static void Draw()
{
GetInstance().DrawImpl();
}
static std::shared_ptr<Submenu> GetActiveSubmenu()
{
return GetInstance().GetActiveSubmenuImpl();
}
static std::shared_ptr<Category> GetActiveCategory()
{
return GetInstance().GetActiveCategoryImpl();
}
static void SetOptionsFont(ImFont* font)
{
GetInstance().m_OptionsFont = font;
}
private:
static inline UIManager& GetInstance()
{
static UIManager instance;
return instance;
}
void AddSubmenuImpl(const std::shared_ptr<Submenu>&& submenu);
void SetActiveSubmenuImpl(const std::shared_ptr<Submenu> submenu);
void DrawImpl();
std::shared_ptr<Submenu> GetActiveSubmenuImpl();
std::shared_ptr<Category> GetActiveCategoryImpl();
std::shared_ptr<Submenu> m_ActiveSubmenu;
std::vector<std::shared_ptr<Submenu>> m_Submenus;
ImFont* m_OptionsFont = nullptr;
};
}
+157
View File
@@ -0,0 +1,157 @@
#pragma once
#include "imgui.h"
namespace ImGui
{
// https://easyrgb.com/en/convert.php
struct Colors
{
// CSS colors
inline static ImColor AliceBlue{0.94f, 0.97f, 1.0f, 1.0f}; // ARGB: #FFF0F8FF
inline static ImColor AntiqueWhite{0.98f, 0.92f, 0.84f, 1.0f}; // ARGB: #FFFAEBD7
inline static ImColor Aqua{0.0f, 1.0f, 1.0f, 1.0f}; // ARGB: #FF00FFFF
inline static ImColor Aquamarine{0.50f, 1.0f, 0.83f, 1.0f}; // ARGB: #FF7FFFD4
inline static ImColor Azure{0.94f, 1.0f, 1.0f, 1.0f}; // ARGB: #FFF0FFFF
inline static ImColor Beige{0.96f, 0.96f, 0.86f, 1.0f}; // ARGB: #FFF5F5DC
inline static ImColor Bisque{1.0f, 0.89f, 0.77f, 1.0f}; // ARGB: #FFFFE4C4
inline static ImColor Black{0.0f, 0.0f, 0.0f, 1.0f}; // ARGB: #FF000000
inline static ImColor BlanchedAlmond{1.0f, 0.92f, 0.80f, 1.0f}; // ARGB: #FFFFEBCD
inline static ImColor Blue{0.0f, 0.0f, 1.0f, 1.0f}; // ARGB: #FF0000FF
inline static ImColor BlueViolet{0.54f, 0.17f, 0.89f, 1.0f}; // ARGB: #FF8A2BE2
inline static ImColor Brown{0.65f, 0.16f, 0.16f, 1.0f}; // ARGB: #FFA52A2A
inline static ImColor BurlyWood{0.87f, 0.72f, 0.53f, 1.0f}; // ARGB: #FFDEB887
inline static ImColor CadetBlue{0.37f, 0.62f, 0.63f, 1.0f}; // ARGB: #FF5F9EA0
inline static ImColor Chartreuse{0.50f, 1.0f, 0.0f, 1.0f}; // ARGB: #FF7FFF00
inline static ImColor Chocolate{0.82f, 0.41f, 0.12f, 1.0f}; // ARGB: #FFD2691E
inline static ImColor Coral{1.0f, 0.50f, 0.31f, 1.0f}; // ARGB: #FFFF7F50
inline static ImColor CornflowerBlue{0.39f, 0.58f, 0.93f, 1.0f}; // ARGB: #FF6495ED
inline static ImColor Cornsilk{1.0f, 0.97f, 0.86f, 1.0f}; // ARGB: #FFFFF8DC
inline static ImColor Crimson{0.86f, 0.08f, 0.24f, 1.0f}; // ARGB: #FFDC143C
inline static ImColor Cyan{0.0f, 1.0f, 1.0f, 1.0f}; // ARGB: #FF00FFFF
inline static ImColor DarkBlue{0.0f, 0.0f, 0.55f, 1.0f}; // ARGB: #FF00008B
inline static ImColor DarkCyan{0.0f, 0.55f, 0.55f, 1.0f}; // ARGB: #FF008B8B
inline static ImColor DarkGoldenrod{0.72f, 0.53f, 0.04f, 1.0f}; // ARGB: #FFB8860B
inline static ImColor DarkGray{0.66f, 0.66f, 0.66f, 1.0f}; // ARGB: #FFA9A9A9
inline static ImColor DarkGreen{0.0f, 0.39f, 0.0f, 1.0f}; // ARGB: #FF006400
inline static ImColor DarkKhaki{0.74f, 0.72f, 0.42f, 1.0f}; // ARGB: #FFBDB76B
inline static ImColor DarkMagenta{0.55f, 0.0f, 0.55f, 1.0f}; // ARGB: #FF8B008B
inline static ImColor DarkOliveGreen{0.33f, 0.42f, 0.18f, 1.0f}; // ARGB: #FF556B2F
inline static ImColor DarkOrange{1.0f, 0.55f, 0.0f, 1.0f}; // ARGB: #FFFF8C00
inline static ImColor DarkOrchid{0.60f, 0.20f, 0.80f, 1.0f}; // ARGB: #FF9932CC
inline static ImColor DarkRed{0.55f, 0.0f, 0.0f, 1.0f}; // ARGB: #FF8B0000
inline static ImColor DarkSalmon{0.91f, 0.59f, 0.48f, 1.0f}; // ARGB: #FFE9967A
inline static ImColor DarkSeaGreen{0.56f, 0.74f, 0.56f, 1.0f}; // ARGB: #FF8FBC8F
inline static ImColor DarkSlateBlue{0.28f, 0.24f, 0.55f, 1.0f}; // ARGB: #FF483D8B
inline static ImColor DarkSlateGray{0.18f, 0.31f, 0.31f, 1.0f}; // ARGB: #FF2F4F4F
inline static ImColor DarkTurquoise{0.0f, 0.81f, 0.82f, 1.0f}; // ARGB: #FF00CED1
inline static ImColor DarkViolet{0.58f, 0.0f, 0.83f, 1.0f}; // ARGB: #FF9400D3
inline static ImColor DeepPink{1.0f, 0.08f, 0.58f, 1.0f}; // ARGB: #FFFF1493
inline static ImColor DeepSkyBlue{0.0f, 0.75f, 1.0f, 1.0f}; // ARGB: #FF00BFFF
inline static ImColor DimGray{0.41f, 0.41f, 0.41f, 1.0f}; // ARGB: #FF696969
inline static ImColor DodgerBlue{0.12f, 0.56f, 1.0f, 1.0f}; // ARGB: #FF1E90FF
inline static ImColor Firebrick{0.70f, 0.13f, 0.13f, 1.0f}; // ARGB: #FFB22222
inline static ImColor FloralWhite{1.0f, 0.98f, 0.94f, 1.0f}; // ARGB: #FFFAF0E6
inline static ImColor ForestGreen{0.13f, 0.55f, 0.13f, 1.0f}; // ARGB: #FF228B22
inline static ImColor Fuchsia{1.0f, 0.0f, 1.0f, 1.0f}; // ARGB: #FFFF00FF
inline static ImColor Gainsboro{0.86f, 0.86f, 0.86f, 1.0f}; // ARGB: #FFDCDCDC
inline static ImColor GhostWhite{0.97f, 0.97f, 1.0f, 1.0f}; // ARGB: #FFF8F8FF
inline static ImColor Gold{1.0f, 0.84f, 0.0f, 1.0f}; // ARGB: #FFFFD700
inline static ImColor Goldenrod{0.85f, 0.65f, 0.13f, 1.0f}; // ARGB: #FFDAA520
inline static ImColor Gray{0.50f, 0.50f, 0.50f, 1.0f}; // ARGB: #FF808080
inline static ImColor Green{0.0f, 0.50f, 0.0f, 1.0f}; // ARGB: #FF008000
inline static ImColor GreenYellow{0.68f, 1.0f, 0.18f, 1.0f}; // ARGB: #FFADFF2F
inline static ImColor Honeydew{0.94f, 1.0f, 0.94f, 1.0f}; // ARGB: #FFF0FFF0
inline static ImColor HotPink{1.0f, 0.41f, 0.71f, 1.0f}; // ARGB: #FFFF69B4
inline static ImColor IndianRed{0.80f, 0.36f, 0.36f, 1.0f}; // ARGB: #FFCD5C5C
inline static ImColor Indigo{0.29f, 0.0f, 0.51f, 1.0f}; // ARGB: #FF4B0082
inline static ImColor Ivory{1.0f, 1.0f, 0.94f, 1.0f}; // ARGB: #FFFFFFF0
inline static ImColor Khaki{0.94f, 0.90f, 0.55f, 1.0f}; // ARGB: #FFF0E68C
inline static ImColor Lavender{0.90f, 0.90f, 0.98f, 1.0f}; // ARGB: #FFE6E6FA
inline static ImColor LavenderBlush{1.0f, 0.94f, 0.96f, 1.0f}; // ARGB: #FFFFF0F5
inline static ImColor LawnGreen{0.49f, 0.99f, 0.0f, 1.0f}; // ARGB: #FF7CFC00
inline static ImColor LemonChiffon{1.0f, 0.98f, 0.80f, 1.0f}; // ARGB: #FFFFFACD
inline static ImColor LightBlue{0.68f, 0.85f, 0.90f, 1.0f}; // ARGB: #FFADD8E6
inline static ImColor LightCoral{0.94f, 0.50f, 0.50f, 1.0f}; // ARGB: #FFF08080
inline static ImColor LightCyan{0.88f, 1.0f, 1.0f, 1.0f}; // ARGB: #FFE0FFFF
inline static ImColor LightGoldenrodYellow{0.98f, 0.98f, 0.82f, 1.0f}; // ARGB: #FFFAFAD2
inline static ImColor LightGray{0.83f, 0.83f, 0.83f, 1.0f}; // ARGB: #FFD3D3D3
inline static ImColor LightGreen{0.56f, 0.93f, 0.56f, 1.0f}; // ARGB: #FF90EE90
inline static ImColor LightPink{1.0f, 0.71f, 0.76f, 1.0f}; // ARGB: #FFFFB6C1
inline static ImColor LightSalmon{1.0f, 0.63f, 0.48f, 1.0f}; // ARGB: #FFFFA07A
inline static ImColor LightSeaGreen{0.13f, 0.70f, 0.67f, 1.0f}; // ARGB: #FF20B2AA
inline static ImColor LightSkyBlue{0.53f, 0.81f, 0.98f, 1.0f}; // ARGB: #FF87CEFA
inline static ImColor LightSlateGray{0.47f, 0.53f, 0.60f, 1.0f}; // ARGB: #FF778899
inline static ImColor LightSteelBlue{0.69f, 0.77f, 0.87f, 1.0f}; // ARGB: #FFB0C4DE
inline static ImColor LightYellow{1.0f, 1.0f, 0.88f, 1.0f}; // ARGB: #FFFFFFE0
inline static ImColor Lime{0.0f, 1.0f, 0.0f, 1.0f}; // ARGB: #FF00FF00
inline static ImColor LimeGreen{0.20f, 0.80f, 0.20f, 1.0f}; // ARGB: #FF32CD32
inline static ImColor Linen{0.98f, 0.94f, 0.90f, 1.0f}; // ARGB: #FFFAF0E6
inline static ImColor Magenta{1.0f, 0.0f, 1.0f, 1.0f}; // ARGB: #FFFF00FF
inline static ImColor Maroon{0.50f, 0.0f, 0.0f, 1.0f}; // ARGB: #FF800000
inline static ImColor MediumAquamarine{0.40f, 0.80f, 0.67f, 1.0f}; // ARGB: #FF66CDAA
inline static ImColor MediumBlue{0.0f, 0.0f, 0.80f, 1.0f}; // ARGB: #FF0000CD
inline static ImColor MediumOrchid{0.73f, 0.33f, 0.83f, 1.0f}; // ARGB: #FFBA55D3
inline static ImColor MediumPurple{0.58f, 0.44f, 0.86f, 1.0f}; // ARGB: #FF9370DB
inline static ImColor MediumSeaGreen{0.24f, 0.70f, 0.44f, 1.0f}; // ARGB: #FF3CB371
inline static ImColor MediumSlateBlue{0.48f, 0.41f, 0.93f, 1.0f}; // ARGB: #FF7B68EE
inline static ImColor MediumSpringGreen{0.0f, 0.98f, 0.60f, 1.0f}; // ARGB: #FF00FA9A
inline static ImColor MediumTurquoise{0.28f, 0.82f, 0.80f, 1.0f}; // ARGB: #FF48D1CC
inline static ImColor MediumVioletRed{0.78f, 0.08f, 0.52f, 1.0f}; // ARGB: #FFC71585
inline static ImColor MidnightBlue{0.10f, 0.10f, 0.44f, 1.0f}; // ARGB: #FF191970
inline static ImColor MintCream{0.96f, 1.0f, 0.98f, 1.0f}; // ARGB: #FFF5FFFA
inline static ImColor MistyRose{1.0f, 0.89f, 0.88f, 1.0f}; // ARGB: #FFFFE4E1
inline static ImColor Moccasin{1.0f, 0.89f, 0.71f, 1.0f}; // ARGB: #FFFFE4B5
inline static ImColor NavajoWhite{1.0f, 0.87f, 0.68f, 1.0f}; // ARGB: #FFFFDEAD
inline static ImColor Navy{0.0f, 0.0f, 0.50f, 1.0f}; // ARGB: #FF000080
inline static ImColor OldLace{0.99f, 0.96f, 0.90f, 1.0f}; // ARGB: #FFFDF5E6
inline static ImColor Olive{0.50f, 0.50f, 0.0f, 1.0f}; // ARGB: #FF808000
inline static ImColor OliveDrab{0.42f, 0.56f, 0.14f, 1.0f}; // ARGB: #FF6B8E23
inline static ImColor Orange{1.0f, 0.65f, 0.0f, 1.0f}; // ARGB: #FFFFA500
inline static ImColor OrangeRed{1.0f, 0.27f, 0.0f, 1.0f}; // ARGB: #FFFF4500
inline static ImColor Orchid{0.85f, 0.44f, 0.84f, 1.0f}; // ARGB: #FFDA70D6
inline static ImColor PaleGoldenrod{0.93f, 0.91f, 0.67f, 1.0f}; // ARGB: #FFEEE8AA
inline static ImColor PaleGreen{0.60f, 0.98f, 0.60f, 1.0f}; // ARGB: #FF98FB98
inline static ImColor PaleTurquoise{0.69f, 0.93f, 0.93f, 1.0f}; // ARGB: #FFAFEEEE
inline static ImColor PaleVioletRed{0.86f, 0.44f, 0.58f, 1.0f}; // ARGB: #FFDB7093
inline static ImColor PapayaWhip{1.0f, 0.94f, 0.84f, 1.0f}; // ARGB: #FFFFEFD5
inline static ImColor PeachPuff{1.0f, 0.85f, 0.73f, 1.0f}; // ARGB: #FFFFDAB9
inline static ImColor Peru{0.80f, 0.52f, 0.25f, 1.0f}; // ARGB: #FFCD853F
inline static ImColor Pink{1.0f, 0.75f, 0.80f, 1.0f}; // ARGB: #FFFFC0CB
inline static ImColor Plum{0.87f, 0.63f, 0.87f, 1.0f}; // ARGB: #FFDDA0DD
inline static ImColor PowderBlue{0.69f, 0.88f, 0.90f, 1.0f}; // ARGB: #FFB0E0E6
inline static ImColor Purple{0.50f, 0.0f, 0.50f, 1.0f}; // ARGB: #FF800080
inline static ImColor Red{1.0f, 0.0f, 0.0f, 1.0f}; // ARGB: #FFFF0000
inline static ImColor RosyBrown{0.74f, 0.56f, 0.56f, 1.0f}; // ARGB: #FFBC8F8F
inline static ImColor RoyalBlue{0.25f, 0.41f, 0.88f, 1.0f}; // ARGB: #FF4169E1
inline static ImColor SaddleBrown{0.55f, 0.27f, 0.07f, 1.0f}; // ARGB: #FF8B4513
inline static ImColor Salmon{0.98f, 0.50f, 0.45f, 1.0f}; // ARGB: #FFFA8072
inline static ImColor SandyBrown{0.96f, 0.64f, 0.38f, 1.0f}; // ARGB: #FFF4A460
inline static ImColor SeaGreen{0.18f, 0.55f, 0.34f, 1.0f}; // ARGB: #FF2E8B57
inline static ImColor SeaShell{1.0f, 0.96f, 0.93f, 1.0f}; // ARGB: #FFFFF5EE
inline static ImColor Sienna{0.63f, 0.32f, 0.18f, 1.0f}; // ARGB: #FFA0522D
inline static ImColor Silver{0.75f, 0.75f, 0.75f, 1.0f}; // ARGB: #FFC0C0C0
inline static ImColor SkyBlue{0.53f, 0.81f, 0.92f, 1.0f}; // ARGB: #FF87CEEB
inline static ImColor SlateBlue{0.42f, 0.35f, 0.80f, 1.0f}; // ARGB: #FF6A5ACD
inline static ImColor SlateGray{0.44f, 0.50f, 0.56f, 1.0f}; // ARGB: #FF708090
inline static ImColor Snow{1.0f, 0.98f, 0.98f, 1.0f}; // ARGB: #FFFFFAFA
inline static ImColor SpringGreen{0.0f, 1.0f, 0.50f, 1.0f}; // ARGB: #FF00FF7F
inline static ImColor SteelBlue{0.27f, 0.51f, 0.71f, 1.0f}; // ARGB: #FF4682B4
inline static ImColor Tan{0.82f, 0.71f, 0.55f, 1.0f}; // ARGB: #FFD2B48C
inline static ImColor Teal{0.0f, 0.50f, 0.50f, 1.0f}; // ARGB: #FF008080
inline static ImColor Thistle{0.85f, 0.75f, 0.85f, 1.0f}; // ARGB: #FFD8BFD8
inline static ImColor Tomato{1.0f, 0.39f, 0.28f, 1.0f}; // ARGB: #FFFF6347
inline static ImColor Transparent{1.0f, 1.0f, 1.0f, 0.0f}; // ARGB: #00FFFFFF
inline static ImColor Turquoise{0.25f, 0.88f, 0.82f, 1.0f}; // ARGB: #FF40E0D0
inline static ImColor Violet{0.93f, 0.51f, 0.93f, 1.0f}; // ARGB: #FFEE82EE
inline static ImColor Wheat{0.96f, 0.87f, 0.70f, 1.0f}; // ARGB: #FFF5DEB3
inline static ImColor White{1.0f, 1.0f, 1.0f, 1.0f}; // ARGB: #FFFFFFFF
inline static ImColor WhiteSmoke{0.96f, 0.96f, 0.96f, 1.0f}; // ARGB: #FFF5F5F5
inline static ImColor Yellow{1.0f, 1.0f, 0.0f, 1.0f}; // ARGB: #FFFFFF00
inline static ImColor YellowGreen{0.60f, 0.80f, 0.20f, 1.0f}; // ARGB: #FF9ACD32
// others
inline static ImColor Freemode{0.17647f, 0.43137f, 0.72549, 1.0f}; // ARGB: #FF2D6EB9
inline static ImColor IngameBg{0.0f, 0.0f, 0.0f, 0.54901f}; // ARGB: #8C000000
inline static ImColor DisabledText{0.0f, 0.0f, 0.0f, 0.8f}; // ARGB: #CC000000
};
}
@@ -0,0 +1,41 @@
#pragma once
#include "imgui.h"
// Helper: ImOffsetRect A set of offsets to apply to an ImRect.
struct IMGUI_API ImOffsetRect
{
union
{
float Offsets[4];
struct
{
float Top;
float Left;
float Bottom;
float Right;
};
};
constexpr ImOffsetRect() : Top(0.0f), Left(0.0f), Bottom(0.0f), Right(0.0f) {}
constexpr ImOffsetRect(const ImVec2& topLeft, const ImVec2& bottomRight) : ImOffsetRect(topLeft.y, topLeft.x, bottomRight.y, bottomRight.x) {}
constexpr ImOffsetRect(const ImVec4& v) : ImOffsetRect(v.x, v.y, v.z, v.w) {}
constexpr ImOffsetRect(float top, float left, float bottom, float right) : Top(top), Left(left), Bottom(bottom), Right(right) {}
constexpr ImOffsetRect(float all) : Top(all), Left(all), Bottom(all), Right(all) {}
ImVec2 GetSize() const { return ImVec2(Left + Right, Top + Bottom); }
float GetWidth() const { return Left + Right; }
float GetHeight() const { return Top + Bottom; }
float GetAverage() const { return (Top + Left + Bottom + Right) / 4.0f; }
ImOffsetRect MirrorHorizontally() const { return ImOffsetRect(Top, Right, Bottom, Left); }
ImOffsetRect MirrorVertically() const { return ImOffsetRect(Bottom, Left, Top, Right); }
ImOffsetRect Mirror() const { return ImOffsetRect(Bottom, Right, Top, Left); }
};
// Helpers: ImOffsetRect operators
IM_MSVC_RUNTIME_CHECKS_OFF
static inline ImOffsetRect operator+(const ImOffsetRect& lhs, const ImOffsetRect& rhs) { return ImOffsetRect(lhs.Top + rhs.Top, lhs.Left + rhs.Left, lhs.Bottom + rhs.Bottom, lhs.Right + rhs.Right); }
static inline ImOffsetRect operator-(const ImOffsetRect& lhs, const ImOffsetRect& rhs) { return ImOffsetRect(lhs.Top - rhs.Top, lhs.Left - rhs.Left, lhs.Bottom - rhs.Bottom, lhs.Right - rhs.Right); }
static inline ImOffsetRect operator*(const ImOffsetRect& lhs, const ImOffsetRect& rhs) { return ImOffsetRect(lhs.Top * rhs.Top, lhs.Left * rhs.Left, lhs.Bottom * rhs.Bottom, lhs.Right * rhs.Right); }
IM_MSVC_RUNTIME_CHECKS_RESTORE
@@ -0,0 +1,115 @@
#include "imgui_toggle.hpp"
#include "imgui.h"
#include "imgui_toggle_math.hpp"
#include "imgui_toggle_palette.hpp"
#include "imgui_toggle_renderer.hpp"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif // IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_internal.h"
using namespace ImGuiToggleConstants;
using namespace ImGuiToggleMath;
namespace
{
bool ToggleInternal(const char* label, bool* value, const ImGuiToggleConfig& config);
// sets the given config structure's values to the
// default ones used by the `Toggle()` overloads.
inline void SetToAliasDefaults(ImGuiToggleConfig& config)
{
config.Flags = ImGuiToggleFlags_Default;
config.AnimationDuration = AnimationDurationDisabled;
config.FrameRounding = FrameRoundingDefault;
config.KnobRounding = KnobRoundingDefault;
}
// thread-local data for the `Toggle()` functions to easily call `ToggleInternal()`.
static thread_local ImGuiToggleConfig _internalConfig;
} // namespace
bool ImGui::Toggle(const char* label, bool* v, const ImVec2& size /*= ImVec2()*/)
{
::SetToAliasDefaults(::_internalConfig);
return ::ToggleInternal(label, v, ::_internalConfig);
}
bool ImGui::Toggle(const char* label, bool* v, ImGuiToggleFlags flags, const ImVec2& size /*= ImVec2()*/)
{
::SetToAliasDefaults(::_internalConfig);
::_internalConfig.Flags = flags;
::_internalConfig.Size = size;
// if the user is using any animation flags,
// set the default animation duration.
if ((flags & ImGuiToggleFlags_Animated) != 0)
{
_internalConfig.AnimationDuration = AnimationDurationDefault;
}
return ::ToggleInternal(label, v, ::_internalConfig);
}
bool ImGui::Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float animation_duration, const ImVec2& size /*= ImVec2()*/)
{
// this overload implies the toggle should be animated.
if (animation_duration > 0 && (flags & ImGuiToggleFlags_Animated) != 0)
{
// if the user didn't specify ImGuiToggleFlags_Animated, enable it.
flags = flags | (ImGuiToggleFlags_Animated);
}
::SetToAliasDefaults(::_internalConfig);
::_internalConfig.Flags = flags;
::_internalConfig.AnimationDuration = animation_duration;
::_internalConfig.Size = size;
return ::ToggleInternal(label, v, ::_internalConfig);
}
bool ImGui::Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float frame_rounding, float knob_rounding, const ImVec2& size /*= ImVec2()*/)
{
::SetToAliasDefaults(::_internalConfig);
::_internalConfig.Flags = flags;
::_internalConfig.FrameRounding = frame_rounding;
::_internalConfig.KnobRounding = knob_rounding;
::_internalConfig.Size = size;
return ::ToggleInternal(label, v, ::_internalConfig);
}
bool ImGui::Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float animation_duration, float frame_rounding, float knob_rounding, const ImVec2& size /*= ImVec2()*/)
{
// this overload implies the toggle should be animated.
if (animation_duration > 0 && (flags & ImGuiToggleFlags_Animated) != 0)
{
// if the user didn't specify ImGuiToggleFlags_Animated, enable it.
flags = flags | (ImGuiToggleFlags_Animated);
}
::_internalConfig.Flags = flags;
::_internalConfig.AnimationDuration = animation_duration;
::_internalConfig.FrameRounding = frame_rounding;
::_internalConfig.KnobRounding = knob_rounding;
::_internalConfig.Size = size;
return ::ToggleInternal(label, v, ::_internalConfig);
}
bool ImGui::Toggle(const char* label, bool* v, const ImGuiToggleConfig& config)
{
return ::ToggleInternal(label, v, config);
}
namespace
{
bool ToggleInternal(const char* label, bool* v, const ImGuiToggleConfig& config)
{
static thread_local ImGuiToggleRenderer renderer;
renderer.SetConfig(label, v, config);
return renderer.Render();
}
}
@@ -0,0 +1,192 @@
#pragma once
#include "imgui.h"
#include "imgui_offset_rect.hpp"
// Type forward declarations, definitions below in this file.
struct ImGuiToggleConfig; // Configuration data to fully customize a toggle.
struct ImGuiToggleStateConfig; // The data describing how to draw a toggle in a given state.
struct ImGuiTogglePalette; // Color data to adjust how a toggle is drawn.
typedef int ImGuiToggleFlags; // -> enum ImGuiToggleFlags_ // Flags: for Toggle() modes
typedef int ImGuiToggleA11yStyle; // -> enum ImGuiToggleA11yStyle_ // Describes how to draw A11y labels.
namespace ImGui
{
// Widgets: Toggle Switches
// - Toggles behave similarly to ImGui::Checkbox()
// - Sometimes called a toggle switch, see also: https://en.wikipedia.org/wiki/Toggle_switch_(widget)
// - They represent two mutually exclusive states, with an optional animation on the UI when toggled.
// Optional parameters:
// - flags: Values from the ImGuiToggleFlags_ enumeration to set toggle modes.
// - animation_duration: Animation duration. Amount of time in seconds the toggle should animate. (0,...] default: 1.0f (Overloads with this parameter imply ImGuiToggleFlags_Animated)
// - frame_rounding: A scalar that controls how rounded the toggle frame is. 0 is square, 1 is round. (0, 1) default 1.0f
// - knob_rounding: A scalar that controls how rounded the toggle knob is. 0 is square, 1 is round. (0, 1) default 1.0f
// - size: A width and height to draw the toggle at. Defaults to `ImGui::GetFrameHeight()` and that height * Phi for the width.
// Config structure:
// - The overload taking a reference to an ImGuiToggleConfig structure allows for more complete control over the widget.
IMGUI_API bool Toggle(const char* label, bool* v, const ImVec2& size = ImVec2());
IMGUI_API bool Toggle(const char* label, bool* v, ImGuiToggleFlags flags, const ImVec2& size = ImVec2());
IMGUI_API bool Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float animation_duration, const ImVec2& size = ImVec2());
IMGUI_API bool Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float frame_rounding, float knob_rounding, const ImVec2& size = ImVec2());
IMGUI_API bool Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float animation_duration, float frame_rounding, float knob_rounding, const ImVec2& size = ImVec2());
IMGUI_API bool Toggle(const char* label, bool* v, const ImGuiToggleConfig& config);
} // namespace ImGui
// ImGuiToggleFlags: A set of flags that adjust behavior and display for ImGui::Toggle().
enum ImGuiToggleFlags_
{
ImGuiToggleFlags_None = 0,
ImGuiToggleFlags_Animated = 1 << 0, // The toggle's knob should be animated.
// Bits 1-2 reserved.
ImGuiToggleFlags_BorderedFrame = 1 << 3, // The toggle should have a border drawn on the frame.
ImGuiToggleFlags_BorderedKnob = 1 << 4, // The toggle should have a border drawn on the knob.
ImGuiToggleFlags_ShadowedFrame = 1 << 5, // The toggle should have a shadow drawn under the frame.
ImGuiToggleFlags_ShadowedKnob = 1 << 6, // The toggle should have a shadow drawn under the knob.
// Bit 7 reserved.
ImGuiToggleFlags_A11y = 1 << 8, // The toggle should draw on and off glyphs to help indicate its state.
ImGuiToggleFlags_Bordered = ImGuiToggleFlags_BorderedFrame | ImGuiToggleFlags_BorderedKnob, // Shorthand for bordered frame and knob.
ImGuiToggleFlags_Shadowed = ImGuiToggleFlags_ShadowedFrame | ImGuiToggleFlags_ShadowedKnob, // Shorthand for shadowed frame and knob.
ImGuiToggleFlags_Default = ImGuiToggleFlags_None, // The default flags used when no ImGuiToggleFlags_ are specified.
};
// ImGuiToggleA11yStyle: Styles to draw A11y labels.
enum ImGuiToggleA11yStyle_
{
ImGuiToggleA11yStyle_Label, // A11y glyphs draw as text labels.
ImGuiToggleA11yStyle_Glyph, // A11y glyphs draw as power-icon style "I/O" glyphs.
ImGuiToggleA11yStyle_Dot, // A11y glyphs draw as a small dot that can be colored separately from the frame.
ImGuiToggleA11yStyle_Default = ImGuiToggleA11yStyle_Label, // Default: text labels.
};
// ImGuiToggleConstants: A set of defaults and limits used by ImGuiToggleConfig
namespace ImGuiToggleConstants
{
// The golden ratio.
constexpr float Phi = 1.6180339887498948482045f;
// d = 2r
constexpr float DiameterToRadiusRatio = 0.5f;
// Animation is disabled with a animation_duration of 0.
constexpr float AnimationDurationDisabled = 0.0f;
// The default animation duration, in seconds. (0.1f: 100 ms.)
constexpr float AnimationDurationDefault = 0.1f;
// The lowest allowable value for animation duration. (0.0f: Disabled animation.)
constexpr float AnimationDurationMinimum = AnimationDurationDisabled;
// The default frame rounding value. (1.0f: Full rounding.)
constexpr float FrameRoundingDefault = 1.0f;
// The minimum frame rounding value. (0.0f: Full rectangle.)
constexpr float FrameRoundingMinimum = 0.0f;
// The maximum frame rounding value. (1.0f: Full rounding.)
constexpr float FrameRoundingMaximum = 1.0f;
// The default knob rounding value. (1.0f: Full rounding.)
constexpr float KnobRoundingDefault = 1.0f;
// The minimum knob rounding value. (0.0f: Full rectangle.)
constexpr float KnobRoundingMinimum = 0.0f;
// The maximum knob rounding value. (1.0f: Full rounding.)
constexpr float KnobRoundingMaximum = 1.0f;
// The default height to width ratio. (Phi: The golden ratio.)
constexpr float WidthRatioDefault = Phi;
// The minimum allowable width ratio. (1.0f: Toggle width==height, not very useful but interesting.)
constexpr float WidthRatioMinimum = 1.0f;
// The maximum allowable width ratio. (10.0f: It starts to get silly quickly.)
constexpr float WidthRatioMaximum = 10.0f;
// The default amount of pixels the knob should be inset into the toggle frame. (1.5f in each direction: Pleasing to the eye.)
constexpr ImOffsetRect KnobInsetDefault = 1.5f;
// The minimum amount of pixels the knob should be negatively inset (outset) from the toggle frame. (-100.0f: Big overgrown toggle.)
constexpr float KnobInsetMinimum = -100.0f;
// The maximum amount of pixels the knob should be inset into the toggle frame. (100.0f: Toggle likely invisible!)
constexpr float KnobInsetMaximum = 100.0f;
// The default thickness for borders drawn on the toggle frame and knob.
constexpr float BorderThicknessDefault = 1.0f;
// The default thickness for shadows drawn under the toggle frame and knob.
constexpr float ShadowThicknessDefault = 2.0f;
// The default a11y string used when the toggle is on.
const char* const LabelA11yOnDefault = "1";
// The default a11y string used when the toggle is off.
const char* const LabelA11yOffDefault = "0";
}
struct ImGuiToggleStateConfig
{
// The thickness the border should be drawn on the frame when ImGuiToggleFlags_BorderedFrame is specified in `Flags`.
float FrameBorderThickness = ImGuiToggleConstants::BorderThicknessDefault;
// The thickness the shadow should be drawn on the frame when ImGuiToggleFlags_ShadowedFrame is specified in `Flags`.
float FrameShadowThickness = ImGuiToggleConstants::ShadowThicknessDefault;
// The thickness the border should be drawn on the frame when ImGuiToggleFlags_BorderedKnob is specified in `Flags`.
float KnobBorderThickness = ImGuiToggleConstants::BorderThicknessDefault;
// The thickness the shadow should be drawn on the frame when ImGuiToggleFlags_ShadowedKnob is specified in `Flags`.
float KnobShadowThickness = ImGuiToggleConstants::ShadowThicknessDefault;
// The label drawn on the toggle to show the toggle is in the when ImGuiToggleFlags_A11yLabels is specified in `Flags`.
// If left null, default strings will be used.
const char* Label = nullptr;
// The number of pixels the knob should be inset into the toggle frame.
// With a round (circle) knob, an average of each offset will be used.
// With a rectangular knob, each offset will be used in it's intended direction.
ImOffsetRect KnobInset = ImGuiToggleConstants::KnobInsetDefault;
// A custom amount of pixels to offset the knob by. Positive x values will move the knob towards the inside, negative the outside.
ImVec2 KnobOffset = ImVec2(0.0f, 0.0f);
// An optional custom palette to use for the colors to use when drawing the toggle. If left null, theme colors will be used.
// If any of the values in the palette are zero, those specific colors will default to theme colors.
const ImGuiTogglePalette* Palette = nullptr;
};
// ImGuiToggleConfig: A collection of data used to customize the appearance and behavior of a toggle widget.
struct ImGuiToggleConfig
{
// Flags that control the toggle's behavior and display.
ImGuiToggleFlags Flags = ImGuiToggleFlags_Default;
// What style of A11y glyph to draw on the widget.
ImGuiToggleFlags A11yStyle = ImGuiToggleA11yStyle_Default;
// The a duration in seconds that the toggle should animate for. If 0, animation will be disabled.
float AnimationDuration = ImGuiToggleConstants::AnimationDurationDefault;
// A scalar that controls how rounded the toggle frame is. 0 is square, 1 is round. (0, 1) default 1.0f
float FrameRounding = ImGuiToggleConstants::FrameRoundingDefault;
//A scalar that controls how rounded the toggle knob is. 0 is square, 1 is round. (0, 1) default 1.0f
float KnobRounding = ImGuiToggleConstants::KnobRoundingDefault;
// A ratio that controls how wide the toggle is compared to it's height. If `Size.x` is non-zero, this value is ignored.
float WidthRatio = ImGuiToggleConstants::WidthRatioDefault;
// A custom size to draw the toggle with. Defaults to (0, 0). If `Size.x` is zero, `WidthRatio` will control the toggle width.
// If `Size.y` is zero, the toggle height will be set by `ImGui::GetFrameHeight()`.
ImVec2 Size = ImVec2(0.0f, 0.0f);
// Specific configuration data to use when the knob is in the on state.
ImGuiToggleStateConfig On;
// Specific configuration data to use when the knob is in the off state.
ImGuiToggleStateConfig Off;
};
@@ -0,0 +1,21 @@
#pragma once
#include "imgui.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif // IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_internal.h"
namespace ImGuiToggleMath
{
// lerp, but backwards!
template<typename T> constexpr inline T ImInvLerp(T a, T b, float value) { return (T)((value - a) / (b - a)); }
// float comparison w/tolerance - can't constexper as ImFabs isn't constexpr.
inline bool ImApproximately(float a, float b, float tolerance = 0.0001f) { return ImAbs(a - b) < tolerance; }
// helpers for checking if an ImVec4 is zero or not.
constexpr inline bool IsZero(const ImVec4& v) { return v.w == 0 && v.x == 0 && v.y == 0 && v.z == 0; }
constexpr inline bool IsNonZero(const ImVec4& v) { return v.w != 0 || v.x != 0 || v.y != 0 || v.z != 0; }
} // namespace
@@ -0,0 +1,84 @@
#include "imgui_toggle_palette.hpp"
#include "imgui_toggle_math.hpp"
#include "imgui.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif // IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_internal.h"
using namespace ImGuiToggleMath;
void ImGui::UnionPalette(ImGuiTogglePalette* target, const ImGuiTogglePalette* candidate, const ImVec4 colors[], bool v)
{
target->Knob = colors[ImGuiCol_Text];
target->KnobHover = colors[ImGuiCol_Text];
target->Frame = colors[!v ? ImGuiCol_FrameBg : ImGuiCol_Button];
target->FrameHover = colors[!v ? ImGuiCol_FrameBgHovered : ImGuiCol_ButtonHovered];
target->FrameBorder = colors[ImGuiCol_Border];
target->FrameShadow = colors[ImGuiCol_BorderShadow];
target->KnobBorder = colors[ImGuiCol_Border];
target->KnobShadow = colors[ImGuiCol_BorderShadow];
target->A11yGlyph = colors[!v ? ImGuiCol_FrameBg : ImGuiCol_Text];
// if the user didn't provide a candidate, just provide the theme colored palette.
if (candidate == nullptr)
{
return;
}
// if the user did provide a candidate, populate all non-zero colors
#define GET_PALETTE_POPULATE_NONZERO(member) \
do { \
if (IsNonZero(candidate->member)) \
{ \
target->member = candidate->member; \
} \
} while (0)
GET_PALETTE_POPULATE_NONZERO(Knob);
GET_PALETTE_POPULATE_NONZERO(KnobHover);
GET_PALETTE_POPULATE_NONZERO(Frame);
GET_PALETTE_POPULATE_NONZERO(FrameHover);
GET_PALETTE_POPULATE_NONZERO(FrameBorder);
GET_PALETTE_POPULATE_NONZERO(FrameShadow);
GET_PALETTE_POPULATE_NONZERO(KnobBorder);
GET_PALETTE_POPULATE_NONZERO(KnobShadow);
GET_PALETTE_POPULATE_NONZERO(A11yGlyph);
#undef GET_PALETTE_POPULATE_NONZERO
}
void ImGui::BlendPalettes(ImGuiTogglePalette* result, const ImGuiTogglePalette& a, const ImGuiTogglePalette& b, float blend_amount)
{
// a quick out for if we are at either end of the blend.
if (ImApproximately(blend_amount, 0.0f))
{
*result = a;
return;
}
else if (ImApproximately(blend_amount, 1.0f))
{
*result = b;
return;
}
#define BLEND_PALETTES_LERP(member) \
do { \
result->member = ImLerp(a.member, b.member, blend_amount); \
} while (0)
BLEND_PALETTES_LERP(Knob);
BLEND_PALETTES_LERP(KnobHover);
BLEND_PALETTES_LERP(Frame);
BLEND_PALETTES_LERP(FrameHover);
BLEND_PALETTES_LERP(FrameBorder);
BLEND_PALETTES_LERP(FrameShadow);
BLEND_PALETTES_LERP(KnobBorder);
BLEND_PALETTES_LERP(KnobShadow);
BLEND_PALETTES_LERP(A11yGlyph);
#undef BLEND_PALETTES_LERP
}
@@ -0,0 +1,41 @@
#pragma once
#include "imgui.h"
// ImGuiTogglePalette: A collection of colors used to customize the rendering of a toggle widget.
// Leaving any ImVec4 as default (zero) will allow the theme color to be used for that member.
struct ImGuiTogglePalette
{
// The default knob color.
ImVec4 Knob;
// The default knob color, used when when the knob is hovered.
ImVec4 KnobHover;
// The background color of the toggle frame.
ImVec4 Frame;
// The background color of the toggle frame when the toggle is hovered.
ImVec4 FrameHover;
// The background color of the toggle frame's border used when ImGuiToggleFlags_BorderedFrame is specified.
ImVec4 FrameBorder;
// The shadow color of the toggle frame.
ImVec4 FrameShadow;
// The background color of the toggle knob's border used when ImGuiToggleFlags_BorderedKnob is specified.
ImVec4 KnobBorder;
// The shadow color of the toggle knob.
ImVec4 KnobShadow;
// The color of the accessibility label or glyph.
ImVec4 A11yGlyph;
};
namespace ImGui
{
void UnionPalette(ImGuiTogglePalette* target, const ImGuiTogglePalette* candidate, const ImVec4 colors[], bool v);
void BlendPalettes(ImGuiTogglePalette* result, const ImGuiTogglePalette& a, const ImGuiTogglePalette& b, float blend_amount);
}
@@ -0,0 +1,205 @@
#include "imgui_toggle_presets.hpp"
#include "imgui_toggle_palette.hpp"
namespace
{
// some color values shared between styles
const ImVec4 White(1.0f, 1.0f, 1.0f, 1.0f);
const ImVec4 Black(0.0f, 0.0f, 0.0f, 1.0f);
const ImVec4 Green(0.24f, 0.52f, 0.15f, 1.0f);
const ImVec4 GreenBorder(0.39f, 0.62f, 0.32f, 1.0f);
const ImVec4 GreenHighlight(0.3f, 1.0f, 0.0f, 0.75f);
const ImVec4 RedHighlight(1.0f, 0.3f, 0.0f, 0.75f);
} // namespace
ImGuiToggleConfig ImGuiTogglePresets::DefaultStyle()
{
return ImGuiToggleConfig();
}
ImGuiToggleConfig ImGuiTogglePresets::RectangleStyle()
{
ImGuiToggleConfig config;
config.Flags |= ImGuiToggleFlags_Animated;
config.FrameRounding = 0.1f;
config.KnobRounding = 0.3f;
config.AnimationDuration = 0.5f;
return config;
}
ImGuiToggleConfig ImGuiTogglePresets::GlowingStyle()
{
static ImGuiTogglePalette palette_on;
palette_on.FrameShadow = ::GreenHighlight;
palette_on.KnobShadow = ::GreenHighlight;
static ImGuiTogglePalette palette_off;
palette_off.FrameShadow = ::RedHighlight;
palette_off.KnobShadow = ::RedHighlight;
ImGuiToggleConfig config;
config.Flags |= ImGuiToggleFlags_Animated | ImGuiToggleFlags_Shadowed;
config.On.Palette = &palette_on;
config.Off.Palette = &palette_off;
return config;
}
ImGuiToggleConfig ImGuiTogglePresets::iOSStyle(const float size_scale /*= 1.0f*/, bool light_mode /*= false*/)
{
const ImVec4 frame_on(0.3f, 0.85f, 0.39f, 1.0f);
const ImVec4 frame_on_hover(0.0f, 1.0f, 0.57f, 1.0f);
const ImVec4 dark_mode_frame_off(0.22f, 0.22f, 0.24f, 1.0f);
const ImVec4 light_mode_frame_off(0.91f, 0.91f, 0.92f, 1.0f);
const ImVec4 dark_mode_frame_off_hover(0.4f, 0.4f, 0.4f, 1.0f);
const ImVec4 light_mode_frame_off_hover(0.7f, 0.7f, 0.7f, 1.0f);
const ImVec4 light_gray(0.89f, 0.89f, 0.89f, 1.0f);
const ImVec4 a11y_glyph_on(1.0f, 1.0f, 1.0f, 1.0f);
const ImVec4 a11y_glyph_off(0.4f, 0.4f, 0.4f, 1.0f);
const float ios_width = 153 * size_scale;
const float ios_height = 93 * size_scale;
const float ios_frame_border_thickness = 0.0f * size_scale;
const float ios_border_thickness = 0.0f * size_scale;
const float ios_offset = 0.0f * size_scale;
const float ios_inset = 6.0f * size_scale;
// setup 'on' colors
static ImGuiTogglePalette ios_palette_on;
ios_palette_on.Knob = ::White;
ios_palette_on.Frame = frame_on;
ios_palette_on.FrameHover = frame_on_hover;
ios_palette_on.KnobBorder = light_gray;
ios_palette_on.FrameBorder = light_gray;
ios_palette_on.A11yGlyph = a11y_glyph_on;
// setup 'off' colors
static ImGuiTogglePalette ios_palette_off;
ios_palette_off.Knob = ::White;
ios_palette_off.Frame = light_mode ? light_mode_frame_off : dark_mode_frame_off;
ios_palette_off.FrameHover = light_mode ? light_mode_frame_off_hover : light_mode_frame_off_hover;
ios_palette_off.KnobBorder = light_gray;
ios_palette_off.FrameBorder = light_gray;
ios_palette_off.A11yGlyph = a11y_glyph_off;
// setup config
ImGuiToggleConfig config;
config.Size = ImVec2(ios_width, ios_height);
config.Flags |= ImGuiToggleFlags_A11y
| ImGuiToggleFlags_Animated
| (light_mode ? ImGuiToggleFlags_Bordered : 0);
config.A11yStyle = ImGuiToggleA11yStyle_Glyph;
// setup 'on' config
config.On.FrameBorderThickness = 0;
config.On.KnobBorderThickness = ios_border_thickness;
config.On.KnobOffset = ImVec2(ios_offset, 0);
config.On.KnobInset = ios_inset;
config.On.Palette = &ios_palette_on;
// setup 'off' config
config.Off.FrameBorderThickness = ios_frame_border_thickness;
config.Off.KnobBorderThickness = ios_border_thickness;
config.Off.KnobOffset = ImVec2(ios_offset, 0);
config.Off.KnobInset = ios_inset;
config.Off.Palette = &ios_palette_off;
return config;
}
ImGuiToggleConfig ImGuiTogglePresets::MaterialStyle(float size_scale /*= 1.0f*/)
{
const ImVec4 purple(0.4f, 0.08f, 0.97f, 1.0f);
const ImVec4 purple_dim(0.78f, 0.65f, 0.99f, 1.0f);
const ImVec4 purple_hover(0.53f, 0.08f, 1.0f, 1.0f);
const ImVec2 material_size(37 * size_scale, 16 * size_scale);
const float material_inset = -2.5f * size_scale;
static ImGuiTogglePalette material_palette_on;
material_palette_on.Frame = purple_dim;
material_palette_on.FrameHover = purple_dim;
material_palette_on.Knob = purple;
material_palette_on.KnobHover = purple_hover;
// setup config
ImGuiToggleConfig config;
config.Flags |= ImGuiToggleFlags_Animated;
config.Size = material_size;
config.On.KnobInset = config.Off.KnobInset = material_inset;
config.On.KnobOffset = config.Off.KnobOffset = ImVec2(-material_inset, 0);
config.On.Palette = &material_palette_on;
return config;
}
ImGuiToggleConfig ImGuiTogglePresets::MinecraftStyle(float size_scale /*= 1.0f*/)
{
const ImVec4 gray(0.35f, 0.35f, 0.35f, 1.0f);
const ImVec4 dark_gray(0.12f, 0.12f, 0.12f, 1.0f);
const ImVec4 frame_border_off(0.6f, 0.6f, 0.61f, 1.0f);
const ImVec4 toggle_frame_off(0.55f, 0.55f, 0.56f, 1.0f);
const ImVec4 gray_knob(0.82f, 0.82f, 0.83f, 1.0f);
const ImVec2 minecraft_knob_size(56.0f * size_scale, 40.0f * size_scale);
const ImVec2 minecraft_size(104.0f * size_scale, 40.0f * size_scale); // 112x48
const float minecraft_borders = 4.0f * size_scale;
const ImVec2 minecraft_offset(0.0f * size_scale, -minecraft_borders * 2.0f);
const ImOffsetRect minecraft_inset(
0.0f * size_scale, // top
-16.0f * size_scale, // left
0.0f * size_scale, // bottom
0.0f * size_scale // right
);
const float minecraft_rounding = 0.0f; // disable rounding
const float minecraft_shadows = 4.0f * size_scale;
// set up the "on" palette.
static ImGuiTogglePalette minecraft_palette_on;
minecraft_palette_on.Frame = ::Green;
minecraft_palette_on.FrameHover = ::Green;
minecraft_palette_on.FrameBorder = ::GreenBorder;
minecraft_palette_on.FrameShadow = ::Black;
minecraft_palette_on.Knob = gray_knob;
minecraft_palette_on.KnobHover = gray_knob;
minecraft_palette_on.A11yGlyph = ::White;
minecraft_palette_on.KnobBorder = ::White;
minecraft_palette_on.KnobShadow = ::Black;
// start the "off" palette as a copy of the on
static ImGuiTogglePalette minecraft_palette_off;
minecraft_palette_off = minecraft_palette_on;
// make changes to the off palette
minecraft_palette_off.Frame = toggle_frame_off;
minecraft_palette_off.FrameHover = toggle_frame_off;
minecraft_palette_off.FrameBorder = frame_border_off;
// setup config
ImGuiToggleConfig config;
config.Flags |= ImGuiToggleFlags_A11y | ImGuiToggleFlags_Bordered | ImGuiToggleFlags_Shadowed;
config.Size = minecraft_size;
config.FrameRounding = minecraft_rounding;
config.KnobRounding = minecraft_rounding;
config.A11yStyle = ImGuiToggleA11yStyle_Glyph;
// set up the "on" state configuration
config.On.KnobInset = minecraft_inset;
config.On.KnobOffset = minecraft_offset;
config.On.FrameBorderThickness = minecraft_borders;
config.On.FrameShadowThickness = minecraft_shadows;
config.On.KnobBorderThickness = minecraft_borders;
config.On.KnobShadowThickness = minecraft_shadows;
config.On.Palette = &minecraft_palette_on;
// duplicate the "on" config to the "off", then make changes.
config.Off = config.On;
config.Off.KnobInset = minecraft_inset.MirrorHorizontally();
config.Off.Palette = &minecraft_palette_off;
return config;
}
@@ -0,0 +1,25 @@
#pragma once
#include "imgui_toggle.hpp"
// ImGuiTogglePresets: A few canned configurations for various presets OOTB.
namespace ImGuiTogglePresets
{
// The default, unmodified style.
ImGuiToggleConfig DefaultStyle();
// A style similar to default, but with rectangular knob and frame.
ImGuiToggleConfig RectangleStyle();
// A style that uses a shadow to appear to glow while it's on.
ImGuiToggleConfig GlowingStyle();
// A style that emulates what a toggle on iOS looks like.
ImGuiToggleConfig iOSStyle(float size_scale = 1.0f, bool light_mode = false);
// A style that emulates what a Material Design toggle looks like.
ImGuiToggleConfig MaterialStyle(float size_scale = 1.0f);
// A style that emulates what a toggle close to one from Minecraft.
ImGuiToggleConfig MinecraftStyle(float size_scale = 1.0f);
}
@@ -0,0 +1,519 @@
#include "imgui_toggle_renderer.hpp"
#include "imgui_toggle_palette.hpp"
#include "imgui_toggle_math.hpp"
using namespace ImGuiToggleConstants;
using namespace ImGuiToggleMath;
namespace
{
// a small helper to quickly check the mixed value flag.
inline bool IsItemMixedValue()
{
return (GImGui->LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0;
}
} // namespace
ImGuiToggleRenderer::ImGuiToggleRenderer()
{
SetConfig(nullptr, nullptr, ImGuiToggleConfig());
}
ImGuiToggleRenderer::ImGuiToggleRenderer(const char* label, bool* value, const ImGuiToggleConfig& user_config) : _style(nullptr), _label(label), _value(value)
{
SetConfig(label, value, user_config);
}
void ImGuiToggleRenderer::SetConfig(const char* label, bool* value, const ImGuiToggleConfig& user_config)
{
// store mandatory settings
_label = label;
_value = value;
// copy our user's config and ensure it's valid.
_config = user_config;
ValidateConfig();
}
bool ImGuiToggleRenderer::Render()
{
ImGuiWindow* window = ImGui::GetCurrentWindow();
IM_ASSERT(window);
IM_ASSERT(_label != nullptr);
IM_ASSERT(_value != nullptr);
if (window->SkipItems)
{
return false;
}
// update igui context
g = GImGui;
_id = window->GetID(_label);
_drawList = ImGui::GetWindowDrawList();
_style = &ImGui::GetStyle();
// calculate the size of the toggle portion
const float height = _config.Size.y > 0
? _config.Size.y
: ImGui::GetFrameHeight();
const float width = _config.Size.x > 0
? _config.Size.x
: height * _config.WidthRatio;
// get the position of the widget and how large the label should be
ImVec2 widget_position = window->DC.CursorPos;
ImVec2 label_size = ImGui::CalcTextSize(_label, nullptr, true);
// if the knob is offset horizontally outside of the frame in the on state, we want to bump our label over.
const float label_x_offset = ImMax(0.0f, -_config.On.KnobOffset.x / 2.0f);
// calculate bounding boxes for the toggle, and the whole widget including the label for interaction
_boundingBox = ImRect(widget_position, widget_position + ImVec2(width, height));
ImRect total_bounding_box = ImRect(widget_position,
widget_position
+ ImVec2(
width + (label_size.x > 0.0f ? _style->ItemInnerSpacing.x + label_size.x : 0.0f) + label_x_offset,
ImMax(height, label_size.y) + _style->FramePadding.y * 2.0f
));
// handle the toggle input behavior
bool pressed = ToggleBehavior(total_bounding_box);
_isMixedValue = ::IsItemMixedValue();
// draw the toggle itself and the label
DrawToggle();
DrawLabel(label_x_offset);
IMGUI_TEST_ENGINE_ITEM_INFO(_id, _label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*_value ? ImGuiItemStatusFlags_Checked : 0));
return pressed;
}
void ImGuiToggleRenderer::ValidateConfig()
{
IM_ASSERT_USER_ERROR(_config.Size.x >= 0, "Size.x specified was negative.");
IM_ASSERT_USER_ERROR(_config.Size.y >= 0, "Size.y specified was negative.");
// if no flags were specified, use defaults.
if (_config.Flags == ImGuiToggleFlags_None)
{
_config.Flags = ImGuiToggleFlags_Default;
}
// a zero or negative duration would prevent animation.
_config.AnimationDuration = ImMax(_config.AnimationDuration, AnimationDurationMinimum);
// keep our size/scale and rounding numbers sane.
_config.FrameRounding = ImClamp(_config.FrameRounding, FrameRoundingMinimum, FrameRoundingMaximum);
_config.KnobRounding = ImClamp(_config.KnobRounding, KnobRoundingMinimum, KnobRoundingMaximum);
_config.WidthRatio = ImClamp(_config.WidthRatio, WidthRatioMinimum, WidthRatioMaximum);
// Make sure our a11y labels have values.
if (_config.On.Label == nullptr)
{
_config.On.Label = LabelA11yOnDefault;
}
if (_config.Off.Label == nullptr)
{
_config.Off.Label = LabelA11yOffDefault;
}
}
bool ImGuiToggleRenderer::ToggleBehavior(const ImRect& interaction_bounding_box)
{
ImGui::ItemSize(interaction_bounding_box, _style->FramePadding.y);
if (!ImGui::ItemAdd(interaction_bounding_box, _id))
{
IMGUI_TEST_ENGINE_ITEM_INFO(_id, _label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*_value ? ImGuiItemStatusFlags_Checked : 0));
return false;
}
// the meat and potatoes: the actual toggle button
const ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(interaction_bounding_box, _id, &hovered, &held, button_flags);
if (pressed)
{
*_value = !(*_value);
ImGui::MarkItemEdited(_id);
}
return pressed;
}
void ImGuiToggleRenderer::DrawToggle()
{
const float height = GetHeight();
const float width = GetWidth();
// update imgui state
_isHovered = g->HoveredId == _id;
_isLastActive = g->LastActiveId == _id;
_lastActiveTimer = g->LastActiveIdTimer;
// radius is by default half the diameter
const float knob_radius = height * DiameterToRadiusRatio;
// update the toggle's animation timer, state, and palette.
UpdateAnimationPercent();
UpdateStateConfig();
UpdatePalette();
// get colors modified by hover.
const ImU32 color_frame = ImGui::GetColorU32((_isHovered || *_value) ? _palette.FrameHover : _palette.Frame);
const ImU32 color_knob = ImGui::GetColorU32((_isHovered || *_value) ? _palette.KnobHover : _palette.Knob);
// draw the background frame
DrawFrame(color_frame);
// draw accessibility labels, if enabled.
if (HasA11yGlyphs())
{
DrawA11yFrameOverlays(knob_radius);
}
// draw the knob
if (HasCircleKnob())
{
DrawCircleKnob(knob_radius, color_knob);
}
else if (HasRectangleKnob())
{
DrawRectangleKnob(knob_radius, color_knob);
}
else
{
// user didn't specify a knob mode, they get no knob.
IM_ASSERT_USER_ERROR(false, "No toggle knob type to draw.");
}
}
void ImGuiToggleRenderer::DrawFrame(ImU32 color_frame)
{
const float height = GetHeight();
const float frame_rounding = _config.FrameRounding >= 0
? height * _config.FrameRounding
: height * 0.5f;
// draw frame shadow, if enabled
if (HasShadowedFrame())
{
const ImU32 color_frame_shadow = ImGui::GetColorU32(_palette.FrameShadow);
DrawRectShadow(_boundingBox, color_frame_shadow, frame_rounding, _state.FrameShadowThickness);
}
// draw frame background
_drawList->AddRectFilled(_boundingBox.Min, _boundingBox.Max, color_frame, frame_rounding);
// draw frame border, if enabled
if (HasBorderedFrame())
{
const ImU32 color_frame_border = ImGui::GetColorU32(_palette.FrameBorder);
DrawRectBorder(_boundingBox, color_frame_border, frame_rounding, _state.FrameBorderThickness);
}
}
void ImGuiToggleRenderer::DrawA11yDot(const ImVec2& pos, ImU32 color)
{
ImGui::RenderBullet(_drawList, pos, color);
}
void ImGuiToggleRenderer::DrawA11yGlyph(ImVec2 pos, ImU32 color, bool state, float radius, float thickness)
{
if (state)
{
// draw the I bar
const float half_thickness = thickness * 0.5f;
const ImVec2 offset(half_thickness, radius);
_drawList->AddRectFilled(pos - offset, pos + offset, color);
}
else
{
// draw the O ring
const float o_adjustment = 1.0f;
const float o_radius = radius - o_adjustment;
const float o_thickness = thickness + o_adjustment;
pos.x += o_adjustment;
_drawList->AddCircle(pos, o_radius, color, 0, o_thickness);
}
}
void ImGuiToggleRenderer::DrawA11yLabel(ImVec2 pos, ImU32 color, const char* label)
{
// subtract out half the sizes of the text to center them
const ImVec2 text_size = ImGui::CalcTextSize(label);
pos.x -= (text_size.x * 0.5f);
pos.y -= (text_size.y * 0.5f);
// draw the label.
_drawList->AddText(pos, color, label);
}
void ImGuiToggleRenderer::DrawA11yFrameOverlay(float knob_radius, bool state)
{
const float AnimationPercentOff = 0.0f;
const float AnimationPercentOn = 1.0f;
// notice we swap the animation percents as compared to the labels/glyphs, as we want to draw the
// a11y labels where the knob *isn't* when it's in a given state.
ImVec2 pos = CalculateKnobCenter(knob_radius, state ? AnimationPercentOff : AnimationPercentOn);
// next, we want to adjust the position to move to a more pleasing spot in the toggle.
// this is just some tinkering that got to a nice looking area based on the sizes,
// but this is subject to change.
const float diameter = ImMax(1.0f, GetHeight() / 3.0f);
const float radius = diameter * 0.5f;
const float thickness = ImCeil(radius * 0.2f);
const ImVec2 adjustment = ImVec2(radius - thickness, 0.0f)
* (state ? -1.0f : 1.0f); // if state is true, we want to subtract rather than add.
pos += adjustment;
const ImU32 color = state
? ImGui::GetColorU32(_colorA11yGlyphOn)
: ImGui::GetColorU32(_colorA11yGlyphOff);
switch (_config.A11yStyle)
{
case ImGuiToggleA11yStyle_Label:
DrawA11yLabel(pos, color, state ? _config.On.Label : _config.Off.Label);
break;
case ImGuiToggleA11yStyle_Glyph:
DrawA11yGlyph(pos, color, state, radius, thickness);
break;
case ImGuiToggleA11yStyle_Dot:
DrawA11yDot(pos, color);
break;
default:
break;
}
}
void ImGuiToggleRenderer::DrawA11yFrameOverlays(float knob_radius)
{
DrawA11yFrameOverlay(knob_radius, true);
DrawA11yFrameOverlay(knob_radius, false);
}
void ImGuiToggleRenderer::DrawCircleKnob(float radius, ImU32 color_knob)
{
const float inset_size = ImMin(_state.KnobInset.GetAverage(), radius);
IM_ASSERT_USER_ERROR(inset_size <= radius, "Inset size needs to be smaller or equal to the knob's radius for circular knobs.");
const ImVec2 knob_center = CalculateKnobCenter(radius, _animationPercent, _state.KnobOffset);
const float knob_radius = radius - inset_size;
// draw knob shadow, if enabled
if (HasShadowedKnob())
{
const ImU32 color_knob_shadow = ImGui::GetColorU32(_palette.KnobShadow);
DrawCircleShadow(knob_center, knob_radius, color_knob_shadow, _state.KnobShadowThickness);
}
// draw circle knob
_drawList->AddCircleFilled(knob_center, knob_radius, color_knob);
// draw knob border, if enabled
if (HasBorderedKnob())
{
const ImU32 color_knob_border = ImGui::GetColorU32(_palette.KnobBorder);
DrawCircleBorder(knob_center, knob_radius, color_knob_border, _state.KnobBorderThickness);
}
}
void ImGuiToggleRenderer::DrawRectangleKnob(float radius, ImU32 color_knob)
{
const ImRect bounds = CalculateKnobBounds(radius, _animationPercent, _state.KnobOffset);
const float knob_diameter_total = bounds.GetHeight();
const float knob_rounded_radius = (knob_diameter_total * 0.5f) * _config.KnobRounding;
// draw knob shadow, if enabled
if (HasShadowedKnob())
{
const ImU32 color_knob_shadow = ImGui::GetColorU32(_palette.KnobShadow);
DrawRectShadow(bounds, color_knob_shadow, _config.KnobRounding, _state.KnobShadowThickness);
}
// draw rectangle/squircle knob
_drawList->AddRectFilled(bounds.Min, bounds.Max, color_knob, knob_rounded_radius);
// draw knob border, if enabled
if (HasBorderedKnob())
{
const ImU32 color_knob_border = ImGui::GetColorU32(_palette.KnobBorder);
DrawRectBorder(bounds, color_knob_border, knob_rounded_radius, _state.KnobBorderThickness);
}
}
void ImGuiToggleRenderer::DrawLabel(float x_offset)
{
const ImVec2 label_size = ImGui::CalcTextSize(_label, nullptr, true);
const float half_height = GetHeight() * 0.5f;
const float label_x = _boundingBox.Max.x + _style->ItemInnerSpacing.x + x_offset;
const float label_y = _boundingBox.Min.y + half_height - (label_size.y * 0.5f);
const ImVec2 label_pos = ImVec2(label_x, label_y);
if (g->LogEnabled)
{
ImGui::LogRenderedText(&label_pos, _isMixedValue ? "[~]" : *_value ? "[x]" : "[ ]");
}
if (label_size.x > 0.0f)
{
ImGui::RenderText(label_pos, _label);
}
}
void ImGuiToggleRenderer::UpdateAnimationPercent()
{
// calculate the lerp percentage for animation,
// but default to 1/0 for if we aren't animating at all,
// or 0.5f if we have a mixed value. Also, trying to keep parity with
// undocumented tristate/mixed/indeterminate checkbox (#2644)
float t = _isMixedValue
? 0.5f
: (*_value ? 1.0f : 0.0f);
if (IsAnimated() && _isLastActive)
{
const float t_anim = ImSaturate(ImInvLerp(0.0f, _config.AnimationDuration, _lastActiveTimer));
t = *_value ? (t_anim) : (1.0f - t_anim);
}
_animationPercent = t;
}
void ImGuiToggleRenderer::UpdateStateConfig()
{
if (!IsAnimated())
{
_state = *_value ? _config.On : _config.Off;
return;
}
_state.FrameBorderThickness = ImLerp(_config.Off.FrameBorderThickness, _config.On.FrameBorderThickness, _animationPercent);
_state.KnobBorderThickness = ImLerp(_config.Off.KnobBorderThickness, _config.On.KnobBorderThickness, _animationPercent);
_state.KnobInset = ImLerp(_config.Off.KnobInset, _config.On.KnobInset, _animationPercent);
_state.KnobOffset = ImLerp(_config.Off.KnobOffset, _config.On.KnobOffset, _animationPercent);
}
void ImGuiToggleRenderer::UpdatePalette()
{
const ImGuiTogglePalette* on_candidate = _config.On.Palette;
const ImGuiTogglePalette* off_candidate = _config.Off.Palette;
if (!IsAnimated())
{
ImGui::UnionPalette(
&_palette,
*_value ? on_candidate : off_candidate,
_style->Colors,
*_value);
// store specific colors that shouldn't blend.
_colorA11yGlyphOff = _palette.A11yGlyph;
_colorA11yGlyphOn = _palette.A11yGlyph;
return;
}
ImGuiTogglePalette off_unioned;
ImGuiTogglePalette on_unioned;
ImGui::UnionPalette(&off_unioned, off_candidate, _style->Colors, false);
ImGui::UnionPalette(&on_unioned, on_candidate, _style->Colors, true);
// otherwise, lets lerp them!
ImGui::BlendPalettes(&_palette, off_unioned, on_unioned, _animationPercent);
// store specific colors that shouldn't blend.
_colorA11yGlyphOff = off_unioned.A11yGlyph;
_colorA11yGlyphOn = on_unioned.A11yGlyph;
}
ImVec2 ImGuiToggleRenderer::CalculateKnobCenter(float radius, float animation_percent, const ImVec2& offset /*= ImVec2()*/) const
{
const ImVec2 pos = GetPosition();
const float double_radius = radius * 2.0f;
const float animation_percent_inverse = 1.0f - animation_percent;
const float knob_x = (pos.x + radius)
+ animation_percent * (GetWidth() - double_radius - offset.x)
+ (animation_percent_inverse * offset.x);
const float knob_y = pos.y + radius + offset.y;
return ImVec2(knob_x, knob_y);
}
ImRect ImGuiToggleRenderer::CalculateKnobBounds(float radius, float animation_percent, const ImVec2& offset /*= ImVec2()*/) const
{
const ImVec2 position = GetPosition();
const float double_radius = radius * 2.0f;
const float animation_percent_inverse = 1.0f - animation_percent;
const float knob_left = (animation_percent * (GetWidth() - double_radius - offset.x))
+ (animation_percent_inverse * offset.x)
+ _state.KnobInset.Left;
const float knob_top = _state.KnobInset.Top + _state.KnobOffset.y;
const float knob_bottom = GetHeight() - _state.KnobInset.Bottom + _state.KnobOffset.y;
const float knob_right = (knob_left - _state.KnobInset.Left) + double_radius - _state.KnobInset.Right;
// if our offsets in the x or y are close to 0,
// we will just skip drawing the whole thing.
if (ImApproximately(knob_left, knob_right) ||
ImApproximately(knob_top, knob_bottom))
{
return ImRect();
}
const ImVec2 knob_min = position + ImVec2(knob_left, knob_top);
const ImVec2 knob_max = position + ImVec2(knob_right, knob_bottom);
return ImRect(knob_min, knob_max);
}
void ImGuiToggleRenderer::DrawRectBorder(ImRect bounds, ImU32 color_border, float rounding, float thickness)
{
// the border should only grow "inside" the bounding box,
// so we need to shrink the bounds used to prevent it from puffing out.
const float half_thickness = thickness * 0.5f;
bounds.Expand(-half_thickness);
_drawList->AddRect(bounds.Min, bounds.Max, color_border, rounding, ImDrawFlags_None, thickness);
}
void ImGuiToggleRenderer::DrawCircleBorder(const ImVec2& center, float radius, ImU32 color_border, float thickness)
{
// the border should only grow "inside" the bounding box,
// so we need to shrink the radius used to prevent it from puffing out.
const float half_thickness = thickness * 0.5f;
radius -= half_thickness;
_drawList->AddCircle(center, radius, color_border, 0, thickness);
}
void ImGuiToggleRenderer::DrawRectShadow(ImRect bounds, ImU32 color_shadow, float rounding, float thickness)
{
// the shadow should only grow "outside" the bounding box,
// so we need to expand the bounds used to puff it out.
const float half_thickness = thickness * 0.5f;
bounds.Expand(half_thickness);
_drawList->AddRect(bounds.Min, bounds.Max, color_shadow, rounding, ImDrawFlags_None, thickness);
}
void ImGuiToggleRenderer::DrawCircleShadow(const ImVec2& center, float radius, ImU32 color_border, float thickness)
{
// the shadow should only grow "outside" the bounding box,
// so we need to expand the radius used to puff it out.
const float half_thickness = thickness * 0.5f;
radius += half_thickness;
_drawList->AddCircle(center, radius, color_border, 0, thickness);
}
@@ -0,0 +1,97 @@
#pragma once
#include "imgui.h"
#include "imgui_toggle.hpp"
#include "imgui_toggle_palette.hpp"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif // IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_internal.h"
class ImGuiToggleRenderer
{
public:
ImGuiToggleRenderer();
ImGuiToggleRenderer(const char* label, bool* value, const ImGuiToggleConfig& user_config);
void SetConfig(const char* label, bool* value, const ImGuiToggleConfig& user_config);
bool Render();
private:
// toggle state & context
ImGuiToggleConfig _config;
ImGuiToggleStateConfig _state;
ImGuiTogglePalette _palette;
bool _isMixedValue;
bool _isHovered;
bool _isLastActive;
float _lastActiveTimer;
float _animationPercent;
// imgui specific context
const ImGuiContext* g;
const ImGuiStyle* _style;
ImDrawList* _drawList;
ImGuiID _id;
// raw ui value & label
const char* _label;
bool* _value;
// calculated values
ImRect _boundingBox;
ImVec4 _colorA11yGlyphOff;
ImVec4 _colorA11yGlyphOn;
// inline accessors
inline float GetWidth() const { return _boundingBox.GetWidth(); }
inline float GetHeight() const { return _boundingBox.GetHeight(); }
inline ImVec2 GetPosition() const { return _boundingBox.Min; }
inline ImVec2 GetToggleSize() const { return _boundingBox.GetSize(); }
inline bool IsAnimated() const { return (_config.Flags & ImGuiToggleFlags_Animated) != 0 && _config.AnimationDuration > 0; }
inline bool HasBorderedFrame() const { return (_config.Flags & ImGuiToggleFlags_BorderedFrame) != 0 && _state.FrameBorderThickness > 0; }
inline bool HasShadowedFrame() const { return (_config.Flags & ImGuiToggleFlags_ShadowedKnob) != 0 && _state.FrameShadowThickness > 0; }
inline bool HasBorderedKnob() const { return (_config.Flags & ImGuiToggleFlags_BorderedKnob) != 0 && _state.KnobBorderThickness > 0; }
inline bool HasShadowedKnob() const { return (_config.Flags & ImGuiToggleFlags_ShadowedKnob) != 0 && _state.KnobShadowThickness > 0; }
inline bool HasA11yGlyphs() const { return (_config.Flags & ImGuiToggleFlags_A11y) != 0; }
inline bool HasCircleKnob() const { return _config.KnobRounding >= 1.0f; }
inline bool HasRectangleKnob() const { return _config.KnobRounding < 1.0f; }
// behavior
void ValidateConfig();
bool ToggleBehavior(const ImRect& interaction_bounding_box);
// drawing - general
void DrawToggle();
// drawing - frame
void DrawFrame(ImU32 color_frame);
// drawing a11y
void DrawA11yDot(const ImVec2& pos, ImU32 color);
void DrawA11yGlyph(ImVec2 pos, ImU32 color, bool state, float radius, float thickness);
void DrawA11yLabel(ImVec2 pos, ImU32 color, const char* label);
void DrawA11yFrameOverlay(float knob_radius, bool state);
void DrawA11yFrameOverlays(float knob_radius);
// drawing - knob
void DrawCircleKnob(float radius, ImU32 color_knob);
void DrawRectangleKnob(float radius, ImU32 color_knob);
// drawing - label
void DrawLabel(float x_offset);
// state updating
void UpdateAnimationPercent();
void UpdateStateConfig();
void UpdatePalette();
// helpers
ImVec2 CalculateKnobCenter(float radius, float animation_percent, const ImVec2& offset = ImVec2()) const;
ImRect CalculateKnobBounds(float radius, float animation_percent, const ImVec2& offset = ImVec2()) const;
void DrawRectBorder(ImRect bounds, ImU32 color_border, float rounding, float thickness);
void DrawCircleBorder(const ImVec2& center, float radius, ImU32 color_border, float thickness);
void DrawRectShadow(ImRect bounds, ImU32 color_shadow, float rounding, float thickness);
void DrawCircleShadow(const ImVec2& center, float radius, ImU32 color_shadow, float thickness);
};
+3 -4
View File
@@ -12,10 +12,9 @@ namespace YimMenu
{
BaseHook::Add<Window::WndProc>(new DetourHook("WndProc", Pointers.WndProc, Window::WndProc));
auto swapchain = new VMTHook<SwapChain::VMTSize>("SwapChain", *Pointers.SwapChain);
swapchain->Hook(SwapChain::VMTPresentIdx, SwapChain::Present);
swapchain->Hook(SwapChain::VMTResizeBuffersIdx, SwapChain::ResizeBuffers);
BaseHook::Add<SwapChain::Present>(swapchain);
auto swapchain_vft = *reinterpret_cast<void***>(*Pointers.SwapChain);
BaseHook::Add<SwapChain::Present>(new DetourHook("Present", swapchain_vft[SwapChain::VMTPresentIdx], SwapChain::Present));
BaseHook::Add<SwapChain::ResizeBuffers>(new DetourHook("ResizeBuffers", swapchain_vft[SwapChain::VMTResizeBuffersIdx], SwapChain::ResizeBuffers));
}
Hooking::~Hooking()
+1
View File
@@ -43,6 +43,7 @@ namespace YimMenu
template<typename T>
inline VMTHook<N>::VMTHook(const std::string_view name, T* vmtAddress) :
BaseHook(name),
m_Enabled(false),
m_VMTAddress(reinterpret_cast<void***>(vmtAddress))
{
m_OriginalVMT = *m_VMTAddress;
+124
View File
@@ -0,0 +1,124 @@
#include "ExceptionHandler.hpp"
#include "StackTrace.hpp"
#include <hde64.h>
#include <unordered_set>
namespace YimMenu
{
inline auto HashStackTrace(std::vector<uint64_t> stack_trace)
{
auto data = reinterpret_cast<const char*>(stack_trace.data());
std::size_t size = stack_trace.size() * sizeof(uint64_t);
return std::hash<std::string_view>()({data, size});
}
ExceptionHandler::ExceptionHandler()
{
LOG(INFO) << "ExceptionHandler initialized";
m_OldErrorMode = SetErrorMode(0);
m_Handler = SetUnhandledExceptionFilter(&VectoredExceptionHandler);
}
ExceptionHandler::~ExceptionHandler()
{
SetErrorMode(m_OldErrorMode);
SetUnhandledExceptionFilter(reinterpret_cast<decltype(&VectoredExceptionHandler)>(m_Handler));
}
inline thread_local static StackTrace trace;
LONG VectoredExceptionHandler(EXCEPTION_POINTERS* exception_info)
{
const auto exception_code = exception_info->ExceptionRecord->ExceptionCode;
if (exception_code == EXCEPTION_BREAKPOINT || exception_code == DBG_PRINTEXCEPTION_C || exception_code == DBG_PRINTEXCEPTION_WIDE_C)
return EXCEPTION_CONTINUE_SEARCH;
static std::unordered_set<std::size_t> logged_exceptions;
trace.NewStackTrace(exception_info);
const auto trace_hash = HashStackTrace(trace.GetFramePointers());
if (const auto it = logged_exceptions.find(trace_hash); it == logged_exceptions.end())
{
LOG(FATAL) << trace;
Logger::FlushQueue();
logged_exceptions.insert(trace_hash);
}
if (exception_info->ExceptionRecord->ExceptionInformation[0] == EXCEPTION_EXECUTE_FAULT)
{
auto return_address_ptr = (uint64_t*)exception_info->ContextRecord->Rsp;
if (IsBadReadPtr(reinterpret_cast<void*>(return_address_ptr), 8))
{
LOG(FATAL) << "Cannot resume execution, crashing (failed to find valid return address)";
return EXCEPTION_CONTINUE_SEARCH;
}
else
{
LOG(FATAL) << "Force returning from function";
exception_info->ContextRecord->Rip = *return_address_ptr;
exception_info->ContextRecord->Rsp += 8;
}
}
else
{
hde64s opcode{};
hde64_disasm(reinterpret_cast<void*>(exception_info->ContextRecord->Rip), &opcode);
if (opcode.flags & F_ERROR)
{
LOG(FATAL) << "Cannot resume execution, crashing (failed to decode insn)";
return EXCEPTION_CONTINUE_SEARCH;
}
if (opcode.opcode == 0xFF && opcode.modrm_reg == 4) // JMP (FF /4)
{
auto return_address_ptr = (uint64_t*)exception_info->ContextRecord->Rsp;
if (IsBadReadPtr(reinterpret_cast<void*>(return_address_ptr), 8))
{
LOG(FATAL) << "Cannot resume execution, crashing";
return EXCEPTION_CONTINUE_SEARCH;
}
else
{
exception_info->ContextRecord->Rip = *return_address_ptr;
exception_info->ContextRecord->Rsp += 8;
}
}
else
{
exception_info->ContextRecord->Rip += opcode.len;
if (opcode.opcode == 0x8B && opcode.modrm_mod != 3)
{
uint8_t regId = opcode.rex_r | opcode.modrm_reg;
switch (regId)
{
case 0: exception_info->ContextRecord->Rax = 0; break;
case 1: exception_info->ContextRecord->Rcx = 0; break;
case 2: exception_info->ContextRecord->Rdx = 0; break;
case 3: exception_info->ContextRecord->Rbx = 0; break;
case 4: exception_info->ContextRecord->Rsp = 0; break;
case 5: exception_info->ContextRecord->Rbp = 0; break;
case 6: exception_info->ContextRecord->Rsi = 0; break;
case 7: exception_info->ContextRecord->Rdi = 0; break;
case 8: exception_info->ContextRecord->R8 = 0; break;
case 9: exception_info->ContextRecord->R9 = 0; break;
case 10: exception_info->ContextRecord->R10 = 0; break;
case 11: exception_info->ContextRecord->R11 = 0; break;
case 12: exception_info->ContextRecord->R12 = 0; break;
case 13: exception_info->ContextRecord->R13 = 0; break;
case 14: exception_info->ContextRecord->R14 = 0; break;
case 15: exception_info->ContextRecord->R15 = 0; break;
}
}
}
}
return EXCEPTION_CONTINUE_EXECUTION;
}
}
static YimMenu::ExceptionHandler _ExceptionHandler{};
+17
View File
@@ -0,0 +1,17 @@
#pragma once
namespace YimMenu
{
class ExceptionHandler final
{
public:
ExceptionHandler();
virtual ~ExceptionHandler();
private:
void* m_Handler;
uint32_t m_OldErrorMode;
};
extern LONG VectoredExceptionHandler(EXCEPTION_POINTERS* exception_info);
}
+247
View File
@@ -0,0 +1,247 @@
#include "StackTrace.hpp"
#include <DbgHelp.h>
#include <winternl.h>
namespace YimMenu
{
StackTrace::StackTrace() :
m_FramePointers(32)
{
SymInitialize(GetCurrentProcess(), nullptr, true);
}
StackTrace::~StackTrace()
{
SymCleanup(GetCurrentProcess());
}
const std::vector<uint64_t>& StackTrace::GetFramePointers()
{
return m_FramePointers;
}
void StackTrace::NewStackTrace(EXCEPTION_POINTERS* exception_info)
{
static std::mutex m;
std::lock_guard lock(m);
m_ExceptionInfo = exception_info;
Clear();
m_Dump << ExceptionCodeToString(exception_info->ExceptionRecord->ExceptionCode) << '\n';
DumpModuleInfo();
DumpRegisters();
DumpStacktrace();
DumpExceptionInfo();
m_Dump << "\n--------End of exception--------\n";
}
std::string StackTrace::GetString() const
{
return m_Dump.str();
}
void StackTrace::Clear()
{
m_Dump.str("");
m_Dump.clear();
}
// I'd prefer to make some sort of global instance that cache all modules once instead of doing this every time
void StackTrace::DumpModuleInfo()
{
// modules cached already
if (m_Modules.size())
return;
m_Dump << "Dumping modules:\n";
const auto peb = reinterpret_cast<PPEB>(NtCurrentTeb()->ProcessEnvironmentBlock);
if (!peb)
return;
const auto ldr_data = peb->Ldr;
if (!ldr_data)
return;
const auto module_list = &ldr_data->InMemoryOrderModuleList;
auto module_entry = module_list->Flink;
for (; module_list != module_entry; module_entry = module_entry->Flink)
{
const auto table_entry = CONTAINING_RECORD(module_entry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (!table_entry)
continue;
if (table_entry->FullDllName.Buffer)
{
auto mod_info = ModuleInfo(table_entry->FullDllName.Buffer, table_entry->DllBase);
m_Dump << mod_info.m_Name << " Base Address: " << HEX(mod_info.m_Base) << " Size: " << mod_info.m_Size << '\n';
m_Modules.emplace_back(std::move(mod_info));
}
}
}
void StackTrace::DumpRegisters()
{
const auto context = m_ExceptionInfo->ContextRecord;
m_Dump << "Dumping registers:\n"
<< "RAX: " << HEX(context->Rax) << '\n'
<< "RCX: " << HEX(context->Rcx) << '\n'
<< "RDX: " << HEX(context->Rdx) << '\n'
<< "RBX: " << HEX(context->Rbx) << '\n'
<< "RSI: " << HEX(context->Rsi) << '\n'
<< "RDI: " << HEX(context->Rdi) << '\n'
<< "RSP: " << HEX(context->Rsp) << '\n'
<< "RBP: " << HEX(context->Rbp) << '\n'
<< "R8: " << HEX(context->R8) << '\n'
<< "R9: " << HEX(context->R9) << '\n'
<< "R10: " << HEX(context->R10) << '\n'
<< "R11: " << HEX(context->R11) << '\n'
<< "R12: " << HEX(context->R12) << '\n'
<< "R13: " << HEX(context->R13) << '\n'
<< "R14: " << HEX(context->R14) << '\n'
<< "R15: " << HEX(context->R15) << '\n';
}
void StackTrace::DumpStacktrace()
{
m_Dump << "Dumping stacktrace:";
GrabStacktrace();
// alloc once
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME];
auto symbol = reinterpret_cast<SYMBOL_INFO*>(buffer);
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;
DWORD64 displacement64;
DWORD displacement;
IMAGEHLP_LINE64 line;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
for (size_t i = 0; i < m_FramePointers.size() && m_FramePointers[i]; ++i)
{
const auto addr = m_FramePointers[i];
const auto module_info = GetModuleByAddress(addr);
m_Dump << "\n[" << i << "]\t";
if (SymFromAddr(GetCurrentProcess(), addr, &displacement64, symbol))
{
if (SymGetLineFromAddr64(GetCurrentProcess(), addr, &displacement, &line))
{
m_Dump << line.FileName << " L: " << line.LineNumber << ' ' << std::string_view(symbol->Name, symbol->NameLen);
continue;
}
if (module_info)
{
m_Dump << module_info->m_Name << ' ' << std::string_view(symbol->Name, symbol->NameLen) << " ("
<< module_info->m_Name << '+' << HEX(addr - module_info->m_Base) << ')';
continue;
}
m_Dump << HEX(addr) << ' ' << std::string_view(symbol->Name, symbol->NameLen);
continue;
}
if (module_info)
{
m_Dump << module_info->m_Name << '+' << HEX(addr - module_info->m_Base) << ' ' << HEX(addr);
continue;
}
m_Dump << HEX(addr);
}
}
void StackTrace::DumpExceptionInfo()
{
DWORD exception_code = m_ExceptionInfo->ExceptionRecord->ExceptionCode;
constexpr DWORD msvc_exception_code = 0xe06d7363;
if (exception_code == msvc_exception_code)
{
m_Dump << '\n'
<< reinterpret_cast<const std::exception*>(m_ExceptionInfo->ExceptionRecord->ExceptionInformation[1])->what() << '\n';
}
else if (exception_code == EXCEPTION_ACCESS_VIOLATION || exception_code == EXCEPTION_IN_PAGE_ERROR)
{
const auto flag = m_ExceptionInfo->ExceptionRecord->ExceptionInformation[0];
const auto addr = m_ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
switch (flag)
{
case EXCEPTION_READ_FAULT: m_Dump << '\n' << "Attempted to read from " << HEX(addr) << '\n'; break;
case EXCEPTION_WRITE_FAULT: m_Dump << '\n' << "Attempted to write to " << HEX(addr) << '\n'; break;
case EXCEPTION_EXECUTE_FAULT: m_Dump << '\n' << "Attempted to execute at " << HEX(addr) << '\n'; break;
default: m_Dump << '\n' << "Inaccessible data at " << HEX(addr) << '\n';
}
if (exception_code == EXCEPTION_IN_PAGE_ERROR)
{
m_Dump << "NTSTATUS code " << HEX(m_ExceptionInfo->ExceptionRecord->ExceptionInformation[2]) << '\n';
}
}
}
void StackTrace::GrabStacktrace()
{
CONTEXT context = *m_ExceptionInfo->ContextRecord;
STACKFRAME64 frame{};
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Mode = AddrModeFlat;
frame.AddrPC.Offset = context.Rip;
frame.AddrFrame.Offset = context.Rbp;
frame.AddrStack.Offset = context.Rsp;
for (size_t i = 0; i < m_FramePointers.size(); ++i)
{
if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &frame, &context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
{
break;
}
m_FramePointers[i] = frame.AddrPC.Offset;
}
}
const StackTrace::ModuleInfo* StackTrace::GetModuleByAddress(uint64_t addr) const
{
for (auto& mod_info : m_Modules)
{
if (mod_info.m_Base < addr && addr < mod_info.m_Base + mod_info.m_Size)
{
return &mod_info;
}
}
return nullptr;
}
std::string StackTrace::ExceptionCodeToString(const DWORD code)
{
#define MAP_PAIR_STRINGIFY(x) \
{ \
x, #x \
}
static const std::map<DWORD, std::string> exceptions = {MAP_PAIR_STRINGIFY(EXCEPTION_ACCESS_VIOLATION), MAP_PAIR_STRINGIFY(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), MAP_PAIR_STRINGIFY(EXCEPTION_DATATYPE_MISALIGNMENT), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DENORMAL_OPERAND), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DIVIDE_BY_ZERO), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INVALID_OPERATION), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_OVERFLOW), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_STACK_CHECK), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_UNDERFLOW), MAP_PAIR_STRINGIFY(EXCEPTION_ILLEGAL_INSTRUCTION), MAP_PAIR_STRINGIFY(EXCEPTION_IN_PAGE_ERROR), MAP_PAIR_STRINGIFY(EXCEPTION_INT_DIVIDE_BY_ZERO), MAP_PAIR_STRINGIFY(EXCEPTION_INT_OVERFLOW), MAP_PAIR_STRINGIFY(EXCEPTION_INVALID_DISPOSITION), MAP_PAIR_STRINGIFY(EXCEPTION_NONCONTINUABLE_EXCEPTION), MAP_PAIR_STRINGIFY(EXCEPTION_PRIV_INSTRUCTION), MAP_PAIR_STRINGIFY(EXCEPTION_STACK_OVERFLOW), MAP_PAIR_STRINGIFY(EXCEPTION_BREAKPOINT), MAP_PAIR_STRINGIFY(EXCEPTION_SINGLE_STEP)};
if (const auto& it = exceptions.find(code); it != exceptions.end())
return it->second;
return "UNKNOWN_EXCEPTION: CODE: " + std::to_string(code);
}
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
namespace YimMenu
{
class StackTrace
{
public:
StackTrace();
virtual ~StackTrace();
const std::vector<uint64_t>& GetFramePointers();
void NewStackTrace(EXCEPTION_POINTERS* exception_info);
std::string GetString() const;
void Clear();
friend std::ostream& operator<<(std::ostream& os, const StackTrace& st);
friend std::ostream& operator<<(std::ostream& os, const StackTrace* st);
private:
struct ModuleInfo
{
ModuleInfo(std::filesystem::path path, void* base) :
m_Name(path.filename().string()),
m_Base(reinterpret_cast<uintptr_t>(base))
{
const auto dos_header = reinterpret_cast<IMAGE_DOS_HEADER*>(base);
const auto nt_header = reinterpret_cast<IMAGE_NT_HEADERS*>(m_Base + dos_header->e_lfanew);
m_Size = nt_header->OptionalHeader.SizeOfCode;
}
std::string m_Name;
uintptr_t m_Base;
size_t m_Size;
};
private:
void DumpModuleInfo();
void DumpRegisters();
void DumpStacktrace();
void DumpExceptionInfo();
void GrabStacktrace();
const ModuleInfo* GetModuleByAddress(uint64_t addr) const;
static std::string ExceptionCodeToString(const DWORD code);
private:
EXCEPTION_POINTERS* m_ExceptionInfo;
std::stringstream m_Dump;
std::vector<uint64_t> m_FramePointers;
inline static std::vector<ModuleInfo> m_Modules;
};
inline std::ostream& operator<<(std::ostream& os, const StackTrace& st)
{
os << st.GetString();
return os;
}
inline std::ostream& operator<<(std::ostream& os, const StackTrace* st)
{
os << st->GetString();
return os;
}
}
-37
View File
@@ -1,37 +0,0 @@
#include "BytePatch.hpp"
namespace YimMenu
{
BytePatch::~BytePatch()
{
Restore();
}
void BytePatch::Apply() const
{
std::copy_n(m_Patch.get(), m_Size, reinterpret_cast<byte*>(m_Address));
}
void BytePatch::Restore() const
{
std::copy_n(m_Original.get(), m_Size, reinterpret_cast<byte*>(m_Address));
}
void BytePatch::Remove() const
{
if (const auto it = std::find(m_Patches.begin(), m_Patches.end(), this); it != m_Patches.end())
{
m_Patches.erase(it);
}
}
void BytePatch::RestoreAll()
{
m_Patches.clear();
}
bool operator==(const std::unique_ptr<BytePatch>& a, const BytePatch* b)
{
return a->m_Address == b->m_Address;
}
}
-90
View File
@@ -1,90 +0,0 @@
#pragma once
#include <memory>
#include <span>
#include <vector>
namespace YimMenu
{
template<typename T>
concept SpanCompatibleType = requires(T a)
{
std::span{a};
};
class BytePatch
{
private:
byte* m_Address;
std::unique_ptr<byte[]> m_Patch;
std::unique_ptr<byte[]> m_Original;
std::size_t m_Size;
protected:
static inline std::vector<std::unique_ptr<BytePatch>> m_Patches;
public:
virtual ~BytePatch();
void Apply() const;
void Restore() const;
void Remove() const;
static void RestoreAll();
template<typename TAddr>
static const std::unique_ptr<BytePatch>& Make(TAddr addr, std::remove_pointer_t<std::remove_reference_t<TAddr>> value);
template<typename TAddr, typename T>
requires SpanCompatibleType<T>
static const std::unique_ptr<BytePatch>& Make(TAddr addr, T spanCompatible);
private:
template<typename TAddr>
BytePatch(TAddr addr, std::remove_pointer_t<std::remove_reference_t<TAddr>> value);
template<typename TAddr, typename T, std::size_t N>
BytePatch(TAddr addr, std::span<T, N> span);
friend bool operator==(const std::unique_ptr<BytePatch>& a, const BytePatch* b);
};
template<typename TAddr>
inline const std::unique_ptr<BytePatch>& BytePatch::Make(TAddr addr, std::remove_pointer_t<std::remove_reference_t<TAddr>> value)
{
return m_Patches.emplace_back(std::unique_ptr<BytePatch>(new BytePatch(addr, value)));
}
template<typename TAddr, typename T>
requires SpanCompatibleType<T>
inline const std::unique_ptr<BytePatch>& BytePatch::Make(TAddr addr, T spanCompatible)
{
return m_Patches.emplace_back(std::unique_ptr<BytePatch>(new BytePatch(addr, std::span{spanCompatible})));
}
template<typename TAddr>
inline BytePatch::BytePatch(TAddr addr, std::remove_pointer_t<std::remove_reference_t<TAddr>> value) :
m_Address(addr)
{
m_Size = sizeof(std::remove_pointer_t<std::remove_reference_t<TAddr>>);
m_Original = std::make_unique<byte[]>(m_Size);
std::copy_n(m_Address, m_Size, m_Original.get());
m_Patch = std::make_unique<byte[]>(m_Size);
std::copy_n(&value, m_Size, m_Patch.get());
}
template<typename TAddr, typename T, std::size_t N>
inline BytePatch::BytePatch(TAddr addr, std::span<T, N> span) :
m_Address((byte*)addr)
{
m_Size = span.size();
m_Original = std::make_unique<byte[]>(m_Size);
std::copy_n(m_Address, m_Size, m_Original.get());
m_Patch = std::make_unique<byte[]>(m_Size);
std::copy(span.begin(), span.end(), m_Patch.get());
}
}
+26
View File
@@ -25,6 +25,8 @@ namespace YimMenu
*/
template<typename T = void*>
T GetExport(const std::string_view symbolName) const;
template<typename T = void*>
T GetExport(int ordinal) const;
/**
* @brief Gets the address of the import function
*
@@ -85,4 +87,28 @@ namespace YimMenu
LOG(FATAL) << "Cannot find export: " << symbolName;
return nullptr;
}
template<typename T>
inline T Module::GetExport(int ordinal) const
{
const auto ntHeader = GetNtHeader();
if (!ntHeader)
return nullptr;
const auto imageDataDirectory = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
const auto exportDirectory = m_Base.Add(imageDataDirectory.VirtualAddress).As<IMAGE_EXPORT_DIRECTORY*>();
const auto ordinalOffsets = m_Base.Add(exportDirectory->AddressOfNameOrdinals).As<uint16_t*>();
const auto funcOffsets = m_Base.Add(exportDirectory->AddressOfFunctions).As<DWORD*>();
for (std::size_t i = 0; i < exportDirectory->NumberOfFunctions; i++)
{
if (ordinalOffsets[i] != ordinal)
continue;
return m_Base.Add(funcOffsets[i]).As<T>();
}
LOG(FATAL) << "Cannot find export: " << ordinal;
return nullptr;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
#include "ModuleMgr.hpp"
#include "util/Joaat.hpp"
#include "core/util/Joaat.hpp"
namespace YimMenu
{
+38 -16
View File
@@ -1,7 +1,9 @@
#pragma once
#include "util/StrToHex.hpp"
#include "PatternHash.hpp"
#include "core/util/StrToHex.hpp"
#include <string_view>
#include <vector>
namespace YimMenu
{
@@ -10,6 +12,7 @@ namespace YimMenu
{
char m_Signature[N]{};
std::size_t m_SignatureByteLength;
PatternHash m_Hash;
constexpr Signature(char const (&signature)[N]) :
m_SignatureByteLength(0)
@@ -18,9 +21,12 @@ namespace YimMenu
for (std::size_t i = 0; i < N; i++)
{
m_Hash = m_Hash.Update(m_Signature[i]);
if (m_Signature[i] == ' ' || m_Signature[i] == '\0')
m_SignatureByteLength++;
}
m_Hash = m_Hash.Update(m_SignatureByteLength);
}
/**
@@ -50,6 +56,11 @@ namespace YimMenu
{
return m_SignatureByteLength;
}
constexpr PatternHash Hash() const
{
return m_Hash;
}
};
class IPattern
@@ -60,6 +71,7 @@ namespace YimMenu
virtual const std::string_view Name() const = 0;
virtual constexpr std::span<const std::optional<std::uint8_t>> Signature() const = 0;
virtual const PatternHash Hash() const = 0;
};
template<Signature S>
@@ -68,6 +80,7 @@ namespace YimMenu
private:
const std::string_view m_Name;
std::array<std::optional<std::uint8_t>, S.ByteLength()> m_Signature;
PatternHash m_Hash;
public:
constexpr Pattern(const std::string_view name);
@@ -80,8 +93,10 @@ namespace YimMenu
{
return m_Signature;
}
friend std::ostream& operator<< <>(std::ostream& os, const Pattern<S>& signature);
inline virtual const PatternHash Hash() const override
{
return m_Hash;
}
};
template<Signature S>
@@ -89,6 +104,8 @@ namespace YimMenu
IPattern(),
m_Name(name)
{
m_Hash = S.Hash();
for (size_t i = 0, pos = 0; i < S.Length(); i++)
{
const auto c = S.Get()[i];
@@ -112,20 +129,25 @@ namespace YimMenu
}
}
template<Signature S>
inline std::ostream& operator<<(std::ostream& os, const Pattern<S>& pattern)
class SimplePattern
{
os << pattern.Name() << ": { ";
for (const auto byte : pattern.Signature())
public:
SimplePattern(std::string_view ida_sig);
inline SimplePattern(const char* ida_sig) :
SimplePattern(std::string_view(ida_sig))
{
if (byte == std::nullopt)
{
os << "?? ";
continue;
}
os << std::hex << std::uppercase << DWORD64(byte.value()) << std::nouppercase << std::dec << ' ';
}
os << "}";
return os;
}
inline SimplePattern(SimplePattern&& other) :
m_Bytes(other.m_Bytes)
{
}
inline SimplePattern(const SimplePattern& other) :
m_Bytes(other.m_Bytes)
{
}
std::vector<std::optional<uint8_t>> m_Bytes;
};
}
+68
View File
@@ -0,0 +1,68 @@
#pragma once
// TODO: use an actual hash function
namespace YimMenu
{
class PatternHash
{
public:
std::uint64_t m_Hash;
constexpr PatternHash() :
m_Hash(0xD84A917A64D4D016ULL)
{
}
constexpr PatternHash(std::uint64_t hash) :
m_Hash(hash)
{
}
constexpr PatternHash(const PatternHash& other) :
m_Hash(other.m_Hash)
{
}
constexpr PatternHash(PatternHash&& other) :
m_Hash(other.m_Hash)
{
}
constexpr void operator=(const PatternHash& other)
{
m_Hash = other.m_Hash;
}
constexpr PatternHash Update(char data) const
{
auto hash = m_Hash;
hash += (static_cast<std::uint64_t>(data));
hash += hash << 10;
hash ^= (hash >> 6);
return PatternHash(hash);
}
constexpr PatternHash Update(int data) const
{
auto hash = m_Hash;
hash ^= (data >> 8);
hash += hash << 10;
hash ^= (hash >> 7);
return PatternHash(hash);
}
constexpr PatternHash Update(std::uint64_t data) const
{
auto hash = m_Hash;
hash ^= (data * 0xA5C38736C426FCB8ULL);
hash += hash << 15;
hash ^= (hash >> 5);
return PatternHash(hash);
}
constexpr std::uint64_t GetHash() const
{
return m_Hash;
}
};
}
+17
View File
@@ -1,6 +1,7 @@
#include "PatternScanner.hpp"
#include "Module.hpp"
#include "core/backend/PatternCache.hpp"
#include <future>
@@ -42,6 +43,17 @@ namespace YimMenu
{
const auto signature = pattern->Signature();
if (PatternCache::IsInitialized())
{
auto offset = PatternCache::GetCachedOffset(pattern->Hash().Update(m_Module->Size()));
if (offset.has_value())
{
LOG(INFO) << "Using cached pattern [" << pattern->Name() << "] : [" << HEX(m_Module->Base() + offset.value()) << "]";
std::invoke(func, m_Module->Base() + offset.value());
return true;
}
}
for (auto i = m_Module->Base(); i < m_Module->End(); ++i)
{
if (signature.size() + i > m_Module->End())
@@ -63,6 +75,11 @@ namespace YimMenu
std::invoke(func, i);
if (PatternCache::IsInitialized())
{
PatternCache::UpdateCachedOffset(pattern->Hash().Update(m_Module->Size()), i - m_Module->Base());
}
return true;
}
}
+267 -30
View File
@@ -1,11 +1,16 @@
#include "Renderer.hpp"
#include "core/memory/ModuleMgr.hpp"
#include "core/memory/PatternScanner.hpp"
#include "game/frontend/GUI.hpp"
#include "game/frontend/Menu.hpp"
#include "game/pointers/Pointers.hpp"
#include <backends/imgui_impl_dx11.h>
#include <backends/imgui_impl_dx12.h>
#include <backends/imgui_impl_win32.h>
#include <imgui.h>
namespace YimMenu
{
Renderer::Renderer()
@@ -18,15 +23,16 @@ namespace YimMenu
void Renderer::DestroyImpl()
{
if (m_SwapChain && m_Device && m_DeviceContext)
{
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
ImGui_ImplWin32_Shutdown();
WaitForLastFrame();
ImGui_ImplDX12_Shutdown();
ImGui::DestroyContext();
}
bool Renderer::InitImpl()
bool Renderer::InitDX12()
{
if (!Pointers.SwapChain)
{
@@ -35,35 +41,161 @@ namespace YimMenu
return false;
}
if (m_SwapChain = *Pointers.SwapChain; !m_SwapChain)
if (!Pointers.CommandQueue)
{
LOG(WARNING) << "CommandQueue pointer is invalid!";
return false;
}
//This is required. In order to stop ComPtr from releasing the original pointer, we create a new ComPtr with the ptr as the intializer. (The '=' operator uses swap which releases the object passed into it)
if (m_GameSwapChain = ComPtr<IDXGISwapChain1>(*Pointers.SwapChain); !m_GameSwapChain.Get())
{
LOG(WARNING) << "Dereferenced SwapChain pointer is invalid!";
return false;
}
if (const auto result = m_SwapChain->GetDevice(__uuidof(ID3D11Device), reinterpret_cast<void**>(&m_Device)); result < 0)
if (m_CommandQueue = ComPtr<ID3D12CommandQueue>(*Pointers.CommandQueue); !m_CommandQueue.Get())
{
LOG(WARNING) << "Dereferenced CommandQueue pointer is invalid!";
return false;
}
m_GameSwapChain.As(&m_SwapChain); //We need GetCurrentBackBufferIndex from IDXGISwapChain3
if (const auto result = m_SwapChain->GetDevice(__uuidof(ID3D12Device), reinterpret_cast<void**>(m_Device.GetAddressOf())); result < 0)
{
LOG(WARNING) << "Failed to get D3D Device with result: [" << result << "]";
return false;
}
m_Device->GetImmediateContext(&m_DeviceContext);
ImGui::CreateContext();
ImGui::StyleColorsDark();
if (const auto result = m_SwapChain->GetDesc(&m_SwapChainDesc); result < 0)
{
LOG(WARNING) << "Failed to get SwapChain Description with result: [" << result << "]";
return false;
}
if (const auto result = m_Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), (void**)m_Fence.GetAddressOf()); result < 0)
{
LOG(WARNING) << "Failed to create Fence with result: [" << result << "]";
return false;
}
if (const auto result = m_FenceEvent = CreateEventA(nullptr, FALSE, FALSE, nullptr); !result)
{
LOG(WARNING) << "Failed to create Fence Event!";
return false;
}
m_FrameContext.resize(m_SwapChainDesc.BufferCount);
D3D12_DESCRIPTOR_HEAP_DESC DescriptorDesc{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, m_SwapChainDesc.BufferCount, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE};
if (const auto result =
m_Device->CreateDescriptorHeap(&DescriptorDesc, __uuidof(ID3D12DescriptorHeap), (void**)m_DescriptorHeap.GetAddressOf());
result < 0)
{
LOG(WARNING) << "Failed to create Descriptor Heap with result: [" << result << "]";
return false;
}
if (const auto result = m_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
__uuidof(ID3D12CommandAllocator),
(void**)m_CommandAllocator.GetAddressOf());
result < 0)
{
LOG(WARNING) << "Failed to create Command Allocator with result: [" << result << "]";
return false;
}
for (size_t i{}; i < m_SwapChainDesc.BufferCount; ++i)
{
m_FrameContext[i].CommandAllocator = m_CommandAllocator.Get();
}
if (const auto result = m_Device->CreateCommandList(0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
m_CommandAllocator.Get(),
NULL,
__uuidof(ID3D12GraphicsCommandList),
(void**)m_CommandList.GetAddressOf());
result < 0)
{
LOG(WARNING) << "Failed to create Command List with result: [" << result << "]";
return false;
}
if (const auto result = m_CommandList->Close(); result < 0)
{
LOG(WARNING) << "Failed to finalize the creation of Command List with result: [" << result << "]";
return false;
}
D3D12_DESCRIPTOR_HEAP_DESC DescriptorBackbufferDesc{D3D12_DESCRIPTOR_HEAP_TYPE_RTV, m_SwapChainDesc.BufferCount, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1};
if (const auto result = m_Device->CreateDescriptorHeap(&DescriptorBackbufferDesc,
__uuidof(ID3D12DescriptorHeap),
(void**)m_BackbufferDescriptorHeap.GetAddressOf());
result < 0)
{
LOG(WARNING) << "Failed to create Backbuffer Descriptor Heap with result: [" << result << "]";
return false;
}
const auto RTVDescriptorSize{m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV)};
D3D12_CPU_DESCRIPTOR_HANDLE RTVHandle{m_BackbufferDescriptorHeap->GetCPUDescriptorHandleForHeapStart()};
LOG(INFO) << m_SwapChainDesc.BufferCount;
for (size_t i = 0; i < m_SwapChainDesc.BufferCount; ++i)
{
ComPtr<ID3D12Resource> BackBuffer{};
m_FrameContext[i].Descriptor = RTVHandle;
m_SwapChain->GetBuffer(i, __uuidof(ID3D12Resource), (void**)BackBuffer.GetAddressOf());
m_Device->CreateRenderTargetView(BackBuffer.Get(), nullptr, RTVHandle);
m_FrameContext[i].Resource = BackBuffer.Get();
RTVHandle.ptr += RTVDescriptorSize;
}
// never returns false, useless to check return
ImGui_ImplDX11_Init(m_Device, m_DeviceContext);
ImGui_ImplWin32_Init(Pointers.Hwnd);
ImGui::CreateContext(&GetInstance().m_FontAtlas);
ImGui_ImplWin32_Init(*Pointers.Hwnd);
ImGui_ImplDX12_Init(m_Device.Get(),
m_SwapChainDesc.BufferCount,
DXGI_FORMAT_R8G8B8A8_UNORM,
m_DescriptorHeap.Get(),
m_DescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
m_DescriptorHeap->GetGPUDescriptorHandleForHeapStart());
LOG(INFO) << "Renderer has finished initializing.";
ImGui::StyleColorsDark();
LOG(INFO) << "DirectX 12 renderer has finished initializing.";
return true;
}
bool Renderer::AddDXCallbackImpl(DXCallback&& callback, std::uint32_t priority)
bool Renderer::InitImpl()
{
return m_DXCallbacks.insert({priority, callback}).second;
while (!*Pointers.Hwnd || !*Pointers.ScreenResX)
{
std::this_thread::sleep_for(1s);
}
LOG(INFO) << "Using DX12, clear shader cache if you're having issues.";
LOG(INFO) << "Waiting...";
// std::this_thread::sleep_for(5s); //Early injection could result in errors.
return InitDX12();
}
bool Renderer::AddRendererCallBackImpl(RendererCallBack&& callback, std::uint32_t priority)
{
return m_RendererCallBacks.insert({priority, callback}).second;
}
void Renderer::AddWindowProcedureCallbackImpl(WindowProcedureCallback&& callback)
@@ -71,12 +203,12 @@ namespace YimMenu
return m_WindowProcedureCallbacks.push_back(callback);
}
void Renderer::OnPresentImpl()
void Renderer::DX12OnPresentImpl()
{
Renderer::NewFrame();
for (const auto& callback : m_DXCallbacks | std::views::values)
Renderer::DX12NewFrame();
for (const auto& callback : m_RendererCallBacks | std::views::values)
callback();
Renderer::EndFrame();
Renderer::DX12EndFrame();
}
LRESULT Renderer::WndProcImpl(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
@@ -87,26 +219,131 @@ namespace YimMenu
return ImGui_ImplWin32_WndProcHandler(hwnd, msg, wparam, lparam);
}
void Renderer::PreResize()
void Renderer::ResizeImpl(float scale)
{
ImGui_ImplDX11_InvalidateDeviceObjects();
DX12PreResize();
if (scale != 1.0f)
ImGui::GetStyle().ScaleAllSizes(scale);
ImGui::GetStyle().MouseCursorScale = 1.0f;
ImGui::GetIO().FontGlobalScale = scale;
DX12PostResize();
}
void Renderer::PostResize()
void Renderer::WaitForLastFrame()
{
ImGui_ImplDX11_CreateDeviceObjects();
FrameContext FrameCtx = GetInstance().m_FrameContext[GetInstance().m_FrameIndex % GetInstance().m_SwapChainDesc.BufferCount];
UINT64 FenceValue = FrameCtx.FenceValue;
if (FenceValue == 0)
{
return;
}
FrameCtx.FenceValue = 0;
if (GetInstance().m_Fence->GetCompletedValue() >= FenceValue)
{
return;
}
GetInstance().m_Fence->SetEventOnCompletion(FenceValue, GetInstance().m_FenceEvent);
WaitForSingleObject(GetInstance().m_FenceEvent, INFINITE);
}
void Renderer::NewFrame()
void Renderer::WaitForNextFrame()
{
ImGui_ImplDX11_NewFrame();
UINT NextFrameIndex = GetInstance().m_FrameIndex + 1;
GetInstance().m_FrameIndex = NextFrameIndex;
HANDLE WaitableObjects[] = {GetInstance().m_SwapchainWaitableObject, nullptr};
DWORD NumWaitableObjets = 1;
FrameContext FrameCtx = GetInstance().m_FrameContext[NextFrameIndex % GetInstance().m_SwapChainDesc.BufferCount];
UINT64 FenceValue = FrameCtx.FenceValue;
if (FenceValue != 0) // means no fence was signaled
{
FrameCtx.FenceValue = 0;
GetInstance().m_Fence->SetEventOnCompletion(FenceValue, GetInstance().m_FenceEvent);
WaitableObjects[1] = GetInstance().m_FenceEvent;
NumWaitableObjets = 2;
}
WaitForMultipleObjects(NumWaitableObjets, WaitableObjects, TRUE, INFINITE);
}
void Renderer::DX12PreResize()
{
SetResizing(true);
WaitForLastFrame();
ImGui_ImplDX12_InvalidateDeviceObjects();
for (size_t i{}; i != GetInstance().m_SwapChainDesc.BufferCount; ++i)
{
REL(GetInstance().m_FrameContext[i].Resource);
}
}
void Renderer::DX12PostResize()
{
//Recreate our pointers and ImGui's
ImGui_ImplDX12_CreateDeviceObjects();
const auto RTVDescriptorSize{GetInstance().m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV)};
D3D12_CPU_DESCRIPTOR_HANDLE RTVHandle{GetInstance().m_BackbufferDescriptorHeap->GetCPUDescriptorHandleForHeapStart()};
for (size_t i{}; i != GetInstance().m_SwapChainDesc.BufferCount; ++i)
{
ComPtr<ID3D12Resource> BackBuffer{};
GetInstance().m_FrameContext[i].Descriptor = RTVHandle;
GetInstance().m_SwapChain->GetBuffer(i, __uuidof(ID3D12Resource), (void**)BackBuffer.GetAddressOf());
GetInstance().m_Device->CreateRenderTargetView(BackBuffer.Get(), nullptr, RTVHandle);
GetInstance().m_FrameContext[i].Resource = BackBuffer.Get();
RTVHandle.ptr += RTVDescriptorSize;
}
SetResizing(false);
}
void Renderer::DX12NewFrame()
{
ImGui_ImplDX12_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
}
void Renderer::EndFrame()
void Renderer::DX12EndFrame()
{
WaitForNextFrame();
FrameContext& CurrentFrameContext{GetInstance().m_FrameContext[GetInstance().m_SwapChain->GetCurrentBackBufferIndex()]};
CurrentFrameContext.CommandAllocator->Reset();
D3D12_RESOURCE_BARRIER Barrier{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{CurrentFrameContext.Resource, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET}}};
GetInstance().m_CommandList->Reset(CurrentFrameContext.CommandAllocator, nullptr);
GetInstance().m_CommandList->ResourceBarrier(1, &Barrier);
GetInstance().m_CommandList->OMSetRenderTargets(1, &CurrentFrameContext.Descriptor, FALSE, nullptr);
GetInstance().m_CommandList->SetDescriptorHeaps(1, GetInstance().m_DescriptorHeap.GetAddressOf());
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), GetInstance().m_CommandList.Get());
Barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
Barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
GetInstance().m_CommandList->ResourceBarrier(1, &Barrier);
GetInstance().m_CommandList->Close();
ID3D12CommandList* CommandLists[]{GetInstance().m_CommandList.Get()};
GetInstance().m_CommandQueue->ExecuteCommandLists(_countof(CommandLists), CommandLists);
UINT64 FenceValue = GetInstance().m_FenceLastSignaledValue + 1;
GetInstance().m_CommandQueue->Signal(GetInstance().m_Fence.Get(), FenceValue);
GetInstance().m_FenceLastSignaledValue = FenceValue;
CurrentFrameContext.FenceValue = FenceValue;
}
}
+84 -17
View File
@@ -1,16 +1,37 @@
#pragma once
#include "util/Joaat.hpp"
#include "core/util/Joaat.hpp"
#include <d3d11.h>
#include <backends/imgui_impl_dx12.h>
#include <comdef.h>
#include <d3d12.h>
#include <dxgi1_4.h>
#include <functional>
#include <map>
#include <windows.h>
#include <wrl/client.h>
#define REL(o) \
o->Release(); \
if (o) \
{ \
o = nullptr; \
}
namespace YimMenu
{
using DXCallback = std::function<void()>;
using namespace Microsoft::WRL;
using RendererCallBack = std::function<void()>;
using WindowProcedureCallback = std::function<void(HWND, UINT, WPARAM, LPARAM)>;
struct FrameContext
{
ID3D12CommandAllocator* CommandAllocator;
ID3D12Resource* Resource;
D3D12_CPU_DESCRIPTOR_HANDLE Descriptor;
UINT64 FenceValue;
};
class Renderer final
{
private:
@@ -41,9 +62,9 @@ namespace YimMenu
* @return true Successfully added callback.
* @return false Duplicate render priority was given.
*/
static bool AddDXCallback(DXCallback&& callback, std::uint32_t priority)
static bool AddRendererCallBack(RendererCallBack&& callback, std::uint32_t priority)
{
return GetInstance().AddDXCallbackImpl(std::move(callback), priority);
return GetInstance().AddRendererCallBackImpl(std::move(callback), priority);
}
/**
* @brief Add a callback function to handle Windows WindowProcedure
@@ -55,30 +76,54 @@ namespace YimMenu
GetInstance().AddWindowProcedureCallbackImpl(std::move(callback));
}
static void OnPresent()
static void DX12OnPresent()
{
GetInstance().OnPresentImpl();
GetInstance().DX12OnPresentImpl();
}
static LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
return GetInstance().WndProcImpl(hwnd, msg, wparam, lparam);
}
static void PreResize();
static void PostResize();
static void WaitForLastFrame();
static void WaitForNextFrame();
static void DX12PreResize();
static void DX12PostResize();
static bool IsResizing()
{
return GetInstance().m_Resizing;
}
static void SetResizing(const bool& status)
{
GetInstance().m_Resizing = status;
}
static void Resize(float scale)
{
GetInstance().ResizeImpl(scale);
}
private:
static void NewFrame();
static void EndFrame();
static void DX12NewFrame();
static void DX12EndFrame();
private:
void DestroyImpl();
bool InitImpl();
bool AddDXCallbackImpl(DXCallback&& callback, std::uint32_t priority);
bool InitDX12();
void ResizeImpl(float scale);
bool AddRendererCallBackImpl(RendererCallBack&& callback, std::uint32_t priority);
void AddWindowProcedureCallbackImpl(WindowProcedureCallback&& callback);
void OnPresentImpl();
void DX12OnPresentImpl();
LRESULT WndProcImpl(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
static Renderer& GetInstance()
@@ -89,11 +134,33 @@ namespace YimMenu
}
private:
ID3D11Device* m_Device;
ID3D11DeviceContext* m_DeviceContext;
IDXGISwapChain* m_SwapChain;
bool m_Resizing;
std::map<joaat_t, DXCallback> m_DXCallbacks;
// Shared
ImFontAtlas m_FontAtlas;
//DX12
std::vector<FrameContext> m_FrameContext;
DXGI_SWAP_CHAIN_DESC m_SwapChainDesc;
ComPtr<IDXGISwapChain1> m_GameSwapChain;
ComPtr<IDXGISwapChain3> m_SwapChain;
ComPtr<ID3D12Device> m_Device;
ComPtr<ID3D12CommandQueue> m_CommandQueue;
ComPtr<ID3D12CommandAllocator> m_CommandAllocator;
ComPtr<ID3D12GraphicsCommandList> m_CommandList;
ComPtr<ID3D12DescriptorHeap> m_BackbufferDescriptorHeap;
ComPtr<ID3D12DescriptorHeap> m_DescriptorHeap;
ComPtr<ID3D12Fence> m_Fence;
HANDLE m_FenceEvent;
UINT64 m_FenceLastSignaledValue;
HANDLE m_SwapchainWaitableObject;
UINT64 m_FrameIndex;
private:
//Other
std::map<joaat_t, RendererCallBack> m_RendererCallBacks;
std::vector<WindowProcedureCallback> m_WindowProcedureCallbacks;
};
}
+13
View File
@@ -0,0 +1,13 @@
#include "IStateSerializer.hpp"
#include "Settings.hpp"
namespace YimMenu
{
IStateSerializer::IStateSerializer(const std::string& name) :
m_SerComponentName(name),
m_IsDirty(false)
{
Settings::AddComponent(this);
}
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
namespace YimMenu
{
class IStateSerializer
{
std::string m_SerComponentName;
bool m_IsDirty;
public:
IStateSerializer(const std::string& name);
virtual void SaveStateImpl(nlohmann::json& state) = 0;
virtual void LoadStateImpl(nlohmann::json& state) = 0;
inline void SaveState(nlohmann::json& state)
{
SaveStateImpl(state);
m_IsDirty = false;
}
inline void LoadState(nlohmann::json& state)
{
LoadStateImpl(state);
m_IsDirty = false;
}
inline bool IsStateDirty()
{
return m_IsDirty;
}
inline void MarkStateDirty()
{
m_IsDirty = true;
}
inline const std::string& GetSerializerComponentName()
{
return m_SerComponentName;
}
};
}
+114
View File
@@ -0,0 +1,114 @@
#include "Settings.hpp"
#include "IStateSerializer.hpp"
#include "Settings.hpp"
namespace YimMenu
{
Settings::Settings() :
m_SettingsFile(),
m_StateSerializers(),
m_InitialLoadDone(false)
{
}
void Settings::InitializeImpl(File settingsFile)
{
m_SettingsFile = settingsFile;
if (!settingsFile.Exists())
{
Reset();
return;
}
std::ifstream file(m_SettingsFile);
try
{
file >> m_Json;
file.close();
}
catch (std::exception&)
{
LOG(WARNING) << "Detected corrupt settings, resetting settings...";
Reset();
return;
}
for (auto& serializer : m_StateSerializers)
LoadComponentImpl(serializer);
LOG(VERBOSE) << "All settings loaded";
m_InitialLoadDone = true;
}
void Settings::TickImpl()
{
std::lock_guard lock(m_Mutex);
while (!m_LateLoaders.empty())
{
if (auto component = std::move(m_LateLoaders.front()))
{
LoadComponent(component);
}
m_LateLoaders.pop();
}
if (m_InitialLoadDone && ShouldSave())
{
for (auto& serializer : m_StateSerializers)
if (serializer->IsStateDirty())
SaveComponentImpl(serializer);
std::ofstream file(m_SettingsFile, std::ios::out | std::ios::trunc);
file << m_Json.dump(4);
file.close();
}
}
void Settings::AddComponentImpl(IStateSerializer* serializer)
{
std::lock_guard lock(m_Mutex);
m_StateSerializers.push_back(serializer);
if (m_InitialLoadDone)
m_LateLoaders.push(serializer);
}
void Settings::LoadComponentImpl(IStateSerializer* serializer)
{
LOG(VERBOSE) << "Loading component: " << serializer->GetSerializerComponentName();
if (!m_Json.contains(serializer->GetSerializerComponentName())
|| !m_Json[serializer->GetSerializerComponentName()].is_object())
m_Json[serializer->GetSerializerComponentName()] = nlohmann::json::object();
serializer->LoadState(m_Json[serializer->GetSerializerComponentName()]);
}
void Settings::SaveComponentImpl(IStateSerializer* serializer)
{
//LOG(VERBOSE) << "Saving component: " << serializer->GetSerializerComponentName();
serializer->SaveState(m_Json[serializer->GetSerializerComponentName()]);
}
void Settings::Reset()
{
std::ofstream file(m_SettingsFile, std::ios::out | std::ios::trunc);
file << "{}" << std::endl;
file.close();
m_Json = "{}";
m_InitialLoadDone = true;
}
bool Settings::ShouldSave()
{
for (auto& serializer : m_StateSerializers)
if (serializer->IsStateDirty())
return true;
return false;
}
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include "core/filemgr/File.hpp"
namespace YimMenu
{
class IStateSerializer;
class Settings
{
private:
std::filesystem::path m_SettingsFile;
std::vector<IStateSerializer*> m_StateSerializers;
std::queue<IStateSerializer*> m_LateLoaders;
bool m_InitialLoadDone;
nlohmann::json m_Json;
std::mutex m_Mutex;
public:
Settings();
static void Initialize(File settingsFile)
{
GetInstance().InitializeImpl(settingsFile);
}
static void Tick()
{
GetInstance().TickImpl();
}
static void AddComponent(IStateSerializer* serializer)
{
GetInstance().AddComponentImpl(serializer);
}
// TODO: this is broken
static void LoadComponent(IStateSerializer* serializer)
{
GetInstance().LoadComponentImpl(serializer);
}
static bool InitialLoadDone()
{
return GetInstance().m_InitialLoadDone;
}
static std::string GetFilePath()
{
return GetInstance().m_SettingsFile.string();
}
private:
static Settings& GetInstance()
{
static Settings Instance;
return Instance;
}
void InitializeImpl(File settingsFile);
void TickImpl();
void AddComponentImpl(IStateSerializer* serializer);
void LoadComponentImpl(IStateSerializer* serializer);
void SaveComponentImpl(IStateSerializer* serializer);
void Reset();
bool ShouldSave();
};
}
+15 -1
View File
@@ -11,7 +11,21 @@ namespace YimMenu
return c >= 'A' && c <= 'Z' ? c | 1 << 5 : c;
}
extern constexpr joaat_t Joaat(const std::string_view str);
constexpr joaat_t Joaat(const std::string_view str)
{
joaat_t hash = 0;
for (auto c : str)
{
hash += ToLower(c);
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
inline consteval joaat_t operator""_J(const char* s, std::size_t n)
{
joaat_t result = 0;
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <chrono>
namespace YimMenu
{
class RateLimiter
{
uint32_t m_AttemptsAllowedInTimePeriod;
std::chrono::milliseconds m_TimePeriod;
std::chrono::system_clock::time_point m_LastEventTime{};
uint32_t m_NumAttemptsAllowed = 0;
public:
RateLimiter(std::chrono::milliseconds period, uint32_t numAllowedAttempts) :
m_AttemptsAllowedInTimePeriod(numAllowedAttempts),
m_TimePeriod(period)
{
}
// Returns true if the rate limit has been exceeded
bool Process()
{
if (std::chrono::system_clock::now() - m_LastEventTime < m_TimePeriod)
{
if (++m_NumAttemptsAllowed > m_AttemptsAllowedInTimePeriod)
return true;
}
else
{
m_LastEventTime = std::chrono::system_clock::now();
m_NumAttemptsAllowed = 1;
}
return false;
}
// Check if the rate limit was exceeded by the last process() call. Use this to prevent the player from being flooded with notifications
bool ExceededLastProcess()
{
return (m_NumAttemptsAllowed - 1) == m_AttemptsAllowedInTimePeriod;
}
};
}
+68
View File
@@ -0,0 +1,68 @@
#include "ChatDisplay.hpp"
#include "Menu.hpp"
#include "core/logger/LogHelper.hpp"
#include "core/backend/FiberPool.hpp"
#include "game/pointers/Pointers.hpp"
#include "core/frontend/widgets/imgui_colors.h"
namespace YimMenu
{
void ChatDisplay::ShowImpl(std::string sender, std::string message, ImColor color)
{
if (sender.empty() || message.empty())
return;
Message notification{};
notification.m_Sender = sender;
notification.m_Message = message;
notification.m_Color = color;
static const bool isBigScreen = *Pointers.ScreenResX > 1600 && *Pointers.ScreenResY > 900;
static const int maxMessages = isBigScreen ? 17 : 7;
std::lock_guard<std::mutex> lock(m_Mutex);
if (m_Messages.size() >= maxMessages)
{
m_Messages.erase(m_Messages.begin());
}
m_Messages.push_back(notification);
}
void ChatDisplay::DrawImpl()
{
std::lock_guard<std::mutex> lock(m_Mutex);
int position = 0;
static const float y_pos = position * 100 + 200;
static const float x_pos = *Pointers.ScreenResX - 470;
ImGui::SetNextWindowSize(ImVec2(*Pointers.ScreenResX - x_pos - 10, *Pointers.ScreenResY - y_pos), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(x_pos, y_pos), ImGuiCond_Always);
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::PushFont(Menu::Font::g_ChatFont);
ImGui::Begin("##chatwin", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs);
for (auto& message : m_Messages)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(ImGui::Colors::Blue));
ImGui::TextWrapped("%s:", message.m_Sender.data());
ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::TextWrapped("%s", message.m_Message.data());
}
ImGui::PopFont();
ImGui::PopStyleColor();
ImGui::End();
}
void ChatDisplay::ClearImpl()
{
m_Messages.clear();
}
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include "game/pointers/Pointers.hpp" // game import in core
#include <mutex>
namespace YimMenu
{
struct Message
{
std::string m_Sender;
std::string m_Message;
ImColor m_Color;
};
class ChatDisplay
{
private:
std::vector<Message> m_Messages = {};
std::mutex m_Mutex;
void ShowImpl(std::string sender, std::string message, ImColor color);
void DrawImpl();
void ClearImpl();
static ChatDisplay& GetInstance()
{
static ChatDisplay instance;
return instance;
}
public:
static void Show(std::string sender, std::string message, ImColor color)
{
return GetInstance().ShowImpl(sender, message, color);
}
static void Draw()
{
GetInstance().DrawImpl();
}
static void Clear()
{
GetInstance().ClearImpl();
}
};
}
+41 -16
View File
@@ -1,17 +1,39 @@
#include "GUI.hpp"
#include "menu/Menu.hpp"
#include "Menu.hpp"
#include "Overlay.hpp"
#include "core/renderer/Renderer.hpp"
#include "core/frontend/Notifications.hpp"
#include "game/frontend/ChatDisplay.hpp"
namespace YimMenu
{
GUI::GUI() :
m_IsOpen(false)
m_IsOpen(false)
{
Renderer::AddDXCallback(Menu::Main, -1);
Menu::SetupFonts();
Menu::SetupStyle();
Menu::Init();
Renderer::AddWindowProcedureCallback([this](HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
GUI::WndProc(hwnd, msg, wparam, lparam);
});
Renderer::AddRendererCallBack(
[&] {
Notifications::Draw();
},
-2);
Renderer::AddRendererCallBack(
[&] {
ChatDisplay::Draw();
},
-5);
Renderer::AddRendererCallBack(
[&] {
Overlay::Draw();
},
-6);
}
GUI::~GUI()
@@ -20,24 +42,27 @@ namespace YimMenu
void GUI::ToggleMouse()
{
auto& io = ImGui::GetIO();
if (GUI::IsOpen())
{
io.MouseDrawCursor = true;
io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse;
}
else
{
io.MouseDrawCursor = false;
io.ConfigFlags |= ImGuiConfigFlags_NoMouse;
}
auto& io = ImGui::GetIO();
io.MouseDrawCursor = GUI::IsOpen();
GUI::IsOpen() ? io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse : io.ConfigFlags |= ImGuiConfigFlags_NoMouse;
}
void GUI::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
if (msg == WM_KEYUP && wparam == VK_INSERT)
{
m_IsOpen = !m_IsOpen;
LOG(INFO) << "fired";
// Persist and restore the cursor position between menu instances
static POINT CursorCoords{};
if (m_IsOpen)
{
GetCursorPos(&CursorCoords);
}
else if (CursorCoords.x + CursorCoords.y)
{
SetCursorPos(CursorCoords.x, CursorCoords.y);
}
Toggle();
ToggleMouse();
}
}
+11 -1
View File
@@ -18,7 +18,7 @@ namespace YimMenu
static void Init()
{
GetInstance();
GetInstance().m_IsOpen = false;
}
static bool IsOpen()
@@ -26,6 +26,16 @@ namespace YimMenu
return GetInstance().m_IsOpen;
}
static void Toggle()
{
GetInstance().m_IsOpen ^= true;
}
static bool IsUsingKeyboard()
{
return ImGui::GetIO().WantTextInput;
}
private:
void WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
+111
View File
@@ -0,0 +1,111 @@
#include "Menu.hpp"
#include "core/commands/Commands.hpp"
#include "core/frontend/manager/UIManager.hpp"
#include "core/renderer/Renderer.hpp"
#include "core/backend/FiberPool.hpp"
#include "core/backend/ScriptMgr.hpp"
#include "game/frontend/fonts/Fonts.hpp"
#include "game/pointers/Pointers.hpp"
#include "submenus/Self.hpp"
namespace YimMenu
{
void Menu::Init()
{
// Arguably the only place this file should be edited at for more menus
UIManager::AddSubmenu(std::make_shared<Submenus::Self>());
Renderer::AddRendererCallBack(
[&] {
if (!GUI::IsOpen())
return;
ImGui::PushFont(Menu::Font::g_DefaultFont);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImU32(ImColor(15, 15, 15)));
ImGui::SetNextWindowSize(ImVec2((*Pointers.ScreenResX / 2.5), (*Pointers.ScreenResY / 2.5)), ImGuiCond_Once);
if (ImGui::Begin("Terminus", nullptr, ImGuiWindowFlags_NoDecoration))
{
//ImGui::BeginDisabled(*Pointers.IsSessionStarted);
if (ImGui::Button("Unload", ImVec2(120, 0)))
{
if (true)
{
FiberPool::Push([] {
Commands::Shutdown();
g_Running = false;
});
}
else
{
g_Running = false;
}
}
//ImGui::EndDisabled();
UIManager::Draw();
ImGui::End();
}
ImGui::PopStyleColor();
ImGui::PopFont();
},
-1);
}
void Menu::SetupStyle()
{
ImGuiStyle& style = ImGui::GetStyle();
style.Colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f);
style.Colors[ImGuiCol_ChildBg] = ImVec4(0.12f, 0.12f, 0.14f, 1.00f);
style.Colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.10f, 0.95f);
style.Colors[ImGuiCol_Border] = ImVec4(0.28f, 0.28f, 0.28f, 0.25f);
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.20f, 1.00f);
style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.35f, 0.40f, 0.50f, 0.75f);
style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.45f, 0.50f, 0.60f, 0.85f);
style.Colors[ImGuiCol_Button] = ImVec4(0.40f, 0.25f, 0.50f, 1.00f);
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.50f, 0.35f, 0.60f, 1.00f);
style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.60f, 0.45f, 0.75f, 1.00f);
style.Colors[ImGuiCol_TitleBg] = ImVec4(0.08f, 0.08f, 0.10f, 1.00f);
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.10f, 0.12f, 0.14f, 1.00f);
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.05f, 0.05f, 0.06f, 1.00f);
style.Colors[ImGuiCol_Tab] = ImVec4(0.25f, 0.20f, 0.45f, 0.85f);
style.Colors[ImGuiCol_TabHovered] = ImVec4(0.35f, 0.30f, 0.65f, 0.85f);
style.Colors[ImGuiCol_TabActive] = ImVec4(0.40f, 0.35f, 0.75f, 1.00f);
style.Colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.35f, 1.00f);
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.35f, 0.40f, 0.50f, 1.00f);
style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.45f, 0.50f, 0.60f, 1.00f);
style.Colors[ImGuiCol_PlotLines] = ImVec4(0.80f, 0.80f, 0.00f, 0.00f);
style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.85f, 0.00f, 1.00f);
style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.20f, 1.00f);
style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.80f, 0.40f, 1.00f);
style.GrabRounding = style.FrameRounding = style.ChildRounding = style.WindowRounding = 8.0f;
}
void Menu::SetupFonts()
{
auto& IO = ImGui::GetIO();
ImFontConfig FontCfg{};
FontCfg.FontDataOwnedByAtlas = false;
Menu::Font::g_DefaultFont = IO.Fonts->AddFontFromMemoryTTF(const_cast<std::uint8_t*>(Fonts::MainFont), sizeof(Fonts::MainFont), Menu::Font::g_DefaultFontSize, &FontCfg);
Menu::Font::g_OptionsFont = IO.Fonts->AddFontFromMemoryTTF(const_cast<std::uint8_t*>(Fonts::MainFont), sizeof(Fonts::MainFont), Menu::Font::g_OptionsFontSize, &FontCfg);
Menu::Font::g_ChildTitleFont = IO.Fonts->AddFontFromMemoryTTF(const_cast<std::uint8_t*>(Fonts::MainFont), sizeof(Fonts::MainFont), Menu::Font::g_ChildTitleFontSize, &FontCfg);
Menu::Font::g_ChatFont = IO.Fonts->AddFontFromMemoryTTF(const_cast<std::uint8_t*>(Fonts::MainFont), sizeof(Fonts::MainFont), Menu::Font::g_ChatFontSize, &FontCfg);
Menu::Font::g_OverlayFont = IO.Fonts->AddFontFromMemoryTTF(const_cast<std::uint8_t*>(Fonts::MainFont), sizeof(Fonts::MainFont), Menu::Font::g_OverlayFontSize, &FontCfg);
UIManager::SetOptionsFont(Menu::Font::g_OptionsFont);
}
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "game/frontend/GUI.hpp"
namespace YimMenu::Menu
{
extern void Init();
extern void SetupStyle();
extern void SetupFonts();
namespace Font
{
inline ImFont* g_DefaultFont = nullptr;
inline float g_DefaultFontSize = 19.0f;
inline ImFont* g_OptionsFont = nullptr;
inline float g_OptionsFontSize = 17.0f;
inline ImFont* g_ChildTitleFont = nullptr;
inline float g_ChildTitleFontSize = 14.5f;
inline ImFont* g_ChatFont = nullptr;
inline float g_ChatFontSize = 24.0f;
inline ImFont* g_OverlayFont = nullptr;
inline float g_OverlayFontSize = 30.0f;
}
}
+36
View File
@@ -0,0 +1,36 @@
#include "Overlay.hpp"
#include "Menu.hpp"
#include "core/commands/BoolCommand.hpp"
#include "game/pointers/Pointers.hpp"
#include "game/gta/invoker/Invoker.hpp"
namespace YimMenu::Features
{
BoolCommand _OverlayEnabled("overlay", "Overlay Enabled", "Show an info overlay at the top left corner of the screen");
BoolCommand _OverlayShowFPS("overlayfps", "Overlay Show FPS", "Show frame rate in the info overlay");
}
namespace YimMenu
{
void Overlay::Draw()
{
#if 0
if (!Features::_OverlayEnabled.GetState() || !NativeInvoker::AreHandlersCached())
return;
ImGui::SetNextWindowSize(ImVec2(*Pointers.ScreenResX - 10.0f, *Pointers.ScreenResY - 10.0f), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(10.0f, 10.0f), ImGuiCond_Always);
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::PushFont(Menu::Font::g_OverlayFont);
ImGui::Begin("##overlay", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs);
if (Features::_OverlayShowFPS.GetState())
ImGui::Text("FPS: %d", (int)(1 / MISC::GET_SYSTEM_TIME_STEP()));
ImGui::PopFont();
ImGui::PopStyleColor();
ImGui::End();
#endif
}
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
namespace YimMenu
{
class Overlay
{
public:
static void Draw();
};
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
namespace YimMenu::Fonts
{
extern const uint8_t MainFont[78948];
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
#include "Items.hpp"
#include "core/commands/Commands.hpp"
#include "core/commands/Command.hpp"
#include "core/commands/LoopedCommand.hpp"
#include "core/frontend/widgets/toggle/imgui_toggle.hpp"
namespace YimMenu
{
BoolCommandItem::BoolCommandItem(joaat_t id, std::optional<std::string> label_override) :
m_Command(Commands::GetCommand<BoolCommand>(id)),
m_LabelOverride(label_override)
{
}
void BoolCommandItem::Draw()
{
if (!m_Command)
{
ImGui::Text("Unknown!");
return;
}
bool enabled = m_Command->GetState();
if (ImGui::Toggle(m_LabelOverride.has_value() ? m_LabelOverride.value().data() : m_Command->GetLabel().data(), &enabled))
m_Command->SetState(enabled);
// TODO: refactor this
auto windowLabel = std::format("{} Hotkey", m_Command->GetLabel());
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", m_Command->GetDescription().data());
if (GetAsyncKeyState(VK_OEM_3) & 0x8000)
ImGui::OpenPopup(std::format("{} Hotkey", m_Command->GetLabel()).data());
}
ImGui::SetNextWindowSize(ImVec2(500, 120));
if (ImGui::BeginPopupModal(windowLabel.data(), nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar))
{
ImGui::BulletText("Hover over the command name to change its hotkey");
ImGui::BulletText("Press any registered key to remove");
ImGui::Separator();
HotkeySetter(m_Command->GetHash()).Draw();
ImGui::Spacing();
if (ImGui::Button("Close") || ((!ImGui::IsWindowHovered() && !ImGui::IsAnyItemHovered()) && ImGui::IsMouseClicked(ImGuiMouseButton_Left)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}
}
+26
View File
@@ -0,0 +1,26 @@
#include "Items.hpp"
#include "core/backend/FiberPool.hpp"
namespace YimMenu
{
Button::Button(const std::string_view& name, std::function<void()> onClick, const ImVec2 size, const std::string_view& information) :
m_Name(name),
m_Information(information),
m_OnClick(onClick),
m_Size(size)
{
}
void Button::Draw()
{
if (ImGui::Button(m_Name.data(), m_Size))
{
if (m_OnClick != nullptr)
{
FiberPool::Push([&] {
m_OnClick();
});
}
}
}
}
@@ -0,0 +1,43 @@
#include "Items.hpp"
#include "core/commands/ColorCommand.hpp"
#include "core/commands/Command.hpp"
#include "core/commands/Commands.hpp"
#include "core/frontend/widgets/toggle/imgui_toggle.hpp"
namespace YimMenu
{
ColorCommandItem::ColorCommandItem(joaat_t id, std::optional<std::string> labelOverride) :
m_Command(Commands::GetCommand<ColorCommand>(id)),
m_LabelOverride(labelOverride)
{
}
void ColorCommandItem::Draw()
{
if (!m_Command)
{
ImGui::Text("Unknown color picker!");
return;
}
auto color = m_Command->GetState();
auto label = m_LabelOverride.has_value() ? m_LabelOverride.value().c_str() : m_Command->GetLabel().c_str();
ImGui::SameLine();
ImGui::SetNextItemWidth(150);
if (ImGui::ColorButton(label, color))
{
ImGui::OpenPopup(label);
}
if (ImGui::BeginPopup(label))
{
if (ImGui::ColorPicker4("##picker", (float*)&color))
{
m_Command->SetState(color);
}
ImGui::EndPopup();
}
}
}
+57
View File
@@ -0,0 +1,57 @@
#include "Items.hpp"
#include "core/commands/Commands.hpp"
#include "core/commands/Command.hpp"
#include "core/backend/FiberPool.hpp"
namespace YimMenu
{
CommandItem::CommandItem(joaat_t id, std::optional<std::string> label_override) :
m_Command(Commands::GetCommand<Command>(id)),
m_LabelOverride(label_override)
{
}
void CommandItem::Draw()
{
if (!m_Command)
{
ImGui::Text("Unknown!");
return;
}
if (ImGui::Button(m_LabelOverride.has_value() ? m_LabelOverride.value().data() : m_Command->GetLabel().data()))
{
FiberPool::Push([this] {
m_Command->Call();
});
}
// TODO: refactor this
auto windowLabel = std::format("{} Hotkey", m_Command->GetLabel());
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", m_Command->GetDescription().data());
if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
ImGui::OpenPopup(std::format("{} Hotkey", m_Command->GetLabel()).data());
}
ImGui::SetNextWindowSize(ImVec2(500, 120));
if (ImGui::BeginPopupModal(windowLabel.data(), nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar))
{
ImGui::BulletText("Hold the command name clicked to change its hotkey");
ImGui::BulletText("Press any registered key to remove");
ImGui::Separator();
HotkeySetter(m_Command->GetHash()).Draw();
ImGui::Spacing();
if (ImGui::Button("Close") || ((!ImGui::IsWindowHovered() && !ImGui::IsAnyItemHovered()) && ImGui::IsMouseClicked(ImGuiMouseButton_Left)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}
}
@@ -0,0 +1,25 @@
#include "Items.hpp"
#include "core/commands/Command.hpp"
#include "core/commands/Commands.hpp"
#include "core/commands/BoolCommand.hpp"
#include "core/frontend/widgets/toggle/imgui_toggle.hpp"
namespace YimMenu
{
ConditionalItem::ConditionalItem(joaat_t bool_cmd_id, std::shared_ptr<UIItem> to_draw) :
m_Condition(Commands::GetCommand<BoolCommand>(bool_cmd_id)),
m_Item(to_draw)
{
}
void ConditionalItem::Draw()
{
if (!m_Condition)
{
return;
}
if (m_Condition->GetState())
m_Item->Draw();
}
}
@@ -0,0 +1,42 @@
#include "Items.hpp"
#include "core/commands/Command.hpp"
#include "core/commands/Commands.hpp"
#include "core/commands/FloatCommand.hpp"
#include "core/frontend/widgets/toggle/imgui_toggle.hpp"
namespace YimMenu
{
FloatCommandItem::FloatCommandItem(joaat_t id, std::optional<std::string> label_override) :
m_Command(Commands::GetCommand<FloatCommand>(id)),
m_LabelOverride(label_override)
{
}
void FloatCommandItem::Draw()
{
if (!m_Command)
{
ImGui::Text("Unknown!");
return;
}
auto value = m_Command->GetState();
auto label = m_LabelOverride.has_value() ? m_LabelOverride.value().c_str() : m_Command->GetLabel().c_str();
if (!m_Command->GetMinimum().has_value() || !m_Command->GetMaximum().has_value())
{
ImGui::SetNextItemWidth(150);
if (ImGui::InputFloat(label, &value))
{
m_Command->SetState(value);
}
}
else
{
ImGui::SetNextItemWidth(150);
if (ImGui::SliderFloat(label, &value, m_Command->GetMinimum().value(), m_Command->GetMaximum().value()))
{
m_Command->SetState(value);
}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
#include "Items.hpp"
#include "core/commands/Command.hpp"
#include "core/commands/Commands.hpp"
#include "core/backend/FiberPool.hpp"
#include "game/frontend/Menu.hpp"
namespace YimMenu
{
Group::Group(const std::string& name, int items_per_row) :
m_Name(name),
m_ItemsPerRow(items_per_row)
{
}
void Group::Draw()
{
if (!m_Name.empty())
{
ImGui::PushFont(Menu::Font::g_ChildTitleFont);
ImGui::Text("%s", m_Name.c_str());
ImGui::PopFont();
ImGui::Separator();
ImGui::Spacing();
}
int item_count = 0;
ImGui::BeginGroup();
for (auto& item : m_Items)
{
item->Draw();
item_count++;
if (m_ItemsPerRow != -1 && item_count % m_ItemsPerRow == 0)
{
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup();
}
}
ImGui::EndGroup();
}
}
+75
View File
@@ -0,0 +1,75 @@
#include "Items.hpp"
#include "core/commands/Command.hpp"
#include "core/commands/Commands.hpp"
#include "core/commands/HotkeySystem.hpp"
namespace YimMenu
{
HotkeySetter::HotkeySetter(joaat_t command_id) :
m_Id(command_id)
{
}
void HotkeySetter::Draw()
{
auto Command = Commands::GetCommand(m_Id);
if (!Command)
ImGui::Text("Unkown Command");
else
{
CommandLink* CommandHotkeyLink = &g_HotkeySystem.m_CommandHotkeys.at(Command->GetHash());
if (!CommandHotkeyLink)
{
ImGui::Text("Unkown CommandLink");
}
else
{
ImGui::Button(Command->GetLabel().data());
CommandHotkeyLink->m_BeingModified = ImGui::IsItemActive();
if (CommandHotkeyLink->m_BeingModified)
{
g_HotkeySystem.CreateHotkey(CommandHotkeyLink->m_Chain);
}
ImGui::SameLine(200);
ImGui::BeginGroup();
if (CommandHotkeyLink->m_Chain.empty())
{
if (CommandHotkeyLink->m_BeingModified)
ImGui::Text("Press any button...");
else
ImGui::Text("No Hotkey Assigned");
}
else
{
ImGui::PushItemWidth(35);
for (auto HotkeyModifier : CommandHotkeyLink->m_Chain)
{
char KeyLabel[32];
strcpy(KeyLabel, g_HotkeySystem.GetHotkeyLabel(HotkeyModifier).data());
ImGui::InputText("##keylabel", KeyLabel, 32, ImGuiInputTextFlags_ReadOnly);
if (ImGui::IsItemClicked())
std::erase_if(CommandHotkeyLink->m_Chain, [HotkeyModifier](int i) {
return i == HotkeyModifier;
});
ImGui::SameLine();
}
ImGui::PopItemWidth();
ImGui::SameLine();
if (ImGui::Button("Clear"))
{
CommandHotkeyLink->m_Chain.clear();
}
}
ImGui::EndGroup();
}
}
}
}
+15
View File
@@ -0,0 +1,15 @@
#include "Items.hpp"
#include "core/backend/FiberPool.hpp"
namespace YimMenu
{
ImGuiItem::ImGuiItem(std::function<void()> callback) :
m_Callback(callback)
{
}
void ImGuiItem::Draw()
{
m_Callback();
}
}
@@ -0,0 +1,28 @@
#include "Items.hpp"
#include "misc/cpp/imgui_stdlib.h"
namespace YimMenu
{
InputTextWithHint::InputTextWithHint(std::string label, std::string hint, std::string* buf, int flags, std::function<void()> cb, ImGuiInputTextCallback inputCallback) :
m_Id(label),
m_Hint(hint),
m_Buf(buf),
m_Flags(flags),
m_Callback(cb),
m_ImGuiInputTextCallback(inputCallback)
{
}
void InputTextWithHint::Draw()
{
if (ImGui::InputTextWithHint(m_Id.data(), m_Hint.data(), m_Buf, m_Flags, m_ImGuiInputTextCallback))
;
{
if (m_Callback != nullptr)
{
m_Callback();
}
}
}
}
@@ -0,0 +1,42 @@
#include "Items.hpp"
#include "core/commands/Command.hpp"
#include "core/commands/Commands.hpp"
#include "core/commands/IntCommand.hpp"
#include "core/frontend/widgets/toggle/imgui_toggle.hpp"
namespace YimMenu
{
IntCommandItem::IntCommandItem(joaat_t id, std::optional<std::string> label_override) :
m_Command(Commands::GetCommand<IntCommand>(id)),
m_LabelOverride(label_override)
{
}
void IntCommandItem::Draw()
{
if (!m_Command)
{
ImGui::Text("Unknown!");
return;
}
int value = m_Command->GetState();
auto label = m_LabelOverride.has_value() ? m_LabelOverride.value().c_str() : m_Command->GetLabel().c_str();
if (!m_Command->GetMinimum().has_value() || !m_Command->GetMaximum().has_value())
{
ImGui::SetNextItemWidth(150);
if (ImGui::InputInt(label, &value))
{
m_Command->SetState(value);
}
}
else
{
ImGui::SetNextItemWidth(150);
if (ImGui::SliderInt(label, &value, m_Command->GetMinimum().value(), m_Command->GetMaximum().value()))
{
m_Command->SetState(value);
}
}
}
}
+198
View File
@@ -0,0 +1,198 @@
#pragma once
#include "core/frontend/manager/UIItem.hpp"
#include "core/util/Joaat.hpp"
#include "game/frontend/GUI.hpp"
namespace YimMenu
{
class BoolCommand;
class PlayerCommand;
class ListCommand;
class IntCommand;
class FloatCommand;
class Vector3Command;
class Command;
class ColorCommand;
class StringCommand;
class Button : public UIItem
{
public:
explicit Button(const std::string_view& Name, std::function<void()> OnClick, ImVec2 size = {0, 25}, const std::string_view& information = "Empty");
void Draw() override;
private:
std::string_view m_Name;
std::string_view m_Information;
std::function<void()> m_OnClick;
ImVec2 m_Size;
};
class CommandItem : public UIItem
{
public:
explicit CommandItem(joaat_t id, std::optional<std::string> label_override = std::nullopt);
void Draw() override;
private:
Command* m_Command;
std::optional<std::string> m_LabelOverride;
};
class PlayerCommandItem : public UIItem
{
public:
explicit PlayerCommandItem(joaat_t id, std::optional<std::string> label_override = std::nullopt);
void Draw() override;
private:
PlayerCommand* m_Command;
std::optional<std::string> m_LabelOverride;
};
class BoolCommandItem : public UIItem
{
public:
explicit BoolCommandItem(joaat_t id, std::optional<std::string> label_override = std::nullopt);
void Draw() override;
private:
BoolCommand* m_Command;
std::optional<std::string> m_LabelOverride;
};
class IntCommandItem : public UIItem
{
public:
explicit IntCommandItem(joaat_t id, std::optional<std::string> label_override = std::nullopt);
void Draw() override;
private:
IntCommand* m_Command;
std::optional<std::string> m_LabelOverride;
};
class FloatCommandItem : public UIItem
{
public:
explicit FloatCommandItem(joaat_t id, std::optional<std::string> label_override = std::nullopt);
void Draw() override;
private:
FloatCommand* m_Command;
std::optional<std::string> m_LabelOverride;
};
class Vector3CommandItem : public UIItem
{
public:
explicit Vector3CommandItem(joaat_t id, std::optional<std::string> label_override = std::nullopt);
void Draw() override;
private:
Vector3Command* m_Command;
std::optional<std::string> m_LabelOverride;
std::string m_CurrentCategory{};
std::string m_CurrentFilter{};
};
class ListCommandItem : public UIItem
{
public:
explicit ListCommandItem(joaat_t id, std::optional<std::string> label_override = std::nullopt);
void Draw() override;
private:
ListCommand* m_Command;
std::optional<std::string> m_LabelOverride;
std::optional<int> m_ItemWidth = std::nullopt;
std::optional<std::string> m_SelectedItem = std::nullopt;
};
class ConditionalItem : public UIItem
{
public:
explicit ConditionalItem(joaat_t bool_cmd_id, std::shared_ptr<UIItem> to_draw);
void Draw() override;
private:
BoolCommand* m_Condition;
std::shared_ptr<UIItem> m_Item;
};
class ImGuiItem : public UIItem
{
public:
explicit ImGuiItem(std::function<void()> callback);
void Draw() override;
private:
std::function<void()> m_Callback;
};
class HotkeySetter : public UIItem
{
public:
explicit HotkeySetter(joaat_t);
void Draw() override;
private:
joaat_t m_Id;
};
class Group : public UIItem
{
public:
explicit Group(const std::string& name, int items_per_row = 7);
void Draw() override;
void AddItem(std::shared_ptr<UIItem>&& item)
{
m_Items.push_back(std::move(item));
}
private:
std::string m_Name;
int m_ItemsPerRow;
std::vector<std::shared_ptr<UIItem>> m_Items;
};
class InputTextWithHint : public UIItem
{
public:
explicit InputTextWithHint(std::string label, std::string hint, std::string* buf, int flags = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr, ImGuiInputTextCallback inputCallback = nullptr);
void Draw() override;
private:
std::string m_Id;
std::string m_Hint;
std::string* m_Buf;
int m_Flags;
std::function<void()> m_Callback;
ImGuiInputTextCallback m_ImGuiInputTextCallback;
};
class ColorCommandItem : public UIItem
{
public:
explicit ColorCommandItem(joaat_t id, std::optional<std::string> label_override = std::nullopt);
void Draw() override;
private:
ColorCommand* m_Command;
std::optional<std::string> m_LabelOverride;
};
class StringCommandItem : public UIItem
{
public:
explicit StringCommandItem(joaat_t id, std::optional<std::string> label_override = std::nullopt);
void Draw() override;
private:
StringCommand* m_Command;
std::optional<std::string> m_LabelOverride;
std::optional<std::string> m_CurrentInput = std::nullopt;
};
}

Some files were not shown because too many files have changed in this diff Show More