diff --git a/DEPS b/DEPS index 623f76d770..a55cbea900 100644 --- a/DEPS +++ b/DEPS @@ -24,7 +24,8 @@ deps = { "vendor/libuv": "https://github.com/libuv/libuv.git@7639dd510ec13dd577677dce2a673f5046432903", "vendor/fmtlib": "https://github.com/fmtlib/fmt.git", "vendor/nghttp2": "https://github.com/nghttp2/nghttp2@v1.20.0", - "vendor/cpr": "https://github.com/whoshuu/cpr.git@master" + "vendor/cpr": "https://github.com/whoshuu/cpr.git@master", + "vendor/imgui": "https://github.com/ocornut/imgui.git@v1.49" } hooks = [ diff --git a/components/config.lua b/components/config.lua index a8c4908953..03514edbf1 100644 --- a/components/config.lua +++ b/components/config.lua @@ -21,7 +21,7 @@ component 'gta-core-ny' --component 'scrt-mono' --component 'voip-mumble' component 'font-renderer' -component 'conhost-posh' +component 'conhost-v2' component 'rage-allocator-payne' component 'rage-device-payne' component 'rage-graphics-payne' diff --git a/components/conhost-v2/component.json b/components/conhost-v2/component.json new file mode 100644 index 0000000000..68307cf404 --- /dev/null +++ b/components/conhost-v2/component.json @@ -0,0 +1,11 @@ +{ + "name": "conhost:v2", + "version": "0.1.0", + "dependencies": [ + "fx[2]", + "rage:input", + "rage:graphics", + "vendor:imgui" + ], + "provides": [] +} diff --git a/components/conhost-v2/component.lua b/components/conhost-v2/component.lua new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/conhost-v2/component.rc b/components/conhost-v2/component.rc new file mode 100644 index 0000000000..a44b2c1a7b --- /dev/null +++ b/components/conhost-v2/component.rc @@ -0,0 +1 @@ +fxComponent 115 component.json diff --git a/components/conhost-v2/include/ConsoleHost.h b/components/conhost-v2/include/ConsoleHost.h new file mode 100644 index 0000000000..e60e5444d1 --- /dev/null +++ b/components/conhost-v2/include/ConsoleHost.h @@ -0,0 +1,21 @@ +/* + * This file is part of the CitizenFX project - http://citizen.re/ + * + * See LICENSE and MENTIONS in the root of the source tree for information + * regarding licensing. + */ + +#pragma once + +#ifdef COMPILING_CONHOST_POSH +#define CONHOST_EXPORT DLL_EXPORT +#else +#define CONHOST_EXPORT DLL_IMPORT +#endif + +namespace ConHost +{ + extern CONHOST_EXPORT fwEvent OnInvokeNative; + + CONHOST_EXPORT void Print(int channel, const std::string& message); +} \ No newline at end of file diff --git a/components/conhost-v2/include/ConsoleHostImpl.h b/components/conhost-v2/include/ConsoleHostImpl.h new file mode 100644 index 0000000000..b43e38f19b --- /dev/null +++ b/components/conhost-v2/include/ConsoleHostImpl.h @@ -0,0 +1,34 @@ +/* + * This file is part of the CitizenFX project - http://citizen.re/ + * + * See LICENSE and MENTIONS in the root of the source tree for information + * regarding licensing. + */ + +#pragma once + +enum ConsoleModifiers +{ + ConsoleModifierNone = 0, + ConsoleModifierAlt = 1, + ConsoleModifierShift = 2, + ConsoleModifierControl = 4 +}; + +// THANKS MICROSOFT +DEFINE_ENUM_FLAG_OPERATORS(ConsoleModifiers); + +void ConHost_Run(); + +void ConHost_KeyEnter(uint32_t vKey, wchar_t character, ConsoleModifiers modifiers); + +// private +void ConHost_AddInternalCalls(); + +void ConHost_WaitForKey(uint32_t& vKey, wchar_t& character, ConsoleModifiers& modifiers); + +void ConHost_GetCursorPos(int& x, int& y); + +void ConHost_NewBuffer(int width, int height); + +void* ConHost_GetBuffer(); \ No newline at end of file diff --git a/components/conhost-v2/include/Local.h b/components/conhost-v2/include/Local.h new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/components/conhost-v2/include/Local.h @@ -0,0 +1 @@ +#pragma once diff --git a/components/conhost-v2/src/Component.cpp b/components/conhost-v2/src/Component.cpp new file mode 100644 index 0000000000..adfaa35d5b --- /dev/null +++ b/components/conhost-v2/src/Component.cpp @@ -0,0 +1,43 @@ +/* + * This file is part of the CitizenFX project - http://citizen.re/ + * + * See LICENSE and MENTIONS in the root of the source tree for information + * regarding licensing. + */ + +#include "StdInc.h" +#include "ComponentLoader.h" + +class ComponentInstance : public Component +{ +public: + virtual bool Initialize(); + + virtual bool DoGameLoad(void* module); + + virtual bool Shutdown(); +}; + +bool ComponentInstance::Initialize() +{ + InitFunctionBase::RunAll(); + + return true; +} + +bool ComponentInstance::DoGameLoad(void* module) +{ + HookFunction::RunAll(); + + return true; +} + +bool ComponentInstance::Shutdown() +{ + return true; +} + +extern "C" __declspec(dllexport) Component* CreateComponent() +{ + return new ComponentInstance(); +} \ No newline at end of file diff --git a/components/conhost-v2/src/ConsoleHost.cpp b/components/conhost-v2/src/ConsoleHost.cpp new file mode 100644 index 0000000000..a85a4f9e6d --- /dev/null +++ b/components/conhost-v2/src/ConsoleHost.cpp @@ -0,0 +1,63 @@ +/* + * This file is part of the CitizenFX project - http://citizen.re/ + * + * See LICENSE and MENTIONS in the root of the source tree for information + * regarding licensing. + */ + +#include "StdInc.h" +#include "ConsoleHostImpl.h" +#include "InputHook.h" +#include +#include + +static std::thread g_consoleThread; +static std::once_flag g_consoleInitialized; +bool g_consoleFlag; +extern int g_scrollTop; +extern int g_bufferHeight; + +static InitFunction initFunction([] () +{ + InputHook::OnWndProc.Connect([] (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, bool& pass, LRESULT& lresult) + { + if (g_consoleFlag) + { + static bool g_consoleClosing = false; + + // should the console be closed? + if (wParam == VK_F8) + { + if (msg == WM_KEYDOWN) + { + g_consoleClosing = true; + return; + } + + if (g_consoleClosing) + { + if (msg == WM_KEYUP) + { + g_consoleClosing = false; + g_consoleFlag = false; + + return; + } + } + } + } + else + { + // check if the console should be opened + if (msg == WM_KEYUP && wParam == VK_F8) + { + g_consoleFlag = true; + + pass = false; + lresult = 0; + + return; + } + } + }, -10); +}); \ No newline at end of file diff --git a/components/conhost-v2/src/ConsoleHostGui.cpp b/components/conhost-v2/src/ConsoleHostGui.cpp new file mode 100644 index 0000000000..3b4d4ec9c3 --- /dev/null +++ b/components/conhost-v2/src/ConsoleHostGui.cpp @@ -0,0 +1,374 @@ +/* +* This file is part of the CitizenFX project - http://citizen.re/ +* +* See LICENSE and MENTIONS in the root of the source tree for information +* regarding licensing. +*/ + +#include "StdInc.h" + +#include +#include + +#include + +struct FiveMConsole +{ + char InputBuf[1024]; + ImVector Items; + bool ScrollToBottom; + ImVector History; + int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + ImVector Commands; + + FiveMConsole() + { + ClearLog(); + memset(InputBuf, 0, sizeof(InputBuf)); + HistoryPos = -1; + Commands.push_back("HELP"); + Commands.push_back("HISTORY"); + Commands.push_back("CLEAR"); + Commands.push_back("LOADLEVEL"); + Commands.push_back("CONNECT"); + Commands.push_back("NETGRAPH"); + } + + ~FiveMConsole() + { + ClearLog(); + for (int i = 0; i < History.Size; i++) + free(History[i]); + } + + // Portable helpers + static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; } + static int Strnicmp(const char* str1, const char* str2, int n) { int d = 0; while (n > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; n--; } return d; } + static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buff = malloc(len); return (char*)memcpy(buff, (const void*)str, len); } + + void ClearLog() + { + for (int i = 0; i < Items.Size; i++) + free(Items[i]); + Items.clear(); + ScrollToBottom = true; + } + + void AddLog(const char* fmt, ...) IM_PRINTFARGS(2) + { + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, _countof(buf), fmt, args); + buf[_countof(buf) - 1] = 0; + va_end(args); + Items.push_back(Strdup(buf)); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetItemsLineHeightWithSpacing() * 12.0f), ImGuiSetCond_FirstUseEver); + + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings; + + if (!ImGui::Begin(title, nullptr, ImVec2(), -1, flags)) + { + ImGui::End(); + return; + } + + /*ImGui::TextWrapped("This example implements a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc."); + ImGui::TextWrapped("Enter 'HELP' for help, press TAB to use text completion."); + + // TODO: display items starting from the bottom + + if (ImGui::SmallButton("Add Dummy Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine(); + if (ImGui::SmallButton("Add Dummy Error")) AddLog("[error] something went wrong"); ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) ClearLog(); ImGui::SameLine(); + if (ImGui::SmallButton("Scroll to bottom")) ScrollToBottom = true; + //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + static ImGuiTextFilter filter; + filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + ImGui::PopStyleVar(); + ImGui::Separator();*/ + + ImGui::BeginChild("ScrollingRegion", ImVec2(0, -ImGui::GetItemsLineHeightWithSpacing()), false, ImGuiWindowFlags_NoScrollbar); + + // Display every line as a separate entry so we can change their color or add custom widgets. If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping to only process visible items. + // You can seek and display only the lines that are visible using the ImGuiListClipper helper, if your elements are evenly spaced and you have cheap random access to the elements. + // To use the clipper we could replace the 'for (int i = 0; i < Items.Size; i++)' loop with: + // ImGuiListClipper clipper(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // However take note that you can not use this code as is if a filter is active because it breaks the 'cheap random-access' property. We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage to improve this example code! + // If your items are of variable size you may want to implement code similar to what ImGuiListClipper does. Or split your data into fixed height items to allow random-seeking into your list. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing + ImGuiListClipper clipper(Items.Size); + while (clipper.Step()) + { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const char* item = Items[i]; + ImVec4 col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // A better implementation may store a type per-item. For the sample let's just parse the text. + if (strstr(item, "[error]")) col = ImColor(1.0f, 0.4f, 0.4f, 1.0f); + else if (strncmp(item, "# ", 2) == 0) col = ImColor(1.0f, 0.78f, 0.58f, 1.0f); + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::TextUnformatted(item); + ImGui::PopStyleColor(); + } + } + + if (ScrollToBottom) + ImGui::SetScrollHere(); + + ScrollToBottom = false; + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::Separator(); + + // Command-line + if (ImGui::InputText("_", InputBuf, _countof(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) + { + char* input_end = InputBuf + strlen(InputBuf); + while (input_end > InputBuf && input_end[-1] == ' ') input_end--; *input_end = 0; + if (InputBuf[0]) + ExecCommand(InputBuf); + strcpy(InputBuf, ""); + } + + // Demonstrate keeping auto focus on the input box + if (ImGui::IsItemHovered() || (ImGui::IsRootWindowOrAnyChildFocused() && !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0))) + ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget + + ImGui::End(); + } + + void ExecCommand(const char* command_line) + { + AddLog("> %s\n", command_line); + + // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal. + HistoryPos = -1; + for (int i = History.Size - 1; i >= 0; i--) + if (Stricmp(History[i], command_line) == 0) + { + free(History[i]); + History.erase(History.begin() + i); + break; + } + + History.push_back(Strdup(command_line)); + + // Process command + if (Stricmp(command_line, "clear") == 0) + { + ClearLog(); + } + else if (Stricmp(command_line, "help") == 0) + { + AddLog("Available commands:"); + for (int i = 0; i < Commands.Size; i++) + AddLog("- %s", Commands[i]); + } + else if (Stricmp(command_line, "history") == 0) + { + for (int i = History.Size >= 10 ? History.Size - 10 : 0; i < History.Size; i++) + AddLog("%3d: %s\n", i, History[i]); + } + else if (Strnicmp(command_line, "loadlevel", 9) == 0 || Strnicmp(command_line, "invoke-levelload", 16) == 0) + { + const char* levelName = strrchr(command_line, ' '); + + if (levelName) + { + ConHost::OnInvokeNative("loadLevel", levelName + 1); + } + else + { + AddLog("Usage: LoadLevel levelName"); + AddLog("Example: LoadLevel gta5"); + } + } + else if (Strnicmp(command_line, "netgraph", 7) == 0 || Strnicmp(command_line, "set-netgraph", 11) == 0) + { + static bool lastState = false; + + lastState = !lastState; + + ConHost::OnInvokeNative("netGraphEnabled", (lastState) ? "y" : "n"); + } + else if (Strnicmp(command_line, "connect", 7) == 0) + { + if (Instance::Get()->GetGameLoaded()) + { + AddLog("You're already connected to a server."); + return; + } + + const char* name = strrchr(command_line, ' '); + + if (name) + { + ConHost::OnInvokeNative("connectTo", name + 1); + } + else + { + AddLog("Usage: Connect host:port"); + AddLog("Example: Connect localhost:30120"); + } + + } + else + { + AddLog("Unknown command: '%s'\n", command_line); + } + } + + static int TextEditCallbackStub(ImGuiTextEditCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks + { + FiveMConsole* console = (FiveMConsole*)data->UserData; + return console->TextEditCallback(data); + } + + int TextEditCallback(ImGuiTextEditCallbackData* data) + { + //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + // Example of TEXT COMPLETION + + // Locate beginning of current word + const char* word_end = data->Buf + data->CursorPos; + const char* word_start = word_end; + while (word_start > data->Buf) + { + const char c = word_start[-1]; + if (c == ' ' || c == '\t' || c == ',' || c == ';') + break; + word_start--; + } + + // Build a list of candidates + ImVector candidates; + for (int i = 0; i < Commands.Size; i++) + if (Strnicmp(Commands[i], word_start, (int)(word_end - word_start)) == 0) + candidates.push_back(Commands[i]); + + if (candidates.Size == 0) + { + // No match + AddLog("No match for \"%.*s\"!\n", (int)(word_end - word_start), word_start); + } + else if (candidates.Size == 1) + { + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); + data->InsertChars(data->CursorPos, candidates[0]); + data->InsertChars(data->CursorPos, " "); + } + else + { + // Multiple matches. Complete as much as we can, so inputing "C" will complete to "CL" and display "CLEAR" and "CLASSIFY" + int match_len = (int)(word_end - word_start); + for (;;) + { + int c = 0; + bool all_candidates_matches = true; + for (int i = 0; i < candidates.Size && all_candidates_matches; i++) + if (i == 0) + c = toupper(candidates[i][match_len]); + else if (c == 0 || c != toupper(candidates[i][match_len])) + all_candidates_matches = false; + if (!all_candidates_matches) + break; + match_len++; + } + + if (match_len > 0) + { + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); + data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); + } + + // List matches + AddLog("Possible matches:\n"); + for (int i = 0; i < candidates.Size; i++) + AddLog("- %s\n", candidates[i]); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + // Example of HISTORY + const int prev_history_pos = HistoryPos; + if (data->EventKey == ImGuiKey_UpArrow) + { + if (HistoryPos == -1) + HistoryPos = History.Size - 1; + else if (HistoryPos > 0) + HistoryPos--; + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (HistoryPos != -1) + if (++HistoryPos >= History.Size) + HistoryPos = -1; + } + + // A better implementation would preserve the data on the current input line along with cursor position. + if (prev_history_pos != HistoryPos) + { + data->CursorPos = data->SelectionStart = data->SelectionEnd = data->BufTextLen = (int)_snprintf(data->Buf, (size_t)data->BufSize, "%s", (HistoryPos >= 0) ? History[HistoryPos] : ""); + data->BufDirty = true; + } + } + } + return 0; + } +}; + +static std::unique_ptr console; + +void DrawConsole() +{ + if (!console) + { + console = std::make_unique(); + } + + static bool pOpen = true; + console->Draw("", &pOpen); +} + +#include + +void SendPrintMessage(const std::string& message) +{ + if (!console) + { + console = std::make_unique(); + } + + std::stringstream ss(message); + std::string to; + + while (std::getline(ss, to, '\n')) + { + console->AddLog(to.c_str()); + } +} \ No newline at end of file diff --git a/components/conhost-v2/src/ConsoleHostImpl.cpp b/components/conhost-v2/src/ConsoleHostImpl.cpp new file mode 100644 index 0000000000..1f1def585d --- /dev/null +++ b/components/conhost-v2/src/ConsoleHostImpl.cpp @@ -0,0 +1,349 @@ +/* + * This file is part of the CitizenFX project - http://citizen.re/ + * + * See LICENSE and MENTIONS in the root of the source tree for information + * regarding licensing. + */ + +#include "StdInc.h" +#include +#include "ConsoleHost.h" +#include "ConsoleHostImpl.h" + +#include +#include + +#include + +#include +#include + +#include +#include + +#include + +#include + +static bool g_conHostInitialized = false; +extern bool g_consoleFlag; +int g_scrollTop; +int g_bufferHeight; + +static uint32_t g_pointSamplerState; +static rage::grcTexture* g_fontTexture; + +struct DrawList +{ + ImVector vertices; + ImVector indices; + ImVector commands; +}; + +static void RenderDrawListInternal(DrawList* drawList) +{ + auto oldRasterizerState = GetRasterizerState(); + SetRasterizerState(GetStockStateIdentifier(RasterizerStateNoCulling)); + + auto oldBlendState = GetBlendState(); + SetBlendState(GetStockStateIdentifier(BlendStateDefault)); + + auto oldDepthStencilState = GetDepthStencilState(); + SetDepthStencilState(GetStockStateIdentifier(DepthStencilStateNoDepth)); + + SetTextureGtaIm(rage::grcTextureFactory::GetNoneTexture()); + + for (auto& cmd : drawList->commands) + { + if (cmd.UserCallback != nullptr) + { + cmd.UserCallback(nullptr, &cmd); + } + else + { + // TODO: cliprect + SetTextureGtaIm((rage::grcTexture*)cmd.TextureId); + + PushDrawBlitImShader(); + + BeginImVertices(3, cmd.ElemCount); + + for (int i = 0; i < cmd.ElemCount; i++) + { + auto& vertex = drawList->vertices[drawList->indices[i]]; + auto color = vertex.col; + + // this swaps ABGR (as CRGBA is ABGR in little-endian) to ARGB by rotating left + if (!rage::grcTexture::IsRenderSystemColorSwapped()) + { + color = (color & 0xFF00FF00) | _rotl(color & 0x00FF00FF, 16); + } + + AddImVertex(vertex.pos.x, vertex.pos.y, 0.0f, 0.0f, 0.0f, -1.0f, color, vertex.uv.x, vertex.uv.y); + } + + DrawImVertices(); + + PopDrawBlitImShader(); + } + } + + SetRasterizerState(oldRasterizerState); + + SetBlendState(oldBlendState); + + SetDepthStencilState(oldDepthStencilState); + + delete drawList; +} + +static void RenderDrawLists(ImDrawData* drawData) +{ + if (!drawData->Valid) + { + return; + } + + for (int i = 0; i < drawData->CmdListsCount; i++) + { + ImDrawList* drawList = drawData->CmdLists[i]; + + DrawList* grDrawList = new DrawList(); + grDrawList->commands.swap(drawList->CmdBuffer); + grDrawList->indices.swap(drawList->IdxBuffer); + grDrawList->vertices.swap(drawList->VtxBuffer); + + if (IsOnRenderThread()) + { + RenderDrawListInternal(grDrawList); + } + else + { + uintptr_t argRef = (uintptr_t)grDrawList; + + EnqueueGenericDrawCommand([](uintptr_t a, uintptr_t b) + { + RenderDrawListInternal((DrawList*)a); + }, &argRef, &argRef); + } + } +} + +static void CreateFontTexture() +{ + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + rage::grcTextureReference reference; + memset(&reference, 0, sizeof(reference)); + reference.width = width; + reference.height = height; + reference.depth = 1; + reference.stride = width * 4; + reference.format = 11; + reference.pixelData = (uint8_t*)pixels; + + rage::grcTexture* texture = rage::grcTextureFactory::getInstance()->createImage(&reference, nullptr); + g_fontTexture = texture; + + io.Fonts->TexID = (void *)g_fontTexture; +} + +void DrawConsole(); + +static InitFunction initFunction([] () +{ + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. + io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = VK_UP; + io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN; + io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR; + io.KeyMap[ImGuiKey_PageDown] = VK_NEXT; + io.KeyMap[ImGuiKey_Home] = VK_HOME; + io.KeyMap[ImGuiKey_End] = VK_END; + io.KeyMap[ImGuiKey_Delete] = VK_DELETE; + io.KeyMap[ImGuiKey_Backspace] = VK_BACK; + io.KeyMap[ImGuiKey_Enter] = VK_RETURN; + io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE; + io.KeyMap[ImGuiKey_A] = 'A'; + io.KeyMap[ImGuiKey_C] = 'C'; + io.KeyMap[ImGuiKey_V] = 'V'; + io.KeyMap[ImGuiKey_X] = 'X'; + io.KeyMap[ImGuiKey_Y] = 'Y'; + io.KeyMap[ImGuiKey_Z] = 'Z'; + + io.RenderDrawListsFn = RenderDrawLists; // Alternatively you can set this to NULL and call ImGui::GetDrawData() after ImGui::Render() to get the same ImDrawData pointer. + //io.ImeWindowHandle = g_hWnd; + + ImGuiStyle& style = ImGui::GetStyle(); + + ImColor hiliteBlue = ImColor(81, 179, 236); + ImColor hiliteBlueTransparent = ImColor(81, 179, 236, 180); + ImColor backgroundBlue = ImColor(22, 24, 28, 200); + ImColor semiTransparentBg = ImColor(255, 255, 255, 0.6); + ImColor semiTransparentBgHover = ImColor(255, 255, 255, 0.8); + + style.Colors[ImGuiCol_WindowBg] = backgroundBlue; + style.Colors[ImGuiCol_TitleBg] = hiliteBlue; + style.Colors[ImGuiCol_TitleBgActive] = hiliteBlue; + style.Colors[ImGuiCol_TitleBgCollapsed] = hiliteBlue; + style.Colors[ImGuiCol_Border] = hiliteBlue; + style.Colors[ImGuiCol_FrameBg] = semiTransparentBg; + style.Colors[ImGuiCol_FrameBgHovered] = semiTransparentBgHover; + style.Colors[ImGuiCol_TextSelectedBg] = hiliteBlueTransparent; + + // fuck rounding + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + { + FILE* font = _wfopen(MakeRelativeCitPath(L"citizen/mensch.ttf").c_str(), L"rb"); + + if (font) + { + fseek(font, 0, SEEK_END); + + auto fontSize = ftell(font); + + uint8_t* fontData = new uint8_t[fontSize]; + + fseek(font, 0, SEEK_SET); + + fread(&fontData[0], 1, fontSize, font); + fclose(font); + + io.Fonts->AddFontFromMemoryTTF(fontData, fontSize, 20.0f); + } + } + + OnGrcCreateDevice.Connect([=]() + { + D3D11_SAMPLER_DESC samplerDesc = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()); + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + samplerDesc.MaxAnisotropy = 0; + + g_pointSamplerState = CreateSamplerState(&samplerDesc); + }); + + InputHook::OnWndProc.Connect([](HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, bool& pass, LRESULT& result) + { + if (!g_consoleFlag || !pass) + { + return; + } + + ImGuiIO& io = ImGui::GetIO(); + switch (msg) + { + case WM_LBUTTONDOWN: + io.MouseDown[0] = true; + pass = false; + break; + case WM_LBUTTONUP: + io.MouseDown[0] = false; + pass = false; + break; + case WM_RBUTTONDOWN: + io.MouseDown[1] = true; + pass = false; + break; + case WM_RBUTTONUP: + io.MouseDown[1] = false; + pass = false; + break; + case WM_MBUTTONDOWN: + io.MouseDown[2] = true; + pass = false; + break; + case WM_MBUTTONUP: + io.MouseDown[2] = false; + pass = false; + break; + case WM_MOUSEWHEEL: + io.MouseWheel += GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1.0f : -1.0f; + pass = false; + break; + case WM_MOUSEMOVE: + io.MousePos.x = (signed short)(lParam); + io.MousePos.y = (signed short)(lParam >> 16); + pass = false; + break; + case WM_KEYDOWN: + if (wParam < 256) + io.KeysDown[wParam] = 1; + pass = false; + break; + case WM_KEYUP: + if (wParam < 256) + io.KeysDown[wParam] = 0; + pass = false; + break; + case WM_CHAR: + // You can also use ToAscii()+GetKeyboardState() to retrieve characters. + if (wParam > 0 && wParam < 0x10000) + io.AddInputCharacter((unsigned short)wParam); + pass = false; + break; + } + + if (!pass) + { + result = true; + } + }); + + OnPostFrontendRender.Connect([]() + { + if (!g_fontTexture) + { + CreateFontTexture(); + } + + if (!g_consoleFlag) + { + return; + } + + ImGuiIO& io = ImGui::GetIO(); + io.MouseDrawCursor = true; + + { + int width, height; + GetGameResolution(width, height); + + io.DisplaySize = ImVec2(width, height); + + // TODO: delta time + } + + io.KeyCtrl = (GetKeyState(VK_CONTROL) & 0x8000) != 0; + io.KeyShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0; + io.KeyAlt = (GetKeyState(VK_MENU) & 0x8000) != 0; + io.KeySuper = false; + + ImGui::NewFrame(); + + DrawConsole(); + + ImGui::Render(); + }); +}); + +struct ConsoleKeyEvent +{ + uint32_t vKey; + wchar_t character; + ConsoleModifiers modifiers; +}; + +void SendPrintMessage(const std::string& message); + +void ConHost::Print(int channel, const std::string& message) +{ + SendPrintMessage(message); +} + +fwEvent ConHost::OnInvokeNative; \ No newline at end of file diff --git a/vendor/config.lua b/vendor/config.lua index 9e7ce815e5..e642f6aa5f 100644 --- a/vendor/config.lua +++ b/vendor/config.lua @@ -20,6 +20,7 @@ vendor_component 'botan' vendor_component 'fmtlib' vendor_component 'fmtlib-crt' vendor_component 'cpr' +vendor_component 'imgui' if os.get() == 'windows' then vendor_component 'boost_program_options' diff --git a/vendor/imgui.lua b/vendor/imgui.lua new file mode 100644 index 0000000000..4ef1d7f3cb --- /dev/null +++ b/vendor/imgui.lua @@ -0,0 +1,15 @@ +return { + include = function() + includedirs "../vendor/imgui/" + end, + + run = function() + language "C++" + kind "StaticLib" + + files_project "../vendor/imgui/" { + "imgui.cpp", + "imgui_draw.cpp" + } + end +} \ No newline at end of file