mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-10 06:31:48 +00:00
853 lines
22 KiB
C++
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;
|
|
}
|