cnq3/code/client/cl_imgui.cpp
2024-11-04 00:23:09 +01:00

853 lines
22 KiB
C++

/*
===========================================================================
Copyright (C) 2022-2024 Gian 'myT' Schellenbaum
This file is part of Challenge Quake 3 (CNQ3).
Challenge Quake 3 is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Challenge Quake 3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
===========================================================================
*/
// Dear ImGui and Im3d client integration and utility functions
#include "client.h"
#include "cl_imgui.h"
#include "../imgui/font_proggy_clean.h"
#include "../imgui/font_sweet16_mono.h"
#include "../im3d/im3d.h"
static int keyMap[256];
bool BeginTable(const char* name, int count)
{
ImGui::Text(name);
const int flags =
ImGuiTableFlags_RowBg |
ImGuiTableFlags_Borders |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchProp;
return ImGui::BeginTable(name, count, flags);
}
void TableHeader(int count, ...)
{
va_list args;
va_start(args, count);
for(int i = 0; i < count; ++i)
{
const char* header = va_arg(args, const char*);
ImGui::TableSetupColumn(header);
}
va_end(args);
ImGui::TableHeadersRow();
}
void TableRow(int count, ...)
{
ImGui::TableNextRow();
va_list args;
va_start(args, count);
for(int i = 0; i < count; ++i)
{
const char* item = va_arg(args, const char*);
ImGui::TableSetColumnIndex(i);
ImGui::Text(item);
}
va_end(args);
}
void TableRow2(const char* item0, bool item1)
{
TableRow(2, item0, item1 ? "YES" : "NO");
}
void TableRow2(const char* item0, int item1)
{
TableRow(2, item0, va("%d", item1));
}
void TableRow2(const char* item0, float item1, const char* format)
{
TableRow(2, item0, va(format, item1));
}
bool IsShortcutPressed(ImGuiKey key)
{
return ImGui::IsKeyDown(ImGuiMod_Ctrl) && ImGui::IsKeyPressed(key, false);
}
void ToggleBooleanWithShortcut(bool& value, ImGuiKey key)
{
if(IsShortcutPressed(key))
{
value = !value;
}
}
void RadioButton(int* argValue, float titleWidth, const char* title, int count, ...)
{
const float x = ImGui::GetCursorPosX();
ImGui::Text(title);
ImGui::SameLine();
if(titleWidth > 0.0f)
{
ImGui::SetCursorPosX(x + titleWidth);
}
ImGui::PushID(title);
va_list args;
va_start(args, count);
for(int i = 0; i < count; i++)
{
const char* const name = va_arg(args, const char*);
const int value = va_arg(args, int);
ImGui::RadioButton(name, argValue, value);
if(i < count - 1)
{
ImGui::SameLine();
}
}
va_end(args);
ImGui::PopID();
}
struct FileDialog
{
char folder[MAX_QPATH];
char fileName[MAX_QPATH];
char filePath[MAX_QPATH];
char extension[MAX_QPATH];
const char* dialogName;
char** filePaths;
int fileCount;
int selectedFileIndex;
bool saveMode;
bool folderMode;
};
static FileDialog saveFile;
static FileDialog openFile;
static FileDialog saveFolder;
static FileDialog openFolder;
static bool PathHasExtension(const char* path, const char* extension)
{
Q_assert(extension[0] == '.');
const int pathLength = strlen(path);
const int extLength = strlen(extension);
if(extLength >= pathLength)
{
return false;
}
return Q_stricmpn(path + pathLength - extLength, extension, extLength) == 0;
}
static void OpenFileDialog(FileDialog& dialog, const char* folder, const char* extension)
{
if(ImGui::IsPopupOpen(dialog.dialogName))
{
return;
}
dialog.fileName[0] = '\0';
Q_strncpyz(dialog.folder, folder, sizeof(dialog.folder));
Q_strncpyz(dialog.extension, extension, sizeof(dialog.extension));
dialog.filePaths = FS_ListFiles(folder, extension, &dialog.fileCount);
dialog.selectedFileIndex = -1;
ImGui::OpenPopup(dialog.dialogName);
}
static bool DoFileDialog(FileDialog& dialog)
{
bool success = false;
if(ImGui::BeginPopupModal(dialog.dialogName, NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
if(BeginTable(dialog.folderMode ? "Folders" : "Files", 1))
{
for(int i = 0; i < dialog.fileCount; i++)
{
if(dialog.folderMode &&
(Q_stricmp(dialog.filePaths[i], ".") == 0 ||
Q_stricmp(dialog.filePaths[i], "..") == 0))
{
continue;
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
if(ImGui::Selectable(dialog.filePaths[i], i == dialog.selectedFileIndex,
ImGuiSelectableFlags_DontClosePopups))
{
dialog.selectedFileIndex = i;
Q_strncpyz(dialog.fileName, dialog.filePaths[i], sizeof(dialog.fileName));
}
}
ImGui::EndTable();
}
if(dialog.saveMode)
{
if(ImGui::InputText(dialog.folderMode ? "Folder path" : "File path", dialog.fileName, sizeof(dialog.fileName)))
{
dialog.selectedFileIndex = -1;
}
if(ImGui::Button("Save") &&
dialog.fileName[0] != '\0' &&
dialog.fileName[0] != '.')
{
if(!PathHasExtension(dialog.fileName, dialog.extension))
{
const int lastCharIndex = strlen(dialog.fileName) - 1;
if(dialog.fileName[lastCharIndex] == '.')
{
dialog.fileName[lastCharIndex] = '\0';
}
Q_strcat(dialog.fileName, sizeof(dialog.fileName), dialog.extension);
}
Com_sprintf(dialog.filePath, sizeof(dialog.filePath),
"%s/%s", dialog.folder, dialog.fileName);
FS_FreeFileList(dialog.filePaths);
dialog.filePaths = NULL;
ImGui::CloseCurrentPopup();
success = true;
}
}
else
{
if(ImGui::Button("Open") &&
dialog.selectedFileIndex >= 0 &&
dialog.selectedFileIndex < dialog.fileCount)
{
Com_sprintf(dialog.filePath, sizeof(dialog.filePath),
"%s/%s", dialog.folder, dialog.filePaths[dialog.selectedFileIndex]);
FS_FreeFileList(dialog.filePaths);
dialog.filePaths = NULL;
ImGui::CloseCurrentPopup();
success = true;
}
}
ImGui::SameLine();
if(ImGui::Button("Cancel"))
{
FS_FreeFileList(dialog.filePaths);
dialog.filePaths = NULL;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return success;
}
void SaveFileDialog_Open(const char* folder, const char* extension)
{
Q_assert(extension[0] == '.');
OpenFileDialog(saveFile, folder, extension);
}
bool SaveFileDialog_Do()
{
return DoFileDialog(saveFile);
}
const char* SaveFileDialog_GetPath()
{
return saveFile.filePath;
}
void OpenFileDialog_Open(const char* folder, const char* extension)
{
Q_assert(extension[0] == '.');
OpenFileDialog(openFile, folder, extension);
}
bool OpenFileDialog_Do()
{
return DoFileDialog(openFile);
}
const char* OpenFileDialog_GetPath()
{
return openFile.filePath;
}
void SaveFolderDialog_Open(const char* folder)
{
OpenFileDialog(saveFolder, folder, "/");
}
bool SaveFolderDialog_Do()
{
return DoFileDialog(saveFolder);
}
const char* SaveFolderDialog_GetPath()
{
return saveFolder.filePath;
}
void OpenFolderDialog_Open(const char* folder)
{
OpenFileDialog(openFolder, folder, "/");
}
bool OpenFolderDialog_Do()
{
return DoFileDialog(openFolder);
}
const char* OpenFolderDialog_GetPath()
{
return openFolder.filePath;
}
struct MainMenuItem
{
GUI_MainMenu::Id menu;
const char* name;
const char* shortcut;
bool* selected;
bool enabled;
};
struct MainMenu
{
MainMenuItem items[64];
int itemCount;
int itemCountPerMenu[GUI_MainMenu::Count]; // effectively a histogram
};
static MainMenu mm;
#define M(Enum, Desc) Desc,
static const char* mainMenuNames[GUI_MainMenu::Count + 1] =
{
MAIN_MENU_LIST(M)
""
};
#undef M
void GUI_AddMainMenuItem(GUI_MainMenu::Id menu, const char* name, const char* shortcut, bool* selected, bool enabled)
{
if(mm.itemCount >= ARRAY_LEN(mm.items) ||
(unsigned int)menu >= GUI_MainMenu::Count)
{
Q_assert(!"GUI_AddMainMenuItem: can't add menu entry");
return;
}
MainMenuItem& item = mm.items[mm.itemCount++];
item.menu = menu;
item.name = name;
item.shortcut = shortcut;
item.selected = selected;
item.enabled = enabled;
mm.itemCountPerMenu[menu]++;
}
void GUI_DrawMainMenu()
{
if(ImGui::BeginMainMenuBar())
{
for(int m = 0; m < GUI_MainMenu::Count; ++m)
{
if(mm.itemCountPerMenu[m] <= 0)
{
continue;
}
if(ImGui::BeginMenu(mainMenuNames[m]))
{
for(int i = 0; i < mm.itemCount; ++i)
{
const MainMenuItem& item = mm.items[i];
if(item.menu == m)
{
ImGui::MenuItem(item.name, item.shortcut, item.selected, item.enabled);
}
}
ImGui::EndMenu();
}
}
RE_DrawMainMenuBarInfo();
ImGui::EndMainMenuBar();
}
mm.itemCount = 0;
memset(mm.itemCountPerMenu, 0, sizeof(mm.itemCountPerMenu));
}
// applies a modified version of Jan Bielak's Deep Dark theme
static void ImGUI_ApplyTheme()
{
ImVec4* colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.19f, 0.19f, 0.19f, 0.92f);
colors[ImGuiCol_Border] = ImVec4(0.19f, 0.19f, 0.19f, 0.29f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.24f);
colors[ImGuiCol_FrameBg] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.19f, 0.19f, 0.19f, 0.54f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 0.54f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f);
colors[ImGuiCol_CheckMark] = ImVec4(0.49f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f);
colors[ImGuiCol_Button] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.19f, 0.19f, 0.19f, 0.54f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.40f, 0.44f, 0.46f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.00f, 0.00f, 0.00f, 0.36f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.40f, 0.44f, 0.46f, 0.33f);
colors[ImGuiCol_Separator] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.28f, 0.28f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.49f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.56f, 0.56f, 0.56f, 1.00f);
colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_TableBorderLight] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f);
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f);
colors[ImGuiCol_DragDropTarget] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f);
colors[ImGuiCol_NavHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(1.00f, 0.00f, 0.00f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.10f, 0.35f);
const ImVec4 hover(0.25f, 0.375f, 0.375f, 0.35f);
const ImVec4 active(0.25f, 0.50f, 0.50f, 0.35f);
colors[ImGuiCol_HeaderHovered] = hover;
colors[ImGuiCol_HeaderActive] = active;
colors[ImGuiCol_TabHovered] = hover;
colors[ImGuiCol_TabActive] = active;
colors[ImGuiCol_NavHighlight] = hover;
colors[ImGuiCol_Separator] = hover;
colors[ImGuiCol_SeparatorHovered] = hover;
colors[ImGuiCol_SeparatorActive] = hover;
const float brightness = 2.0f;
const float gamma = 1.3f;
for(int i = 0; i < ImGuiCol_COUNT; i++)
{
colors[i].x = min(powf(colors[i].x * brightness, gamma), 1.0f);
colors[i].y = min(powf(colors[i].y * brightness, gamma), 1.0f);
colors[i].z = min(powf(colors[i].z * brightness, gamma), 1.0f);
Q_assert(colors[i].x >= 0.0f && colors[i].x <= 1.0f);
Q_assert(colors[i].y >= 0.0f && colors[i].y <= 1.0f);
Q_assert(colors[i].z >= 0.0f && colors[i].z <= 1.0f);
Q_assert(colors[i].w >= 0.0f && colors[i].w <= 1.0f);
}
ImGuiStyle& style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.00f, 8.00f);
style.FramePadding = ImVec2(5.00f, 2.00f);
style.CellPadding = ImVec2(6.00f, 6.00f);
style.ItemSpacing = ImVec2(6.00f, 6.00f);
style.ItemInnerSpacing = ImVec2(6.00f, 6.00f);
style.TouchExtraPadding = ImVec2(0.00f, 0.00f);
style.IndentSpacing = 25;
style.ScrollbarSize = 15;
style.GrabMinSize = 10;
style.WindowBorderSize = 1;
style.ChildBorderSize = 1;
style.PopupBorderSize = 1;
style.FrameBorderSize = 1;
style.TabBorderSize = 1;
style.WindowRounding = 7;
style.ChildRounding = 4;
style.FrameRounding = 3;
style.PopupRounding = 4;
style.ScrollbarRounding = 9;
style.GrabRounding = 3;
style.LogSliderDeadzone = 4;
style.TabRounding = 4;
}
static void ToggleGui_f()
{
const bool guiActive = Cvar_VariableIntegerValue("r_debugUI") != 0;
const char* const newValue = guiActive ? "0" : "1";
Cvar_Set("r_debugUI", newValue);
Cvar_Set("r_debugInput", newValue);
}
static void ToggleGuiInput_f()
{
Cvar_Set("r_debugInput", Cvar_VariableIntegerValue("r_debugInput") ? "0" : "1");
}
static const cmdTableItem_t imgui_cmds[] =
{
{ "togglegui", &ToggleGui_f, NULL, "toggles the CNQ3 GUI" },
{ "toggleguiinput", &ToggleGuiInput_f, NULL, "toggles CNQ3 GUI input capture" }
};
static const char* GetClipboardText(void*)
{
return Sys_GetClipboardData();
}
static void SetClipboardText(void*, const char* text)
{
Sys_SetClipboardData(text);
}
static const ImWchar codepointRanges[] =
{
32, 126,
0
};
static void AddProggyCleanFont()
{
ImFontConfig config;
config.FontDataOwnedByAtlas = false;
config.OversampleH = 1;
config.OversampleV = 1;
config.PixelSnapH = true;
config.SizePixels = 13.0f;
Q_strncpyz(config.Name, "Proggy Clean (13px)", sizeof(config.Name));
ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(
ProggyClean_compressed_data, ProggyClean_compressed_size, config.SizePixels, &config, codepointRanges);
}
static void AddSweet16MonoFont()
{
ImFontConfig config;
config.FontDataOwnedByAtlas = false;
config.OversampleH = 1;
config.OversampleV = 1;
config.PixelSnapH = true;
config.SizePixels = 16.0f;
Q_strncpyz(config.Name, "Sweet16 Mono (16px)", sizeof(config.Name));
ImGui::GetIO().Fonts->AddFontFromMemoryCompressedBase85TTF(
Sweet16mono_compressed_data_base85, config.SizePixels, &config, codepointRanges);
}
static void AddCustomFont()
{
const char* const filePath = Cvar_VariableString("r_guiFontFile");
if(filePath == NULL || filePath[0] == '\0')
{
return;
}
const int height = Cvar_VariableIntegerValue("r_guiFontHeight");
const char* name = filePath;
for(int i = strlen(filePath) - 2; i > 0; i--)
{
if(filePath[i] == '/' || filePath[i] == '\\')
{
name = filePath + i + 1;
break;
}
}
ImFontConfig config;
config.FontDataOwnedByAtlas = false;
config.OversampleH = 1;
config.OversampleV = 1;
config.PixelSnapH = true;
config.SizePixels = height;
Com_sprintf(config.Name, sizeof(config.Name), "%s (%dpx)", name, height);
void* data = NULL;
const int dataSize = FS_ReadFile(filePath, &data);
if(data == NULL || dataSize <= 0)
{
Com_Printf("^3WARNING: failed to open font file: %s\n", filePath);
return;
}
ImGui::GetIO().Fonts->AddFontFromMemoryTTF(data, dataSize, config.SizePixels, &config, codepointRanges);
FS_FreeFile(data);
}
void CL_IMGUI_Init()
{
Cmd_RegisterArray(imgui_cmds, MODULE_CLIENT);
ImGui::CreateContext();
ImPlot::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
io.IniFilename = "cnq3/imgui.ini";
io.GetClipboardTextFn = &GetClipboardText;
io.SetClipboardTextFn = &SetClipboardText;
io.MouseDrawCursor = false; // just use the operating system's
AddProggyCleanFont();
AddSweet16MonoFont();
AddCustomFont();
const int fontIndex = Cvar_VariableIntegerValue("r_guiFont");
if(fontIndex >= 0 && fontIndex < io.Fonts->Fonts.Size)
{
io.FontDefault = io.Fonts->Fonts[fontIndex];
}
else
{
io.FontDefault = io.Fonts->Fonts[0];
Cvar_Set("r_guiFont", "0");
}
ImGUI_ApplyTheme();
memset(keyMap, 0xFF, sizeof(keyMap));
keyMap[K_CTRL] = ImGuiMod_Ctrl;
keyMap[K_ALT] = ImGuiMod_Alt;
keyMap[K_SHIFT] = ImGuiMod_Shift;
keyMap[K_TAB] = ImGuiKey_Tab;
keyMap[K_LEFTARROW] = ImGuiKey_LeftArrow;
keyMap[K_RIGHTARROW] = ImGuiKey_RightArrow;
keyMap[K_UPARROW] = ImGuiKey_UpArrow;
keyMap[K_DOWNARROW] = ImGuiKey_DownArrow;
keyMap[K_PGUP] = ImGuiKey_PageUp;
keyMap[K_PGDN] = ImGuiKey_PageDown;
keyMap[K_HOME] = ImGuiKey_Home;
keyMap[K_END] = ImGuiKey_End;
keyMap[K_INS] = ImGuiKey_Insert;
keyMap[K_DEL] = ImGuiKey_Delete;
keyMap[K_BACKSPACE] = ImGuiKey_Backspace;
keyMap[K_SPACE] = ImGuiKey_Space;
keyMap[K_ENTER] = ImGuiKey_Enter;
keyMap[K_ESCAPE] = ImGuiKey_Escape;
keyMap[K_CAPSLOCK] = ImGuiKey_CapsLock;
keyMap[K_PAUSE] = ImGuiKey_Pause;
keyMap[K_BACKSLASH] = ImGuiKey_Backslash;
keyMap[K_KP_INS] = ImGuiKey_Keypad0;
keyMap[K_KP_END] = ImGuiKey_Keypad1;
keyMap[K_KP_DOWNARROW] = ImGuiKey_Keypad2;
keyMap[K_KP_PGDN] = ImGuiKey_Keypad3;
keyMap[K_KP_LEFTARROW] = ImGuiKey_Keypad4;
keyMap[K_KP_5] = ImGuiKey_Keypad5;
keyMap[K_KP_RIGHTARROW] = ImGuiKey_Keypad6;
keyMap[K_KP_HOME] = ImGuiKey_Keypad7;
keyMap[K_KP_UPARROW] = ImGuiKey_Keypad8;
keyMap[K_KP_PGUP] = ImGuiKey_Keypad9;
keyMap[K_KP_ENTER] = ImGuiKey_KeyPadEnter;
keyMap[K_KP_SLASH] = ImGuiKey_KeypadDivide;
keyMap[K_KP_MINUS] = ImGuiKey_KeypadSubtract;
keyMap[K_KP_PLUS] = ImGuiKey_KeypadAdd;
keyMap[K_KP_STAR] = ImGuiKey_KeypadMultiply;
keyMap[K_KP_EQUALS] = ImGuiKey_KeypadEqual;
for(int i = 0; i < 26; ++i)
{
keyMap['a' + i] = ImGuiKey_A + i;
}
for(int i = 0; i < 10; ++i)
{
keyMap['0' + i] = ImGuiKey_0 + i;
}
for(int i = 0; i < 12; ++i)
{
keyMap[K_F1 + i] = ImGuiKey_F1 + i;
}
saveFile.saveMode = true;
saveFile.dialogName = "Save file";
openFile.dialogName = "Open file";
saveFolder.folderMode = true;
saveFolder.saveMode = true;
saveFolder.dialogName = "Save folder";
openFolder.folderMode = true;
openFolder.dialogName = "Open folder";
}
void CL_IMGUI_Frame()
{
if(Cvar_VariableIntegerValue("r_debugInput"))
{
cls.keyCatchers |= KEYCATCH_IMGUI;
}
else
{
cls.keyCatchers &= ~KEYCATCH_IMGUI;
}
static int64_t prevUS = 0;
const int64_t currUS = Sys_Microseconds();
const int64_t elapsedUS = currUS - prevUS;
prevUS = currUS;
int x, y;
Sys_GetCursorPosition(&x, &y);
re.ComputeCursorPosition(&x, &y);
ImGuiIO& io = ImGui::GetIO();
io.DeltaTime = (float)((double)elapsedUS / 1000000.0);
io.MousePos[0] = x;
io.MousePos[1] = y;
}
void CL_IMGUI_MouseEvent(int dx, int dy)
{
ImGui::GetIO().AddMousePosEvent(dx, dy);
}
qbool CL_IMGUI_KeyEvent(int key, qbool down, const char* cmd)
{
static bool shiftDown = false;
if(key == K_SHIFT)
{
shiftDown = down != qfalse;
}
if(down)
{
if(cmd != NULL)
{
const char* const prefix = "keycatchgui";
if(Q_stristr(cmd, prefix) == cmd)
{
Cbuf_AddText(cmd + strlen(prefix));
Cbuf_AddText("\n");
return qtrue;
}
}
}
if(cls.keyCatchers & KEYCATCH_IMGUI)
{
if(down && (key == '`' || key == '~'))
{
// continue displaying the GUI but route input to the console
Cvar_Set("r_debugInput", "0");
return qfalse;
}
unsigned int imguiKey;
ImGuiIO& io = ImGui::GetIO();
switch(key)
{
case K_MOUSE1:
case K_MOUSE2:
case K_MOUSE3:
case K_MOUSE4:
case K_MOUSE5:
io.AddMouseButtonEvent(key - K_MOUSE1, !!down);
break;
case K_MWHEELDOWN:
case K_MWHEELUP:
io.AddMouseWheelEvent(0.0f, key == K_MWHEELDOWN ? -1.0f : 1.0f);
break;
default:
imguiKey = (unsigned int)keyMap[key];
if(imguiKey != 0xFFFFFFFF)
{
io.AddKeyEvent((ImGuiKey)imguiKey, !!down);
}
break;
}
if(!io.WantCaptureMouse)
{
Im3d::AppData& ad = Im3d::GetAppData();
switch(key)
{
case K_MOUSE1:
ad.m_keyDown[Im3d::Action_Select] = !!down;
break;
case 'l':
ad.m_keyDown[Im3d::Action_GizmoLocal] = shiftDown && !!down;
break;
case 't':
ad.m_keyDown[Im3d::Action_GizmoTranslation] = shiftDown && !!down;
break;
case 'r':
ad.m_keyDown[Im3d::Action_GizmoRotation] = shiftDown && !!down;
break;
case 's':
ad.m_keyDown[Im3d::Action_GizmoScale] = shiftDown && !!down;
break;
default:
break;
}
ad.m_snapTranslation = shiftDown ? 4.0f : 0.0f;
ad.m_snapRotation = shiftDown ? DEG2RAD(30.0f) : 0.0f;
ad.m_snapScale = shiftDown ? 0.5f : 0.0f;
}
return qtrue;
}
return qfalse;
}
void CL_IMGUI_CharEvent(char key)
{
ImGui::GetIO().AddInputCharacter(key);
}
void CL_IMGUI_Shutdown()
{
ImPlot::DestroyContext();
ImGui::DestroyContext();
Cmd_UnregisterArray(imgui_cmds);
}
qbool CL_IMGUI_IsCustomFontLoaded(const char** debugName)
{
if(ImGui::GetIO().Fonts->Fonts.Size != 3)
{
return qfalse;
}
if(debugName != NULL)
{
*debugName = ImGui::GetIO().Fonts->Fonts[2]->GetDebugName();
}
return qtrue;
}