欢迎大家 PR 各种好看的主题配色和控件样式,或者提交你们的使用示例代码!
- 您可以通过赞助支持我们的开发,感谢大家的支持!
- If this project helps you, feel free to support my work!
- USDT (TRC-20):
THzBDDbBkDh3nXRkCEeG4p5r733tWeAdib
#include "src/Shadow.h"#include "src/Shadow_Texture.h"#include "src/Shadow_Nav.h" // 如果需要 Nav UI#include "src/Shadow_Demo.h" // 如果需要 Demo Menu- 修改
Shadow.h的#include "../external/CppSDK/SDK.hpp"为实际路径 - 在
UGameViewportClient::PostRender运行Shadow::NewFrame(Canvas); - 在
Shadow::NewFrame(Canvas);之后添加Shadow::Render();
#include "src/Shadow.h"#include "src/Shadow_Texture.h"#include "src/Shadow_Nav.h" // if you need Nav UI#include "src/Shadow_Demo.h" // if you need Demo Menu- Modify
#include "../external/CppSDK/SDK.hpp"inShadow.hto the actual path - Run
Shadow::NewFrame(Canvas);inUGameViewportClient::PostRender - Run
Shadow::Render();afterShadow::NewFrame(Canvas);
Show code
// 初始化 Shadow GUI 的时候需要先 RegisterHotkey,否则在上下文执行到 Shadow::HotKey 之前无法使用对应热键
Shadow::RegisterHotkey(&g_Config::kTestKey, &g_Config::eTestKey, &g_Config::bTestKey);
// 与 RegisterHotkey 保持一致,正常声明即可
Shadow::HotKey("TestKey", &g_Config::kTestKey, &g_Config::bTestKey, &g_Config::eTestKey);Show code
LRESULT APIENTRY WndProcHook(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// 始终处理全局热键(菜单打开或关闭都需要)
Shadow::ProcessGlobalHotkeys(hwnd, uMsg, wParam, lParam);
// 菜单切换键处理(如果正在分配热键,则不切换菜单)
if (uMsg == WM_KEYDOWN && wParam == keyMenu && !Shadow::g_Ctx.AssigningHotkey) {
bool isFirstPress = ((lParam & (1 << 30)) == 0);
if (isFirstPress) {
bShowMenu = !bShowMenu;
}
}
if (bShowMenu || currentAlpha > 0.001f) {
// 键盘消息处理
if (uMsg == WM_KEYDOWN || uMsg == WM_KEYUP || uMsg == WM_SYSKEYDOWN || uMsg == WM_SYSKEYUP || uMsg == WM_CHAR) {
// 如果是切换键本身,放行给游戏(防止菜单无法关闭)
if (wParam == keyMenu) {
return CallWindowProc(oWndProc, hwnd, uMsg, wParam, lParam);
}
// 如果是已注册的全局热键,放行给游戏(热键状态已在 ProcessGlobalHotkeys 中更新)
if (Shadow::IsHotkeyRegistered(static_cast<int>(wParam))) {
return CallWindowProc(oWndProc, hwnd, uMsg, wParam, lParam);
}
// 先让框架处理按键(更新 KeyPressed, KeyStates 等)
Shadow::Input(hwnd, uMsg, wParam, lParam);
// 如果是允许放行的按键,再转发给游戏
if (Shadow::IsKeyAllowed(static_cast<int>(wParam))) {
// 注意:这里不能再次调用 Shadow::Input,因为上面已经调用过了
return CallWindowProc(oWndProc, hwnd, uMsg, wParam, lParam);
}
// 非允许按键,被菜单吃掉
return 1;
}
// 如果是鼠标消息,且不在白名单里,直接阻塞
if (!Shadow::IsMouseMsgAllowed(uMsg)) {
Shadow::Input(hwnd, uMsg, wParam, lParam);
return 1;
}
// 鼠标消息在白名单里,也需要先处理,再放行
Shadow::Input(hwnd, uMsg, wParam, lParam);
return CallWindowProc(oWndProc, hwnd, uMsg, wParam, lParam);
}
// 菜单关闭时,也要处理输入以更新全局鼠标状态
Shadow::Input(hwnd, uMsg, wParam, lParam);
return CallWindowProc(oWndProc, hwnd, uMsg, wParam, lParam);
}Show code
void __fastcall hkPostRender(SDK::UGameViewportClient* rcx, SDK::UCanvas* canvas) {
// 放行常用移动按键给游戏,避免菜单打开时玩家动不了
// Pass common movement keys through to the game to prevent players from getting stuck when the menu is open
Shadow::SetAllowedKeys({ 'W', 'A', 'S', 'D', VK_SPACE });
// 开始新的一帧
// Begin a new frame
Shadow::NewFrame(canvas);
// 如果使用 Hotkey,需要先注册热键,虽然缺少手动注册也能用
// Shadow::RegisterHotkey(&g_Config::kTestKey, &g_Config::eTestKey, &g_Config::bTestKey);
// 更新所有热键状态
// Update all hotkey states
Shadow::UpdateAllHotkeyStates();
if (Shadow::Begin("Main Menu##main_window", Shadow::ShadowWindowFlags_NoResize)) {
if (Shadow::BeginTabBar("MainTabs##tabs", Shadow::ShadowTabBarFlags_Reorderable)) {
if (Shadow::BeginTabItem("Misc##tab0")) {
Shadow::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, U8("你好!"));
}
Shadow::EndTabItem();
}
Shadow::EndTabBar();
}
Shadow::End();
Shadow::Render();
}
Important
与 Dear ImGui 不同,Shadow GUI 的所有压栈操作都必须在作用域之外出栈。
示例:
Show code
// -> BeginTabBar
if (Shadow::BeginTabBar("TEST_BeginTabBar")) {
// -> BeginTabItem
if (Shadow::BeginTabItem("TEST_BeginTabItem")) {
// -> TreeNode
if (Shadow::TreeNode("TEST_TreeNode")) {
}
// -> TreePop
Shadow::TreePop();
}
// -> EndTabItem
Shadow::EndTabItem();
}
// -> EndTabBar
Shadow::EndTabBar();
Important
LoadTextureFromBuffer、LoadTextureFromFile、LoadFontFromBuffer、LoadFontFromFile 必须每帧调用
示例:
Show code
void DrawLogoFromBuffer()
{
// 不要对 LoadTextureFromBuffer / LoadTextureFromFile 返回的指针进行跨帧保存。因为引擎GC会导致旧的指针得到一个损坏的UObject从而导致崩溃。
// 每帧调用函数更新该指针即可,库内部会自动管理UObject的生命周期。
SDK::UTexture2D* Texture = Shadow::LoadTextureFromBuffer(Example_Texture::Logo, sizeof(Example_Texture::Logo));
if (Texture)
{
Shadow::GetBackgroundDrawList()->AddTexture({ 100.f, 100.f }, { 480.f, 173.f }, { 1.f, 1.f, 1.f, 1.f }, Texture);
}
}
void DrawLogoFromFile()
{
SDK::UTexture2D* Texture = Shadow::LoadTextureFromFile(L"C:\\Example_Texture.png");
if (Texture)
{
Shadow::GetBackgroundDrawList()->AddTexture({ 100.f, 100.f }, { 480.f, 173.f }, { 1.f, 1.f, 1.f, 1.f }, Texture);
}
}
void DrawCustomText()
{
// 内存
SDK::UFont* Icon = Shadow::LoadFontFromBuffer(Example_Font::Icon, sizeof(Example_Font::Icon));
// 文件
SDK::UFont* msyh = Shadow::LoadFontFromFile(L"C:\\Windows\\Fonts\\msyh.ttc");
SDK::UFont* tahoma_bold = Shadow::LoadFontFromFile(L"C:\\Windows\\Fonts\\tahomabd.ttf");
SDK::UFont* verdana_bold = Shadow::LoadFontFromFile(L"C:\\Windows\\Fonts\\verdanab.ttf");
}Show code
if (Shadow::Begin(U8("测试菜单 / Demo Menu##main_window"), Shadow::ShadowWindowFlags_TextAlignCenter)) {
if (Shadow::BeginTabBar("MainTabs##tabs", Shadow::ShadowTabBarFlags_NoScrollbar)) {
if (Shadow::BeginTabItem(U8("设置 / Settings##tab0"))) {
Shadow::Switch(U8("测试开关 / Test Switch"), &bTest);
Shadow::SameLine();
Shadow::ColorPicker(U8("测试开关 / Test Switch"),
&cTest.r, &cTest.g, &cTest.b, &cTest.a,
Shadow::ShadowColorPickerFlags_NoText | Shadow::ShadowColorPickerFlags_NoRightAlign
);
}
Shadow::EndTabItem();
}
Shadow::EndTabBar();
}
Shadow::End();
Show code
if (Shadow::Begin(U8("测试菜单 / Demo Menu##main_window"), Shadow::ShadowWindowFlags_TextAlignCenter)) {
if (Shadow::BeginTabBar("MainTabs##tabs", Shadow::ShadowTabBarFlags_NoScrollbar)) {
if (Shadow::BeginTabItem(U8("设置 / Settings##tab0"))) {
static std::string input_buffer = U8("");
static std::vector<std::string> item_list = {
U8("默认项目 1"),
U8("默认项目 2")
};
static int selected_index = -1;
static std::string input_str = U8("输入要新建的项目名称...");
Shadow::InputTextWithHint(U8("##ItemInput"), input_str, input_buffer, Shadow::ShadowInputTextFlags_NoName, { Shadow::MeasureTextSize(input_str).x + Shadow::g_Ctx.Style.WindowPadding.x });
if (Shadow::Button(U8("创建"))) {
if (!input_buffer.empty()) {
item_list.push_back(input_buffer);
input_buffer.clear();
}
}
Shadow::SameLine();
if (Shadow::Button(U8("删除"))) {
if (selected_index >= 0 && selected_index < static_cast<int>(item_list.size())) {
item_list.erase(item_list.begin() + selected_index);
selected_index = -1;
}
}
if (Shadow::BeginListBox(U8("##DynamicItemListBox"), { 300.f, 180.f })) {
for (int i = 0; i < static_cast<int>(item_list.size()); ++i) {
bool is_selected = (selected_index == i);
if (Shadow::Selectable(item_list[i], &is_selected)) {
selected_index = i;
}
}
}
Shadow::EndListBox();
if (selected_index >= 0 && selected_index < static_cast<int>(item_list.size())) {
Shadow::TextColored({ 0.4f, 0.8f, 1.0f, 1.0f }, U8("当前选中: ") + item_list[selected_index]);
}
else {
Shadow::TextDisabled(U8("当前未选中任何项目"));
}
}
Shadow::EndTabItem();
}
Shadow::EndTabBar();
}
Shadow::End();
Show code
// 内存
SDK::UFont* Icon = Shadow::LoadFontFromBuffer(Example_Font::Icon, sizeof(Example_Font::Icon));
// 文件
SDK::UFont* msyh = Shadow::LoadFontFromFile(L"C:\\Windows\\Fonts\\msyh.ttc");
SDK::UFont* tahoma_bold = Shadow::LoadFontFromFile(L"C:\\Windows\\Fonts\\tahomabd.ttf");
SDK::UFont* verdana_bold = Shadow::LoadFontFromFile(L"C:\\Windows\\Fonts\\verdanab.ttf");
if (Icon)
{
Shadow::Color Color = { 1.f, 1.f, 1.f, 0.75f };
Shadow::PushFont(Icon, 2.f);
Shadow::GetBackgroundDrawList()->AddText({ 0.f, 50.f }, Color, "A");
Shadow::GetBackgroundDrawList()->AddText({ 0.f, 100.f }, Color, "B");
Shadow::GetBackgroundDrawList()->AddText({ 0.f, 150.f }, Color, "C");
Shadow::GetBackgroundDrawList()->AddText({ 0.f, 200.f }, Color, "D");
Shadow::GetBackgroundDrawList()->AddText({ 0.f, 250.f }, Color, "E");
Shadow::PushTextOutline(Shadow::Color(0.f, 0.f, 0.f, 0.75f));
Shadow::GetBackgroundDrawList()->AddText({ 50.f, 50.f }, Color, "A");
Shadow::GetBackgroundDrawList()->AddText({ 50.f, 100.f }, Color, "B");
Shadow::GetBackgroundDrawList()->AddText({ 50.f, 150.f }, Color, "C");
Shadow::GetBackgroundDrawList()->AddText({ 50.f, 200.f }, Color, "D");
Shadow::GetBackgroundDrawList()->AddText({ 50.f, 250.f }, Color, "E");
Shadow::PopTextOutline();
Shadow::PopFont();
}
if (msyh)
{
Shadow::Color Color = { 1.f, 1.f, 1.f, 1.f };
Shadow::PushFont(msyh, 2.f);
Shadow::PushTextOutline(Shadow::Color(0.f, 0.f, 0.f, 1.f));
Shadow::GetBackgroundDrawList()->AddText({ 0.f, 300.f }, Color, "This is a test message using the msyh.ttc font.");
Shadow::PopTextOutline();
Shadow::PopFont();
}
if (tahoma_bold)
{
Shadow::Color Color = { 1.f, 1.f, 0.f, 1.f };
Shadow::PushFont(tahoma_bold, 2.f);
Shadow::PushTextOutline(Shadow::Color(0.f, 0.f, 0.f, 1.f));
Shadow::GetBackgroundDrawList()->AddText({ 0.f, 350.f }, Color, "This is a test message using the tahomabd.ttf font.");
Shadow::PopTextOutline();
Shadow::PopFont();
}
if (verdana_bold)
{
Shadow::Color Color = { 0.f, 1.f, 1.f, 1.f };
Shadow::PushFont(verdana_bold, 2.f);
Shadow::PushTextOutline(Shadow::Color(0.f, 0.f, 0.f, 1.f));
Shadow::GetBackgroundDrawList()->AddText({ 0.f, 400.f }, Color, "This is a test message using the verdanab.ttf font.");
Shadow::PopTextOutline();
Shadow::PopFont();
}