dhewm3/neo/sys/sys_imgui.cpp

543 lines
17 KiB
C++
Raw Normal View History

#define IMGUI_DEFINE_MATH_OPERATORS
#include <SDL.h>
#include "sys_imgui.h"
#ifdef D3_SDL_X11
#include <dlfcn.h>
#include <SDL_syswm.h>
//char *XGetDefault(Display* display, const char* program, const char* option)
typedef char* (*MY_XGETDEFAULTFUN)(Display*, const char*, const char*);
#endif
#include "../libs/imgui/backends/imgui_impl_opengl2.h"
#include "../libs/imgui/backends/imgui_impl_sdl2.h"
#include "framework/Common.h"
#include "framework/KeyInput.h"
#include "renderer/qgl.h"
2024-03-29 00:16:30 +00:00
#include "renderer/tr_local.h" // glconfig
extern void Com_DrawDhewm3SettingsMenu(); // in framework/dhewm3SettingsMenu.cpp
static idCVar imgui_scale( "imgui_scale", "-1.0", CVAR_SYSTEM|CVAR_FLOAT|CVAR_ARCHIVE, "factor to scale ImGUI menus by (-1: auto)" ); // TODO: limit values?
idCVar imgui_style( "imgui_style", "0", CVAR_SYSTEM|CVAR_INTEGER|CVAR_ARCHIVE, "Which ImGui style to use. 0: Dhewm3 theme, 1: Default ImGui theme, 2: User theme", 0.0f, 2.0f );
// implemented in imgui_savestyle.cpp
namespace DG {
// writes the given ImGuiStyle to the given filename (opened with fopen())
// returns true on success, false if opening the file failed
extern bool WriteImGuiStyle( const ImGuiStyle& style, const char* filename );
// reads the the given filename (opened with fopen())
// and sets the given ImGuiStyle accordingly.
// if any attributes/colors/behaviors are missing the the file,
// they are not modified in style, so it probably makes sense to initialize
// style to a sane default before calling this function.
// returns true on success, false if opening the file failed
extern bool ReadImGuiStyle( ImGuiStyle& style, const char* filename );
// generate C++ code that replicates the given style into a text buffer
// (that you can write to a file or set the clipboard from or whatever)
// if refStyle is set, only differences in style compared to refStyle are written
extern ImGuiTextBuffer WriteImGuiStyleToCode( const ImGuiStyle& style, const ImGuiStyle* refStyle = nullptr );
} //namespace DG
namespace D3 {
namespace ImGuiHooks {
#include "proggyvector_font.h"
static SDL_Window* sdlWindow = NULL;
ImGuiContext* imguiCtx = NULL;
static bool haveNewFrame = false;
static int openImguiWindows = 0; // or-ed enum D3ImGuiWindow values
static ImGuiStyle userStyle;
// was there a key down or button (mouse/gamepad) down event this frame?
// used to make the warning overlay disappear
static bool hadKeyDownEvent = false;
static idStr warningOverlayText;
static double warningOverlayStartTime = -100.0;
static ImVec2 warningOverlayStartPos;
idStr GetUserStyleFilename()
{
// TODO: put this into the config dir
return idStr( "user.imstyle" );
}
static void UpdateWarningOverlay()
{
double timeNow = ImGui::GetTime();
if ( timeNow - warningOverlayStartTime > 4.0f ) {
warningOverlayStartTime = -100.0f;
return;
}
// also hide if a key was pressed or maybe even if the mouse was moved (too much)
ImVec2 mdv = ImGui::GetMousePos() - warningOverlayStartPos; // Mouse Delta Vector
float mouseDelta = sqrtf( mdv.x * mdv.x + mdv.y * mdv.y );
const float fontSize = ImGui::GetFontSize();
if ( mouseDelta > fontSize * 4.0f || hadKeyDownEvent ) {
warningOverlayStartTime = -100.0f;
return;
}
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::PushStyleColor( ImGuiCol_WindowBg, ImVec4(1.0f, 0.4f, 0.4f, 0.4f) );
float padSize = fontSize * 2.0f;
ImGui::PushStyleVar( ImGuiStyleVar_WindowPadding, ImVec2(padSize, padSize) );
int winFlags = ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize;
ImGui::Begin("WarningOverlay", NULL, winFlags);
ImDrawList* drawList = ImGui::GetWindowDrawList();
ImVec2 points[] = {
{0, 40}, {40, 40}, {20, 0}, // triangle
{20, 12}, {20, 28}, // line
{20, 33} // dot
};
float iconScale = 1.0f; // TODO: global scale also used for fontsize
ImVec2 offset = ImGui::GetWindowPos() + ImVec2(fontSize, fontSize);
for ( ImVec2& v : points ) {
v.x = roundf( v.x * iconScale );
v.y = roundf( v.y * iconScale );
v += offset;
}
ImU32 color = ImGui::GetColorU32( ImVec4(0.1f, 0.1f, 0.1f, 1.0f) );
drawList->AddTriangle( points[0], points[1], points[2], color, roundf( iconScale * 4.0f ) );
drawList->AddPolyline( points+3, 2, color, 0, roundf( iconScale * 3.0f ) );
float dotRadius = 2.0f * iconScale;
drawList->AddEllipseFilled( points[5], ImVec2(dotRadius, dotRadius), color, 0, 6 );
ImGui::Indent( 40.0f * iconScale );
ImGui::TextUnformatted( warningOverlayText.c_str() );
ImGui::End();
ImGui::PopStyleVar(); // WindowPadding
ImGui::PopStyleColor(); // WindowBg
}
void ShowWarningOverlay( const char* text )
{
warningOverlayText = text;
warningOverlayStartTime = ImGui::GetTime();
warningOverlayStartPos = ImGui::GetMousePos();
}
static float GetDefaultDPI()
{
SDL_Window* win = sdlWindow;
float dpi = -1.0f;
#ifdef D3_SDL_X11
SDL_SysWMinfo wmInfo = {};
SDL_VERSION(&wmInfo.version)
if(SDL_GetWindowWMInfo(win, &wmInfo) && wmInfo.subsystem == SDL_SYSWM_X11) {
Display* display = wmInfo.info.x11.display;
static void* libX11 = NULL;
if(libX11 == NULL) {
libX11 = dlopen("libX11.so.6", RTLD_LAZY);
}
if(libX11 == NULL) {
libX11 = dlopen("libX11.so", RTLD_LAZY);
}
if(libX11 != NULL) {
MY_XGETDEFAULTFUN my_xgetdefault = (MY_XGETDEFAULTFUN)dlsym(libX11, "XGetDefault");
if(my_xgetdefault != NULL) {
//char *XGetDefault(Display* display, const char* program, const char* option)
const char* dpiStr = my_xgetdefault(display, "Xft", "dpi");
printf("XX dpistr = '%s'\n", dpiStr);
if(dpiStr != NULL) {
dpi = atof(dpiStr);
}
}
}
}
if (dpi == -1.0f)
#endif
{
int winIdx = SDL_GetWindowDisplayIndex( win );
if (winIdx >= 0) {
SDL_GetDisplayDPI(winIdx, NULL, &dpi, NULL);
}
}
return dpi;
}
static float GetDefaultScale()
{
float ret = GetDefaultDPI() / 96.0f;
ret = round(ret*2.0)*0.5; // round to .0 or .5
return ret;
}
float GetScale()
{
float ret = imgui_scale.GetFloat();
if (ret < 0.0f) {
ret = GetDefaultScale();
}
return ret;
}
void SetScale( float scale )
{
ImGuiIO& io = ImGui::GetIO();
float realScale = (scale < 0.0f) ? GetDefaultScale() : scale;
io.FontGlobalScale = realScale;
// TODO: could instead set fontsize to 18.0f * scale
// (io.Fonts->ClearFonts() and then add again with new size - but must be done before NewFrame() / after EndFrame())
imgui_scale.SetFloat( scale );
}
static bool imgui_initialized = false;
// using void* instead of SDL_Window and SDL_GLContext to avoid dragging SDL headers into sys_imgui.h
bool Init(void* _sdlWindow, void* sdlGlContext)
{
common->Printf( "Initializing ImGui\n" );
sdlWindow = (SDL_Window*)_sdlWindow;
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
imguiCtx = ImGui::CreateContext();
2024-03-29 00:16:30 +00:00
if ( imguiCtx == NULL ) {
common->Warning( "Failed to create ImGui Context!\n" );
return false;
}
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
SetImGuiStyle( Style::Dhewm3 );
userStyle = ImGui::GetStyle(); // set dhewm3 style as default, in case the user style is missing values
if ( DG::ReadImGuiStyle( userStyle, GetUserStyleFilename() ) && imgui_style.GetInteger() == 2 ) {
ImGui::GetStyle() = userStyle;
} else if ( imgui_style.GetInteger() == 1 ) {
ImGui::GetStyle() = ImGuiStyle();
ImGui::StyleColorsDark();
}
ImFontConfig fontCfg;
strcpy(fontCfg.Name, "ProggyVector");
ImFont* font = io.Fonts->AddFontFromMemoryCompressedTTF(ProggyVector_compressed_data, ProggyVector_compressed_size, 18.0f, nullptr);
SetScale( GetScale() );
// Setup Platform/Renderer backends
if ( ! ImGui_ImplSDL2_InitForOpenGL( sdlWindow, sdlGlContext ) ) {
2024-03-29 00:16:30 +00:00
ImGui::DestroyContext( imguiCtx );
imguiCtx = NULL;
2024-03-29 00:16:30 +00:00
common->Warning( "Failed to initialize ImGui SDL platform backend!\n" );
return false;
}
2024-03-29 00:16:30 +00:00
if ( ! ImGui_ImplOpenGL2_Init() ) {
ImGui_ImplSDL2_Shutdown();
2024-03-29 00:16:30 +00:00
ImGui::DestroyContext( imguiCtx );
imguiCtx = NULL;
2024-03-29 00:16:30 +00:00
common->Warning( "Failed to initialize ImGui OpenGL renderer backend!\n" );
return false;
}
// Load Fonts
// - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
// - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
// - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
// - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
// - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering.
// - Read 'docs/FONTS.md' for more instructions and details.
// - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
//io.Fonts->AddFontDefault();
//io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());
//IM_ASSERT(font != nullptr);
const char* f10bind = idKeyInput::GetBinding( K_F10 );
if ( f10bind && f10bind[0] != '\0' ) {
if ( idStr::Icmp( f10bind, "dhewm3Settings" ) != 0 ) {
// if F10 is already bound, but not to dhewm3Settings, show a message
common->Printf( "... the F10 key is already bound to '%s', otherwise it could be used to open the dhewm3 Settings Menu\n" , f10bind );
}
} else {
idKeyInput::SetBinding( K_F10, "dhewm3Settings" );
}
imgui_initialized = true;
return true;
}
void Shutdown()
{
if ( imgui_initialized ) {
common->Printf( "Shutting down ImGui\n" );
// TODO: only if init was successful!
ImGui_ImplOpenGL2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext( imguiCtx );
imgui_initialized = false;
}
}
// NewFrame() is called once per D3 frame, after all events have been gotten
// => ProcessEvent() has already been called (probably multiple times)
void NewFrame()
{
if (openImguiWindows == 0)
return;
// Start the Dear ImGui frame
ImGui_ImplOpenGL2_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
haveNewFrame = true;
UpdateWarningOverlay();
if (openImguiWindows & D3_ImGuiWin_Settings) {
Com_DrawDhewm3SettingsMenu();
}
if (openImguiWindows & D3_ImGuiWin_Demo) {
bool show_demo_window = true;
ImGui::ShowDemoWindow(&show_demo_window);
if(!show_demo_window)
CloseWindow(D3_ImGuiWin_Demo);
}
}
bool keybindModeEnabled = false;
// called with every SDL event by Sys_GetEvent()
// returns true if ImGui has handled the event (so it shouldn't be handled by D3)
bool ProcessEvent(const void* sdlEvent)
{
if (openImguiWindows == 0)
return false;
2024-03-29 00:16:30 +00:00
const SDL_Event* ev = (const SDL_Event*)sdlEvent;
// ImGui_ImplSDL2_ProcessEvent() doc says:
// 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.
bool imguiUsedEvent = ImGui_ImplSDL2_ProcessEvent( ev );
if ( keybindModeEnabled ) {
// in keybind mode, all input events are passed to Doom3 so it can translate them
// to internal events and we can access and use them to create a new binding
return false;
}
if ( ImGui::IsWindowFocused( ImGuiFocusedFlags_AnyWindow )
&& (ev->type == SDL_CONTROLLERAXISMOTION || ev->type == SDL_CONTROLLERBUTTONDOWN) ) {
// don't pass on controller axis events to avoid moving the mouse cursor
// and controller button events to avoid emulating mouse clicks
return true;
}
switch( ev->type ) {
// Hack: detect if any key was pressed to close the warning overlay
case SDL_CONTROLLERBUTTONDOWN:
case SDL_MOUSEWHEEL:
case SDL_MOUSEBUTTONDOWN:
case SDL_KEYDOWN:
// TODO: controller trigger?
hadKeyDownEvent = true;
}
if( imguiUsedEvent ) {
2024-03-29 00:16:30 +00:00
ImGuiIO& io = ImGui::GetIO();
2024-03-29 00:16:30 +00:00
if ( io.WantCaptureMouse ) {
switch( ev->type ) {
case SDL_MOUSEMOTION:
case SDL_MOUSEWHEEL:
case SDL_MOUSEBUTTONDOWN:
// Note: still pass button up events to the engine, so if they were pressed down
// before an imgui window got focus they don't remain pressed indefinitely
//case SDL_MOUSEBUTTONUP:
2024-03-29 00:16:30 +00:00
return true;
}
}
if ( io.WantCaptureKeyboard ) {
switch( ev->type ) {
case SDL_TEXTINPUT:
return true;
case SDL_KEYDOWN:
//case SDL_KEYUP: NOTE: see above why key up events are passed to the engine
if ( ev->key.keysym.sym < SDLK_F1 || ev->key.keysym.sym > SDLK_F12) {
// F1 - F12 are passed to the engine so its shortcuts
// (like quickload or screenshot) still work
// Doom3's menu does the same
return true;
}
2024-03-29 00:16:30 +00:00
}
}
2024-03-29 00:16:30 +00:00
}
2024-03-29 00:16:30 +00:00
return false;
}
void SetKeyBindMode( bool enable )
{
keybindModeEnabled = enable;
// make sure no keys are registered as down, neither when entering nor when exiting keybind mode
idKeyInput::ClearStates();
}
void EndFrame()
{
if (openImguiWindows == 0 && !haveNewFrame)
return;
// I think this can happen if we're not coming from idCommon::Frame() but screenshot or sth
2024-03-29 00:16:30 +00:00
if ( !haveNewFrame ) {
NewFrame();
}
haveNewFrame = false;
ImGui::Render();
2024-03-29 00:16:30 +00:00
// Doom3 uses the OpenGL ARB shader extensions, for most things it renders.
// disable those shaders, the OpenGL classic integration of ImGui doesn't use shaders
qglDisable( GL_VERTEX_PROGRAM_ARB );
qglDisable( GL_FRAGMENT_PROGRAM_ARB );
2024-03-29 00:16:30 +00:00
// Doom3 uses OpenGL's ARB_vertex_buffer_object extension to use VBOs on the GPU
// as buffers for glDrawElements() (instead of passing userspace buffers to that function)
// ImGui however uses userspace buffers, so remember the currently bound VBO
// and unbind it (after drawing, bind it again)
GLint curArrayBuffer = 0;
if ( glConfig.ARBVertexBufferObjectAvailable ) {
qglGetIntegerv( GL_ARRAY_BUFFER_BINDING_ARB, &curArrayBuffer );
qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
}
2024-03-29 00:16:30 +00:00
// disable all texture units, ImGui_ImplOpenGL2_RenderDrawData() will enable texture 0
// and bind its own textures to it as needed
for ( int i = glConfig.maxTextureUnits - 1 ; i >= 0 ; i-- ) {
GL_SelectTexture( i );
qglDisable( GL_TEXTURE_2D );
if ( glConfig.texture3DAvailable ) {
qglDisable( GL_TEXTURE_3D );
}
if ( glConfig.cubeMapAvailable ) {
qglDisable( GL_TEXTURE_CUBE_MAP_EXT );
}
}
2024-03-29 00:16:30 +00:00
ImGui_ImplOpenGL2_RenderDrawData( ImGui::GetDrawData() );
2024-03-29 00:16:30 +00:00
if ( curArrayBuffer != 0 ) {
qglBindBufferARB( GL_ARRAY_BUFFER_ARB, curArrayBuffer );
}
// reset this at the end of each frame, will be set again by ProcessEvent()
if ( hadKeyDownEvent ) {
hadKeyDownEvent = false;
}
}
void OpenWindow( D3ImGuiWindow win )
{
openImguiWindows |= win;
}
void CloseWindow( D3ImGuiWindow win )
{
openImguiWindows &= ~win;
}
int GetOpenWindowsMask()
{
return openImguiWindows;
}
ImGuiStyle GetImGuiStyle( Style d3style )
{
ImGuiStyle style; // default style
if ( d3style == Style::Dhewm3 ) {
// make it look a bit nicer with rounded edges
style.WindowRounding = 2.0f;
style.FrameRounding = 3.0f;
//style.ChildRounding = 6.0f;
style.ScrollbarRounding = 8.0f;
style.GrabRounding = 1.0f;
style.PopupRounding = 2.0f;
SetDhewm3StyleColors( &style );
} else if ( d3style == Style::User ) {
style = userStyle;
} else {
assert( d3style == Style::ImGui_Default && "invalid/unknown style" );
ImGui::StyleColorsDark( &style );
}
return style;
}
void SetImGuiStyle( Style d3style )
{
ImGui::GetStyle() = GetImGuiStyle( d3style );
}
void SetDhewm3StyleColors( ImGuiStyle* dst )
{
if ( dst == nullptr )
dst = &ImGui::GetStyle();
ImGui::StyleColorsDark( dst );
ImVec4* colors = dst->Colors;
colors[ImGuiCol_TitleBg] = ImVec4(0.28f, 0.36f, 0.48f, 0.88f);
colors[ImGuiCol_TabHovered] = ImVec4(0.42f, 0.69f, 1.00f, 0.80f);
colors[ImGuiCol_TabActive] = ImVec4(0.24f, 0.51f, 0.83f, 1.00f);
}
void SetUserStyleColors()
{
ImGuiStyle& style = ImGui::GetStyle();
for ( int i=0; i < ImGuiCol_COUNT; ++i ) {
style.Colors[i] = userStyle.Colors[i];
}
}
bool WriteUserStyle()
{
userStyle = ImGui::GetStyle();
if( !DG::WriteImGuiStyle( ImGui::GetStyle(), GetUserStyleFilename() ) ) {
common->Warning( "Couldn't write ImGui userstyle!\n" );
return false;
}
return true;
}
void CopyCurrentStyle( bool onlyChanges )
{
ImGuiStyle refStyle = GetImGuiStyle( Style::ImGui_Default );
ImGuiTextBuffer buf = DG::WriteImGuiStyleToCode( ImGui::GetStyle(), onlyChanges ? &refStyle : nullptr );
Sys_SetClipboardData( buf.c_str() );
}
}} //namespace D3::ImGuiHooks