2024-03-27 00:02:03 +00:00
2024-05-22 14:39:37 +00:00
# define IMGUI_DEFINE_MATH_OPERATORS
2024-04-06 02:03:22 +00:00
# include <SDL.h>
2024-03-27 00:02:03 +00:00
# include "sys_imgui.h"
2024-04-06 02:03:22 +00:00
# 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
2024-03-27 00:02:03 +00:00
# include "../libs/imgui/backends/imgui_impl_opengl2.h"
# include "../libs/imgui/backends/imgui_impl_sdl2.h"
# include "framework/Common.h"
2024-05-22 14:39:37 +00:00
# include "framework/KeyInput.h"
2024-03-27 00:02:03 +00:00
# include "renderer/qgl.h"
2024-03-29 00:16:30 +00:00
# include "renderer/tr_local.h" // glconfig
2024-03-27 00:02:03 +00:00
2024-04-05 04:33:33 +00:00
extern void Com_DrawDhewm3SettingsMenu ( ) ; // in framework/dhewm3SettingsMenu.cpp
2024-03-27 00:02:03 +00:00
2024-04-19 10:30:37 +00:00
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?
2024-03-27 00:02:03 +00:00
namespace D3 {
namespace ImGuiHooks {
2024-04-19 10:30:37 +00:00
# include "proggyvector_font.h"
2024-04-06 02:03:22 +00:00
static SDL_Window * sdlWindow = NULL ;
2024-03-27 00:02:03 +00:00
ImGuiContext * imguiCtx = NULL ;
static bool haveNewFrame = false ;
2024-04-05 04:33:33 +00:00
static int openImguiWindows = 0 ; // or-ed enum D3ImGuiWindow values
2024-05-22 14:39:37 +00:00
// 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 ;
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 ( ) ;
}
2024-04-19 10:30:37 +00:00
static float GetDefaultDPI ( )
2024-04-06 02:03:22 +00:00
{
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 ;
}
2024-04-19 10:30:37 +00:00
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 ;
2024-03-27 00:02:03 +00:00
// using void* instead of SDL_Window and SDL_GLContext to avoid dragging SDL headers into sys_imgui.h
2024-04-06 02:03:22 +00:00
bool Init ( void * _sdlWindow , void * sdlGlContext )
2024-03-27 00:02:03 +00:00
{
common - > Printf ( " Initializing ImGui \n " ) ;
2024-04-19 10:30:37 +00:00
sdlWindow = ( SDL_Window * ) _sdlWindow ;
2024-04-06 02:03:22 +00:00
2024-03-27 00:02:03 +00:00
// 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 " ) ;
2024-03-27 00:02:03 +00:00
return false ;
}
ImGuiIO & io = ImGui : : GetIO ( ) ; ( void ) io ;
io . ConfigFlags | = ImGuiConfigFlags_NavEnableKeyboard ; // Enable Keyboard Controls
io . ConfigFlags | = ImGuiConfigFlags_NavEnableGamepad ; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui : : StyleColorsDark ( ) ;
//ImGui::StyleColorsLight();
2024-03-29 00:16:30 +00:00
//ImGui::StyleColorsClassic();
// make it a bit prettier with rounded edges
ImGuiStyle & style = ImGui : : GetStyle ( ) ;
style . WindowRounding = 2.0f ;
style . FrameRounding = 3.0f ;
//style.ChildRounding = 6.0f;
style . ScrollbarRounding = 8.0f ;
style . GrabRounding = 1.0f ;
style . PopupRounding = 2.0f ;
ImVec4 * colors = style . Colors ;
colors [ ImGuiCol_TitleBg ] = ImVec4 ( 0.28f , 0.36f , 0.48f , 0.88f ) ;
2024-04-05 04:33:33 +00:00
colors [ ImGuiCol_TabHovered ] = ImVec4 ( 0.42f , 0.69f , 1.00f , 0.80f ) ;
colors [ ImGuiCol_TabActive ] = ImVec4 ( 0.24f , 0.51f , 0.83f , 1.00f ) ;
2024-04-19 10:30:37 +00:00
ImFontConfig fontCfg ;
strcpy ( fontCfg . Name , " ProggyVector " ) ;
ImFont * font = io . Fonts - > AddFontFromMemoryCompressedTTF ( ProggyVector_compressed_data , ProggyVector_compressed_size , 18.0f , nullptr ) ;
SetScale ( GetScale ( ) ) ;
2024-03-27 00:02:03 +00:00
// Setup Platform/Renderer backends
2024-04-19 10:30:37 +00:00
if ( ! ImGui_ImplSDL2_InitForOpenGL ( sdlWindow , sdlGlContext ) ) {
2024-03-29 00:16:30 +00:00
ImGui : : DestroyContext ( imguiCtx ) ;
2024-03-27 00:02:03 +00:00
imguiCtx = NULL ;
2024-03-29 00:16:30 +00:00
common - > Warning ( " Failed to initialize ImGui SDL platform backend! \n " ) ;
2024-03-27 00:02:03 +00:00
return false ;
}
2024-03-29 00:16:30 +00:00
if ( ! ImGui_ImplOpenGL2_Init ( ) ) {
2024-03-27 00:02:03 +00:00
ImGui_ImplSDL2_Shutdown ( ) ;
2024-03-29 00:16:30 +00:00
ImGui : : DestroyContext ( imguiCtx ) ;
2024-03-27 00:02:03 +00:00
imguiCtx = NULL ;
2024-03-29 00:16:30 +00:00
common - > Warning ( " Failed to initialize ImGui OpenGL renderer backend! \n " ) ;
2024-03-27 00:02:03 +00:00
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);
2024-05-23 00:11:54 +00:00
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 " ) ;
}
2024-04-19 10:30:37 +00:00
imgui_initialized = true ;
2024-03-27 00:02:03 +00:00
return true ;
}
void Shutdown ( )
{
2024-04-19 10:30:37 +00:00
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 ;
}
2024-03-27 00:02:03 +00:00
}
2024-04-05 04:33:33 +00:00
// NewFrame() is called once per D3 frame, after all events have been gotten
// => ProcessEvent() has already been called (probably multiple times)
2024-03-27 00:02:03 +00:00
void NewFrame ( )
{
2024-04-05 04:33:33 +00:00
if ( openImguiWindows = = 0 )
return ;
2024-03-27 00:02:03 +00:00
// Start the Dear ImGui frame
ImGui_ImplOpenGL2_NewFrame ( ) ;
ImGui_ImplSDL2_NewFrame ( ) ;
ImGui : : NewFrame ( ) ;
haveNewFrame = true ;
2024-05-22 14:39:37 +00:00
UpdateWarningOverlay ( ) ;
2024-04-05 04:33:33 +00:00
if ( openImguiWindows & D3_ImGuiWin_Settings ) {
Com_DrawDhewm3SettingsMenu ( ) ;
2024-03-27 00:02:03 +00:00
}
2024-04-05 04:33:33 +00:00
if ( openImguiWindows & D3_ImGuiWin_Demo ) {
bool show_demo_window = true ;
ImGui : : ShowDemoWindow ( & show_demo_window ) ;
if ( ! show_demo_window )
CloseWindow ( D3_ImGuiWin_Demo ) ;
2024-03-27 00:02:03 +00:00
}
}
2024-05-22 14:39:37 +00:00
bool keybindModeEnabled = false ;
2024-04-05 04:33:33 +00:00
// called with every SDL event by Sys_GetEvent()
// returns true if ImGui has handled the event (so it shouldn't be handled by D3)
2024-03-27 00:02:03 +00:00
bool ProcessEvent ( const void * sdlEvent )
{
2024-04-05 04:33:33 +00:00
if ( openImguiWindows = = 0 )
2024-04-19 10:30:37 +00:00
return false ;
2024-04-05 04:33:33 +00:00
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.
2024-05-22 14:39:37 +00:00
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-05-22 14:39:37 +00:00
2024-03-29 00:16:30 +00:00
if ( io . WantCaptureMouse ) {
switch ( ev - > type ) {
case SDL_MOUSEMOTION :
case SDL_MOUSEWHEEL :
case SDL_MOUSEBUTTONDOWN :
2024-05-22 14:39:37 +00:00
// 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 ;
2024-05-22 14:39:37 +00:00
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-05-22 14:39:37 +00:00
2024-03-29 00:16:30 +00:00
}
2024-05-22 14:39:37 +00:00
2024-03-29 00:16:30 +00:00
return false ;
2024-03-27 00:02:03 +00:00
}
2024-05-22 14:39:37 +00:00
void SetKeyBindMode ( bool enable )
{
keybindModeEnabled = enable ;
// make sure no keys are registered as down, neither when entering nor when exiting keybind mode
idKeyInput : : ClearStates ( ) ;
}
2024-03-27 00:02:03 +00:00
void EndFrame ( )
{
2024-04-05 04:33:33 +00:00
if ( openImguiWindows = = 0 & & ! haveNewFrame )
return ;
2024-03-27 00:02:03 +00:00
// 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 ) {
2024-03-27 00:02:03 +00:00
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
2024-03-27 00:02:03 +00:00
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-27 00:02:03 +00:00
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-27 00:02:03 +00:00
2024-03-29 00:16:30 +00:00
ImGui_ImplOpenGL2_RenderDrawData ( ImGui : : GetDrawData ( ) ) ;
2024-03-27 00:02:03 +00:00
2024-03-29 00:16:30 +00:00
if ( curArrayBuffer ! = 0 ) {
2024-03-27 00:02:03 +00:00
qglBindBufferARB ( GL_ARRAY_BUFFER_ARB , curArrayBuffer ) ;
}
2024-05-22 14:39:37 +00:00
// reset this at the end of each frame, will be set again by ProcessEvent()
if ( hadKeyDownEvent ) {
hadKeyDownEvent = false ;
}
2024-03-27 00:02:03 +00:00
}
2024-04-05 04:33:33 +00:00
void OpenWindow ( D3ImGuiWindow win )
{
openImguiWindows | = win ;
}
void CloseWindow ( D3ImGuiWindow win )
{
openImguiWindows & = ~ win ;
}
2024-04-19 10:30:37 +00:00
int GetOpenWindowsMask ( )
{
return openImguiWindows ;
}
2024-03-27 00:02:03 +00:00
} } //namespace D3::ImGuiHooks