This commit is contained in:
chinosk 2025-03-18 09:28:58 +00:00
parent 45338b40cd
commit 3fe2d1775b
Signed by: chinosk
GPG Key ID: 00610B08C1BF7BE9
39 changed files with 56771 additions and 68 deletions

View File

@ -1,29 +1,58 @@
#define KEY_W 51
#define KEY_S 47
#define KEY_A 29
#define KEY_D 32
#define KEY_R 46
#define KEY_Q 45
#define KEY_E 33
#define KEY_F 34
#define KEY_I 37
#define KEY_K 39
#define KEY_J 38
#define KEY_L 40
#define KEY_V 50
#define KEY_UP 19
#define KEY_DOWN 20
#define KEY_LEFT 21
#define KEY_RIGHT 22
#define KEY_CTRL 113
#define KEY_SHIFT 59
#define KEY_ALT 57
#define KEY_SPACE 62
#define KEY_ADD 70
#define KEY_SUB 69
#include "../platformDefine.hpp"
#define WM_KEYDOWN 0
#define WM_KEYUP 1
#ifndef GKMS_WINDOWS
#define KEY_W 51
#define KEY_S 47
#define KEY_A 29
#define KEY_D 32
#define KEY_R 46
#define KEY_Q 45
#define KEY_E 33
#define KEY_F 34
#define KEY_I 37
#define KEY_K 39
#define KEY_J 38
#define KEY_L 40
#define KEY_V 50
#define KEY_UP 19
#define KEY_DOWN 20
#define KEY_LEFT 21
#define KEY_RIGHT 22
#define KEY_CTRL 113
#define KEY_SHIFT 59
#define KEY_ALT 57
#define KEY_SPACE 62
#define KEY_ADD 70
#define KEY_SUB 69
#define WM_KEYDOWN 0
#define WM_KEYUP 1
#else
#define KEY_W 'W'
#define KEY_S 'S'
#define KEY_A 'A'
#define KEY_D 'D'
#define KEY_R 'R'
#define KEY_Q 'Q'
#define KEY_E 'E'
#define KEY_F 'F'
#define KEY_I 'I'
#define KEY_K 'K'
#define KEY_J 'J'
#define KEY_L 'L'
#define KEY_V 'V'
#define KEY_UP 38
#define KEY_DOWN 40
#define KEY_LEFT 37
#define KEY_RIGHT 39
#define KEY_CTRL 17
#define KEY_SHIFT 16
#define KEY_ALT 18
#define KEY_SPACE 32
#define KEY_ADD 187
#define KEY_SUB 189
#endif
#define BTN_A 96
#define BTN_B 97

View File

@ -17,6 +17,7 @@
#ifdef GKMS_WINDOWS
#include "../windowsPlatform.hpp"
#include "cpprest/details/http_helpers.h"
#include "../resourceUpdate/resourceUpdate.hpp"
#endif
@ -210,14 +211,25 @@ namespace GakumasLocal::HookMain {
return Unity_set_position_Injected_Orig(self, data);
}
#ifdef GKMS_WINDOWS
DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* retstr, void* self, int type, void* c, void* tc, void* mtd)) {
switch (Config::gameOrientation) {
case 1: type = 0x2; break; // FixedPortrait
case 2: type = 0x3; break; // FixedLandscape
default: break;
}
return InternalSetOrientationAsync_Orig(retstr, self, type, c, tc, mtd);
}
#else
DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* self, int type, void* c, void* tc, void* mtd)) {
switch (Config::gameOrientation) {
case 1: type = 0x2; break; // FixedPortrait
case 2: type = 0x3; break; // FixedLandscape
default: break;
case 1: type = 0x2; break; // FixedPortrait
case 2: type = 0x3; break; // FixedLandscape
default: break;
}
return InternalSetOrientationAsync_Orig(self, type, c, tc, mtd);
}
#endif
DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) {
EndCameraRendering_Orig(ctx, camera, method);
@ -670,6 +682,17 @@ namespace GakumasLocal::HookMain {
return ret;
}
#ifdef GKMS_WINDOWS
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetDataAsync, (void* retstr, void* self, void* liveData, bool isReleased, bool isUnlocked, bool isNew, bool hasLiveSkin, void* ct, void* mtd)) {
// Log::DebugFmt("PictureBookLiveThumbnailView_SetDataAsync: isReleased: %d, isUnlocked: %d, isNew: %d, hasLiveSkin: %d", isReleased, isUnlocked, isNew, hasLiveSkin);
if (Config::dbgMode && Config::unlockAllLive) {
isUnlocked = true;
isReleased = true;
hasLiveSkin = true;
}
PictureBookLiveThumbnailView_SetDataAsync_Orig(retstr, self, liveData, isReleased, isUnlocked, isNew, hasLiveSkin, ct, mtd);
}
#else
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetDataAsync, (void* self, void* liveData, bool isReleased, bool isUnlocked, bool isNew, bool hasLiveSkin, void* ct, void* mtd)) {
// Log::DebugFmt("PictureBookLiveThumbnailView_SetDataAsync: isReleased: %d, isUnlocked: %d, isNew: %d, hasLiveSkin: %d", isReleased, isUnlocked, isNew, hasLiveSkin);
if (Config::dbgMode && Config::unlockAllLive) {
@ -679,6 +702,7 @@ namespace GakumasLocal::HookMain {
}
PictureBookLiveThumbnailView_SetDataAsync_Orig(self, liveData, isReleased, isUnlocked, isNew, hasLiveSkin, ct, mtd);
}
#endif
enum class GetIdolIdType {
MusicId,
@ -899,6 +923,12 @@ namespace GakumasLocal::HookMain {
}
// std::string lastMusicId;
#ifdef GKMS_WINDOWS
DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* retstr, void* self, void* itemModel, void* ct, void* mtd)) {
// if (!itemModel) return nullptr;
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(retstr, self, itemModel, ct, mtd);
}
#else
DEFINE_HOOK(void, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* self, void* itemModel, void* ct, void* mtd)) {
/* // 修改角色后Live 结束返回时, itemModel 为 null
Log::DebugFmt("OnSelectMusic itemModel at %p", itemModel);
@ -932,6 +962,7 @@ namespace GakumasLocal::HookMain {
if (!itemModel) return;
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(self, itemModel, ct, mtd);
}
#endif
DEFINE_HOOK(bool, VLDOF_IsActive, (void* self)) {
if (Config::enableFreeCamera) return false;
@ -1183,10 +1214,14 @@ namespace GakumasLocal::HookMain {
}
else {
if (processedIOS) {
Log::DebugFmt("Restore API to Android");
Log::DebugFmt("Restore API");
static auto ApiBase_klass = Il2cppUtils::get_class_from_instance(self);
static auto platform_field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>("il2cpp_class_get_field_from_name", ApiBase_klass, "_platform");
#ifdef GKMS_WINDOWS
Il2cppUtils::ClassSetFieldValue(self, platform_field, Il2cppString::New("dmm"));
#else
Il2cppUtils::ClassSetFieldValue(self, platform_field, Il2cppString::New("Android"));
#endif
processedIOS = nullptr;
}
}
@ -1382,6 +1417,7 @@ namespace GakumasLocal::HookMain {
Log::ErrorFmt("GameAssembly.dll not loaded.");
}
UnityResolve::Init(il2cpp_module, UnityResolve::Mode::Il2Cpp, Config::lazyInit);
GakumasLocal::WinHooks::Keyboard::InstallWndProcHook();
#else
UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW),
UnityResolve::Mode::Il2Cpp, Config::lazyInit);
@ -1493,9 +1529,11 @@ namespace GakumasLocal::HookMain {
ADD_HOOK(UserCostumeCollection_FindBy, UserCostumeCollection_FindBy_mtd->methodPointer);
}
// 双端
ADD_HOOK(PictureBookLiveThumbnailView_SetDataAsync,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
"PictureBookLiveThumbnailView", "SetDataAsync", {"*", "*", "*", "*", "*"}));
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
"PictureBookLiveThumbnailView", "SetDataAsync", { "*", "*", "*", "*", "*" }));
ADD_HOOK(PictureBookWindowPresenter_GetLiveMusics,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookWindowPresenter", "GetLiveMusics"));
@ -1506,9 +1544,11 @@ namespace GakumasLocal::HookMain {
ADD_HOOK(PictureBookLiveSelectScreenPresenter_MoveLiveScene,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookLiveSelectScreenPresenter", "MoveLiveScene"));
// 双端
ADD_HOOK(PictureBookLiveSelectScreenPresenter_OnSelectMusic,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookLiveSelectScreenPresenter", "OnSelectMusicAsync"));
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookLiveSelectScreenPresenter", "OnSelectMusicAsync"));
ADD_HOOK(VLDOF_IsActive,
Il2cppUtils::GetMethodPointer("Unity.RenderPipelines.Universal.Runtime.dll", "VL.Rendering",
@ -1575,9 +1615,10 @@ namespace GakumasLocal::HookMain {
ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall(
"UnityEngine.DebugLogHandler::Internal_Log(UnityEngine.LogType,UnityEngine.LogOption,System.String,UnityEngine.Object)"));
// 双端
ADD_HOOK(InternalSetOrientationAsync,
Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common",
"ScreenOrientationControllerBase", "InternalSetOrientationAsync"));
Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common",
"ScreenOrientationControllerBase", "InternalSetOrientationAsync"));
ADD_HOOK(Unity_set_position_Injected, Il2cppUtils::il2cpp_resolve_icall(
"UnityEngine.Transform::set_position_Injected(UnityEngine.Vector3&)"));
@ -1595,6 +1636,7 @@ namespace GakumasLocal::HookMain {
#ifdef GKMS_WINDOWS
g_extra_assetbundle_paths.push_back((gakumasLocalPath / "local-files/gakumasassets").string());
LoadExtraAssetBundle();
GkmsResourceUpdate::CheckUpdateFromAPI(false);
#endif // GKMS_WINDOWS
}

View File

@ -694,6 +694,7 @@ namespace GakumasLocal::MasterLocal {
} break;
case JsonValueType::JVT_String: {
auto readValue = fc.ReadStringField(mainPk);
if (!readValue) return;
baseDataKey.append(readValue->ToString());
baseDataKey.push_back('|');
} break;

View File

@ -3,7 +3,6 @@
#include <thread>
#include "../Misc.hpp"
#include "../BaseDefine.h"
#include "../../platformDefine.hpp"
#ifdef GKMS_WINDOWS

View File

@ -2,6 +2,7 @@
#include "nlohmann/json.hpp"
#include "../Log.h"
#include <thread>
#include <fstream>
namespace GakumasLocal::Config {
bool isConfigInit = false;
@ -107,4 +108,66 @@ namespace GakumasLocal::Config {
}
isConfigInit = true;
}
void SaveConfig(const std::string& configPath) {
try {
nlohmann::json config;
#define SetConfigItem(name) config[#name] = name
SetConfigItem(dbgMode);
SetConfigItem(enabled);
SetConfigItem(lazyInit);
SetConfigItem(replaceFont);
SetConfigItem(forceExportResource);
SetConfigItem(gameOrientation);
SetConfigItem(textTest);
SetConfigItem(useMasterTrans);
SetConfigItem(dumpText);
SetConfigItem(targetFrameRate);
SetConfigItem(enableFreeCamera);
SetConfigItem(unlockAllLive);
SetConfigItem(unlockAllLiveCostume);
SetConfigItem(enableLiveCustomeDress);
SetConfigItem(liveCustomeHeadId);
SetConfigItem(liveCustomeCostumeId);
SetConfigItem(loginAsIOS);
SetConfigItem(useCustomeGraphicSettings);
SetConfigItem(renderScale);
SetConfigItem(qualitySettingsLevel);
SetConfigItem(volumeIndex);
SetConfigItem(maxBufferPixel);
SetConfigItem(reflectionQualityLevel);
SetConfigItem(lodQualityLevel);
SetConfigItem(enableBreastParam);
SetConfigItem(bDamping);
SetConfigItem(bStiffness);
SetConfigItem(bSpring);
SetConfigItem(bPendulum);
SetConfigItem(bPendulumRange);
SetConfigItem(bAverage);
SetConfigItem(bRootWeight);
SetConfigItem(bUseArmCorrection);
SetConfigItem(bUseScale);
SetConfigItem(bScale);
SetConfigItem(bUseLimit);
SetConfigItem(bLimitXx);
SetConfigItem(bLimitXy);
SetConfigItem(bLimitYx);
SetConfigItem(bLimitYy);
SetConfigItem(bLimitZx);
SetConfigItem(bLimitZy);
std::ofstream out(configPath);
if (!out) {
Log::ErrorFmt("SaveConfig error: Cannot open file: %s", configPath.c_str());
return;
}
out << config.dump(4);
Log::Info("SaveConfig success");
}
catch (std::exception& e) {
Log::ErrorFmt("SaveConfig error: %s", e.what());
}
}
}

View File

@ -52,4 +52,5 @@ namespace GakumasLocal::Config {
extern float bLimitZy;
void LoadConfig(const std::string& configStr);
void SaveConfig(const std::string& configPath);
}

41
src/gkmsGUI/GUII18n.cpp Normal file
View File

@ -0,0 +1,41 @@
#include "stdinclude.hpp"
#include "i18nData/strings_en.hpp"
#include "i18nData/strings_ja.hpp"
#include "i18nData/strings_zh-rCN.hpp"
namespace GkmsGUII18n {
const LANGID localLanguage = GetUserDefaultUILanguage();
std::unordered_set<LANGID> sChineseLangIds{ { 0x0004, 0x0804, 0x1004 } }; // zh-Hans, zh-CN, zh-SG
// std::unordered_set<LANGID> tChineseLangIds{ { 0x0404, 0x0c04, 0x1404, 0x048E } }; // zh-TW, zh-HK, zh-MO, zh-yue-HK
std::unordered_set<LANGID> jpnLangIds{ { 0x0011, 0x0411 } }; // ja, ja-JP
std::unordered_map<std::string, std::string> GetI18nData() {
if (sChineseLangIds.contains(localLanguage)) {
return I18nData::i18nData_zh_rCN;
}
else if (jpnLangIds.contains(localLanguage)) {
return I18nData::i18nData_ja;
}
else {
return I18nData::i18nData_default;
}
}
const char* ts(const std::string& key) {
static std::unordered_map<std::string, std::string> i18nData = GetI18nData();
if (auto it = i18nData.find(key); it != i18nData.end()) {
return it->second.c_str();
}
if (auto it = I18nData::i18nData_default.find(key); it != I18nData::i18nData_default.end()) {
return it->second.c_str();
}
static std::unordered_map<std::string, std::string> fallbackMap;
auto [iter, inserted] = fallbackMap.emplace(key, key);
return iter->second.c_str();
}
}

7
src/gkmsGUI/GUII18n.hpp Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <string>
namespace GkmsGUII18n {
const char* ts(const std::string& key);
}

407
src/gkmsGUI/gkmsGUILoop.cpp Normal file
View File

@ -0,0 +1,407 @@
#include "imgui/imgui.h"
#include "stdinclude.hpp"
#include "GUII18n.hpp"
#include "GakumasLocalify/config/Config.hpp"
#include "resourceUpdate/resourceUpdate.hpp"
extern void* SetResolution_orig;
extern std::function<void()> g_reload_all_data;
extern std::filesystem::path ConfigJson;
bool downloading = false;
float downloadProgress = 0.0f;
#define INPUT_AND_SLIDER_FLOAT(label, data, min, max) \
ImGui::SetNextItemWidth(inputFloatWidth);\
ImGui::InputFloat("##"##label, data);\
ImGui::SameLine();\
ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - inputFloatWidth - 1.0f);\
ImGui::SliderFloat(label, data, min, max)
#define FOR_INPUT_AND_SLIDER_FLOAT(label, data, min, max, hideIdName) \
ImGui::SetNextItemWidth(inputFloatWidth);\
ImGui::InputFloat(("##"##label + hideIdName).c_str(), data);\
ImGui::SameLine();\
ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - inputFloatWidth - 1.0f);\
ImGui::SliderFloat((label##"##" + hideIdName).c_str(), data, min, max)
#define INPUT_AND_SLIDER_INT(label, data, min, max) \
ImGui::SetNextItemWidth(inputFloatWidth);\
ImGui::InputInt((std::string("##") + label).c_str(), data);\
ImGui::SameLine();\
ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - inputFloatWidth - 1.0f);\
ImGui::SliderInt(label, data, min, max)
#define InputFloatWithItemWidth(label, flt) \
ImGui::PushItemWidth(itemWidth); \
ImGui::InputFloat(label, flt); \
ImGui::PopItemWidth()
#define HELP_TOOLTIP(label, text) \
ImGui::TextDisabled(label); \
if (ImGui::IsItemHovered()) { \
ImGui::BeginTooltip(); \
ImGui::Text(text); \
ImGui::EndTooltip(); \
}
// 回调函数,用于处理字符串大小变化
static int InputTextCallback(ImGuiInputTextCallbackData * data)
{
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
{
// 当用户输入导致需要扩容时,会调用该回调
std::string* str = static_cast<std::string*>(data->UserData);
IM_ASSERT(data->Buf == str->c_str());
// 根据新的长度调整 std::string 大小
str->resize(data->BufTextLen);
// 更新回调数据中的缓冲区指针
data->Buf = const_cast<char*>(str->c_str());
}
return 0;
}
// 封装的 InputText 函数,可以直接编辑 std::string
bool InputTextString(const char* label, std::string* str, ImGuiInputTextFlags flags = 0)
{
// 如果字符串为空,则确保有至少一个字符空间(空字符)
if (str->empty())
*str = "";
// 调用 ImGui::InputText并通过回调处理动态扩容
return ImGui::InputText(label,
// 注意:需要获得非 const 的指针c_str() 返回的是 const char*
const_cast<char*>(str->c_str()),
// 使用当前字符串容量加 1 的空间(注意:初始容量可能较小)
str->capacity() + 1,
flags | ImGuiInputTextFlags_CallbackResize,
InputTextCallback,
static_cast<void*>(str));
}
void onBClickPresetChanged(int index) {
using namespace GakumasLocal;
std::vector<float> setData;
switch (index) {
case 0:
setData = { 0.33f, 0.07f, 0.7f, 0.06f, 0.25f, 0.2f, 0.5f,
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
break;
case 1:
setData = { 0.365f, 0.06f, 0.62f, 0.07f, 0.25f, 0.2f, 0.5f,
1.0f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f };
break;
case 2:
setData = { 0.4f, 0.065f, 0.55f, 0.075f, 0.25f, 0.2f, 0.5f,
1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f };
break;
case 3:
setData = { 0.4f, 0.065f, 0.55f, 0.075f, 0.25f, 0.2f, 0.5f,
1.0f, 4.0f, 4.0f, 4.0f, 4.0f, 4.0f, 3.0f };
break;
case 4:
setData = { 0.4f, 0.06f, 0.4f, 0.075f, 0.55f, 0.2f, 0.8f,
1.0f, 6.0f, 6.0f, 6.0f, 6.0f, 6.0f, 3.5f };
break;
case 5:
setData = { 0.33f, 0.08f, 0.8f, 0.12f, 0.55f, 0.2f, 1.0f,
0.0f };
break;
default:
setData = { 0.33f, 0.08f, 1.0f, 0.055f, 0.15f, 0.2f, 0.5f,
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
break;
}
Config::bDamping = setData[0];
Config::bStiffness = setData[1];
Config::bSpring = setData[2];
Config::bPendulum = setData[3];
Config::bPendulumRange = setData[4];
Config::bAverage = setData[5];
Config::bRootWeight = setData[6];
if (setData.size() > 7 && setData[7] != 0.0f && setData.size() >= 14) {
Config::bLimitXx = setData[8];
Config::bLimitXy = setData[9];
Config::bLimitYx = setData[10];
Config::bLimitYy = setData[11];
Config::bLimitZx = setData[12];
Config::bLimitZy = setData[13];
Config::bUseLimit = true;
}
else {
Config::bUseLimit = false;
}
Config::bUseArmCorrection = true;
}
namespace GkmsGUILoop {
static float inputFloatWidth = 50.0f;
static float indentWidth = 10.0f;
void mainLoop() {
using namespace GkmsGUII18n;
using namespace GakumasLocal;
if (ImGui::Begin("Gakumas Plugin Config")) {
ImGui::Text("Resource Version: %s", GkmsResourceUpdate::GetCurrentResourceVersion(true).c_str());
if (ImGui::Button("Reload Config And Translation Data")) {
g_reload_all_data();
}
if (ImGui::Button(ts("Save Config"))) {
Config::SaveConfig(ConfigJson.string());
GkmsResourceUpdate::saveProgramConfig();
}
// 基础设置
if (ImGui::CollapsingHeader(ts("basic_settings"), ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(indentWidth);
ImGui::Checkbox(ts("lazy_init"), &Config::lazyInit);
ImGui::Checkbox(ts("replace_font"), &Config::replaceFont);
ImGui::Unindent(indentWidth);
}
// 资源设置
if (ImGui::CollapsingHeader((std::string(ts("resource_settings")) + "##Header").c_str(), ImGuiTreeNodeFlags_None)) {
ImGui::Indent(indentWidth);
if (ImGui::Checkbox(ts("check_resource_from_api"), &g_useAPIAssets)) {
if (g_useAPIAssets) g_useRemoteAssets = false;
}
if (g_useAPIAssets) {
InputTextString(ts("api_addr"), &g_useAPIAssetsURL);
if (!downloading && ImGui::Button((std::string(ts("check_update")) + "##APIResource").c_str())) {
GkmsResourceUpdate::CheckUpdateFromAPI(true);
}
if (downloading) {
ImGui::ProgressBar(downloadProgress);
ImGui::SameLine();
ImGui::Text("Downloading");
}
}
if (ImGui::Checkbox(ts("use_remote_zip_resource"), &g_useRemoteAssets)) {
if (g_useRemoteAssets) g_useAPIAssets = false;
}
if (g_useRemoteAssets) {
InputTextString(ts("resource_url"), &g_remoteResourceUrl);
if (!downloading && ImGui::Button((std::string(ts("download")) + "##RemoteResource").c_str())) {
GkmsResourceUpdate::checkUpdateFromURL(g_remoteResourceUrl);
}
if (downloading) {
ImGui::ProgressBar(downloadProgress);
ImGui::SameLine();
ImGui::Text("Downloading");
}
}
ImGui::Unindent(indentWidth);
}
// 画面设置
if (ImGui::CollapsingHeader(ts("graphic_settings"), ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(indentWidth);
INPUT_AND_SLIDER_INT(ts("setFpsTitle"), &Config::targetFrameRate, 0, 540);
if (ImGui::CollapsingHeader(ts("orientation_lock"), ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::RadioButton(ts("orientation_orig"), &Config::gameOrientation, 0);
ImGui::SameLine();
ImGui::RadioButton(ts("orientation_portrait"), &Config::gameOrientation, 1);
ImGui::SameLine();
ImGui::RadioButton(ts("orientation_landscape"), &Config::gameOrientation, 2);
}
if (ImGui::CollapsingHeader((std::string(ts("useCustomeGraphicSettings")) + "##customeGraphicSettings1").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(indentWidth);
ImGui::Checkbox(ts("useCustomeGraphicSettings"), &Config::useCustomeGraphicSettings);
if (Config::useCustomeGraphicSettings) {
if (ImGui::Button(ts("max_high"))) {
Config::renderScale = 1.0f;
Config::qualitySettingsLevel = 5;
Config::volumeIndex = 4;
Config::maxBufferPixel = 8190;
Config::lodQualityLevel = 5;
Config::reflectionQualityLevel = 5;
}
ImGui::SameLine();
if (ImGui::Button(ts("very_high"))) {
Config::renderScale = 0.77f;
Config::qualitySettingsLevel = 3;
Config::volumeIndex = 3;
Config::maxBufferPixel = 3384;
Config::lodQualityLevel = 4;
Config::reflectionQualityLevel = 4;
}
ImGui::SameLine();
if (ImGui::Button(ts("hign"))) {
Config::renderScale = 0.67f;
Config::qualitySettingsLevel = 2;
Config::volumeIndex = 2;
Config::maxBufferPixel = 2538;
Config::lodQualityLevel = 3;
Config::reflectionQualityLevel = 3;
}
ImGui::SameLine();
if (ImGui::Button(ts("middle"))) {
Config::renderScale = 0.59f;
Config::qualitySettingsLevel = 1;
Config::volumeIndex = 1;
Config::maxBufferPixel = 1440;
Config::lodQualityLevel = 2;
Config::reflectionQualityLevel = 2;
}
ImGui::SameLine();
if (ImGui::Button(ts("low"))) {
Config::renderScale = 0.5f;
Config::qualitySettingsLevel = 1;
Config::volumeIndex = 0;
Config::maxBufferPixel = 1024;
Config::lodQualityLevel = 1;
Config::reflectionQualityLevel = 1;
}
ImGui::Separator();
ImGui::SliderFloat(ts("renderscale"), &Config::renderScale, 0, 5);
ImGui::SliderInt("QualityLevel (1/1/2/3/5)", &Config::qualitySettingsLevel, 1, 5);
ImGui::SliderInt("VolumeIndex (0/1/2/3/4)", &Config::volumeIndex, 0, 4);
ImGui::SliderInt("MaxBufferPixel (1024/1440/2538/3384/8190)", &Config::maxBufferPixel, 0, 16380);
ImGui::SliderInt("ReflectionLevel (0~5)", &Config::reflectionQualityLevel, 0, 5);
ImGui::SliderInt("LOD Level (0~5)", &Config::lodQualityLevel, 0, 5);
}
ImGui::Unindent(indentWidth);
}
ImGui::Unindent(indentWidth);
}
// 摄像机设置
if (ImGui::CollapsingHeader(ts("camera_settings"), ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(indentWidth);
ImGui::Checkbox(ts("enable_free_camera"), &Config::enableFreeCamera);
ImGui::Unindent(indentWidth);
}
// 调试设置
if (ImGui::CollapsingHeader(ts("debug_settings"), ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(indentWidth);
ImGui::Checkbox(ts("useMasterDBTrans"), &Config::useMasterTrans);
ImGui::Checkbox(ts("text_hook_test_mode"), &Config::textTest);
ImGui::Checkbox(ts("export_text"), &Config::dumpText);
ImGui::Checkbox(ts("login_as_ios"), &Config::loginAsIOS);
ImGui::Unindent(indentWidth);
}
// 胸部参数,失效
if (Config::enableBreastParam && ImGui::CollapsingHeader((std::string(ts("enable_breast_param")) + "##Header").c_str(), ImGuiTreeNodeFlags_None)) {
ImGui::Indent(indentWidth);
ImGui::Checkbox(ts("enable_breast_param"), &Config::enableBreastParam);
if (ImGui::Button("??##Breast")) {
onBClickPresetChanged(5);
}
ImGui::SameLine();
if (ImGui::Button("+5##Breast")) {
onBClickPresetChanged(4);
}
ImGui::SameLine();
if (ImGui::Button("+4##Breast")) {
onBClickPresetChanged(3);
}
ImGui::SameLine();
if (ImGui::Button("+3##Breast")) {
onBClickPresetChanged(2);
}
ImGui::SameLine();
if (ImGui::Button("+2##Breast")) {
onBClickPresetChanged(1);
}
ImGui::SameLine();
if (ImGui::Button("+1##Breast")) {
onBClickPresetChanged(0);
}
float availWidth = ImGui::GetContentRegionAvail().x;
float spacing = ImGui::GetStyle().ItemSpacing.x;
float itemWidth = (availWidth - spacing) / 4.0f;
InputFloatWithItemWidth(ts("damping"), &Config::bDamping);
ImGui::SameLine();
InputFloatWithItemWidth(ts("stiffness"), &Config::bStiffness);
InputFloatWithItemWidth(ts("spring"), &Config::bSpring);
ImGui::SameLine();
InputFloatWithItemWidth(ts("pendulum"), &Config::bPendulum);
InputFloatWithItemWidth(ts("pendulumrange"), &Config::bPendulumRange);
ImGui::SameLine();
InputFloatWithItemWidth(ts("average"), &Config::bAverage);
InputFloatWithItemWidth(ts("rootweight"), &Config::bRootWeight);
ImGui::Checkbox(ts("##bUseScale"), &Config::bUseScale);
ImGui::SameLine();
ImGui::InputFloat(ts("breast_scale"), &Config::bScale);
ImGui::Checkbox(ts("usearmcorrection"), &Config::bUseArmCorrection);
ImGui::Checkbox(ts("uselimit_0_1"), &Config::bUseLimit);
if (Config::bUseLimit) {
float availWidth = ImGui::GetContentRegionAvail().x;
float spacing = ImGui::GetStyle().ItemSpacing.x;
float itemWidth = (availWidth - spacing) / 6.0f;
InputFloatWithItemWidth(ts("axisx_x"), &Config::bLimitXx);
ImGui::SameLine();
InputFloatWithItemWidth(ts("axisy_x"), &Config::bLimitYx);
ImGui::SameLine();
InputFloatWithItemWidth(ts("axisz_x"), &Config::bLimitZx);
InputFloatWithItemWidth(ts("axisx_y"), &Config::bLimitXy);
ImGui::SameLine();
InputFloatWithItemWidth(ts("axisy_y"), &Config::bLimitYy);
ImGui::SameLine();
InputFloatWithItemWidth(ts("axisz_y"), &Config::bLimitZy);
}
ImGui::Unindent(indentWidth);
}
// 测试模式
if (Config::dbgMode) {
if (ImGui::CollapsingHeader(ts("test_mode_live"), ImGuiTreeNodeFlags_None)) {
ImGui::Indent(indentWidth);
ImGui::Checkbox(ts("unlockAllLive"), &Config::unlockAllLive);
ImGui::Checkbox(ts("unlockAllLiveCostume"), &Config::unlockAllLiveCostume);
ImGui::Checkbox(ts("liveUseCustomeDress"), &Config::enableLiveCustomeDress);
if (Config::enableLiveCustomeDress) {
InputTextString(ts("live_costume_head_id"), &Config::liveCustomeHeadId);
InputTextString(ts("live_custome_dress_id"), &Config::liveCustomeCostumeId);
}
ImGui::Unindent(indentWidth);
}
}
}
ImGui::End();
}
}

View File

@ -0,0 +1,5 @@
namespace GkmsGUILoop {
void mainLoop();
}

340
src/gkmsGUI/gkmsGUIMain.cpp Normal file
View File

@ -0,0 +1,340 @@
#include "imgui/imgui.h"
#include "imgui/imgui_impl_win32.h"
#include "imgui/imgui_impl_dx11.h"
#include <d3d11.h>
#include <tchar.h>
#include "stdinclude.hpp"
#include "gkmsGUI/gkmsGUILoop.hpp"
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
// Data
static ID3D11Device* g_pd3dDevice = NULL;
static ID3D11DeviceContext* g_pd3dDeviceContext = NULL;
static IDXGISwapChain* g_pSwapChain = NULL;
static ID3D11RenderTargetView* g_mainRenderTargetView = NULL;
// Forward declarations of helper functions
bool CreateDeviceD3D(HWND hWnd);
void CleanupDeviceD3D();
void CreateRenderTarget();
void CleanupRenderTarget();
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
bool guiDone = true;
bool attachToGame = false;
HWND hwnd;
RECT cacheRect{ 100, 100, 730, 910 };
void SetGuiDone(bool isDone) {
guiDone = isDone;
}
void SetWindowTop()
{
::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}
void CancelWindowTop()
{
::SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}
bool lastTopStat = false;
bool nowTopStat = false;
void changeTopState() {
if (lastTopStat == nowTopStat) return;
lastTopStat = nowTopStat;
if (nowTopStat) {
SetWindowTop();
}
else {
CancelWindowTop();
}
}
bool getUmaGuiDone() {
return guiDone;
}
// Main code
void guimain()
{
guiDone = false;
WNDCLASSW wc;
RECT WindowRectangle;
HWND GameHwnd;
int WindowWide;
int WindowHeight;
// GameHwnd = GetConsoleWindow();
// if (GameHwnd) attachToGame = true;
// Create application window
//ImGui_ImplWin32_EnableDpiAwareness();
if (!attachToGame) {
WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"Gakumas Localify", NULL };
::RegisterClassExW(&wc);
hwnd = ::CreateWindowW(wc.lpszClassName, L"Gakumas Config GUI", WS_OVERLAPPEDWINDOW, cacheRect.left, cacheRect.top,
cacheRect.right - cacheRect.left, cacheRect.bottom - cacheRect.top, NULL, NULL, wc.hInstance, NULL);
if (nowTopStat) SetWindowTop();
}
else {
// 注册窗体类
// 附加到指定窗体上
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 0, 0));
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.lpszClassName = L" ";
wc.lpszMenuName = L" ";
wc.style = CS_VREDRAW | CS_HREDRAW;
RegisterClassW(&wc);
// 得到窗口句柄
WindowRectangle;
GameHwnd = FindWindowW(L"UnityWndClass", L"gakumas");
GetWindowRect(GameHwnd, &WindowRectangle);
WindowWide = WindowRectangle.right - WindowRectangle.left;
WindowHeight = WindowRectangle.bottom - WindowRectangle.top;
// 创建窗体
// HWND hwnd = ::CreateWindowW(WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW, L" ", L" ", WS_POPUP, 1, 1, WindowWide, WindowHeight, 0, 0, wc.hInstance, 0);
hwnd = ::CreateWindowW(wc.lpszClassName, L"", WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_LAYERED | WS_POPUP, 1, 1, WindowWide, WindowHeight, NULL, NULL, wc.hInstance, NULL);
LONG lWinStyleEx = GetWindowLong(hwnd, GWL_EXSTYLE);
lWinStyleEx = lWinStyleEx | WS_EX_LAYERED;
SetWindowLong(hwnd, GWL_EXSTYLE, lWinStyleEx);
SetLayeredWindowAttributes(hwnd, 0, 0, LWA_ALPHA);
// 去掉标题栏及边框
LONG_PTR Style = GetWindowLongPtr(hwnd, GWL_STYLE);
Style = Style & ~WS_CAPTION & ~WS_SYSMENU & ~WS_SIZEBOX;
SetWindowLongPtr(hwnd, GWL_STYLE, Style);
// 至顶层窗口 最大化
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, WindowWide, WindowHeight, SWP_SHOWWINDOW);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
// 设置窗体可穿透鼠标
// SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT | WS_EX_LAYERED);
}
// Initialize Direct3D
if (!CreateDeviceD3D(hwnd))
{
printf("init D3D failed\n");
CleanupDeviceD3D();
if (attachToGame) ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
return;
}
if (attachToGame) {
// Show the window
SetLayeredWindowAttributes(hwnd, 0, 0, LWA_ALPHA);
SetLayeredWindowAttributes(hwnd, 0, 0, LWA_COLORKEY);
}
::ShowWindow(hwnd, SW_SHOW);
::UpdateWindow(hwnd);
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
ImFontGlyphRangesBuilder builder;
ImFontConfig config;
builder.AddRanges(io.Fonts->GetGlyphRangesJapanese());
builder.AddRanges(io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
builder.AddRanges(io.Fonts->GetGlyphRangesChineseFull());
builder.AddRanges(io.Fonts->GetGlyphRangesKorean());
builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
builder.AddText("○◎△×☆");
ImVector<ImWchar> glyphRanges;
builder.BuildRanges(&glyphRanges);
config.GlyphRanges = glyphRanges.Data;
if (std::filesystem::exists("c:\\Windows\\Fonts\\msyh.ttc")) {
io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\msyh.ttc", 18.0f, &config);
}
else {
io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f, &config);
}
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 0.00f);
// Main loop
while (!guiDone)
{
// Poll and handle messages (inputs, window resize, etc.)
// See the WndProc() function below for our to dispatch events to the Win32 backend.
MSG msg;
while (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
if (msg.message == WM_QUIT)
guiDone = true;
}
if (guiDone)
break;
if (attachToGame) {
// 每次都将窗体置顶并跟随游戏窗体移动
GetWindowRect(GameHwnd, &WindowRectangle);
WindowWide = (WindowRectangle.right) - (WindowRectangle.left);
WindowHeight = (WindowRectangle.bottom) - (WindowRectangle.top);
DWORD dwStyle = GetWindowLong(GameHwnd, GWL_STYLE);
if (dwStyle & WS_BORDER)
{
WindowRectangle.top += 23;
WindowHeight -= 23;
}
// 跟随窗口移动
MoveWindow(hwnd, WindowRectangle.left + 9, WindowRectangle.top + 9, WindowWide - 18, WindowHeight - 18, true);
}
// Start the Dear ImGui frame
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
GkmsGUILoop::mainLoop();
ImGui::Render();
const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, NULL);
g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
g_pSwapChain->Present(1, 0); // Present with vsync
// g_pSwapChain->Present(0, 0); // Present without vsync
}
// Cleanup
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
CleanupDeviceD3D();
::DestroyWindow(hwnd);
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
}
// Helper functions
bool CreateDeviceD3D(HWND hWnd)
{
// Setup swap chain
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
sd.BufferCount = 2;
sd.BufferDesc.Width = 0;
sd.BufferDesc.Height = 0;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
UINT createDeviceFlags = 0;
//createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
D3D_FEATURE_LEVEL featureLevel;
const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, };
HRESULT res = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
if (res == DXGI_ERROR_UNSUPPORTED) // Try high-performance WARP software driver if hardware is not available.
res = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_WARP, NULL, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
if (res != S_OK)
return false;
CreateRenderTarget();
return true;
}
void CleanupDeviceD3D()
{
CleanupRenderTarget();
if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = NULL; }
if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = NULL; }
if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = NULL; }
}
void CreateRenderTarget()
{
ID3D11Texture2D* pBackBuffer;
g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_mainRenderTargetView);
pBackBuffer->Release();
}
void CleanupRenderTarget()
{
if (g_mainRenderTargetView) { g_mainRenderTargetView->Release(); g_mainRenderTargetView = NULL; }
}
// Forward declare message handler from imgui_impl_win32.cpp
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
// Win32 message handler
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
return true;
switch (msg)
{
case WM_SIZE:
if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED)
{
CleanupRenderTarget();
g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0);
CreateRenderTarget();
}
return 0;
case WM_SIZING: {
RECT* rect = reinterpret_cast<RECT*>(lParam);
cacheRect.left = rect->left;
cacheRect.right = rect->right;
cacheRect.top = rect->top;
cacheRect.bottom = rect->bottom;
}; break;
case WM_SYSCOMMAND:
if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
return 0;
break;
case WM_DESTROY:
::PostQuitMessage(0);
return 0;
}
return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}

View File

@ -0,0 +1,6 @@
#pragma once
#include "stdinclude.hpp"
void SetGuiDone(bool isDone);
bool getUmaGuiDone();
void guimain();

View File

@ -0,0 +1,115 @@
#pragma once
#include <unordered_map>
#include <string>
namespace I18nData {
static const std::unordered_map<std::string, std::string> i18nData_default = {
{ "local_file_already_latest", "The local file is the latest version. Do you want to update it?" },
{ "app_name", "Gakumas Localify" },
{ "gakumas_localify", "Gakumas Localify" },
{ "enable_plugin", "Enable Plugin (Not Hot Reloadable)" },
{ "replace_font", "Replace Font" },
{ "lazy_init", "Fast Initialization (Lazy loading)" },
{ "enable_free_camera", "Enable Free Camera" },
{ "start_game", "Start Game / Hot Reload Config" },
{ "setFpsTitle", "Max FPS (0 is Use Original Settings)" },
{ "unlockAllLive", "Unlock All Live" },
{ "unlockAllLiveCostume", "Unlock All Live Costume" },
{ "liveUseCustomeDress", "Live Custom Character" },
{ "live_costume_head_id", "Live Custom Head ID (eg. costume_head_hski-cstm-0002)" },
{ "live_custome_dress_id", "Live Custom Dress ID (eg. hski-cstm-0002)" },
{ "useCustomeGraphicSettings", "Use Custom Graphics Settings" },
{ "renderscale", "RenderScale (0.5/0.59/0.67/0.77/1.0)" },
{ "text_hook_test_mode", "Text Hook Test Mode" },
{ "useMasterDBTrans", "Enable MasterDB Localization" },
{ "export_text", "Export Text" },
{ "force_export_resource", "Force Update Resource" },
{ "login_as_ios", "Login as iOS" },
{ "max_high", "Ultra" },
{ "very_high", "Very High" },
{ "hign", "High" },
{ "middle", "Mid" },
{ "low", "Low" },
{ "orientation_orig", "Original" },
{ "orientation_portrait", "Portrait" },
{ "orientation_landscape", "Landscape" },
{ "orientation_lock", "Orientation Lock" },
{ "enable_breast_param", "Enable Breast Param" },
{ "damping", "Damping" },
{ "stiffness", "Stiffness" },
{ "spring", "Spring" },
{ "pendulum", "Pendulum" },
{ "pendulumrange", "Pendulum Range" },
{ "average", "Average" },
{ "rootweight", "Root Weight" },
{ "uselimit_0_1", "Limit Range Multiplier (0 is Unlimited)" },
{ "usearmcorrection", "Use Arm Correction" },
{ "isdirty", "IsDirty" },
{ "usescale", "Use Breast Scale" },
{ "breast_scale", "Breast Scale" },
{ "uselimitmultiplier", "Use Limit Multiplier" },
{ "axisx_x", "axisX.x" },
{ "axisy_x", "axisY.x" },
{ "axisz_x", "axisZ.x" },
{ "axisx_y", "axisX.y" },
{ "axisy_y", "axisY.y" },
{ "axisz_y", "axisZ.y" },
{ "basic_settings", "Basic Ssettings" },
{ "graphic_settings", "Graphic Settings" },
{ "camera_settings", "Camera Settings" },
{ "test_mode_live", "Test Mode - LIVE" },
{ "debug_settings", "Debug Settings" },
{ "breast_param", "Breast Parameters" },
{ "about", "About" },
{ "home", "Home" },
{ "advanced_settings", "Advanced" },
{ "about_warn_title", "WARNING" },
{ "about_warn_p1", "This plugin is for learning and communication only." },
{ "about_warn_p2", "Using external plugin against the relevant TOS so proceed at your own risk." },
{ "about_about_title", "About This Plugin" },
{ "about_about_p1", "This plugin is completely free. If you paid for this plugin, please report the seller." },
{ "about_about_p2", "Plugin QQ group: 975854705" },
{ "project_contribution", "Project Contribution" },
{ "plugin_code", "Plugin Code" },
{ "contributors", "Contributors" },
{ "translation_repository", "Translation Repository" },
{ "resource_settings", "Resource Settings" },
{ "check_built_in_resource", "Check Built-in Assets Update" },
{ "delete_plugin_resource", "Delete Plugin Resource" },
{ "use_remote_zip_resource", "Use Remote ZIP Resource" },
{ "resource_url", "Resource URL" },
{ "download", "Download" },
{ "invalid_zip_file", "Invalid file" },
{ "invalid_zip_file_warn", "This file is not a valid ZIP translation resource pack." },
{ "cancel", "Cancel" },
{ "ok", "OK" },
{ "downloaded_resource_version", "Downloaded Version" },
{ "del_remote_after_update", "Delete Cache File After Update" },
{ "warning", "Warning" },
{ "install", "Install" },
{ "installing", "Installing" },
{ "check_resource_from_api", "Check Resource Update From API" },
{ "api_addr", "API AddressGithub Latest Release API" },
{ "check_update", "Check" },
{ "translation_resource_update", "Translation Resource Update" },
{ "game_patch", "Game Patch" },
{ "patch_mode", "Patch Mode" },
{ "patch_local", "Local" },
{ "patch_local_desc", "Patch an app without modules embedded.\\nXposed scope can be changed dynamically without re-patch.\\nLocal patched apps can only run on the local device." },
{ "patch_integrated", "Integrated" },
{ "patch_integrated_desc", "Patch an app with modules embedded.\\nThe patched app can run without the manager, but cannot be managed dynamically.\\nIntegrated patched apps can be used on devices that do not have LSPatch Manager installed." },
{ "shizuku_available", "Shizuku service available" },
{ "shizuku_unavailable", "Shizuku service not connected" },
{ "home_shizuku_warning", "Some functions unavailable" },
{ "patch_debuggable", "Debuggable" },
{ "reserve_patched", "Reserve Patched APK" },
{ "support_file_types", "Supported files:\\nSingle/Multiple Choice: apk\\nSingle Choice: apks, xapk, zip" },
{ "patch_uninstall_text", "Due to different signatures, you need to uninstall the original app before installing the patched one.\\nMake sure you have backed up personal data." },
{ "patch_uninstall_confirm", "Are you sure you want to uninstall?" },
{ "patch_finished", "Patch finished. Start installing?" },
{ "about_contributors_asset_file", "about_contributors_en.json" },
{ "default_assets_check_api", "https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest" },
};
}

View File

@ -0,0 +1,140 @@
#pragma once
#include <unordered_map>
#include <string>
namespace I18nData {
static const std::unordered_map<std::string, std::string> i18nData_ja = {
{ "local_file_already_latest", "ローカルファイルはすでに最新バージョンです。それでも更新を続けますか?" },
{ "about", "情報" },
{ "about_about_p1", "このプラグインは完全に無料で提供されます。このプラグインで料金を支払ってしまった場合は、販売者に報告をしてください。" },
{ "about_about_p2", "プラグインの QQ グループ: 975854705" },
{ "about_about_title", "このプラグインについて" },
{ "about_contributors_asset_file", "about_contributors_en.json" },
{ "about_warn_p1", "このプラグインは学習とコミュニケーションのみを目的としています。" },
{ "about_warn_p2", "外部プラグインは関連する TOS に違反するため、自己責任でご使用ください。" },
{ "about_warn_title", "警告" },
{ "advanced_settings", "高度な設定" },
{ "api_addr", "APIアドレス (GitHub の最新リリース API)" },
{ "app_name", "Gakumas Localify" },
{ "average", "平均" },
{ "axisx_x", "X 軸.x" },
{ "axisx_y", "X 軸.y" },
{ "axisy_x", "Y 軸.x" },
{ "axisy_y", "Y 軸.y" },
{ "axisz_x", "Z 軸.x" },
{ "axisz_y", "Z 軸.y" },
{ "basic_settings", "基本設定" },
{ "breast_param", "胸のパラメーター" },
{ "breast_scale", "胸の大きさ" },
{ "camera_settings", "カメラ設定" },
{ "cancel", "キャンセル" },
{ "character_counter_content_description", "%1$d の %2$d に入力された文字" },
{ "character_counter_overflowed_content_description", "文字制限が %2$d 文字中、 %1$d 文字を超えています" },
{ "character_counter_pattern", "%1$d/%2$d" },
{ "check_built_in_resource", "内蔵アセットのアップデートを確認" },
{ "check_resource_from_api", "リソースアップデートを API から確認" },
{ "check_update", "確認" },
{ "clear_text_end_icon_content_description", "テキストを消去" },
{ "close_drawer", "ナビゲーションメニューを閉じる" },
{ "close_sheet", "シートを閉じる" },
{ "contributors", "貢献者" },
{ "damping", "ダンプ中" },
{ "debug_settings", "デバッグ設定" },
{ "default_assets_check_api", "https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest" },
{ "default_error_message", "入力が無効です" },
{ "default_popup_window_title", "ポップアップウィンドウ" },
{ "del_remote_after_update", "キャッシュファイルをアップデート後に削除" },
{ "delete_plugin_resource", "プラグインリソースを削除" },
{ "download", "ダウンロード" },
{ "downloaded_resource_version", "ダウンロードされたバージョン" },
{ "dropdown_menu", "ドロップダウンメニュー" },
{ "enable_breast_param", "胸のパラメーターを有効化" },
{ "enable_free_camera", "フリーカメラを有効化" },
{ "enable_plugin", "プラグイン有効化 (ホットリロードなし)" },
{ "error_a11y_label", "エラー: 無効" },
{ "error_icon_content_description", "エラー" },
{ "export_text", "テキストをエクスポート" },
{ "exposed_dropdown_menu_content_description", "ドロップダウンメニューを表示" },
{ "force_export_resource", "リソースのアップデートを強制する" },
{ "login_as_ios", "iOSとしてログイン" },
{ "gakumas_localify", "Gakumas Localify" },
{ "game_patch", "ゲームパッチ" },
{ "graphic_settings", "グラフィック設定" },
{ "hign", "" },
{ "home", "ホーム" },
{ "home_shizuku_warning", "一部の機能が使用できません" },
{ "icon_content_description", "ダイアログアイコン" },
{ "in_progress", "実行中" },
{ "indeterminate", "部分的にチェック済み" },
{ "install", "インストール" },
{ "installing", "インストール中" },
{ "invalid_zip_file", "無効なファイル" },
{ "invalid_zip_file_warn", "このファイルは有効な ZIP 翻訳リソースパックではありません。" },
{ "isdirty", "IsDirty" },
{ "item_view_role_description", "タブ" },
{ "lazy_init", "高速な初期化 (読み込みを遅延)" },
{ "liveUseCustomeDress", "ライブのキャラクターをカスタム" },
{ "live_costume_head_id", "ライブのカスタムヘッド ID (例: costume_head_hski-cstm-0002)" },
{ "live_custome_dress_id", "ライブ衣装のカスタム ID (例: hski-cstm-0002)" },
{ "low", "" },
{ "max_high", "ウルトラ" },
{ "middle", "" },
{ "off", "OFF" },
{ "ok", "OK" },
{ "on", "ON" },
{ "orientation_landscape", "横画面" },
{ "orientation_lock", "画面を固定" },
{ "orientation_orig", "オリジナル" },
{ "orientation_portrait", "縦画面" },
{ "password_toggle_content_description", "パスワードを表示" },
{ "patch_debuggable", "デバッグを可能にする" },
{ "patch_finished", "パッチが完了しました。インストールをしますか?" },
{ "patch_integrated", "統合" },
{ "patch_integrated_desc", "\"モジュールを埋め込んだ状態なアプリでパッチを当てます。\\nパッチを適用したアプリは LSPatch Manager なしで実行できますが、動的に管理はできません。\\n統合パッチが適用されたアプリは、LSPatch Manager がインストールされていないデバイスでも使用が可能です。\"" },
{ "patch_local", "ローカル" },
{ "patch_local_desc", "\"モジュールを埋め込まずにアプリにパッチを当てます。\\nXposed スコープは再パッチなしで動的に変更が可能です。\\nローカルでのパッチを当てたアプリは、ローカルのデバイスでのみ実行可能です。\"" },
{ "patch_mode", "パッチモード" },
{ "patch_uninstall_confirm", "アンインストールをしてもよろしいですか?" },
{ "patch_uninstall_text", "\"署名が異なるため、パッチをインストールする前に元となるアプリをアンインストールする必要があります。\\n個人データのバックアップを設定済みであることを確認してください。\"" },
{ "pendulum", "揺れ" },
{ "pendulumrange", "揺れの範囲" },
{ "plugin_code", "プラグインのコード" },
{ "project_contribution", "プロジェクトの貢献者" },
{ "range_end", "範囲の終了" },
{ "range_start", "範囲の開始" },
{ "renderscale", "RenderScale (0.5/0.59/0.67/0.77/1.0)" },
{ "replace_font", "フォントを置換する" },
{ "reserve_patched", "パッチ済みの APK を予約する" },
{ "resource_settings", "リソース設定" },
{ "resource_url", "リソース URL" },
{ "rootweight", "ルートウェイト" },
{ "selected", "選択済み" },
{ "setFpsTitle", "最大 FPS (0 はオリジナルの設定を使用します)" },
{ "shizuku_available", "Shizuku サービスが有効です" },
{ "shizuku_unavailable", "Shizuku サービスが接続されていません" },
{ "spring", "跳ね" },
{ "start_game", "ゲーム開始 / ホットリロードの設定" },
{ "stiffness", "剛性" },
{ "support_file_types", "\"対応ファイル:\\n単一または複数選択: apk\\n単一選択: apks、xapk、zip\"" },
{ "switch_role", "切り替え" },
{ "tab", "タブ" },
{ "template_percent", "%1$d パーセント。" },
{ "test_mode_live", "テストモード - ライブ" },
{ "text_hook_test_mode", "テキストフックテストモード" },
{ "useMasterDBTrans", "MasterDB をローカライズする" },
{ "translation_repository", "翻訳のリポジトリ" },
{ "translation_resource_update", "翻訳リソースをアップデート" },
{ "unlockAllLive", "すべてのライブを開放" },
{ "unlockAllLiveCostume", "すべてのライブ衣装を開放" },
{ "useCustomeGraphicSettings", "カスタムグラフィック設定を使用する" },
{ "use_remote_zip_resource", "リモート ZIP リソースを使用する" },
{ "usearmcorrection", "Arm コレクションを使用する" },
{ "uselimit_0_1", "リミットレンジの倍率 (0 は無制限)" },
{ "uselimitmultiplier", "乗数制限を使用する" },
{ "usescale", "胸の大きさを使用する" },
{ "very_high", "最高" },
{ "warning", "警告" },
};
}

View File

@ -0,0 +1,115 @@
#pragma once
#include <unordered_map>
#include <string>
namespace I18nData {
static const std::unordered_map<std::string, std::string> i18nData_zh_rCN = {
{ "local_file_already_latest", "本地文件已经是最新版本,是否继续更新?" },
{ "app_name", "Gakumas Localify" },
{ "gakumas_localify", "Gakumas Localify" },
{ "enable_plugin", "启用插件 (不可热重载)" },
{ "replace_font", "替换字体" },
{ "lazy_init", "快速初始化(懒加载配置)" },
{ "enable_free_camera", "启用自由视角(可热重载; 需使用实体键盘)" },
{ "start_game", "以上述配置启动游戏/重载配置" },
{ "setFpsTitle", "最大 FPS (0 为保持游戏原设置)" },
{ "unlockAllLive", "解锁所有 Live" },
{ "unlockAllLiveCostume", "解锁所有 Live 服装" },
{ "liveUseCustomeDress", "Live 使用自定义角色" },
{ "live_costume_head_id", "Live 自定义头部 ID (例: costume_head_hski-cstm-0002)" },
{ "live_custome_dress_id", "Live 自定义服装 ID (例: hski-cstm-0002)" },
{ "useCustomeGraphicSettings", "使用自定义画质设置" },
{ "renderscale", "RenderScale (0.5/0.59/0.67/0.77/1.0)" },
{ "text_hook_test_mode", "文本 hook 测试模式" },
{ "useMasterDBTrans", "使用 MasterDB 本地化" },
{ "export_text", "导出文本" },
{ "force_export_resource", "启动后强制导出资源" },
{ "login_as_ios", "以 iOS 登陆" },
{ "max_high", "极高" },
{ "very_high", "超高" },
{ "hign", "" },
{ "middle", "" },
{ "low", "" },
{ "orientation_orig", "原版" },
{ "orientation_portrait", "竖屏" },
{ "orientation_landscape", "横屏" },
{ "orientation_lock", "方向锁定" },
{ "enable_breast_param", "启用胸部参数" },
{ "damping", "阻尼 (Damping)" },
{ "stiffness", "刚度 (Stiffness)" },
{ "spring", "弹簧系数 (Spring)" },
{ "pendulum", "钟摆系数 (Pendulum)" },
{ "pendulumrange", "钟摆范围 (PendulumRange)" },
{ "average", "Average" },
{ "rootweight", "RootWeight" },
{ "uselimit_0_1", "范围限制倍率 (0 为不限制, 1 为原版)" },
{ "usearmcorrection", "使用手臂矫正" },
{ "isdirty", "IsDirty" },
{ "usescale", "应用缩放" },
{ "breast_scale", "胸部缩放倍率" },
{ "uselimitmultiplier", "启用范围限制倍率" },
{ "axisx_x", "axisX.x" },
{ "axisy_x", "axisY.x" },
{ "axisz_x", "axisZ.x" },
{ "axisx_y", "axisX.y" },
{ "axisy_y", "axisY.y" },
{ "axisz_y", "axisZ.y" },
{ "basic_settings", "基础设置" },
{ "graphic_settings", "画面设置" },
{ "camera_settings", "摄像机设置" },
{ "test_mode_live", "测试模式 - LIVE" },
{ "debug_settings", "调试设置" },
{ "breast_param", "胸部参数" },
{ "about", "关于" },
{ "home", "主页" },
{ "advanced_settings", "高级设置" },
{ "about_warn_title", "使用前警告" },
{ "about_warn_p1", "本插件仅供学习和交流使用。" },
{ "about_warn_p2", "使用外部插件属于违反游戏条款的行为。若使用插件后账号被封禁,造成的后果由用户自行承担。" },
{ "about_about_title", "关于本插件" },
{ "about_about_p1", "本插件完全免费。若您付费购买了本插件,请举报店家。" },
{ "about_about_p2", "插件交流群: 975854705" },
{ "project_contribution", "项目贡献" },
{ "plugin_code", "插件本体" },
{ "contributors", "贡献者列表" },
{ "translation_repository", "译文仓库" },
{ "resource_settings", "资源设置" },
{ "check_built_in_resource", "检查内置数据更新" },
{ "delete_plugin_resource", "清除游戏目录内的插件资源" },
{ "use_remote_zip_resource", "使用远程 ZIP 数据" },
{ "resource_url", "资源地址" },
{ "download", "下载" },
{ "invalid_zip_file", "文件解析失败" },
{ "invalid_zip_file_warn", "此文件不是一个有效的 ZIP 翻译资源包" },
{ "cancel", "取消" },
{ "ok", "确定" },
{ "downloaded_resource_version", "已下载资源版本" },
{ "del_remote_after_update", "替换文件后删除下载缓存" },
{ "warning", "注意" },
{ "install", "安装" },
{ "installing", "安装中" },
{ "check_resource_from_api", "从服务器检查热更新资源" },
{ "api_addr", "API 地址Github Latest Release API" },
{ "check_update", "检查更新" },
{ "translation_resource_update", "翻译资源更新" },
{ "game_patch", "游戏修补" },
{ "patch_mode", "修补模式" },
{ "patch_local", "本地模式" },
{ "patch_local_desc", "为未嵌入模块的应用程序打补丁。\\nXposed 范围可动态更改,无需重新打补丁。\\n打了本地补丁的应用程序只能在本地设备上运行。" },
{ "patch_integrated", "集成模式" },
{ "patch_integrated_desc", "修补 App 并内置模块。\\n经修补的应用可以在没有管理器的情况下运行但不能动态管理配置。\\n以集成模式修补的应用可在未安装 LSPatch 管理器的设备上运行。" },
{ "shizuku_available", "Shizuku 服务可用" },
{ "shizuku_unavailable", "Shizuku 服务未连接" },
{ "home_shizuku_warning", "部分功能不可用" },
{ "patch_debuggable", "可调试" },
{ "reserve_patched", "安装时保留修补包" },
{ "support_file_types", "支持文件类型:\\n单/多选 apk\\n单选 apks, xapk, zip" },
{ "patch_uninstall_text", "由于签名不同,安装修补的应用前需要先卸载原应用。\\n确保您已备份好个人数据。" },
{ "patch_uninstall_confirm", "您确定要卸载吗" },
{ "patch_finished", "修补完成,是否开始安装?" },
{ "about_contributors_asset_file", "about_contributors_zh_cn.json" },
{ "default_assets_check_api", "https://uma.chinosk6.cn/api/gkms_trans_data" },
};
}

View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
import os
import argparse
import xml.etree.ElementTree as ET
def parse_strings_xml(file_path):
"""
解析 Android strings.xml 文件返回 (key, value) 列表
"""
pairs = []
try:
tree = ET.parse(file_path)
root = tree.getroot()
# 遍历所有 <string> 元素
for string_elem in root.findall('string'):
key = string_elem.get('name')
if key is None:
continue
value = string_elem.text or ""
# 处理转义字符:将 \ 转为 \\ " 转为 \"
value = value.replace('\\', '\\\\').replace('"', '\\"')
pairs.append((key, value))
except Exception as e:
print(f"解析 {file_path} 失败: {e}")
return pairs
def get_var_name(folder_name):
"""
根据文件夹名称生成对应的 C++ 变量名
"""
if folder_name == "values":
return "i18nData_default"
elif folder_name.startswith("values-"):
suffix = folder_name[len("values-"):]
suffix = suffix.replace('-', '_')
return "i18nData_" + suffix
else:
return "i18nData"
def generate_header(pairs, output_filename, var_name):
"""
根据 key/value 对生成 C++ 头文件代码并写入 output_filename 文件中
变量名使用传入的 var_name
"""
header_lines = [
"#pragma once",
"",
"#include <unordered_map>",
"#include <string>",
"",
"namespace I18nData {",
f" static const std::unordered_map<std::string, std::string> {var_name} = {{"
]
# 为每个 key/value 对生成一行代码
for key, value in pairs:
header_lines.append(f' {{ "{key}", "{value}" }},')
header_lines.append(" };")
header_lines.append("}")
# 将生成的内容写入文件
with open(output_filename, "w", encoding="utf-8") as f:
f.write("\n".join(header_lines))
print(f"生成 {output_filename} 成功,变量名为 {var_name}.")
def main():
parser = argparse.ArgumentParser(description="根据安卓 strings.xml 文件生成 C++ unordered_map 代码")
parser.add_argument("folder", help="根目录文件夹路径,该文件夹下应包含 app/src/main/res")
args = parser.parse_args()
# 定义需要转换的文件夹和对应的输出 .hpp 文件名
files = {
"values-zh-rCN": "strings_zh-rCN.hpp",
"values-ja": "strings_ja.hpp",
"values": "strings_en.hpp"
}
base_path = os.path.join(args.folder, "app", "src", "main", "res")
for folder_name, output_file in files.items():
xml_path = os.path.join(base_path, folder_name, "strings.xml")
if not os.path.isfile(xml_path):
print(f"文件 {xml_path} 不存在,跳过.")
continue
print(f"解析文件:{xml_path}")
pairs = parse_strings_xml(xml_path)
var_name = get_var_name(folder_name)
generate_header(pairs, output_file, var_name)
if __name__ == "__main__":
main()

21
src/imgui/LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2023 Omar Cornut
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

122
src/imgui/imconfig.h Normal file
View File

@ -0,0 +1,122 @@
//-----------------------------------------------------------------------------
// COMPILE-TIME OPTIONS FOR DEAR IMGUI
// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure.
// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions.
//-----------------------------------------------------------------------------
// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it)
// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template.
//-----------------------------------------------------------------------------
// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp
// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures.
// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts.
// Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using.
//-----------------------------------------------------------------------------
#pragma once
//---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
// DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
//#define IMGUI_API __declspec( dllexport )
//#define IMGUI_API __declspec( dllimport )
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87: disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This will be folded into IMGUI_DISABLE_OBSOLETE_FUNCTIONS in a few versions.
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty.
//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowStackToolWindow() will be empty (this was called IMGUI_DISABLE_METRICS_WINDOW before 1.88).
//---- Don't implement some functions to reduce linkage requirements.
//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a)
//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime).
//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Include imgui_user.h at the end of imgui.h as a convenience
//#define IMGUI_INCLUDE_IMGUI_USER_H
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another)
//#define IMGUI_USE_BGRA_PACKED_COLOR
//---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h"
//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h"
//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if enabled
//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined)
// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h.
//#define IMGUI_USE_STB_SPRINTF
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE
//---- Use stb_truetype to build and rasterize the font atlas (default)
// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.
//#define IMGUI_ENABLE_STB_TRUETYPE
//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.
// This will be inlined as part of ImVec2 and ImVec4 class declarations.
/*
#define IM_VEC2_CLASS_EXTRA \
constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \
operator MyVec2() const { return MyVec2(x,y); }
#define IM_VEC4_CLASS_EXTRA \
constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \
operator MyVec4() const { return MyVec4(x,y,z,w); }
*/
//---- ...Or use Dear ImGui's own very basic math operators.
//#define IMGUI_DEFINE_MATH_OPERATORS
//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices.
// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices).
// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer.
// Read about ImGuiBackendFlags_RendererHasVtxOffset for details.
//#define ImDrawIdx unsigned int
//---- Override ImDrawCallback signature (will need to modify renderer backends accordingly)
//struct ImDrawList;
//struct ImDrawCmd;
//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data);
//#define ImDrawCallback MyImDrawCallback
//---- Debug Tools: Macro to break in Debugger
// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.)
//#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID
//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files.
/*
namespace ImGui
{
void MyFunction(const char* name, const MyMatrix44& v);
}
*/

14627
src/imgui/imgui.cpp Normal file

File diff suppressed because it is too large Load Diff

3124
src/imgui/imgui.h Normal file

File diff suppressed because it is too large Load Diff

8045
src/imgui/imgui_demo.cpp Normal file

File diff suppressed because it is too large Load Diff

4174
src/imgui/imgui_draw.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,596 @@
// dear imgui: Renderer Backend for DirectX11
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer.
// 2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled).
// 2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.
// 2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
// 2018-08-01: DirectX11: Querying for IDXGIFactory instead of IDXGIFactory1 to increase compatibility.
// 2018-07-13: DirectX11: Fixed unreleased resources in Init and Shutdown functions.
// 2018-06-08: Misc: Extracted imgui_impl_dx11.cpp/.h away from the old combined DX11+Win32 example.
// 2018-06-08: DirectX11: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX11_RenderDrawData() in the .h file so you can call it yourself.
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
// 2016-05-07: DirectX11: Disabling depth-write.
#include "imgui.h"
#include "imgui_impl_dx11.h"
// DirectX
#include <stdio.h>
#include <d3d11.h>
#include <d3dcompiler.h>
#ifdef _MSC_VER
#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
#endif
// DirectX11 data
struct ImGui_ImplDX11_Data
{
ID3D11Device* pd3dDevice;
ID3D11DeviceContext* pd3dDeviceContext;
IDXGIFactory* pFactory;
ID3D11Buffer* pVB;
ID3D11Buffer* pIB;
ID3D11VertexShader* pVertexShader;
ID3D11InputLayout* pInputLayout;
ID3D11Buffer* pVertexConstantBuffer;
ID3D11PixelShader* pPixelShader;
ID3D11SamplerState* pFontSampler;
ID3D11ShaderResourceView* pFontTextureView;
ID3D11RasterizerState* pRasterizerState;
ID3D11BlendState* pBlendState;
ID3D11DepthStencilState* pDepthStencilState;
int VertexBufferSize;
int IndexBufferSize;
ImGui_ImplDX11_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
};
struct VERTEX_CONSTANT_BUFFER_DX11
{
float mvp[4][4];
};
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Functions
static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceContext* ctx)
{
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
// Setup viewport
D3D11_VIEWPORT vp;
memset(&vp, 0, sizeof(D3D11_VIEWPORT));
vp.Width = draw_data->DisplaySize.x;
vp.Height = draw_data->DisplaySize.y;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = vp.TopLeftY = 0;
ctx->RSSetViewports(1, &vp);
// Setup shader and vertex buffers
unsigned int stride = sizeof(ImDrawVert);
unsigned int offset = 0;
ctx->IASetInputLayout(bd->pInputLayout);
ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
ctx->VSSetShader(bd->pVertexShader, nullptr, 0);
ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
ctx->PSSetShader(bd->pPixelShader, nullptr, 0);
ctx->PSSetSamplers(0, 1, &bd->pFontSampler);
ctx->GSSetShader(nullptr, nullptr, 0);
ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
// Setup blend state
const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
ctx->RSSetState(bd->pRasterizerState);
}
// Render function
void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
ID3D11DeviceContext* ctx = bd->pd3dDeviceContext;
// Create and grow vertex/index buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
D3D11_BUFFER_DESC desc;
memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
desc.MiscFlags = 0;
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
return;
}
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
{
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
D3D11_BUFFER_DESC desc;
memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
return;
}
// Upload vertex/index data into a single contiguous GPU buffer
D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;
if (ctx->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)
return;
if (ctx->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)
return;
ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;
ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
vtx_dst += cmd_list->VtxBuffer.Size;
idx_dst += cmd_list->IdxBuffer.Size;
}
ctx->Unmap(bd->pVB, 0);
ctx->Unmap(bd->pIB, 0);
// Setup orthographic projection matrix into our constant buffer
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
{
D3D11_MAPPED_SUBRESOURCE mapped_resource;
if (ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK)
return;
VERTEX_CONSTANT_BUFFER_DX11* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX11*)mapped_resource.pData;
float L = draw_data->DisplayPos.x;
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
float T = draw_data->DisplayPos.y;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
float mvp[4][4] =
{
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
{ 0.0f, 0.0f, 0.5f, 0.0f },
{ (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f },
};
memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));
ctx->Unmap(bd->pVertexConstantBuffer, 0);
}
// Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)
struct BACKUP_DX11_STATE
{
UINT ScissorRectsCount, ViewportsCount;
D3D11_RECT ScissorRects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
D3D11_VIEWPORT Viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
ID3D11RasterizerState* RS;
ID3D11BlendState* BlendState;
FLOAT BlendFactor[4];
UINT SampleMask;
UINT StencilRef;
ID3D11DepthStencilState* DepthStencilState;
ID3D11ShaderResourceView* PSShaderResource;
ID3D11SamplerState* PSSampler;
ID3D11PixelShader* PS;
ID3D11VertexShader* VS;
ID3D11GeometryShader* GS;
UINT PSInstancesCount, VSInstancesCount, GSInstancesCount;
ID3D11ClassInstance *PSInstances[256], *VSInstances[256], *GSInstances[256]; // 256 is max according to PSSetShader documentation
D3D11_PRIMITIVE_TOPOLOGY PrimitiveTopology;
ID3D11Buffer* IndexBuffer, *VertexBuffer, *VSConstantBuffer;
UINT IndexBufferOffset, VertexBufferStride, VertexBufferOffset;
DXGI_FORMAT IndexBufferFormat;
ID3D11InputLayout* InputLayout;
};
BACKUP_DX11_STATE old = {};
old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;
ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
ctx->RSGetViewports(&old.ViewportsCount, old.Viewports);
ctx->RSGetState(&old.RS);
ctx->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
ctx->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
ctx->PSGetShaderResources(0, 1, &old.PSShaderResource);
ctx->PSGetSamplers(0, 1, &old.PSSampler);
old.PSInstancesCount = old.VSInstancesCount = old.GSInstancesCount = 256;
ctx->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);
ctx->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);
ctx->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
ctx->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);
ctx->IAGetPrimitiveTopology(&old.PrimitiveTopology);
ctx->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
ctx->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
ctx->IAGetInputLayout(&old.InputLayout);
// Setup desired DX state
ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
// Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_idx_offset = 0;
int global_vtx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr)
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
else
pcmd->UserCallback(cmd_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
// Apply scissor/clipping rectangle
const D3D11_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
ctx->RSSetScissorRects(1, &r);
// Bind texture, Draw
ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->GetTexID();
ctx->PSSetShaderResources(0, 1, &texture_srv);
ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
}
}
global_idx_offset += cmd_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size;
}
// Restore modified DX state
ctx->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
ctx->RSSetViewports(old.ViewportsCount, old.Viewports);
ctx->RSSetState(old.RS); if (old.RS) old.RS->Release();
ctx->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
ctx->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
ctx->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
ctx->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
ctx->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();
for (UINT i = 0; i < old.PSInstancesCount; i++) if (old.PSInstances[i]) old.PSInstances[i]->Release();
ctx->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();
ctx->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
ctx->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();
for (UINT i = 0; i < old.VSInstancesCount; i++) if (old.VSInstances[i]) old.VSInstances[i]->Release();
ctx->IASetPrimitiveTopology(old.PrimitiveTopology);
ctx->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
ctx->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
ctx->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
}
static void ImGui_ImplDX11_CreateFontsTexture()
{
// Build texture atlas
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
// Upload texture to graphics system
{
D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.CPUAccessFlags = 0;
ID3D11Texture2D* pTexture = nullptr;
D3D11_SUBRESOURCE_DATA subResource;
subResource.pSysMem = pixels;
subResource.SysMemPitch = desc.Width * 4;
subResource.SysMemSlicePitch = 0;
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);
IM_ASSERT(pTexture != nullptr);
// Create texture view
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(srvDesc));
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = desc.MipLevels;
srvDesc.Texture2D.MostDetailedMip = 0;
bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView);
pTexture->Release();
}
// Store our identifier
io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView);
// Create texture sampler
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
{
D3D11_SAMPLER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
desc.MipLODBias = 0.f;
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
desc.MinLOD = 0.f;
desc.MaxLOD = 0.f;
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);
}
}
bool ImGui_ImplDX11_CreateDeviceObjects()
{
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
if (!bd->pd3dDevice)
return false;
if (bd->pFontSampler)
ImGui_ImplDX11_InvalidateDeviceObjects();
// By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
// If you would like to use this DX11 sample code but remove this dependency you can:
// 1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution]
// 2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL.
// See https://github.com/ocornut/imgui/pull/638 for sources and details.
// Create the vertex shader
{
static const char* vertexShader =
"cbuffer vertexBuffer : register(b0) \
{\
float4x4 ProjectionMatrix; \
};\
struct VS_INPUT\
{\
float2 pos : POSITION;\
float4 col : COLOR0;\
float2 uv : TEXCOORD0;\
};\
\
struct PS_INPUT\
{\
float4 pos : SV_POSITION;\
float4 col : COLOR0;\
float2 uv : TEXCOORD0;\
};\
\
PS_INPUT main(VS_INPUT input)\
{\
PS_INPUT output;\
output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
output.col = input.col;\
output.uv = input.uv;\
return output;\
}";
ID3DBlob* vertexShaderBlob;
if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), nullptr, nullptr, nullptr, "main", "vs_4_0", 0, 0, &vertexShaderBlob, nullptr)))
return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), nullptr, &bd->pVertexShader) != S_OK)
{
vertexShaderBlob->Release();
return false;
}
// Create the input layout
D3D11_INPUT_ELEMENT_DESC local_layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK)
{
vertexShaderBlob->Release();
return false;
}
vertexShaderBlob->Release();
// Create the constant buffer
{
D3D11_BUFFER_DESC desc;
desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX11);
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
desc.MiscFlags = 0;
bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVertexConstantBuffer);
}
}
// Create the pixel shader
{
static const char* pixelShader =
"struct PS_INPUT\
{\
float4 pos : SV_POSITION;\
float4 col : COLOR0;\
float2 uv : TEXCOORD0;\
};\
sampler sampler0;\
Texture2D texture0;\
\
float4 main(PS_INPUT input) : SV_Target\
{\
float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \
return out_col; \
}";
ID3DBlob* pixelShaderBlob;
if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), nullptr, nullptr, nullptr, "main", "ps_4_0", 0, 0, &pixelShaderBlob, nullptr)))
return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), nullptr, &bd->pPixelShader) != S_OK)
{
pixelShaderBlob->Release();
return false;
}
pixelShaderBlob->Release();
}
// Create the blending setup
{
D3D11_BLEND_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.AlphaToCoverageEnable = false;
desc.RenderTarget[0].BlendEnable = true;
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState);
}
// Create the rasterizer state
{
D3D11_RASTERIZER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.FillMode = D3D11_FILL_SOLID;
desc.CullMode = D3D11_CULL_NONE;
desc.ScissorEnable = true;
desc.DepthClipEnable = true;
bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState);
}
// Create depth-stencil State
{
D3D11_DEPTH_STENCIL_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.DepthEnable = false;
desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
desc.DepthFunc = D3D11_COMPARISON_ALWAYS;
desc.StencilEnable = false;
desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
desc.BackFace = desc.FrontFace;
bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);
}
ImGui_ImplDX11_CreateFontsTexture();
return true;
}
void ImGui_ImplDX11_InvalidateDeviceObjects()
{
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
if (!bd->pd3dDevice)
return;
if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }
if (bd->pFontTextureView) { bd->pFontTextureView->Release(); bd->pFontTextureView = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well.
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
if (bd->pBlendState) { bd->pBlendState->Release(); bd->pBlendState = nullptr; }
if (bd->pDepthStencilState) { bd->pDepthStencilState->Release(); bd->pDepthStencilState = nullptr; }
if (bd->pRasterizerState) { bd->pRasterizerState->Release(); bd->pRasterizerState = nullptr; }
if (bd->pPixelShader) { bd->pPixelShader->Release(); bd->pPixelShader = nullptr; }
if (bd->pVertexConstantBuffer) { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = nullptr; }
if (bd->pInputLayout) { bd->pInputLayout->Release(); bd->pInputLayout = nullptr; }
if (bd->pVertexShader) { bd->pVertexShader->Release(); bd->pVertexShader = nullptr; }
}
bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context)
{
ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Setup backend capabilities flags
ImGui_ImplDX11_Data* bd = IM_NEW(ImGui_ImplDX11_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx11";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
// Get factory from device
IDXGIDevice* pDXGIDevice = nullptr;
IDXGIAdapter* pDXGIAdapter = nullptr;
IDXGIFactory* pFactory = nullptr;
if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK)
if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK)
if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK)
{
bd->pd3dDevice = device;
bd->pd3dDeviceContext = device_context;
bd->pFactory = pFactory;
}
if (pDXGIDevice) pDXGIDevice->Release();
if (pDXGIAdapter) pDXGIAdapter->Release();
bd->pd3dDevice->AddRef();
bd->pd3dDeviceContext->AddRef();
return true;
}
void ImGui_ImplDX11_Shutdown()
{
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplDX11_InvalidateDeviceObjects();
if (bd->pFactory) { bd->pFactory->Release(); }
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); }
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
IM_DELETE(bd);
}
void ImGui_ImplDX11_NewFrame()
{
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX11_Init()?");
if (!bd->pFontSampler)
ImGui_ImplDX11_CreateDeviceObjects();
}

View File

@ -0,0 +1,26 @@
// dear imgui: Renderer Backend for DirectX11
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
struct ID3D11Device;
struct ID3D11DeviceContext;
IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);
IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
// Use if you want to reset your rendering device without losing Dear ImGui state.
IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects();
IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects();

View File

@ -0,0 +1,826 @@
// dear imgui: Platform Backend for Windows (standard windows API for 32-bits AND 64-bits applications)
// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
// Implemented features:
// [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui)
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
#include "imgui.h"
#include "imgui_impl_win32.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <windowsx.h> // GET_X_LPARAM(), GET_Y_LPARAM()
#include <tchar.h>
#include <dwmapi.h>
// Configuration flags to add in your imconfig.h file:
//#define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD // Disable gamepad support. This was meaningful before <1.81 but we now load XInput dynamically so the option is now less relevant.
// Using XInput for gamepad (will load DLL dynamically)
#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
#include <xinput.h>
typedef DWORD (WINAPI *PFN_XInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*);
typedef DWORD (WINAPI *PFN_XInputGetState)(DWORD, XINPUT_STATE*);
#endif
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2023-02-15: Inputs: Use WM_NCMOUSEMOVE / WM_NCMOUSELEAVE to track mouse position over non-client area (e.g. OS decorations) when app is not focused. (#6045, #6162)
// 2023-02-02: Inputs: Flipping WM_MOUSEHWHEEL (horizontal mouse-wheel) value to match other backends and offer consistent horizontal scrolling direction. (#4019, #6096, #1463)
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2022-09-28: Inputs: Convert WM_CHAR values with MultiByteToWideChar() when window class was registered as MBCS (not Unicode).
// 2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
// 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
// 2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
// 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
// 2022-01-17: Inputs: always update key mods next and before a key event (not in NewFrame) to fix input queue with very low framerates.
// 2022-01-12: Inputs: Update mouse inputs using WM_MOUSEMOVE/WM_MOUSELEAVE + fallback to provide it when focused but not hovered/captured. More standard and will allow us to pass it to future input queue API.
// 2022-01-12: Inputs: Maintain our own copy of MouseButtonsDown mask instead of using ImGui::IsAnyMouseDown() which will be obsoleted.
// 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
// 2021-12-16: Inputs: Fill VK_LCONTROL/VK_RCONTROL/VK_LSHIFT/VK_RSHIFT/VK_LMENU/VK_RMENU for completeness.
// 2021-08-17: Calling io.AddFocusEvent() on WM_SETFOCUS/WM_KILLFOCUS messages.
// 2021-08-02: Inputs: Fixed keyboard modifiers being reported when host window doesn't have focus.
// 2021-07-29: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using TrackMouseEvent() to receive WM_MOUSELEAVE events).
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-06-08: Fixed ImGui_ImplWin32_EnableDpiAwareness() and ImGui_ImplWin32_GetDpiScaleForMonitor() to handle Windows 8.1/10 features without a manifest (per-monitor DPI, and properly calls SetProcessDpiAwareness() on 8.1).
// 2021-03-23: Inputs: Clearing keyboard down array when losing focus (WM_KILLFOCUS).
// 2021-02-18: Added ImGui_ImplWin32_EnableAlphaCompositing(). Non Visual Studio users will need to link with dwmapi.lib (MinGW/gcc: use -ldwmapi).
// 2021-02-17: Fixed ImGui_ImplWin32_EnableDpiAwareness() attempting to get SetProcessDpiAwareness from shcore.dll on Windows 8 whereas it is only supported on Windows 8.1.
// 2021-01-25: Inputs: Dynamically loading XInput DLL.
// 2020-12-04: Misc: Fixed setting of io.DisplaySize to invalid/uninitialized data when after hwnd has been closed.
// 2020-03-03: Inputs: Calling AddInputCharacterUTF16() to support surrogate pairs leading to codepoint >= 0x10000 (for more complete CJK inputs)
// 2020-02-17: Added ImGui_ImplWin32_EnableDpiAwareness(), ImGui_ImplWin32_GetDpiScaleForHwnd(), ImGui_ImplWin32_GetDpiScaleForMonitor() helper functions.
// 2020-01-14: Inputs: Added support for #define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD/IMGUI_IMPL_WIN32_DISABLE_LINKING_XINPUT.
// 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
// 2019-05-11: Inputs: Don't filter value from WM_CHAR before calling AddInputCharacter().
// 2019-01-17: Misc: Using GetForegroundWindow()+IsChild() instead of GetActiveWindow() to be compatible with windows created in a different thread or parent.
// 2019-01-17: Inputs: Added support for mouse buttons 4 and 5 via WM_XBUTTON* messages.
// 2019-01-15: Inputs: Added support for XInput gamepads (if ImGuiConfigFlags_NavEnableGamepad is set by user application).
// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
// 2018-06-10: Inputs: Fixed handling of mouse wheel messages to support fine position messages (typically sent by track-pads).
// 2018-06-08: Misc: Extracted imgui_impl_win32.cpp/.h away from the old combined DX9/DX10/DX11/DX12 examples.
// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors and ImGuiBackendFlags_HasSetMousePos flags + honor ImGuiConfigFlags_NoMouseCursorChange flag.
// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling).
// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
// 2018-02-06: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set).
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
// 2018-01-08: Inputs: Added mapping for ImGuiKey_Insert.
// 2018-01-05: Inputs: Added WM_LBUTTONDBLCLK double-click handlers for window classes with the CS_DBLCLKS flag.
// 2017-10-23: Inputs: Added WM_SYSKEYDOWN / WM_SYSKEYUP handlers so e.g. the VK_MENU key can be read.
// 2017-10-23: Inputs: Using Win32 ::SetCapture/::GetCapture() to retrieve mouse positions outside the client area when dragging.
// 2016-11-12: Inputs: Only call Win32 ::SetCursor(nullptr) when io.MouseDrawCursor is set.
struct ImGui_ImplWin32_Data
{
HWND hWnd;
HWND MouseHwnd;
int MouseTrackedArea; // 0: not tracked, 1: client are, 2: non-client area
int MouseButtonsDown;
INT64 Time;
INT64 TicksPerSecond;
ImGuiMouseCursor LastMouseCursor;
#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
bool HasGamepad;
bool WantUpdateHasGamepad;
HMODULE XInputDLL;
PFN_XInputGetCapabilities XInputGetCapabilities;
PFN_XInputGetState XInputGetState;
#endif
ImGui_ImplWin32_Data() { memset((void*)this, 0, sizeof(*this)); }
};
// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
static ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplWin32_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
}
// Functions
bool ImGui_ImplWin32_Init(void* hwnd)
{
ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
INT64 perf_frequency, perf_counter;
if (!::QueryPerformanceFrequency((LARGE_INTEGER*)&perf_frequency))
return false;
if (!::QueryPerformanceCounter((LARGE_INTEGER*)&perf_counter))
return false;
// Setup backend capabilities flags
ImGui_ImplWin32_Data* bd = IM_NEW(ImGui_ImplWin32_Data)();
io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = "imgui_impl_win32";
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
bd->hWnd = (HWND)hwnd;
bd->TicksPerSecond = perf_frequency;
bd->Time = perf_counter;
bd->LastMouseCursor = ImGuiMouseCursor_COUNT;
// Set platform dependent data in viewport
ImGui::GetMainViewport()->PlatformHandleRaw = (void*)hwnd;
// Dynamically load XInput library
#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
bd->WantUpdateHasGamepad = true;
const char* xinput_dll_names[] =
{
"xinput1_4.dll", // Windows 8+
"xinput1_3.dll", // DirectX SDK
"xinput9_1_0.dll", // Windows Vista, Windows 7
"xinput1_2.dll", // DirectX SDK
"xinput1_1.dll" // DirectX SDK
};
for (int n = 0; n < IM_ARRAYSIZE(xinput_dll_names); n++)
if (HMODULE dll = ::LoadLibraryA(xinput_dll_names[n]))
{
bd->XInputDLL = dll;
bd->XInputGetCapabilities = (PFN_XInputGetCapabilities)::GetProcAddress(dll, "XInputGetCapabilities");
bd->XInputGetState = (PFN_XInputGetState)::GetProcAddress(dll, "XInputGetState");
break;
}
#endif // IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
return true;
}
void ImGui_ImplWin32_Shutdown()
{
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();
IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
// Unload XInput library
#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
if (bd->XInputDLL)
::FreeLibrary(bd->XInputDLL);
#endif // IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
io.BackendPlatformName = nullptr;
io.BackendPlatformUserData = nullptr;
IM_DELETE(bd);
}
static bool ImGui_ImplWin32_UpdateMouseCursor()
{
ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
return false;
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)
{
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
::SetCursor(nullptr);
}
else
{
// Show OS mouse cursor
LPTSTR win32_cursor = IDC_ARROW;
switch (imgui_cursor)
{
case ImGuiMouseCursor_Arrow: win32_cursor = IDC_ARROW; break;
case ImGuiMouseCursor_TextInput: win32_cursor = IDC_IBEAM; break;
case ImGuiMouseCursor_ResizeAll: win32_cursor = IDC_SIZEALL; break;
case ImGuiMouseCursor_ResizeEW: win32_cursor = IDC_SIZEWE; break;
case ImGuiMouseCursor_ResizeNS: win32_cursor = IDC_SIZENS; break;
case ImGuiMouseCursor_ResizeNESW: win32_cursor = IDC_SIZENESW; break;
case ImGuiMouseCursor_ResizeNWSE: win32_cursor = IDC_SIZENWSE; break;
case ImGuiMouseCursor_Hand: win32_cursor = IDC_HAND; break;
case ImGuiMouseCursor_NotAllowed: win32_cursor = IDC_NO; break;
}
::SetCursor(::LoadCursor(nullptr, win32_cursor));
}
return true;
}
static bool IsVkDown(int vk)
{
return (::GetKeyState(vk) & 0x8000) != 0;
}
static void ImGui_ImplWin32_AddKeyEvent(ImGuiKey key, bool down, int native_keycode, int native_scancode = -1)
{
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(key, down);
io.SetKeyEventNativeData(key, native_keycode, native_scancode); // To support legacy indexing (<1.87 user code)
IM_UNUSED(native_scancode);
}
static void ImGui_ImplWin32_ProcessKeyEventsWorkarounds()
{
// Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one.
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) && !IsVkDown(VK_LSHIFT))
ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftShift, false, VK_LSHIFT);
if (ImGui::IsKeyDown(ImGuiKey_RightShift) && !IsVkDown(VK_RSHIFT))
ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightShift, false, VK_RSHIFT);
// Sometimes WM_KEYUP for Win key is not passed down to the app (e.g. for Win+V on some setups, according to GLFW).
if (ImGui::IsKeyDown(ImGuiKey_LeftSuper) && !IsVkDown(VK_LWIN))
ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftSuper, false, VK_LWIN);
if (ImGui::IsKeyDown(ImGuiKey_RightSuper) && !IsVkDown(VK_RWIN))
ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightSuper, false, VK_RWIN);
}
static void ImGui_ImplWin32_UpdateKeyModifiers()
{
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(ImGuiMod_Ctrl, IsVkDown(VK_CONTROL));
io.AddKeyEvent(ImGuiMod_Shift, IsVkDown(VK_SHIFT));
io.AddKeyEvent(ImGuiMod_Alt, IsVkDown(VK_MENU));
io.AddKeyEvent(ImGuiMod_Super, IsVkDown(VK_APPS));
}
static void ImGui_ImplWin32_UpdateMouseData()
{
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();
ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(bd->hWnd != 0);
HWND focused_window = ::GetForegroundWindow();
const bool is_app_focused = (focused_window == bd->hWnd);
if (is_app_focused)
{
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
if (io.WantSetMousePos)
{
POINT pos = { (int)io.MousePos.x, (int)io.MousePos.y };
if (::ClientToScreen(bd->hWnd, &pos))
::SetCursorPos(pos.x, pos.y);
}
// (Optional) Fallback to provide mouse position when focused (WM_MOUSEMOVE already provides this when hovered or captured)
// This also fills a short gap when clicking non-client area: WM_NCMOUSELEAVE -> modal OS move -> gap -> WM_NCMOUSEMOVE
if (!io.WantSetMousePos && bd->MouseTrackedArea == 0)
{
POINT pos;
if (::GetCursorPos(&pos) && ::ScreenToClient(bd->hWnd, &pos))
io.AddMousePosEvent((float)pos.x, (float)pos.y);
}
}
}
// Gamepad navigation mapping
static void ImGui_ImplWin32_UpdateGamepads()
{
#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();
//if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
// return;
// Calling XInputGetState() every frame on disconnected gamepads is unfortunately too slow.
// Instead we refresh gamepad availability by calling XInputGetCapabilities() _only_ after receiving WM_DEVICECHANGE.
if (bd->WantUpdateHasGamepad)
{
XINPUT_CAPABILITIES caps = {};
bd->HasGamepad = bd->XInputGetCapabilities ? (bd->XInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS) : false;
bd->WantUpdateHasGamepad = false;
}
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
XINPUT_STATE xinput_state;
XINPUT_GAMEPAD& gamepad = xinput_state.Gamepad;
if (!bd->HasGamepad || bd->XInputGetState == nullptr || bd->XInputGetState(0, &xinput_state) != ERROR_SUCCESS)
return;
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
#define IM_SATURATE(V) (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
#define MAP_BUTTON(KEY_NO, BUTTON_ENUM) { io.AddKeyEvent(KEY_NO, (gamepad.wButtons & BUTTON_ENUM) != 0); }
#define MAP_ANALOG(KEY_NO, VALUE, V0, V1) { float vn = (float)(VALUE - V0) / (float)(V1 - V0); io.AddKeyAnalogEvent(KEY_NO, vn > 0.10f, IM_SATURATE(vn)); }
MAP_BUTTON(ImGuiKey_GamepadStart, XINPUT_GAMEPAD_START);
MAP_BUTTON(ImGuiKey_GamepadBack, XINPUT_GAMEPAD_BACK);
MAP_BUTTON(ImGuiKey_GamepadFaceLeft, XINPUT_GAMEPAD_X);
MAP_BUTTON(ImGuiKey_GamepadFaceRight, XINPUT_GAMEPAD_B);
MAP_BUTTON(ImGuiKey_GamepadFaceUp, XINPUT_GAMEPAD_Y);
MAP_BUTTON(ImGuiKey_GamepadFaceDown, XINPUT_GAMEPAD_A);
MAP_BUTTON(ImGuiKey_GamepadDpadLeft, XINPUT_GAMEPAD_DPAD_LEFT);
MAP_BUTTON(ImGuiKey_GamepadDpadRight, XINPUT_GAMEPAD_DPAD_RIGHT);
MAP_BUTTON(ImGuiKey_GamepadDpadUp, XINPUT_GAMEPAD_DPAD_UP);
MAP_BUTTON(ImGuiKey_GamepadDpadDown, XINPUT_GAMEPAD_DPAD_DOWN);
MAP_BUTTON(ImGuiKey_GamepadL1, XINPUT_GAMEPAD_LEFT_SHOULDER);
MAP_BUTTON(ImGuiKey_GamepadR1, XINPUT_GAMEPAD_RIGHT_SHOULDER);
MAP_ANALOG(ImGuiKey_GamepadL2, gamepad.bLeftTrigger, XINPUT_GAMEPAD_TRIGGER_THRESHOLD, 255);
MAP_ANALOG(ImGuiKey_GamepadR2, gamepad.bRightTrigger, XINPUT_GAMEPAD_TRIGGER_THRESHOLD, 255);
MAP_BUTTON(ImGuiKey_GamepadL3, XINPUT_GAMEPAD_LEFT_THUMB);
MAP_BUTTON(ImGuiKey_GamepadR3, XINPUT_GAMEPAD_RIGHT_THUMB);
MAP_ANALOG(ImGuiKey_GamepadLStickLeft, gamepad.sThumbLX, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
MAP_ANALOG(ImGuiKey_GamepadLStickRight, gamepad.sThumbLX, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
MAP_ANALOG(ImGuiKey_GamepadLStickUp, gamepad.sThumbLY, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
MAP_ANALOG(ImGuiKey_GamepadLStickDown, gamepad.sThumbLY, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
MAP_ANALOG(ImGuiKey_GamepadRStickLeft, gamepad.sThumbRX, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
MAP_ANALOG(ImGuiKey_GamepadRStickRight, gamepad.sThumbRX, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
MAP_ANALOG(ImGuiKey_GamepadRStickUp, gamepad.sThumbRY, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
MAP_ANALOG(ImGuiKey_GamepadRStickDown, gamepad.sThumbRY, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
#undef MAP_BUTTON
#undef MAP_ANALOG
#endif // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
}
void ImGui_ImplWin32_NewFrame()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();
IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplWin32_Init()?");
// Setup display size (every frame to accommodate for window resizing)
RECT rect = { 0, 0, 0, 0 };
::GetClientRect(bd->hWnd, &rect);
io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top));
// Setup time step
INT64 current_time = 0;
::QueryPerformanceCounter((LARGE_INTEGER*)&current_time);
io.DeltaTime = (float)(current_time - bd->Time) / bd->TicksPerSecond;
bd->Time = current_time;
// Update OS mouse position
ImGui_ImplWin32_UpdateMouseData();
// Process workarounds for known Windows key handling issues
ImGui_ImplWin32_ProcessKeyEventsWorkarounds();
// Update OS mouse cursor with the cursor requested by imgui
ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor();
if (bd->LastMouseCursor != mouse_cursor)
{
bd->LastMouseCursor = mouse_cursor;
ImGui_ImplWin32_UpdateMouseCursor();
}
// Update game controllers (if enabled and available)
ImGui_ImplWin32_UpdateGamepads();
}
// There is no distinct VK_xxx for keypad enter, instead it is VK_RETURN + KF_EXTENDED, we assign it an arbitrary value to make code more readable (VK_ codes go up to 255)
#define IM_VK_KEYPAD_ENTER (VK_RETURN + 256)
// Map VK_xxx to ImGuiKey_xxx.
static ImGuiKey ImGui_ImplWin32_VirtualKeyToImGuiKey(WPARAM wParam)
{
switch (wParam)
{
case VK_TAB: return ImGuiKey_Tab;
case VK_LEFT: return ImGuiKey_LeftArrow;
case VK_RIGHT: return ImGuiKey_RightArrow;
case VK_UP: return ImGuiKey_UpArrow;
case VK_DOWN: return ImGuiKey_DownArrow;
case VK_PRIOR: return ImGuiKey_PageUp;
case VK_NEXT: return ImGuiKey_PageDown;
case VK_HOME: return ImGuiKey_Home;
case VK_END: return ImGuiKey_End;
case VK_INSERT: return ImGuiKey_Insert;
case VK_DELETE: return ImGuiKey_Delete;
case VK_BACK: return ImGuiKey_Backspace;
case VK_SPACE: return ImGuiKey_Space;
case VK_RETURN: return ImGuiKey_Enter;
case VK_ESCAPE: return ImGuiKey_Escape;
case VK_OEM_7: return ImGuiKey_Apostrophe;
case VK_OEM_COMMA: return ImGuiKey_Comma;
case VK_OEM_MINUS: return ImGuiKey_Minus;
case VK_OEM_PERIOD: return ImGuiKey_Period;
case VK_OEM_2: return ImGuiKey_Slash;
case VK_OEM_1: return ImGuiKey_Semicolon;
case VK_OEM_PLUS: return ImGuiKey_Equal;
case VK_OEM_4: return ImGuiKey_LeftBracket;
case VK_OEM_5: return ImGuiKey_Backslash;
case VK_OEM_6: return ImGuiKey_RightBracket;
case VK_OEM_3: return ImGuiKey_GraveAccent;
case VK_CAPITAL: return ImGuiKey_CapsLock;
case VK_SCROLL: return ImGuiKey_ScrollLock;
case VK_NUMLOCK: return ImGuiKey_NumLock;
case VK_SNAPSHOT: return ImGuiKey_PrintScreen;
case VK_PAUSE: return ImGuiKey_Pause;
case VK_NUMPAD0: return ImGuiKey_Keypad0;
case VK_NUMPAD1: return ImGuiKey_Keypad1;
case VK_NUMPAD2: return ImGuiKey_Keypad2;
case VK_NUMPAD3: return ImGuiKey_Keypad3;
case VK_NUMPAD4: return ImGuiKey_Keypad4;
case VK_NUMPAD5: return ImGuiKey_Keypad5;
case VK_NUMPAD6: return ImGuiKey_Keypad6;
case VK_NUMPAD7: return ImGuiKey_Keypad7;
case VK_NUMPAD8: return ImGuiKey_Keypad8;
case VK_NUMPAD9: return ImGuiKey_Keypad9;
case VK_DECIMAL: return ImGuiKey_KeypadDecimal;
case VK_DIVIDE: return ImGuiKey_KeypadDivide;
case VK_MULTIPLY: return ImGuiKey_KeypadMultiply;
case VK_SUBTRACT: return ImGuiKey_KeypadSubtract;
case VK_ADD: return ImGuiKey_KeypadAdd;
case IM_VK_KEYPAD_ENTER: return ImGuiKey_KeypadEnter;
case VK_LSHIFT: return ImGuiKey_LeftShift;
case VK_LCONTROL: return ImGuiKey_LeftCtrl;
case VK_LMENU: return ImGuiKey_LeftAlt;
case VK_LWIN: return ImGuiKey_LeftSuper;
case VK_RSHIFT: return ImGuiKey_RightShift;
case VK_RCONTROL: return ImGuiKey_RightCtrl;
case VK_RMENU: return ImGuiKey_RightAlt;
case VK_RWIN: return ImGuiKey_RightSuper;
case VK_APPS: return ImGuiKey_Menu;
case '0': return ImGuiKey_0;
case '1': return ImGuiKey_1;
case '2': return ImGuiKey_2;
case '3': return ImGuiKey_3;
case '4': return ImGuiKey_4;
case '5': return ImGuiKey_5;
case '6': return ImGuiKey_6;
case '7': return ImGuiKey_7;
case '8': return ImGuiKey_8;
case '9': return ImGuiKey_9;
case 'A': return ImGuiKey_A;
case 'B': return ImGuiKey_B;
case 'C': return ImGuiKey_C;
case 'D': return ImGuiKey_D;
case 'E': return ImGuiKey_E;
case 'F': return ImGuiKey_F;
case 'G': return ImGuiKey_G;
case 'H': return ImGuiKey_H;
case 'I': return ImGuiKey_I;
case 'J': return ImGuiKey_J;
case 'K': return ImGuiKey_K;
case 'L': return ImGuiKey_L;
case 'M': return ImGuiKey_M;
case 'N': return ImGuiKey_N;
case 'O': return ImGuiKey_O;
case 'P': return ImGuiKey_P;
case 'Q': return ImGuiKey_Q;
case 'R': return ImGuiKey_R;
case 'S': return ImGuiKey_S;
case 'T': return ImGuiKey_T;
case 'U': return ImGuiKey_U;
case 'V': return ImGuiKey_V;
case 'W': return ImGuiKey_W;
case 'X': return ImGuiKey_X;
case 'Y': return ImGuiKey_Y;
case 'Z': return ImGuiKey_Z;
case VK_F1: return ImGuiKey_F1;
case VK_F2: return ImGuiKey_F2;
case VK_F3: return ImGuiKey_F3;
case VK_F4: return ImGuiKey_F4;
case VK_F5: return ImGuiKey_F5;
case VK_F6: return ImGuiKey_F6;
case VK_F7: return ImGuiKey_F7;
case VK_F8: return ImGuiKey_F8;
case VK_F9: return ImGuiKey_F9;
case VK_F10: return ImGuiKey_F10;
case VK_F11: return ImGuiKey_F11;
case VK_F12: return ImGuiKey_F12;
default: return ImGuiKey_None;
}
}
// Allow compilation with old Windows SDK. MinGW doesn't have default _WIN32_WINNT/WINVER versions.
#ifndef WM_MOUSEHWHEEL
#define WM_MOUSEHWHEEL 0x020E
#endif
#ifndef DBT_DEVNODES_CHANGED
#define DBT_DEVNODES_CHANGED 0x0007
#endif
// Win32 message handler (process Win32 mouse/keyboard inputs, etc.)
// Call from your application's message handler. Keep calling your message handler unless this function returns TRUE.
// When implementing your own backend, you can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if Dear ImGui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to Dear ImGui, and hide them from your application based on those two flags.
// PS: In this Win32 handler, we use the capture API (GetCapture/SetCapture/ReleaseCapture) to be able to read mouse coordinates when dragging mouse outside of our window bounds.
// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag.
#if 0
// Copy this line into your .cpp file to forward declare the function.
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
#endif
IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (ImGui::GetCurrentContext() == nullptr)
return 0;
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();
switch (msg)
{
case WM_MOUSEMOVE:
case WM_NCMOUSEMOVE:
{
// We need to call TrackMouseEvent in order to receive WM_MOUSELEAVE events
const int area = (msg == WM_MOUSEMOVE) ? 1 : 2;
bd->MouseHwnd = hwnd;
if (bd->MouseTrackedArea != area)
{
TRACKMOUSEEVENT tme_cancel = { sizeof(tme_cancel), TME_CANCEL, hwnd, 0 };
TRACKMOUSEEVENT tme_track = { sizeof(tme_track), (DWORD)((area == 2) ? (TME_LEAVE | TME_NONCLIENT) : TME_LEAVE), hwnd, 0 };
if (bd->MouseTrackedArea != 0)
::TrackMouseEvent(&tme_cancel);
::TrackMouseEvent(&tme_track);
bd->MouseTrackedArea = area;
}
POINT mouse_pos = { (LONG)GET_X_LPARAM(lParam), (LONG)GET_Y_LPARAM(lParam) };
if (msg == WM_NCMOUSEMOVE && ::ScreenToClient(hwnd, &mouse_pos) == FALSE) // WM_NCMOUSEMOVE are provided in absolute coordinates.
break;
io.AddMousePosEvent((float)mouse_pos.x, (float)mouse_pos.y);
break;
}
case WM_MOUSELEAVE:
case WM_NCMOUSELEAVE:
{
const int area = (msg == WM_MOUSELEAVE) ? 1 : 2;
if (bd->MouseTrackedArea == area)
{
if (bd->MouseHwnd == hwnd)
bd->MouseHwnd = nullptr;
bd->MouseTrackedArea = 0;
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
}
break;
}
case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK:
case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK:
case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK:
case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK:
{
int button = 0;
if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK) { button = 0; }
if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { button = 1; }
if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; }
if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; }
if (bd->MouseButtonsDown == 0 && ::GetCapture() == nullptr)
::SetCapture(hwnd);
bd->MouseButtonsDown |= 1 << button;
io.AddMouseButtonEvent(button, true);
return 0;
}
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_XBUTTONUP:
{
int button = 0;
if (msg == WM_LBUTTONUP) { button = 0; }
if (msg == WM_RBUTTONUP) { button = 1; }
if (msg == WM_MBUTTONUP) { button = 2; }
if (msg == WM_XBUTTONUP) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; }
bd->MouseButtonsDown &= ~(1 << button);
if (bd->MouseButtonsDown == 0 && ::GetCapture() == hwnd)
::ReleaseCapture();
io.AddMouseButtonEvent(button, false);
return 0;
}
case WM_MOUSEWHEEL:
io.AddMouseWheelEvent(0.0f, (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA);
return 0;
case WM_MOUSEHWHEEL:
io.AddMouseWheelEvent(-(float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0.0f);
return 0;
case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
{
const bool is_key_down = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);
if (wParam < 256)
{
// Submit modifiers
ImGui_ImplWin32_UpdateKeyModifiers();
// Obtain virtual key code
// (keypad enter doesn't have its own... VK_RETURN with KF_EXTENDED flag means keypad enter, see IM_VK_KEYPAD_ENTER definition for details, it is mapped to ImGuiKey_KeyPadEnter.)
int vk = (int)wParam;
if ((wParam == VK_RETURN) && (HIWORD(lParam) & KF_EXTENDED))
vk = IM_VK_KEYPAD_ENTER;
// Submit key event
const ImGuiKey key = ImGui_ImplWin32_VirtualKeyToImGuiKey(vk);
const int scancode = (int)LOBYTE(HIWORD(lParam));
if (key != ImGuiKey_None)
ImGui_ImplWin32_AddKeyEvent(key, is_key_down, vk, scancode);
// Submit individual left/right modifier events
if (vk == VK_SHIFT)
{
// Important: Shift keys tend to get stuck when pressed together, missing key-up events are corrected in ImGui_ImplWin32_ProcessKeyEventsWorkarounds()
if (IsVkDown(VK_LSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftShift, is_key_down, VK_LSHIFT, scancode); }
if (IsVkDown(VK_RSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightShift, is_key_down, VK_RSHIFT, scancode); }
}
else if (vk == VK_CONTROL)
{
if (IsVkDown(VK_LCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftCtrl, is_key_down, VK_LCONTROL, scancode); }
if (IsVkDown(VK_RCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightCtrl, is_key_down, VK_RCONTROL, scancode); }
}
else if (vk == VK_MENU)
{
if (IsVkDown(VK_LMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftAlt, is_key_down, VK_LMENU, scancode); }
if (IsVkDown(VK_RMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightAlt, is_key_down, VK_RMENU, scancode); }
}
}
return 0;
}
case WM_SETFOCUS:
case WM_KILLFOCUS:
io.AddFocusEvent(msg == WM_SETFOCUS);
return 0;
case WM_CHAR:
if (::IsWindowUnicode(hwnd))
{
// You can also use ToAscii()+GetKeyboardState() to retrieve characters.
if (wParam > 0 && wParam < 0x10000)
io.AddInputCharacterUTF16((unsigned short)wParam);
}
else
{
wchar_t wch = 0;
::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (char*)&wParam, 1, &wch, 1);
io.AddInputCharacter(wch);
}
return 0;
case WM_SETCURSOR:
// This is required to restore cursor when transitioning from e.g resize borders to client area.
if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor())
return 1;
return 0;
case WM_DEVICECHANGE:
#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
if ((UINT)wParam == DBT_DEVNODES_CHANGED)
bd->WantUpdateHasGamepad = true;
#endif
return 0;
}
return 0;
}
//--------------------------------------------------------------------------------------------------------
// DPI-related helpers (optional)
//--------------------------------------------------------------------------------------------------------
// - Use to enable DPI awareness without having to create an application manifest.
// - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps.
// - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc.
// but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime,
// neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies.
//---------------------------------------------------------------------------------------------------------
// This is the scheme successfully used by GLFW (from which we borrowed some of the code) and other apps aiming to be highly portable.
// ImGui_ImplWin32_EnableDpiAwareness() is just a helper called by main.cpp, we don't call it automatically.
// If you are trying to implement your own backend for your own engine, you may ignore that noise.
//---------------------------------------------------------------------------------------------------------
// Perform our own check with RtlVerifyVersionInfo() instead of using functions from <VersionHelpers.h> as they
// require a manifest to be functional for checks above 8.1. See https://github.com/ocornut/imgui/issues/4200
static BOOL _IsWindowsVersionOrGreater(WORD major, WORD minor, WORD)
{
typedef LONG(WINAPI* PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*, ULONG, ULONGLONG);
static PFN_RtlVerifyVersionInfo RtlVerifyVersionInfoFn = nullptr;
if (RtlVerifyVersionInfoFn == nullptr)
if (HMODULE ntdllModule = ::GetModuleHandleA("ntdll.dll"))
RtlVerifyVersionInfoFn = (PFN_RtlVerifyVersionInfo)GetProcAddress(ntdllModule, "RtlVerifyVersionInfo");
if (RtlVerifyVersionInfoFn == nullptr)
return FALSE;
RTL_OSVERSIONINFOEXW versionInfo = { };
ULONGLONG conditionMask = 0;
versionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);
versionInfo.dwMajorVersion = major;
versionInfo.dwMinorVersion = minor;
VER_SET_CONDITION(conditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(conditionMask, VER_MINORVERSION, VER_GREATER_EQUAL);
return (RtlVerifyVersionInfoFn(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask) == 0) ? TRUE : FALSE;
}
#define _IsWindowsVistaOrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0600), LOBYTE(0x0600), 0) // _WIN32_WINNT_VISTA
#define _IsWindows8OrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0602), LOBYTE(0x0602), 0) // _WIN32_WINNT_WIN8
#define _IsWindows8Point1OrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0603), LOBYTE(0x0603), 0) // _WIN32_WINNT_WINBLUE
#define _IsWindows10OrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0A00), LOBYTE(0x0A00), 0) // _WIN32_WINNT_WINTHRESHOLD / _WIN32_WINNT_WIN10
#ifndef DPI_ENUMS_DECLARED
typedef enum { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS;
typedef enum { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI } MONITOR_DPI_TYPE;
#endif
#ifndef _DPI_AWARENESS_CONTEXTS_
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE (DPI_AWARENESS_CONTEXT)-3
#endif
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 (DPI_AWARENESS_CONTEXT)-4
#endif
typedef HRESULT(WINAPI* PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); // Shcore.lib + dll, Windows 8.1+
typedef HRESULT(WINAPI* PFN_GetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); // Shcore.lib + dll, Windows 8.1+
typedef DPI_AWARENESS_CONTEXT(WINAPI* PFN_SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); // User32.lib + dll, Windows 10 v1607+ (Creators Update)
// Helper function to enable DPI awareness without setting up a manifest
void ImGui_ImplWin32_EnableDpiAwareness()
{
if (_IsWindows10OrGreater())
{
static HINSTANCE user32_dll = ::LoadLibraryA("user32.dll"); // Reference counted per-process
if (PFN_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContextFn = (PFN_SetThreadDpiAwarenessContext)::GetProcAddress(user32_dll, "SetThreadDpiAwarenessContext"))
{
SetThreadDpiAwarenessContextFn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
return;
}
}
if (_IsWindows8Point1OrGreater())
{
static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process
if (PFN_SetProcessDpiAwareness SetProcessDpiAwarenessFn = (PFN_SetProcessDpiAwareness)::GetProcAddress(shcore_dll, "SetProcessDpiAwareness"))
{
SetProcessDpiAwarenessFn(PROCESS_PER_MONITOR_DPI_AWARE);
return;
}
}
#if _WIN32_WINNT >= 0x0600
::SetProcessDPIAware();
#endif
}
#if defined(_MSC_VER) && !defined(NOGDI)
#pragma comment(lib, "gdi32") // Link with gdi32.lib for GetDeviceCaps(). MinGW will require linking with '-lgdi32'
#endif
float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor)
{
UINT xdpi = 96, ydpi = 96;
if (_IsWindows8Point1OrGreater())
{
static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process
static PFN_GetDpiForMonitor GetDpiForMonitorFn = nullptr;
if (GetDpiForMonitorFn == nullptr && shcore_dll != nullptr)
GetDpiForMonitorFn = (PFN_GetDpiForMonitor)::GetProcAddress(shcore_dll, "GetDpiForMonitor");
if (GetDpiForMonitorFn != nullptr)
{
GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi);
IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert!
return xdpi / 96.0f;
}
}
#ifndef NOGDI
const HDC dc = ::GetDC(nullptr);
xdpi = ::GetDeviceCaps(dc, LOGPIXELSX);
ydpi = ::GetDeviceCaps(dc, LOGPIXELSY);
IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert!
::ReleaseDC(nullptr, dc);
#endif
return xdpi / 96.0f;
}
float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd)
{
HMONITOR monitor = ::MonitorFromWindow((HWND)hwnd, MONITOR_DEFAULTTONEAREST);
return ImGui_ImplWin32_GetDpiScaleForMonitor(monitor);
}
//---------------------------------------------------------------------------------------------------------
// Transparency related helpers (optional)
//--------------------------------------------------------------------------------------------------------
#if defined(_MSC_VER)
#pragma comment(lib, "dwmapi") // Link with dwmapi.lib. MinGW will require linking with '-ldwmapi'
#endif
// [experimental]
// Borrowed from GLFW's function updateFramebufferTransparency() in src/win32_window.c
// (the Dwm* functions are Vista era functions but we are borrowing logic from GLFW)
void ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd)
{
if (!_IsWindowsVistaOrGreater())
return;
BOOL composition;
if (FAILED(::DwmIsCompositionEnabled(&composition)) || !composition)
return;
BOOL opaque;
DWORD color;
if (_IsWindows8OrGreater() || (SUCCEEDED(::DwmGetColorizationColor(&color, &opaque)) && !opaque))
{
HRGN region = ::CreateRectRgn(0, 0, -1, -1);
DWM_BLURBEHIND bb = {};
bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
bb.hRgnBlur = region;
bb.fEnable = TRUE;
::DwmEnableBlurBehindWindow((HWND)hwnd, &bb);
::DeleteObject(region);
}
else
{
DWM_BLURBEHIND bb = {};
bb.dwFlags = DWM_BB_ENABLE;
::DwmEnableBlurBehindWindow((HWND)hwnd, &bb);
}
}
//---------------------------------------------------------------------------------------------------------

View File

@ -0,0 +1,44 @@
// dear imgui: Platform Backend for Windows (standard windows API for 32-bits AND 64-bits applications)
// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
// Implemented features:
// [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui)
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void* hwnd);
IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown();
IMGUI_IMPL_API void ImGui_ImplWin32_NewFrame();
// Win32 message handler your application need to call.
// - Intentionally commented out in a '#if 0' block to avoid dragging dependencies on <windows.h> from this helper.
// - You should COPY the line below into your .cpp code to forward declare the function and then you can call it.
// - Call from your application's message handler. Keep calling your message handler unless this function returns TRUE.
#if 0
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
#endif
// DPI-related helpers (optional)
// - Use to enable DPI awareness without having to create an application manifest.
// - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps.
// - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc.
// but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime,
// neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies.
IMGUI_IMPL_API void ImGui_ImplWin32_EnableDpiAwareness();
IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd); // HWND hwnd
IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor); // HMONITOR monitor
// Transparency related helpers (optional) [experimental]
// - Use to enable alpha compositing transparency with the desktop.
// - Use together with e.g. clearing your framebuffer with zero-alpha.
IMGUI_IMPL_API void ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd); // HWND hwnd

3264
src/imgui/imgui_internal.h Normal file

File diff suppressed because it is too large Load Diff

4117
src/imgui/imgui_tables.cpp Normal file

File diff suppressed because it is too large Load Diff

8555
src/imgui/imgui_widgets.cpp Normal file

File diff suppressed because it is too large Load Diff

627
src/imgui/imstb_rectpack.h Normal file
View File

@ -0,0 +1,627 @@
// [DEAR IMGUI]
// This is a slightly modified version of stb_rect_pack.h 1.01.
// Grep for [DEAR IMGUI] to find the changes.
//
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
// Sean Barrett 2014
//
// Useful for e.g. packing rectangular textures into an atlas.
// Does not do rotation.
//
// Before #including,
//
// #define STB_RECT_PACK_IMPLEMENTATION
//
// in the file that you want to have the implementation.
//
// Not necessarily the awesomest packing method, but better than
// the totally naive one in stb_truetype (which is primarily what
// this is meant to replace).
//
// Has only had a few tests run, may have issues.
//
// More docs to come.
//
// No memory allocations; uses qsort() and assert() from stdlib.
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
//
// This library currently uses the Skyline Bottom-Left algorithm.
//
// Please note: better rectangle packers are welcome! Please
// implement them to the same API, but with a different init
// function.
//
// Credits
//
// Library
// Sean Barrett
// Minor features
// Martins Mozeiko
// github:IntellectualKitty
//
// Bugfixes / warning fixes
// Jeremy Jaussaud
// Fabian Giesen
//
// Version history:
//
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
// 0.99 (2019-02-07) warning fixes
// 0.11 (2017-03-03) return packing success/fail result
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
// 0.09 (2016-08-27) fix compiler warnings
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
// 0.05: added STBRP_ASSERT to allow replacing assert
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
// 0.01: initial release
//
// LICENSE
//
// See end of file for license information.
//////////////////////////////////////////////////////////////////////////////
//
// INCLUDE SECTION
//
#ifndef STB_INCLUDE_STB_RECT_PACK_H
#define STB_INCLUDE_STB_RECT_PACK_H
#define STB_RECT_PACK_VERSION 1
#ifdef STBRP_STATIC
#define STBRP_DEF static
#else
#define STBRP_DEF extern
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct stbrp_context stbrp_context;
typedef struct stbrp_node stbrp_node;
typedef struct stbrp_rect stbrp_rect;
typedef int stbrp_coord;
#define STBRP__MAXVAL 0x7fffffff
// Mostly for internal use, but this is the maximum supported coordinate value.
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
// Assign packed locations to rectangles. The rectangles are of type
// 'stbrp_rect' defined below, stored in the array 'rects', and there
// are 'num_rects' many of them.
//
// Rectangles which are successfully packed have the 'was_packed' flag
// set to a non-zero value and 'x' and 'y' store the minimum location
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
// if you imagine y increasing downwards). Rectangles which do not fit
// have the 'was_packed' flag set to 0.
//
// You should not try to access the 'rects' array from another thread
// while this function is running, as the function temporarily reorders
// the array while it executes.
//
// To pack into another rectangle, you need to call stbrp_init_target
// again. To continue packing into the same rectangle, you can call
// this function again. Calling this multiple times with multiple rect
// arrays will probably produce worse packing results than calling it
// a single time with the full rectangle array, but the option is
// available.
//
// The function returns 1 if all of the rectangles were successfully
// packed and 0 otherwise.
struct stbrp_rect
{
// reserved for your use:
int id;
// input:
stbrp_coord w, h;
// output:
stbrp_coord x, y;
int was_packed; // non-zero if valid packing
}; // 16 bytes, nominally
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
// Initialize a rectangle packer to:
// pack a rectangle that is 'width' by 'height' in dimensions
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
//
// You must call this function every time you start packing into a new target.
//
// There is no "shutdown" function. The 'nodes' memory must stay valid for
// the following stbrp_pack_rects() call (or calls), but can be freed after
// the call (or calls) finish.
//
// Note: to guarantee best results, either:
// 1. make sure 'num_nodes' >= 'width'
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
//
// If you don't do either of the above things, widths will be quantized to multiples
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
//
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
// may run out of temporary storage and be unable to pack some rectangles.
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
// Optionally call this function after init but before doing any packing to
// change the handling of the out-of-temp-memory scenario, described above.
// If you call init again, this will be reset to the default (false).
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
// Optionally select which packing heuristic the library should use. Different
// heuristics will produce better/worse results for different data sets.
// If you call init again, this will be reset to the default.
enum
{
STBRP_HEURISTIC_Skyline_default=0,
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
STBRP_HEURISTIC_Skyline_BF_sortHeight
};
//////////////////////////////////////////////////////////////////////////////
//
// the details of the following structures don't matter to you, but they must
// be visible so you can handle the memory allocations for them
struct stbrp_node
{
stbrp_coord x,y;
stbrp_node *next;
};
struct stbrp_context
{
int width;
int height;
int align;
int init_mode;
int heuristic;
int num_nodes;
stbrp_node *active_head;
stbrp_node *free_head;
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
};
#ifdef __cplusplus
}
#endif
#endif
//////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION SECTION
//
#ifdef STB_RECT_PACK_IMPLEMENTATION
#ifndef STBRP_SORT
#include <stdlib.h>
#define STBRP_SORT qsort
#endif
#ifndef STBRP_ASSERT
#include <assert.h>
#define STBRP_ASSERT assert
#endif
#ifdef _MSC_VER
#define STBRP__NOTUSED(v) (void)(v)
#define STBRP__CDECL __cdecl
#else
#define STBRP__NOTUSED(v) (void)sizeof(v)
#define STBRP__CDECL
#endif
enum
{
STBRP__INIT_skyline = 1
};
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
{
switch (context->init_mode) {
case STBRP__INIT_skyline:
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
context->heuristic = heuristic;
break;
default:
STBRP_ASSERT(0);
}
}
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
{
if (allow_out_of_mem)
// if it's ok to run out of memory, then don't bother aligning them;
// this gives better packing, but may fail due to OOM (even though
// the rectangles easily fit). @TODO a smarter approach would be to only
// quantize once we've hit OOM, then we could get rid of this parameter.
context->align = 1;
else {
// if it's not ok to run out of memory, then quantize the widths
// so that num_nodes is always enough nodes.
//
// I.e. num_nodes * align >= width
// align >= width / num_nodes
// align = ceil(width/num_nodes)
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
}
}
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
{
int i;
for (i=0; i < num_nodes-1; ++i)
nodes[i].next = &nodes[i+1];
nodes[i].next = NULL;
context->init_mode = STBRP__INIT_skyline;
context->heuristic = STBRP_HEURISTIC_Skyline_default;
context->free_head = &nodes[0];
context->active_head = &context->extra[0];
context->width = width;
context->height = height;
context->num_nodes = num_nodes;
stbrp_setup_allow_out_of_mem(context, 0);
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
context->extra[0].x = 0;
context->extra[0].y = 0;
context->extra[0].next = &context->extra[1];
context->extra[1].x = (stbrp_coord) width;
context->extra[1].y = (1<<30);
context->extra[1].next = NULL;
}
// find minimum y position if it starts at x1
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
{
stbrp_node *node = first;
int x1 = x0 + width;
int min_y, visited_width, waste_area;
STBRP__NOTUSED(c);
STBRP_ASSERT(first->x <= x0);
#if 0
// skip in case we're past the node
while (node->next->x <= x0)
++node;
#else
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
#endif
STBRP_ASSERT(node->x <= x0);
min_y = 0;
waste_area = 0;
visited_width = 0;
while (node->x < x1) {
if (node->y > min_y) {
// raise min_y higher.
// we've accounted for all waste up to min_y,
// but we'll now add more waste for everything we've visted
waste_area += visited_width * (node->y - min_y);
min_y = node->y;
// the first time through, visited_width might be reduced
if (node->x < x0)
visited_width += node->next->x - x0;
else
visited_width += node->next->x - node->x;
} else {
// add waste area
int under_width = node->next->x - node->x;
if (under_width + visited_width > width)
under_width = width - visited_width;
waste_area += under_width * (min_y - node->y);
visited_width += under_width;
}
node = node->next;
}
*pwaste = waste_area;
return min_y;
}
typedef struct
{
int x,y;
stbrp_node **prev_link;
} stbrp__findresult;
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
{
int best_waste = (1<<30), best_x, best_y = (1 << 30);
stbrp__findresult fr;
stbrp_node **prev, *node, *tail, **best = NULL;
// align to multiple of c->align
width = (width + c->align - 1);
width -= width % c->align;
STBRP_ASSERT(width % c->align == 0);
// if it can't possibly fit, bail immediately
if (width > c->width || height > c->height) {
fr.prev_link = NULL;
fr.x = fr.y = 0;
return fr;
}
node = c->active_head;
prev = &c->active_head;
while (node->x + width <= c->width) {
int y,waste;
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
// bottom left
if (y < best_y) {
best_y = y;
best = prev;
}
} else {
// best-fit
if (y + height <= c->height) {
// can only use it if it first vertically
if (y < best_y || (y == best_y && waste < best_waste)) {
best_y = y;
best_waste = waste;
best = prev;
}
}
}
prev = &node->next;
node = node->next;
}
best_x = (best == NULL) ? 0 : (*best)->x;
// if doing best-fit (BF), we also have to try aligning right edge to each node position
//
// e.g, if fitting
//
// ____________________
// |____________________|
//
// into
//
// | |
// | ____________|
// |____________|
//
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
//
// This makes BF take about 2x the time
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
tail = c->active_head;
node = c->active_head;
prev = &c->active_head;
// find first node that's admissible
while (tail->x < width)
tail = tail->next;
while (tail) {
int xpos = tail->x - width;
int y,waste;
STBRP_ASSERT(xpos >= 0);
// find the left position that matches this
while (node->next->x <= xpos) {
prev = &node->next;
node = node->next;
}
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
if (y + height <= c->height) {
if (y <= best_y) {
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
best_x = xpos;
//STBRP_ASSERT(y <= best_y); [DEAR IMGUI]
best_y = y;
best_waste = waste;
best = prev;
}
}
}
tail = tail->next;
}
}
fr.prev_link = best;
fr.x = best_x;
fr.y = best_y;
return fr;
}
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
{
// find best position according to heuristic
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
stbrp_node *node, *cur;
// bail if:
// 1. it failed
// 2. the best node doesn't fit (we don't always check this)
// 3. we're out of memory
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
res.prev_link = NULL;
return res;
}
// on success, create new node
node = context->free_head;
node->x = (stbrp_coord) res.x;
node->y = (stbrp_coord) (res.y + height);
context->free_head = node->next;
// insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be
// stiched back in
cur = *res.prev_link;
if (cur->x < res.x) {
// preserve the existing one, so start testing with the next one
stbrp_node *next = cur->next;
cur->next = node;
cur = next;
} else {
*res.prev_link = node;
}
// from here, traverse cur and free the nodes, until we get to one
// that shouldn't be freed
while (cur->next && cur->next->x <= res.x + width) {
stbrp_node *next = cur->next;
// move the current node to the free list
cur->next = context->free_head;
context->free_head = cur;
cur = next;
}
// stitch the list back in
node->next = cur;
if (cur->x < res.x + width)
cur->x = (stbrp_coord) (res.x + width);
#ifdef _DEBUG
cur = context->active_head;
while (cur->x < context->width) {
STBRP_ASSERT(cur->x < cur->next->x);
cur = cur->next;
}
STBRP_ASSERT(cur->next == NULL);
{
int count=0;
cur = context->active_head;
while (cur) {
cur = cur->next;
++count;
}
cur = context->free_head;
while (cur) {
cur = cur->next;
++count;
}
STBRP_ASSERT(count == context->num_nodes+2);
}
#endif
return res;
}
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
if (p->h > q->h)
return -1;
if (p->h < q->h)
return 1;
return (p->w > q->w) ? -1 : (p->w < q->w);
}
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
}
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
{
int i, all_rects_packed = 1;
// we use the 'was_packed' field internally to allow sorting/unsorting
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = i;
}
// sort according to heuristic
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
for (i=0; i < num_rects; ++i) {
if (rects[i].w == 0 || rects[i].h == 0) {
rects[i].x = rects[i].y = 0; // empty rect needs no space
} else {
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
if (fr.prev_link) {
rects[i].x = (stbrp_coord) fr.x;
rects[i].y = (stbrp_coord) fr.y;
} else {
rects[i].x = rects[i].y = STBRP__MAXVAL;
}
}
}
// unsort
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
// set was_packed flags and all_rects_packed status
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
if (!rects[i].was_packed)
all_rects_packed = 0;
}
// return the all_rects_packed status
return all_rects_packed;
}
#endif
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/

1437
src/imgui/imstb_textedit.h Normal file

File diff suppressed because it is too large Load Diff

5085
src/imgui/imstb_truetype.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -23,8 +23,10 @@ std::filesystem::path ConfigJson = gakumasLocalPath / "localizationConfig.json";
bool g_has_config_file = false;
bool g_enable_console = true;
bool g_confirm_every_times = false;
std::vector<std::string> g_pluginPath{};
bool g_useRemoteAssets = false;
bool g_useAPIAssets = false;
std::string g_remoteResourceUrl = "";
std::string g_useAPIAssetsURL = "";
namespace
{
@ -47,30 +49,43 @@ namespace
}
}
namespace
{
void readProgramConfig()
void readProgramConfig() {
std::vector<std::string> dicts{};
std::ifstream config_stream{ ProgramConfigJson };
if (!config_stream.is_open())
return;
rapidjson::IStreamWrapper wrapper{ config_stream };
rapidjson::Document document;
document.ParseStream(wrapper);
if (!document.HasParseError())
{
std::vector<std::string> dicts{};
std::ifstream config_stream{ ProgramConfigJson };
if (!config_stream.is_open())
return;
rapidjson::IStreamWrapper wrapper{ config_stream };
rapidjson::Document document;
document.ParseStream(wrapper);
if (!document.HasParseError())
{
g_has_config_file = true;
if (document.HasMember("enableConsole")) {
g_enable_console = document["enableConsole"].GetBool();
}
g_has_config_file = true;
if (document.HasMember("enableConsole")) {
g_enable_console = document["enableConsole"].GetBool();
}
config_stream.close();
if (document.HasMember("useRemoteAssets")) {
g_useRemoteAssets = document["useRemoteAssets"].GetBool();
}
if (document.HasMember("transRemoteZipUrl")) {
g_remoteResourceUrl = document["transRemoteZipUrl"].GetString();
}
if (document.HasMember("useAPIAssets")) {
g_useAPIAssets = document["useAPIAssets"].GetBool();
}
if (document.HasMember("useAPIAssetsURL")) {
g_useAPIAssetsURL = document["useAPIAssetsURL"].GetString();
}
}
config_stream.close();
}
LONG WINAPI AddressExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) {

View File

@ -0,0 +1,224 @@
#include "stdinclude.hpp"
#include "cpprest/http_client.h"
#include "cpprest/filestream.h"
#include "nlohmann/json.hpp"
#include "GakumasLocalify/Log.h"
#include "gkmsGUI/GUII18n.hpp"
#include <format>
#include "unzip.hpp"
extern std::filesystem::path gakumasLocalPath;
extern std::filesystem::path ProgramConfigJson;
extern bool downloading;
extern float downloadProgress;
extern std::function<void()> g_reload_all_data;
std::string resourceVersionCache = "";
namespace GkmsResourceUpdate {
void saveProgramConfig() {
nlohmann::json config;
config["enableConsole"] = g_enable_console;
config["useRemoteAssets"] = g_useRemoteAssets;
config["transRemoteZipUrl"] = g_remoteResourceUrl;
config["useAPIAssets"] = g_useAPIAssets;
config["useAPIAssetsURL"] = g_useAPIAssetsURL;
std::ofstream out(ProgramConfigJson);
if (!out) {
GakumasLocal::Log::ErrorFmt("SaveProgramConfig error: Cannot open file: %s", ProgramConfigJson.c_str());
return;
}
out << config.dump(4);
GakumasLocal::Log::Info("SaveProgramConfig success");
}
web::http::http_response send_get(std::string url, int timeout) {
web::http::client::http_client_config cfg;
cfg.set_timeout(utility::seconds(30));
web::http::client::http_client client(utility::conversions::to_utf16string(url), cfg);
return client.request(web::http::methods::GET).get();
}
bool DownloadFile(const std::string& url, const std::string& outputPath) {
using namespace utility;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace concurrency::streams;
try {
// 打开输出文件流(同步方式)
auto outTask = fstream::open_ostream(conversions::to_string_t(outputPath));
outTask.wait();
auto fileStream = outTask.get();
// 创建 HTTP 客户端,注意:如果 url 包含完整路径cpprestsdk 会自动解析
http_client client(conversions::to_string_t(url));
downloading = true;
downloadProgress = 0.0f;
// 发起 GET 请求
auto responseTask = client.request(methods::GET);
responseTask.wait();
http_response response = responseTask.get();
if (response.status_code() != status_codes::OK) {
downloading = false;
GakumasLocal::Log::ErrorFmt("DownloadFile error: %d", response.status_code());
return false;
}
// 获取响应头中的文件大小(如果存在)
uint64_t contentLength = 0;
if (response.headers().has(L"Content-Length"))
contentLength = std::stoull(conversions::to_utf8string(response.headers().find(L"Content-Length")->second));
// 读取响应体,逐块写入文件,同时更新进度
auto inStream = response.body();
const size_t bufferSize = 8192;
// std::vector<unsigned char> buffer(bufferSize);
size_t totalDownloaded = 0;
while (true) {
auto readTask = inStream.read(fileStream.streambuf(), bufferSize);
readTask.wait();
size_t bytesRead = readTask.get();
if (bytesRead == 0)
break;
totalDownloaded += bytesRead;
if (contentLength > 0)
downloadProgress = static_cast<float>(totalDownloaded) / static_cast<float>(contentLength);
}
fileStream.close().wait();
downloading = false;
return true;
}
catch (const std::exception& e) {
downloading = false;
GakumasLocal::Log::ErrorFmt("DownloadFile error: %s", e.what());
return false;
}
}
std::string GetCurrentResourceVersion(bool useCache) {
if (useCache) {
if (!resourceVersionCache.empty()) {
return resourceVersionCache;
}
}
auto resourceVersionFile = gakumasLocalPath / "version.txt";
std::ifstream file(resourceVersionFile);
if (!file) {
// GakumasLocal::Log::ErrorFmt("Can't open file: %s", resourceVersionFile.string().c_str());
return "Unknown";
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
// 去除首尾空格和换行符
auto is_not_space = [](unsigned char ch) {
return !std::isspace(ch);
};
// 去除前导空白
content.erase(content.begin(), std::find_if(content.begin(), content.end(), is_not_space));
// 去除尾部空白
content.erase(std::find_if(content.rbegin(), content.rend(), is_not_space).base(), content.end());
resourceVersionCache = content;
return content;
}
bool unzipFileFromURL(std::string downloadUrl, const std::string& unzipPath) {
std::string tempZipFile = (gakumasLocalPath / "temp_download.zip").string();
if (!DownloadFile(downloadUrl, tempZipFile)) {
GakumasLocal::Log::Error("Download zip file failed.");
return false;
}
if (!UnzipFile(tempZipFile, unzipPath)) {
GakumasLocal::Log::Error("Unzip file failed.");
return false;
}
return true;
}
void CheckUpdateFromAPI(bool isManual) {
std::thread([&isManual]() {
try {
if (!g_useAPIAssets) {
return;
}
GakumasLocal::Log::Info("Checking update from API...");
auto response = send_get(g_useAPIAssetsURL, 30);
if (response.status_code() != 200) {
GakumasLocal::Log::ErrorFmt("Failed to check update from API: %d\n", response.status_code());
return;
}
auto data = nlohmann::json::parse(response.extract_utf8string().get());
std::string remoteVersion = data["tag_name"];
const auto localVersion = GetCurrentResourceVersion(false);
if (localVersion == remoteVersion) {
if (isManual) {
auto check = MessageBoxA(NULL, GkmsGUII18n::ts("local_file_already_latest"), "Check Update", MB_OKCANCEL);
if (check != IDOK) {
return;
}
}
else {
return;
}
}
std::string description = data["body"];
auto check = MessageBoxW(NULL, std::format(L"{} -> {}\n\n{}", utility::conversions::to_string_t(localVersion),
utility::conversions::to_string_t(remoteVersion), utility::conversions::to_string_t(description)).c_str(),
L"Resource Update", MB_OKCANCEL);
if (check != IDOK) {
return;
}
if (!data.contains("assets") || !data["assets"].is_array()) {
GakumasLocal::Log::Error("API response doesn't contain assets array.");
return;
}
for (const auto& asset : data["assets"]) {
if (!asset.contains("name") || !asset.contains("browser_download_url"))
continue;
std::string name = asset["name"];
if (name.ends_with(".zip")) {
std::string downloadUrl = asset["browser_download_url"];
if (unzipFileFromURL(downloadUrl, gakumasLocalPath.string())) {
g_reload_all_data();
GakumasLocal::Log::Info("Update completed.");
}
// 仅解压一个文件
return;
}
}
GakumasLocal::Log::Error("No .zip file found.");
}
catch (std::exception& e) {
GakumasLocal::Log::ErrorFmt("Exception occurred in CheckUpdateFromAPI: %s\n", e.what());
}
}).detach();
}
void checkUpdateFromURL(std::string downloadUrl) {
std::thread([&downloadUrl]() {
if (unzipFileFromURL(downloadUrl, gakumasLocalPath.string())) {
g_reload_all_data();
GakumasLocal::Log::Info("Update completed.");
}
}).detach();
}
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <string>
namespace GkmsResourceUpdate {
void saveProgramConfig();
std::string GetCurrentResourceVersion(bool useCache);
void CheckUpdateFromAPI(bool isManual);
void checkUpdateFromURL(std::string downloadUrl);
}

View File

@ -0,0 +1,149 @@
#pragma once
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <errno.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#endif
#include "minizip/unzip.h"
#include "GakumasLocalify/Log.h"
// 辅助函数:递归创建目录
static bool CreateDirectoryRecursively(const std::string& dir) {
if (dir.empty())
return false;
// 尝试创建目录
#ifdef _WIN32
int ret = _mkdir(dir.c_str());
#else
int ret = mkdir(dir.c_str(), 0755);
#endif
if (ret == 0 || errno == EEXIST)
return true;
// 如果创建失败,尝试先创建父目录
size_t pos = dir.find_last_of("/\\");
if (pos != std::string::npos) {
std::string parentDir = dir.substr(0, pos);
if (!CreateDirectoryRecursively(parentDir))
return false;
#ifdef _WIN32
ret = _mkdir(dir.c_str());
#else
ret = mkdir(dir.c_str(), 0755);
#endif
return (ret == 0 || errno == EEXIST);
}
return false;
}
// 解压缩函数
static bool UnzipFile(const std::string& zipPath, const std::string& destinationFolder) {
// 打开zip文件
unzFile zipfile = unzOpen(zipPath.c_str());
if (zipfile == nullptr) {
GakumasLocal::Log::ErrorFmt( "Can't open zip file: %s", zipPath.c_str());
return false;
}
int ret = unzGoToFirstFile(zipfile);
if (ret != UNZ_OK) {
GakumasLocal::Log::ErrorFmt("Can't read first file %s", zipPath.c_str());
unzClose(zipfile);
return false;
}
// 遍历zip内的每个文件
do {
char filename[512] = { 0 };
unz_file_info fileInfo;
ret = unzGetCurrentFileInfo(zipfile, &fileInfo,
filename, sizeof(filename),
nullptr, 0, nullptr, 0);
if (ret != UNZ_OK) {
GakumasLocal::Log::ErrorFmt("Read ZIP File Info Error");
unzClose(zipfile);
return false;
}
std::string filePath = filename;
std::string fullPath = destinationFolder;
// 保证目标目录以路径分隔符结尾
if (fullPath.back() != '/' && fullPath.back() != '\\') {
fullPath += "/";
}
fullPath += filePath;
// 判断是否为目录(目录条目通常以'/'结尾)
if (!filePath.empty() && (filePath.back() == '/' || filePath.back() == '\\')) {
// 创建目录
if (!CreateDirectoryRecursively(fullPath)) {
GakumasLocal::Log::ErrorFmt("Create Dir Failed: %s", fullPath.c_str());
unzClose(zipfile);
return false;
}
}
else {
// 对于文件,先确保其上级目录存在
size_t pos = fullPath.find_last_of("/\\");
if (pos != std::string::npos) {
std::string directory = fullPath.substr(0, pos);
if (!CreateDirectoryRecursively(directory)) {
GakumasLocal::Log::ErrorFmt("Create Dir Failed: %s", directory.c_str());
unzClose(zipfile);
return false;
}
}
// 打开zip中文件
ret = unzOpenCurrentFile(zipfile);
if (ret != UNZ_OK) {
GakumasLocal::Log::ErrorFmt("Open file in zip failed: %s", filePath.c_str());
unzClose(zipfile);
return false;
}
// 在目标路径上创建新文件
FILE* outFile = fopen(fullPath.c_str(), "wb");
if (outFile == nullptr) {
GakumasLocal::Log::ErrorFmt("Can't create output file: %s", fullPath.c_str());
unzCloseCurrentFile(zipfile);
unzClose(zipfile);
return false;
}
// 读取数据并写入文件
const int bufferSize = 8192;
std::vector<char> buffer(bufferSize);
int bytesRead = 0;
do {
bytesRead = unzReadCurrentFile(zipfile, buffer.data(), bufferSize);
if (bytesRead < 0) {
GakumasLocal::Log::ErrorFmt("Read File Error: %s", filePath.c_str());
fclose(outFile);
unzCloseCurrentFile(zipfile);
unzClose(zipfile);
return false;
}
if (bytesRead > 0) {
fwrite(buffer.data(), 1, bytesRead, outFile);
}
} while (bytesRead > 0);
fclose(outFile);
unzCloseCurrentFile(zipfile);
}
ret = unzGoToNextFile(zipfile);
} while (ret == UNZ_OK);
unzClose(zipfile);
return true;
}

View File

@ -38,6 +38,10 @@
extern bool g_has_config_file;
// config 区
extern bool g_enable_console;
extern bool g_confirm_every_times;
extern std::vector<std::string> g_pluginPath;
extern bool g_useRemoteAssets;
extern bool g_useAPIAssets;
extern std::string g_remoteResourceUrl;
extern std::string g_useAPIAssetsURL;
// config 区结束

View File

@ -1,9 +1,14 @@
#include "windowsPlatform.hpp"
#include "GakumasLocalify/Plugin.h"
#include "GakumasLocalify/Log.h"
#include "GakumasLocalify/Local.h"
#include "GakumasLocalify/MasterLocal.h"
#include "GakumasLocalify/camera/camera.hpp"
#include "GakumasLocalify/config/Config.hpp"
#include "GakumasLocalify/Il2cppUtils.hpp"
#include "gkmsGUI/gkmsGUIMain.hpp"
#include "gkmsGUI/GUII18n.hpp"
#include "resourceUpdate/resourceUpdate.hpp"
#include <stdinclude.hpp>
#include "deps/UnityResolve/UnityResolve.hpp"
@ -12,8 +17,18 @@
bool mh_inited;
extern std::filesystem::path gakumasLocalPath;
extern std::filesystem::path ConfigJson;
extern std::filesystem::path ProgramConfigJson;
int hotk = 'u';
void reload_all_data();
std::function<void()> g_on_close;
std::function<void()> on_hotKey_0;
std::function<void()> g_reload_all_data = reload_all_data;
void patchGameAssenbly();
void readProgramConfig();
namespace
{
@ -56,7 +71,8 @@ HMODULE __stdcall load_library_w_hook(const wchar_t* path)
// GakumasLocal::Log::DebugFmt("LoadLibrary %s", utility::conversions::to_utf8string(path).c_str());
using namespace std;
if (path == L"cri_ware_unity.dll"sv) {
if (path == L"VuplexWebViewWindows.dll"sv) {
// if (path == L"cri_ware_unity.dll"sv) {
// if (path == L"NVUnityPlugin"sv) {
patchGameAssenbly();
}
@ -72,6 +88,12 @@ void patchGameAssenbly() {
plugin.InstallHook(std::make_unique<AndroidHookInstaller>("GameAssembly.dll", gakumasLocalPath.string()));
}
void unInitHook() {
if (!mh_inited) return;
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
}
void initHook() {
mh_inited = true;
@ -81,13 +103,34 @@ void initHook() {
auto stat1 = MH_CreateHook(LoadLibraryW, load_library_w_hook, &load_library_w_orig);
auto stat2 = MH_EnableHook(LoadLibraryW);
g_on_close = []() {
unInitHook();
TerminateProcess(GetCurrentProcess(), 0);
};
static bool guiStarting = false;
on_hotKey_0 = []() {
if (guiStarting) return;
guiStarting = true;
std::thread([]() {
printf("GUI START\n");
guimain();
guiStarting = false;
printf("GUI END\n");
}).detach();
};
GakumasLocal::Log::InfoFmt("initHook stat: %s - %s\n", MH_StatusToString(stat1), MH_StatusToString(stat2));
}
void unInitHook() {
if (!mh_inited) return;
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
void checkAndInitConfig(const std::filesystem::path& LocalConfigFile) {
if (!std::filesystem::exists(ProgramConfigJson)) {
g_useAPIAssetsURL = GkmsGUII18n::ts("default_assets_check_api");
GkmsResourceUpdate::saveProgramConfig();
}
if (!std::filesystem::exists(LocalConfigFile)) {
GakumasLocal::Config::SaveConfig(LocalConfigFile.string());
}
}
@ -96,6 +139,8 @@ void loadConfig(const std::string& configJson) {
}
void loadConfig(const std::filesystem::path& filePath) {
checkAndInitConfig(filePath);
std::ifstream file(filePath);
if (!file.is_open()) {
GakumasLocal::Log::ErrorFmt("Load config %s failed.\n", filePath.string().c_str());
@ -108,6 +153,14 @@ void loadConfig(const std::filesystem::path& filePath) {
loadConfig(fileContent);
}
void reload_all_data() {
readProgramConfig();
loadConfig(ConfigJson);
GkmsResourceUpdate::GetCurrentResourceVersion(false);
GakumasLocal::Local::LoadData();
GakumasLocal::MasterLocal::LoadData();
}
bool getCurrentLodingProgress(int* stepTotal, int* stepCurrent, int* currTotal, int* currCurrent) {
*stepTotal = UnityResolveProgress::assembliesProgress.total;
*stepCurrent = UnityResolveProgress::assembliesProgress.current;
@ -117,6 +170,36 @@ bool getCurrentLodingProgress(int* stepTotal, int* stepCurrent, int* currTotal,
return UnityResolveProgress::startInit;
}
void checkDBGKey(int action, int key_code) {
if (action != WM_KEYDOWN) return;
static const std::vector<int> targetDbgKeyList = { 38, 38, 40, 40, 37, 39, 37, 39, 66, 65};
static int currentIndex = 0;
if (targetDbgKeyList[currentIndex] == key_code) {
if (currentIndex == targetDbgKeyList.size() - 1) {
currentIndex = 0;
GakumasLocal::Config::dbgMode = !GakumasLocal::Config::dbgMode;
GakumasLocal::Config::SaveConfig(ConfigJson.string());
GakumasLocal::Log::InfoFmt("Debug Mode: %d", GakumasLocal::Config::dbgMode);
}
else {
currentIndex++;
}
}
else {
currentIndex = 0;
}
}
void keyboardEvents(int action, int key_code) {
// GakumasLocal::Log::DebugFmt("keyboardEvents: %d - %d", action, key_code);
checkDBGKey(action, key_code);
GKCamera::on_cam_rawinput_keyboard(action, key_code);
const auto msg = GakumasLocal::Local::OnKeyDown(action, key_code);
if (!msg.empty()) {
GakumasLocal::Log::Info(msg.c_str());
}
}
namespace GakumasLocal::WinHooks {
using Il2cppString = UnityResolve::UnityType::String;
@ -134,6 +217,131 @@ namespace GakumasLocal::WinHooks {
return get_assetBundle->Invoke<void*>(bundleTask);
}
}
namespace Keyboard {
std::function<void(int, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD)> mKeyBoardCallBack = nullptr;
// TODO keyboard events
WNDPROC g_pfnOldWndProc = NULL;
LRESULT CALLBACK WndProcCallback(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
DWORD SHIFT_key = 0;
DWORD CTRL_key = 0;
DWORD ALT_key = 0;
DWORD SPACE_key = 0;
DWORD UP_key = 0;
DWORD DOWN_key = 0;
DWORD LEFT_key = 0;
DWORD RIGHT_key = 0;
switch (uMsg) {
case WM_INPUT: {
RAWINPUT rawInput;
UINT size = sizeof(RAWINPUT);
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &rawInput, &size, sizeof(RAWINPUTHEADER)) == size) {
/* Êó±êʼþ£¬ºóÃæ¼ÓÉÏ
if (rawInput.header.dwType == RIM_TYPEMOUSE)
{
switch (rawInput.data.mouse.ulButtons) {
case 0: { // move
SCCamera::mouseMove(rawInput.data.mouse.lLastX, rawInput.data.mouse.lLastY, 3);
}; break;
case 4: { // press
SCCamera::mouseMove(0, 0, 1);
}; break;
case 8: { // release
SCCamera::mouseMove(0, 0, 2);
}; break;
default: break;
}
if (rawInput.data.mouse.usButtonFlags == RI_MOUSE_WHEEL) {
if (rawInput.data.mouse.usButtonData == 120) {
SCCamera::mouseMove(0, 1, 4);
}
else {
SCCamera::mouseMove(0, -1, 4);
}
}
}*/
}
}; break;
case WM_SYSKEYUP:
case WM_KEYUP: {
int key = wParam;
keyboardEvents(uMsg, key);
}; break;
case WM_SYSKEYDOWN:
case WM_KEYDOWN: {
int key = wParam;
keyboardEvents(uMsg, key);
SHIFT_key = GetAsyncKeyState(VK_SHIFT);
CTRL_key = GetAsyncKeyState(VK_CONTROL);
ALT_key = GetAsyncKeyState(VK_MENU);
SPACE_key = GetAsyncKeyState(VK_SPACE);
UP_key = GetAsyncKeyState(VK_UP);
DOWN_key = GetAsyncKeyState(VK_DOWN);
LEFT_key = GetAsyncKeyState(VK_LEFT);
RIGHT_key = GetAsyncKeyState(VK_RIGHT);
if (mKeyBoardCallBack != nullptr) {
mKeyBoardCallBack(key, SHIFT_key, CTRL_key, ALT_key, SPACE_key, UP_key, DOWN_key, LEFT_key, RIGHT_key);
}
if (key >= 'A' && key <= 'Z')
{
if (GetAsyncKeyState(VK_SHIFT) >= 0) key += 32;
if (CTRL_key != 0 && key == hotk)
{
// fopenExternalPlugin(tlgport);
printf("hotKey pressed.\n");
if (on_hotKey_0) on_hotKey_0();
}
SHIFT_key = 0;
CTRL_key = 0;
ALT_key = 0;
SPACE_key = 0;
DWORD UP_key = 0;
DWORD DOWN_key = 0;
DWORD LEFT_key = 0;
DWORD RIGHT_key = 0;
}
}; break;
case WM_NCACTIVATE: {
if (!wParam) {
// SCCamera::onKillFocus();
return FALSE;
}
}; break;
case WM_KILLFOCUS: {
// SCCamera::onKillFocus();
return FALSE;
}; break;
case WM_CLOSE: {
if (g_on_close) g_on_close();
}; break;
default: break;
}
return CallWindowProc(g_pfnOldWndProc, hWnd, uMsg, wParam, lParam);
}
void InstallWndProcHook() {
g_pfnOldWndProc = (WNDPROC)GetWindowLongPtr(FindWindowW(L"UnityWndClass", L"gakumas"), GWLP_WNDPROC);
SetWindowLongPtr(FindWindowW(L"UnityWndClass", L"gakumas"), GWLP_WNDPROC, (LONG_PTR)WndProcCallback);
}
void UninstallWndProcHook(HWND hWnd)
{
SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)g_pfnOldWndProc);
}
}
}

View File

@ -1,5 +1,6 @@
#pragma once
#include <string>
#include <filesystem>
void initHook();
@ -15,4 +16,8 @@ bool getCurrentLodingProgress(int* stepTotal, int* stepCurrent, int* currTotal,
namespace GakumasLocal::WinHooks {
void* LoadAssetBundle(const std::string& path);
namespace Keyboard {
void InstallWndProcHook();
}
}