diff --git a/game/client/cdll_client_int.cpp b/game/client/cdll_client_int.cpp index ab98663a..ef72e10e 100644 --- a/game/client/cdll_client_int.cpp +++ b/game/client/cdll_client_int.cpp @@ -87,6 +87,7 @@ #include "ihudlcd.h" #include "toolframework_client.h" #include "hltvcamera.h" +#include "imgui/imgui_system.h" #if defined( REPLAY_ENABLED ) #include "replay/replaycamera.h" #include "replay/replay_ragdoll.h" @@ -1101,6 +1102,8 @@ int CHLClient::Init( CreateInterfaceFn appSystemFactory, CreateInterfaceFn physi HookHapticMessages(); // Always hook the messages #endif + g_pImguiSystem->Init(); + return true; } @@ -1180,6 +1183,8 @@ void CHLClient::Shutdown( void ) g_pAchievementsAndStatsInterface->ReleasePanel(); } + g_pImguiSystem->Shutdown(); + #ifdef SIXENSE g_pSixenseInput->Shutdown(); delete g_pSixenseInput; diff --git a/game/client/client_base.vpc b/game/client/client_base.vpc index 6dd0a34c..94b81329 100644 --- a/game/client/client_base.vpc +++ b/game/client/client_base.vpc @@ -14,6 +14,7 @@ $Macro GENERATED_PROTO_DIR "$SRCDIR\game\client\generated_proto_$GAMENAME" $MacroRequired "GAMENAME" $Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" +$Include "$SRCDIR\game\client\imgui\imgui_system.vpc" $include "$SRCDIR\vpc_scripts\protobuf_builder.vpc" $Include "$SRCDIR\vpc_scripts\source_replay.vpc" [$TF] @@ -51,7 +52,7 @@ $Configuration $Compiler { $AdditionalIncludeDirectories ".\;$BASE;$SRCDIR\vgui2\include;$SRCDIR\vgui2\controls;$SRCDIR\game\shared;.\game_controls;$SRCDIR\thirdparty\sixensesdk\include" - $PreprocessorDefinitions "$BASE;GLOWS_ENABLE;MAPBASE_VSCRIPT;NO_STRING_T;CLIENT_DLL;VECTOR;VERSION_SAFE_STEAM_API_INTERFACES;PROTECTED_THINGS_ENABLE;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead" + $PreprocessorDefinitions "$BASE;GLOWS_ENABLE;MAPBASE_VSCRIPT;NO_STRING_T;CLIENT_DLL;VECTOR;VERSION_SAFE_STEAM_API_INTERFACES;PROTECTED_THINGS_ENABLE;" $PreprocessorDefinitions "$BASE;ENABLE_CHROMEHTMLWINDOW;fopen=dont_use_fopen" [$WIN32] $PreprocessorDefinitions "$BASE;ENABLE_CHROMEHTMLWINDOW;" [$OSXALL] $PreprocessorDefinitions "$BASE;ENABLE_CHROMEHTMLWINDOW;USE_WEBM_FOR_REPLAY;" [$LINUXALL] diff --git a/game/client/imgui/imconfig_source.h b/game/client/imgui/imconfig_source.h new file mode 100644 index 00000000..e5b9e020 --- /dev/null +++ b/game/client/imgui/imconfig_source.h @@ -0,0 +1,60 @@ +#pragma once + +//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. + +// Use tier0's asserts +#include "tier0/dbg.h" + +#undef IM_ASSERT +#define IM_ASSERT Assert + +// We provide our own allocators. +#define IMGUI_DISABLE_DEFAULT_ALLOCATORS + + +// We do not define Dear ImGui's api to cross dll boundaries. +// Instead, everything that wants to use Dear ImGui can link devui_static.lib. +#define IMGUI_API + +// Disable obsolete APIs. No need to make more work for ourselves. +#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS +#define IMGUI_DISABLE_OBSOLETE_KEYIO + +// Let's not link win32 for everything we include Dear ImGui with... +#define IMGUI_DISABLE_WIN32_FUNCTIONS + +// We need to disable Dear ImGui's file functions, so we can pass them to Source's filesystem. Otherwise, we won't be able to access VPKs +#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS +using ImFileHandle = void*; +IMGUI_API ImFileHandle ImFileOpen( const char *filename, const char *mode ); +IMGUI_API bool ImFileClose( ImFileHandle file ); +IMGUI_API uint64 ImFileGetSize( ImFileHandle file ); +IMGUI_API uint64 ImFileRead( void *data, uint64 size, uint64 count, ImFileHandle file ); +IMGUI_API uint64 ImFileWrite( const void *data, uint64 size, uint64 count, ImFileHandle file ); + + +// Source's colors are stored as BGRA. Setting this allows us to avoid per vertex swizzles in mesh builder. +// On Linux and Mac, mesh builder already does these swizzles regardless +#if !defined( OPENGL_COLOR_SWAP ) +#define IMGUI_USE_BGRA_PACKED_COLOR +#endif + + +//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) +// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. +//#define IMGUI_USE_STB_SPRINTF + + +#include "mathlib/vector2d.h" +#include "mathlib/vector4d.h" +#define IM_VEC2_CLASS_EXTRA \ + ImVec2( const Vector2D& f ) : x( f.x ), y( f.y ) {} \ + operator Vector2D() const { return Vector2D( x, y ); } + +#define IM_VEC4_CLASS_EXTRA \ + ImVec4( const Vector4D& f ) : x( f.x ), y( f.y ), z( f.z ), w( f.w ) {} \ + operator Vector4D() const { return Vector4D( x, y, z, w ); } + + +class IMaterial; +#define ImTextureID IMaterial* diff --git a/game/client/imgui/imgui_impl_source.cpp b/game/client/imgui/imgui_impl_source.cpp new file mode 100644 index 00000000..f8dcd6eb --- /dev/null +++ b/game/client/imgui/imgui_impl_source.cpp @@ -0,0 +1,275 @@ +/********************************************************************************* +* MIT License +* +* Copyright (c) 2023 Strata Source Contributors +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*********************************************************************************/ +#include "imgui_impl_source.h" + +#include "KeyValues.h" +#include "materialsystem/imesh.h" +#include "materialsystem/itexture.h" +#include "imgui/imgui.h" +#include "imgui/imgui_internal.h" +#include "pixelwriter.h" +#include "filesystem.h" + +#include "vgui/ISystem.h" +#include "vgui_controls/Controls.h" + +#include "tier0/memdbgon.h" + +static IMaterial *g_pFontMat = nullptr; + +class CDearImGuiFontTextureRegenerator : public ITextureRegenerator +{ +public: + CDearImGuiFontTextureRegenerator() = default; + + // Inherited from ITextureRegenerator + void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) override + { + ImGuiIO &io = ImGui::GetIO(); + unsigned char *pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32( &pixels, &width, &height ); + + Assert( pVTFTexture->Width() == width ); + Assert( pVTFTexture->Height() == height ); + // if we ever use freetype for font loading, this should do format conversion instead + memcpy( pVTFTexture->ImageData(), pixels, 4ULL * width * height ); + } + + void Release() override + { + delete this; + } +}; + +void ImGui_ImplSource_SetupRenderState( IMatRenderContext *ctx, ImDrawData *draw_data ) +{ + // Apply imgui's display dimensions + ctx->Viewport( draw_data->DisplayPos.x, draw_data->DisplayPos.y, draw_data->DisplaySize.x, draw_data->DisplaySize.y ); + + // Setup orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + ctx->MatrixMode( MATERIAL_PROJECTION ); + ctx->LoadIdentity(); + ctx->Scale( 1, -1, 1 ); + + float L = draw_data->DisplayPos.x + 0.5f; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f; + float T = draw_data->DisplayPos.y + 0.5f; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f; + ctx->Ortho( L, T, R, B, 0.f, 1.f ); + + ctx->MatrixMode( MATERIAL_VIEW ); + ctx->LoadIdentity(); +} + +void ImGui_ImplSource_RenderDrawData( ImDrawData *draw_data ) +{ + // Avoid rendering when minimized + if ( draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f ) + return; + + CMatRenderContextPtr ctx( materials ); + + ctx->MatrixMode( MATERIAL_VIEW ); + ctx->PushMatrix(); + ctx->MatrixMode( MATERIAL_PROJECTION ); + ctx->PushMatrix(); + + ImGui_ImplSource_SetupRenderState( ctx, draw_data ); + + // Render command lists + ImVec2 clip_off = draw_data->DisplayPos; + for ( int n = 0; n < draw_data->CmdListsCount; n++ ) + { + const ImDrawList *cmd_list = draw_data->CmdLists[n]; + const ImDrawIdx *idx_buffer = cmd_list->IdxBuffer.Data; + + // Draw the mesh + for ( int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++ ) + { + const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i]; + if ( pcmd->UserCallback != nullptr ) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if ( pcmd->UserCallback == ImDrawCallback_ResetRenderState ) + ImGui_ImplSource_SetupRenderState( ctx, draw_data ); + else + pcmd->UserCallback( cmd_list, pcmd ); + } + else + { + if ( pcmd->GetTexID() ) + { + Vector2D clipmin = { pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y }; + Vector2D clipmax = { pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y }; + + // Avoid rendering completely clipped draws + if ( clipmax.x <= clipmin.x || clipmax.y <= clipmin.y ) + continue; + + ctx->SetScissorRect( clipmin.x, clipmin.y, clipmax.x, clipmax.y, true ); + IMesh *mesh = ctx->GetDynamicMesh( false, nullptr, nullptr, static_cast( pcmd->GetTexID() ) ); + CMeshBuilder mb; + mb.Begin( mesh, MATERIAL_TRIANGLES, cmd_list->VtxBuffer.Size, pcmd->ElemCount ); + + const ImDrawVert *vtx_src = cmd_list->VtxBuffer.Data; + for ( int i = 0; i < cmd_list->VtxBuffer.Size; i++ ) + { + mb.Position3f( Vector2DExpand( vtx_src->pos ), 0 ); + mb.Color4ubv( reinterpret_cast( &vtx_src->col ) ); + mb.TexCoord2fv( 0, &vtx_src->uv.x ); + mb.AdvanceVertexF(); + vtx_src++; + } + + static_cast( mb ).FastIndexList( idx_buffer + pcmd->IdxOffset, 0, pcmd->ElemCount ); + mb.End( false, true ); + ctx->SetScissorRect( clipmin.x, clipmin.y, clipmax.x, clipmax.y, false ); + } + } + } + } + + ctx->MatrixMode( MATERIAL_PROJECTION ); + ctx->PopMatrix(); + ctx->MatrixMode( MATERIAL_VIEW ); + ctx->PopMatrix(); +} + +bool ImGui_ImplSource_Init() +{ + // Setup backend capabilities flags + ImGuiIO &io = ImGui::GetIO(); + io.BackendPlatformName = "source"; + io.BackendRendererName = "imgui_impl_source"; + io.BackendFlags = ImGuiBackendFlags_None; + io.SetClipboardTextFn = []( void *, const char *c ) + { + vgui::system()->SetClipboardText( c, V_strlen( c ) ); + }; + io.GetClipboardTextFn = []( void *ctx ) -> const char * + { + auto &g = *static_cast( ctx ); + g.ClipboardHandlerData.clear(); + auto len = vgui::system()->GetClipboardTextCount(); + if ( !len ) + return nullptr; + g.ClipboardHandlerData.resize( len ); + vgui::system()->GetClipboardText( 0, g.ClipboardHandlerData.Data, g.ClipboardHandlerData.Size ); + return g.ClipboardHandlerData.Data; + }; + + ImGui_ImplSource_CreateDeviceObjects(); + return true; +} + +void ImGui_ImplSource_Shutdown() +{ + ImGui_ImplSource_InvalidateDeviceObjects(); +} + +static bool ImGui_ImplSource_CreateFontsTexture() +{ + if ( g_pFontMat ) + return true; + + // Build texture atlas + ImGuiIO &io = ImGui::GetIO(); + int width, height; + unsigned char* pixels; + io.Fonts->GetTexDataAsRGBA32( &pixels, &width, &height ); + + // Create a material for the texture + ITexture *fonttex = g_pMaterialSystem->CreateProceduralTexture( "imgui_font", TEXTURE_GROUP_OTHER, width, height, IMAGE_FORMAT_RGBA8888, TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_POINTSAMPLE | TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_NOLOD ); + fonttex->SetTextureRegenerator( new CDearImGuiFontTextureRegenerator ); + + KeyValues *vmt = new KeyValues( "UnlitGeneric" ); + vmt->SetString( "$basetexture", "imgui_font" ); + vmt->SetInt( "$nocull", 1 ); + vmt->SetInt( "$vertexcolor", 1 ); + vmt->SetInt( "$vertexalpha", 1 ); + vmt->SetInt( "$translucent", 1 ); + g_pFontMat = materials->CreateMaterial( "imgui_font_mat", vmt ); + g_pFontMat->AddRef(); + + // Store our identifier + io.Fonts->SetTexID( g_pFontMat ); + + return true; +} + +bool ImGui_ImplSource_CreateDeviceObjects() +{ + return ImGui_ImplSource_CreateFontsTexture(); +} + +void ImGui_ImplSource_InvalidateDeviceObjects() +{ + if ( g_pFontMat ) + { + g_pFontMat->DecrementReferenceCount(); + g_pFontMat = nullptr; + } +} + +// The following functions are declared in imconfig_source.h and must not be renamed + +ImFileHandle ImFileOpen( const char *filename, const char *mode ) +{ + Assert( g_pFullFileSystem ); + return g_pFullFileSystem->Open( filename, mode ); +} + +bool ImFileClose( ImFileHandle f ) +{ + if ( f == nullptr ) + return false; + + Assert( g_pFullFileSystem ); + g_pFullFileSystem->Close( f ); + return true; +} + +uint64 ImFileGetSize( ImFileHandle f ) +{ + Assert( g_pFullFileSystem && f ); + + return g_pFullFileSystem->Size( f ); +} + +uint64 ImFileRead( void *data, uint64 sz, uint64 count, ImFileHandle f ) +{ + Assert( g_pFullFileSystem && f ); + + return g_pFullFileSystem->Read( data, sz * count, f ); +} + +uint64 ImFileWrite( const void *data, uint64 sz, uint64 count, ImFileHandle f ) +{ + Assert( g_pFullFileSystem && f ); + + return g_pFullFileSystem->Write( data, sz * count, f ); +} diff --git a/game/client/imgui/imgui_impl_source.h b/game/client/imgui/imgui_impl_source.h new file mode 100644 index 00000000..d82e4367 --- /dev/null +++ b/game/client/imgui/imgui_impl_source.h @@ -0,0 +1,147 @@ +/********************************************************************************* +* MIT License +* +* Copyright (c) 2023 Strata Source Contributors +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*********************************************************************************/ +#pragma once + +#include "imgui/imgui.h" + +struct ImDrawData; +bool ImGui_ImplSource_Init(); +void ImGui_ImplSource_Shutdown(); +void ImGui_ImplSource_RenderDrawData(ImDrawData* draw_data); + +// Use if you want to reset your rendering device without losing Dear ImGui state. +bool ImGui_ImplSource_CreateDeviceObjects(); +void ImGui_ImplSource_InvalidateDeviceObjects(); + +// Translation table for imgui keys +constexpr ImGuiKey IMGUI_KEY_TABLE[] = { + + /*KEY_NONE*/ ImGuiKey_None, + /*KEY_0*/ ImGuiKey_0, + /*KEY_1*/ ImGuiKey_1, + /*KEY_2*/ ImGuiKey_2, + /*KEY_3*/ ImGuiKey_3, + /*KEY_4*/ ImGuiKey_4, + /*KEY_5*/ ImGuiKey_5, + /*KEY_6*/ ImGuiKey_6, + /*KEY_7*/ ImGuiKey_7, + /*KEY_8*/ ImGuiKey_8, + /*KEY_9*/ ImGuiKey_9, + /*KEY_A*/ ImGuiKey_A, + /*KEY_B*/ ImGuiKey_B, + /*KEY_C*/ ImGuiKey_C, + /*KEY_D*/ ImGuiKey_D, + /*KEY_E*/ ImGuiKey_E, + /*KEY_F*/ ImGuiKey_F, + /*KEY_G*/ ImGuiKey_G, + /*KEY_H*/ ImGuiKey_H, + /*KEY_I*/ ImGuiKey_I, + /*KEY_J*/ ImGuiKey_J, + /*KEY_K*/ ImGuiKey_K, + /*KEY_L*/ ImGuiKey_L, + /*KEY_M*/ ImGuiKey_M, + /*KEY_N*/ ImGuiKey_N, + /*KEY_O*/ ImGuiKey_O, + /*KEY_P*/ ImGuiKey_P, + /*KEY_Q*/ ImGuiKey_Q, + /*KEY_R*/ ImGuiKey_R, + /*KEY_S*/ ImGuiKey_S, + /*KEY_T*/ ImGuiKey_T, + /*KEY_U*/ ImGuiKey_U, + /*KEY_V*/ ImGuiKey_V, + /*KEY_W*/ ImGuiKey_W, + /*KEY_X*/ ImGuiKey_X, + /*KEY_Y*/ ImGuiKey_Y, + /*KEY_Z*/ ImGuiKey_Z, + /*KEY_PAD_0*/ ImGuiKey_Keypad0, + /*KEY_PAD_1*/ ImGuiKey_Keypad1, + /*KEY_PAD_2*/ ImGuiKey_Keypad2, + /*KEY_PAD_3*/ ImGuiKey_Keypad3, + /*KEY_PAD_4*/ ImGuiKey_Keypad4, + /*KEY_PAD_5*/ ImGuiKey_Keypad5, + /*KEY_PAD_6*/ ImGuiKey_Keypad6, + /*KEY_PAD_7*/ ImGuiKey_Keypad7, + /*KEY_PAD_8*/ ImGuiKey_Keypad8, + /*KEY_PAD_9*/ ImGuiKey_Keypad9, + /*KEY_PAD_DIVIDE*/ ImGuiKey_KeypadDivide, + /*KEY_PAD_MULTIPLY*/ ImGuiKey_KeypadMultiply, + /*KEY_PAD_MINUS*/ ImGuiKey_KeypadSubtract, + /*KEY_PAD_PLUS*/ ImGuiKey_KeypadAdd, + /*KEY_PAD_ENTER*/ ImGuiKey_KeypadEnter, + /*KEY_PAD_DECIMAL*/ ImGuiKey_KeypadDecimal, + /*KEY_LBRACKET*/ ImGuiKey_LeftBracket, + /*KEY_RBRACKET*/ ImGuiKey_RightBracket, + /*KEY_SEMICOLON*/ ImGuiKey_Semicolon, + /*KEY_APOSTROPHE*/ ImGuiKey_Apostrophe, + /*KEY_BACKQUOTE*/ ImGuiKey_None, // ? + /*KEY_COMMA*/ ImGuiKey_Comma, + /*KEY_PERIOD*/ ImGuiKey_Period, + /*KEY_SLASH*/ ImGuiKey_Slash, + /*KEY_BACKSLASH*/ ImGuiKey_Backslash, + /*KEY_MINUS*/ ImGuiKey_Minus, + /*KEY_EQUAL*/ ImGuiKey_Equal, + /*KEY_ENTER*/ ImGuiKey_Enter, + /*KEY_SPACE*/ ImGuiKey_Space, + /*KEY_BACKSPACE*/ ImGuiKey_Backspace, + /*KEY_TAB*/ ImGuiKey_Tab, + /*KEY_CAPSLOCK*/ ImGuiKey_CapsLock, + /*KEY_NUMLOCK*/ ImGuiKey_NumLock, + /*KEY_ESCAPE*/ ImGuiKey_Escape, + /*KEY_SCROLLLOCK*/ ImGuiKey_ScrollLock, + /*KEY_INSERT*/ ImGuiKey_Insert, + /*KEY_DELETE*/ ImGuiKey_Delete, + /*KEY_HOME*/ ImGuiKey_Home, + /*KEY_END*/ ImGuiKey_End, + /*KEY_PAGEUP*/ ImGuiKey_PageUp, + /*KEY_PAGEDOWN*/ ImGuiKey_PageDown, + /*KEY_BREAK*/ ImGuiKey_None, // ? + /*KEY_LSHIFT*/ ImGuiKey_LeftShift, + /*KEY_RSHIFT*/ ImGuiKey_RightShift, + /*KEY_LALT*/ ImGuiKey_LeftAlt, + /*KEY_RALT*/ ImGuiKey_RightAlt, + /*KEY_LCONTROL*/ ImGuiKey_LeftCtrl, + /*KEY_RCONTROL*/ ImGuiKey_RightCtrl, + /*KEY_LWIN*/ ImGuiKey_LeftSuper, + /*KEY_RWIN*/ ImGuiKey_RightSuper, + /*KEY_APP*/ ImGuiKey_None, // ? + /*KEY_UP*/ ImGuiKey_UpArrow, + /*KEY_LEFT*/ ImGuiKey_LeftArrow, + /*KEY_DOWN*/ ImGuiKey_DownArrow, + /*KEY_RIGHT*/ ImGuiKey_RightArrow, + /*KEY_F1*/ ImGuiKey_F1, + /*KEY_F2*/ ImGuiKey_F2, + /*KEY_F3*/ ImGuiKey_F3, + /*KEY_F4*/ ImGuiKey_F4, + /*KEY_F5*/ ImGuiKey_F5, + /*KEY_F6*/ ImGuiKey_F6, + /*KEY_F7*/ ImGuiKey_F7, + /*KEY_F8*/ ImGuiKey_F8, + /*KEY_F9*/ ImGuiKey_F9, + /*KEY_F10*/ ImGuiKey_F10, + /*KEY_F11*/ ImGuiKey_F11, + /*KEY_F12*/ ImGuiKey_F12, + /*KEY_CAPSLOCKTOGGLE*/ ImGuiKey_None, // ? + /*KEY_NUMLOCKTOGGLE*/ ImGuiKey_None, // ? + /*KEY_SCROLLLOCKTOGGLE*/ImGuiKey_None, // ? +}; diff --git a/game/client/imgui/imgui_system.cpp b/game/client/imgui/imgui_system.cpp new file mode 100644 index 00000000..12f96bc7 --- /dev/null +++ b/game/client/imgui/imgui_system.cpp @@ -0,0 +1,631 @@ +/********************************************************************************* +* MIT License +* +* Copyright (c) 2023 Strata Source Contributors +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*********************************************************************************/ +#include "imgui_system.h" +#include "imgui_window.h" + +#include "filesystem.h" +#include "fmtstr.h" +#include "imgui_impl_source.h" +#include "inputsystem/iinputsystem.h" +#include "materialsystem/imaterialsystem.h" +#include "strtools.h" +#include "tier2/tier2.h" +#include "tier3/tier3.h" +#include "utldict.h" +#include "imgui/imgui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ienginevgui.h" + +#include "tier0/memdbgon.h" + +class CDummyOverlayPanel; + +CUtlVector &ImGuiWindows(); + +static ConVar imgui_font_scale( "imgui_font_scale", "1", FCVAR_ARCHIVE, "Global scale applied to Imgui fonts" ); +static ConVar imgui_display_scale( "imgui_display_scale", "1", FCVAR_ARCHIVE, "Global imgui scale, usually used for Hi-DPI displays" ); + +void *ImGui_MemAlloc( size_t sz, void *user_data ) +{ + return MemAlloc_Alloc( sz, "Dear ImGui", 0 ); +} + +static void ImGui_MemFree( void *ptr, void *user_data ) +{ + MemAlloc_Free( ptr ); +} + +//---------------------------------------------------------------------------------------// +// Global helpers +//---------------------------------------------------------------------------------------// + +CUtlVector &ImGuiWindows() +{ + static CUtlVector s_DearImGuiWindows; + return s_DearImGuiWindows; +} + +void RegisterImGuiWindowFactory( IImguiWindow *pWindow ) +{ + ImGuiWindows().AddToTail( pWindow ); +} + + +//---------------------------------------------------------------------------------------// +// Purpose: Implementation of the imgui system +//---------------------------------------------------------------------------------------// +class CDearImGuiSystem : public IImguiSystem +{ + using BaseClass = IImguiSystem; + +public: + // IAppSystem + bool Init() override; + void Shutdown() override; + + // IDearImGuiSystem + DearImGuiSysData_t GetData() override; + void Render() override; + void RegisterWindowFactories( IImguiWindow **arrpWindows, int nCount ) override; + IImguiWindow *FindWindow( const char *szName ) override; + void UnregisterWindowFactories( IImguiWindow **ppWindows, int nCount ) override; + void GetAllWindows( CUtlVector &windows ) override; + void SetWindowVisible( IImguiWindow* pWindow, bool bVisible, bool bEnableInput ) override; + + bool DrawWindow( IImguiWindow *pWindow ); + + void PushInputContext(); + void PopInputContext(); + + // True if our input context is enabled in any capacity + bool IsInputContextEnabled() const { return m_bInputEnabled; } + + void SetStyle(); + void DrawMenuBar(); + + void SetDrawMenuBar( bool bEnable ) { m_bDrawMenuBar = bEnable; } + bool IsDrawingMenuBar() const { return m_bDrawMenuBar; } + void ToggleMenuBar() { m_bDrawMenuBar = !m_bDrawMenuBar; } + +public: + CUtlDict m_ImGuiWindows; + + double m_flLastFrameTime; + bool m_bInputEnabled = false; + + bool m_bDrawMenuBar = false; + bool m_bDrawMetrics = false; + bool m_bDrawDemo = false; + CDummyOverlayPanel* m_pInputOverlay = nullptr; +}; + +static CDearImGuiSystem g_ImguiSystem; +IImguiSystem* g_pImguiSystem = &g_ImguiSystem; + +//---------------------------------------------------------------------------------------// +// Purpose: Dummy overlay panel for capturing input +//---------------------------------------------------------------------------------------// +class CDummyOverlayPanel : public vgui::Panel +{ +public: + CDummyOverlayPanel() + { + SetVisible( false ); + SetParent( enginevgui->GetPanel( PANEL_GAMEUIDLL ) ); + SetPaintEnabled( true ); + SetCursor( vgui::dc_arrow ); + + SetPaintBorderEnabled( false ); + SetPaintBackgroundEnabled( false ); + MakePopup(); + } + + void OnMousePressed( ButtonCode_t code ) override + { + auto& io = ImGui::GetIO(); + if ( io.WantCaptureMouse ) + io.AddMouseButtonEvent( code - MOUSE_FIRST, true ); + } + + void OnMouseReleased( ButtonCode_t code ) override + { + auto& io = ImGui::GetIO(); + if ( io.WantCaptureMouse ) + { + io.AddMouseButtonEvent( code - MOUSE_FIRST, false ); + } + } + + void OnMouseWheeled( int delta ) override + { + auto& io = ImGui::GetIO(); + io.AddMouseWheelEvent( 0, delta ); + } + + void OnCursorMoved( int x, int y ) override + { + vgui::Panel::OnCursorMoved( x, y ); + ImGui::GetIO().AddMousePosEvent( x, y ); + } + + void OnMouseDoublePressed( ButtonCode_t code ) override + { + } + + void OnKeyTyped( wchar_t code ) override + { + auto& io = ImGui::GetIO(); + if ( io.WantCaptureKeyboard ) + io.AddInputCharacter( code ); + } + + void OnKeyCodePressed( vgui::KeyCode code ) override + { + auto& io = ImGui::GetIO(); + if ( io.WantCaptureKeyboard ) + io.AddKeyEvent( IMGUI_KEY_TABLE[code], true ); + } + + void OnKeyCodeReleased( vgui::KeyCode code ) override + { + auto& io = ImGui::GetIO(); + if ( io.WantCaptureKeyboard ) + io.AddKeyEvent( IMGUI_KEY_TABLE[code], false ); + } + + void Paint() override + { + g_pImguiSystem->Render(); + } + + void Activate( bool bActive ) + { + SetVisible( bActive ); + SetEnabled( bActive ); + SetKeyBoardInputEnabled( bActive ); + SetMouseInputEnabled( bActive ); + + if ( bActive ) + { + MoveToFront(); + RequestFocus(); + vgui::input()->SetMouseCapture( NULL ); + } + } +}; + +//---------------------------------------------------------------------------------------// +// Purpose: Init our dear friend +//---------------------------------------------------------------------------------------// +bool CDearImGuiSystem::Init() +{ + ImGui::SetAllocatorFunctions( ImGui_MemAlloc, ImGui_MemFree, nullptr ); + ImFontAtlas *atlas = new ImFontAtlas(); + ImGui::CreateContext( atlas ); + ImGui_ImplSource_Init(); + + SetStyle(); + + m_flLastFrameTime = Plat_FloatTime(); + + DearImGuiSysData_t data = g_pImguiSystem->GetData(); + + ImGui::SetAllocatorFunctions( (ImGuiMemAllocFunc)data.memallocfn, (ImGuiMemFreeFunc)data.memfreefn, nullptr ); + ImGui::SetCurrentContext( (ImGuiContext *)data.context ); + + g_pImguiSystem->RegisterWindowFactories( ImGuiWindows().Base(), ImGuiWindows().Count() ); + + return true; +} + +void CDearImGuiSystem::Shutdown() +{ + g_pImguiSystem->UnregisterWindowFactories( ImGuiWindows().Base(), ImGuiWindows().Count() ); + ImGui_ImplSource_Shutdown(); + + ImGui::DestroyContext(); +} + +DearImGuiSysData_t CDearImGuiSystem::GetData() +{ + DearImGuiSysData_t data; + data.context = ImGui::GetCurrentContext(); + data.memallocfn = ImGui_MemAlloc; + data.memfreefn = ImGui_MemFree; + return data; +} + +//---------------------------------------------------------------------------------------// +// Purpose: Render all imgui windows +//---------------------------------------------------------------------------------------// +void CDearImGuiSystem::Render() +{ + // Update the IO + auto &io = ImGui::GetIO(); + + // Create input overlay helper if it doesn't yet exist + if ( !m_pInputOverlay ) + { + m_pInputOverlay = new CDummyOverlayPanel(); + } + + // Update the screen size before drawing + CMatRenderContextPtr pRenderContext( materials ); + int w, h; + pRenderContext->GetWindowSize( w, h ); + if ( !w || !h ) + return; + + m_pInputOverlay->SetSize( w, h ); + + io.DisplaySize.x = static_cast( w ); + io.DisplaySize.y = static_cast( h ); + io.DisplayFramebufferScale.x = io.DisplayFramebufferScale.y = imgui_display_scale.GetFloat(); + io.FontGlobalScale = imgui_font_scale.GetFloat(); + + // Create new frame + ImGui::NewFrame(); + + // Draw menubar first + if ( m_bDrawMenuBar ) + DrawMenuBar(); + + // Draw imgui-specific debug menus + if ( m_bDrawDemo ) + ImGui::ShowDemoWindow( &m_bDrawDemo ); + + if ( m_bDrawMetrics ) + ImGui::ShowMetricsWindow( &m_bDrawMetrics ); + + // Draw everything else + bool bDrawn = false; + FOR_EACH_DICT( m_ImGuiWindows, i ) + { + auto *pWindow = m_ImGuiWindows[i]; + if ( pWindow->ShouldDraw() ) + { + DrawWindow( pWindow ); + bDrawn = true; + } + } + + ImGui::Render(); + ImDrawData *drawdata = ImGui::GetDrawData(); + if ( drawdata ) + ImGui_ImplSource_RenderDrawData( drawdata ); + + // Post render, update deltas + auto curtime = Plat_FloatTime(); + auto dt = curtime - m_flLastFrameTime; + m_flLastFrameTime = curtime; + + io.DeltaTime = static_cast( dt ); + + // Deactivate our overlay if nothing is being drawn anymore + if ( !bDrawn && !m_bDrawDemo && !m_bDrawMetrics && !m_bDrawMenuBar ) + { + PopInputContext(); + } +} + +//---------------------------------------------------------------------------------------// +// Purpose: Draws a window +//---------------------------------------------------------------------------------------// +bool CDearImGuiSystem::DrawWindow( IImguiWindow *pWindow ) +{ + bool closeButton = pWindow->ShouldDraw(); + ImGui::Begin( pWindow->GetWindowTitle(), &closeButton, pWindow->GetFlags() ); + pWindow->SetDraw( closeButton ); + + bool stayOpen = pWindow->Draw(); + ImGui::End(); + return stayOpen; +} + +//---------------------------------------------------------------------------------------// +// Purpose: Register all window factories from another DLL +//---------------------------------------------------------------------------------------// +void CDearImGuiSystem::RegisterWindowFactories( IImguiWindow **arrpWindows, int nCount ) +{ + for ( int i = 0; i < nCount; i++ ) + { + auto *pWindow = arrpWindows[i]; + m_ImGuiWindows.Insert( pWindow->GetName(), pWindow ); + } +} + +//---------------------------------------------------------------------------------------// +// Purpose: Unregister window factories, usually called on DLL shutdown +//---------------------------------------------------------------------------------------// +void CDearImGuiSystem::UnregisterWindowFactories( IImguiWindow **ppWindows, int nCount ) +{ + for ( int i = 0; i < nCount; ++i ) + { + m_ImGuiWindows.Remove( ppWindows[i]->GetName() ); + } +} + +//---------------------------------------------------------------------------------------// +// Purpose: find window by name +//---------------------------------------------------------------------------------------// +IImguiWindow *CDearImGuiSystem::FindWindow( const char *szName ) +{ + if ( auto it = m_ImGuiWindows.Find( szName ); it != m_ImGuiWindows.InvalidIndex() ) + return m_ImGuiWindows[it]; + return nullptr; +} + +//---------------------------------------------------------------------------------------// +// Purpose: Returns a list of all windows into `windows` +//---------------------------------------------------------------------------------------// +void CDearImGuiSystem::GetAllWindows( CUtlVector &windows ) +{ + windows.Purge(); + FOR_EACH_DICT( m_ImGuiWindows, i ) + windows.AddToTail( m_ImGuiWindows[i] ); +} + +//---------------------------------------------------------------------------------------// +// Purpose: Make a window visible +//---------------------------------------------------------------------------------------// +void CDearImGuiSystem::SetWindowVisible( IImguiWindow* pWindow, bool bVisible, bool bEnableInput ) +{ + Assert( pWindow ); + pWindow->SetDraw( bVisible ); + if ( bVisible && bEnableInput ) + PushInputContext(); + else if ( !bVisible && bEnableInput ) + PopInputContext(); +} + +//---------------------------------------------------------------------------------------// +// Purpose: Push a new input context so we can show the mouse cursor +//---------------------------------------------------------------------------------------// +void CDearImGuiSystem::PushInputContext() +{ + if ( m_bInputEnabled ) + return; + + if ( !m_pInputOverlay ) + m_pInputOverlay = new CDummyOverlayPanel(); + + m_pInputOverlay->Activate( true ); + m_bInputEnabled = true; +} + +//---------------------------------------------------------------------------------------// +// Purpose: Pop input context, returning mouse control to whatever needed it last +//---------------------------------------------------------------------------------------// +void CDearImGuiSystem::PopInputContext() +{ + m_pInputOverlay->Activate( false ); + m_bInputEnabled = false; +} + +//---------------------------------------------------------------------------------------// +// Purpose: Update Imgui style +// TODO: User-configurable styles +//---------------------------------------------------------------------------------------// +void CDearImGuiSystem::SetStyle() +{ + ImGuiStyle& style = ImGui::GetStyle(); + style.Colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.13f, 0.14f, 0.15f, 1.00f); + style.Colors[ImGuiCol_ChildBg] = ImVec4(0.13f, 0.14f, 0.15f, 1.00f); + style.Colors[ImGuiCol_PopupBg] = ImVec4(0.13f, 0.14f, 0.15f, 1.00f); + style.Colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + style.Colors[ImGuiCol_FrameBg] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.38f, 0.38f, 0.38f, 1.00f); + style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.67f, 0.67f, 0.67f, 0.39f); + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.08f, 0.08f, 0.09f, 1.00f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.08f, 0.09f, 1.00f); + style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(0.11f, 0.64f, 0.92f, 1.00f); + style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.11f, 0.64f, 0.92f, 1.00f); + style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.08f, 0.50f, 0.72f, 1.00f); + style.Colors[ImGuiCol_Button] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.38f, 0.38f, 0.38f, 1.00f); + style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.67f, 0.67f, 0.67f, 0.39f); + style.Colors[ImGuiCol_Header] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f); + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); + style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.67f, 0.67f, 0.67f, 0.39f); + style.Colors[ImGuiCol_Separator] = style.Colors[ImGuiCol_Border]; + style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.41f, 0.42f, 0.44f, 1.00f); + style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.29f, 0.30f, 0.31f, 0.67f); + style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + style.Colors[ImGuiCol_Tab] = ImVec4(0.08f, 0.08f, 0.09f, 0.83f); + style.Colors[ImGuiCol_TabHovered] = ImVec4(0.33f, 0.34f, 0.36f, 0.83f); + style.Colors[ImGuiCol_TabActive] = ImVec4(0.23f, 0.23f, 0.24f, 1.00f); + style.Colors[ImGuiCol_TabUnfocused] = ImVec4(0.08f, 0.08f, 0.09f, 1.00f); + style.Colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.13f, 0.14f, 0.15f, 1.00f); + style.Colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + style.Colors[ImGuiCol_DragDropTarget] = ImVec4(0.11f, 0.64f, 0.92f, 1.00f); + style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + style.GrabRounding = style.FrameRounding = 2.3f; +} + +//---------------------------------------------------------------------------------------// +// Purpose: Draws the menu bar as a top-level window +//---------------------------------------------------------------------------------------// +void CDearImGuiSystem::DrawMenuBar() +{ + if ( ImGui::BeginMainMenuBar() ) + { + if ( ImGui::BeginMenu( "File" ) ) + { + if ( ImGui::MenuItem( "Close Menu" ) ) + { + this->m_bDrawMenuBar = false; + this->PopInputContext(); + } + ImGui::EndMenu(); + } + if ( ImGui::BeginMenu( "Windows" ) ) + { + CUtlVector windows; + g_pImguiSystem->GetAllWindows( windows ); + + for ( auto &window : windows ) + { + bool bToggle = window->ShouldDraw(); + if ( ImGui::MenuItem( window->GetWindowTitle(), "", &bToggle ) ) + window->SetDraw( bToggle ); + } + + ImGui::EndMenu(); + } + + if ( ImGui::BeginMenu( "Debug" ) ) + { + ImGui::MenuItem( "Show Demo Window", "", &m_bDrawDemo ); + ImGui::MenuItem( "Show Metrics Window", "", &m_bDrawMetrics ); + + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} + +//---------------------------------------------------------------------------------------// +// Purpose: Implementation of imgui_show command +//---------------------------------------------------------------------------------------// +class CImGuiShowAutoCompletionFunctor : public ICommandCallback, + public ICommandCompletionCallback +{ +public: + void CommandCallback( const CCommand &command ) override + { + if ( command.ArgC() < 2 ) + { + Msg( "Format: imgui_show \n" ); + return; + } + + // Get our window + const char *pKey = command.Arg( 1 ); + auto *pWindow = g_ImguiSystem.FindWindow( pKey ); + + if ( !pWindow ) + { + Warning( "Failed to find ImGUI window called %s\n", pKey ); + return; + } + + pWindow->ToggleDraw(); + } + + int CommandCompletionCallback( const char *partial, CUtlVector &commands ) override + { + auto &windows = g_ImguiSystem.m_ImGuiWindows; + FOR_EACH_DICT( windows, i ) + { + commands.AddToTail( CFmtStr( "%s %s", "imgui_show", windows[i]->GetName() ).Get() ); + } + + return commands.Count(); + } +}; + +static CImGuiShowAutoCompletionFunctor g_ImGuiShowAutoComplete; +static ConCommand imgui_show( "imgui_show", &g_ImGuiShowAutoComplete, "Toggles the specified imgui window", FCVAR_CLIENTDLL, &g_ImGuiShowAutoComplete ); + +//---------------------------------------------------------------------------------------// +// Purpose: Toggle input for imgui windows +//---------------------------------------------------------------------------------------// +CON_COMMAND_F( imgui_toggle_input, "Toggles the mouse cursor for imgui windows", FCVAR_CLIENTDLL ) +{ + if ( g_ImguiSystem.IsInputContextEnabled() ) + g_ImguiSystem.PopInputContext(); + else + g_ImguiSystem.PushInputContext(); +} + +static ConCommand imgui_input_start( + "+imgui_input", []( const CCommand &args ) + { + g_ImguiSystem.PushInputContext(); + }, + "Toggles the mouse cursor for imgui windows, same as imgui_toggle_input", FCVAR_CLIENTDLL ); + +static ConCommand imgui_input_end( + "-imgui_input", []( const CCommand &args ) + { + g_ImguiSystem.PopInputContext(); + }, + "Toggles the mouse cursor for imgui windows, same as imgui_toggle_input", FCVAR_CLIENTDLL ); + +//---------------------------------------------------------------------------------------// +// Purpose: Toggles the built-in menu bar (and input) +//---------------------------------------------------------------------------------------// + +CON_COMMAND_F( imgui_toggle_menu, "Toggles the imgui menu bar", FCVAR_CLIENTDLL ) +{ + g_ImguiSystem.ToggleMenuBar(); + if ( g_ImguiSystem.IsDrawingMenuBar() ) + g_ImguiSystem.PushInputContext(); + else + g_ImguiSystem.PopInputContext(); +} + +template +void CC_ToggleMenu( const CCommand &args ) +{ + g_ImguiSystem.SetDrawMenuBar( ON ); + if constexpr ( ON ) + g_ImguiSystem.PushInputContext(); + else + g_ImguiSystem.PopInputContext(); +} + +static ConCommand imgui_menu_start( "+imgui_menu", CC_ToggleMenu, "Toggles the menu bar and input", FCVAR_CLIENTDLL ); +static ConCommand imgui_menu_end( "-imgui_menu", CC_ToggleMenu, "Toggles the menu bar and input", FCVAR_CLIENTDLL ); + +CON_COMMAND_F( imgui_show_demo, "Shows the imgui demo", FCVAR_CLIENTDLL ) +{ + g_ImguiSystem.m_bDrawDemo = true; +} diff --git a/game/client/imgui/imgui_system.h b/game/client/imgui/imgui_system.h new file mode 100644 index 00000000..a85b1a85 --- /dev/null +++ b/game/client/imgui/imgui_system.h @@ -0,0 +1,61 @@ +/********************************************************************************* +* MIT License +* +* Copyright (c) 2023 Strata Source Contributors +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*********************************************************************************/ +#pragma once + +#include "appframework/IAppSystem.h" +#include "inputsystem/InputEnums.h" +#include "utlvector.h" + +struct DearImGuiSysData_t +{ + void *context; + void *( *memallocfn )( size_t sz, void *user_data ); + void ( *memfreefn )( void *ptr, void *user_data ); +}; + +class IImguiWindow; + +abstract_class IImguiSystem +{ +public: + virtual bool Init() = 0; + + virtual void Shutdown() = 0; + + virtual DearImGuiSysData_t GetData() = 0; + + virtual void Render() = 0; + + virtual void RegisterWindowFactories( IImguiWindow * *arrpWindows, int nCount ) = 0; + + virtual void UnregisterWindowFactories( IImguiWindow * *ppWindows, int nCount ) = 0; + + virtual IImguiWindow *FindWindow( const char *szName ) = 0; + + virtual void GetAllWindows( CUtlVector & windows ) = 0; + + virtual void SetWindowVisible( IImguiWindow* pWindow, bool bVisible, bool bEnableInput = true ) = 0; +}; + +extern IImguiSystem *g_pImguiSystem; diff --git a/game/client/imgui/imgui_system.vpc b/game/client/imgui/imgui_system.vpc new file mode 100644 index 00000000..86ed055c --- /dev/null +++ b/game/client/imgui/imgui_system.vpc @@ -0,0 +1,39 @@ + +$Macro IMGUI_DIR "$SRCDIR\game\client\imgui" + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;IMGUI_DISABLE_INCLUDE_IMCONFIG_H;IMGUI_USER_CONFIG=" + $AdditionalIncludeDirectories "$BASE;$IMGUI_DIR;$SRCDIR/thirdparty;" + } +} + +$Project +{ + $Folder "Source Files" + { + $File "$SRCDIR\game\client\imgui\imgui_impl_source.cpp" + $File "$SRCDIR\game\client\imgui\imgui_system.cpp" + + $Folder "ImGUI" + { + $File "$SRCDIR/thirdparty/imgui/imgui.cpp" \ + "$SRCDIR/thirdparty/imgui/imgui_demo.cpp" \ + "$SRCDIR/thirdparty/imgui/imgui_draw.cpp" \ + "$SRCDIR/thirdparty/imgui/imgui_tables.cpp" \ + "$SRCDIR/thirdparty/imgui/imgui_widgets.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Not Using Precompiled Headers" + } + } + } + } + } +} + diff --git a/game/client/imgui/imgui_window.h b/game/client/imgui/imgui_window.h new file mode 100644 index 00000000..53333035 --- /dev/null +++ b/game/client/imgui/imgui_window.h @@ -0,0 +1,109 @@ +/********************************************************************************* +* MIT License +* +* Copyright (c) 2023 Strata Source Contributors +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*********************************************************************************/ +#pragma once + +#include "imgui/imgui.h" + +#include "imgui_system.h" +#include "utlmap.h" + +// Implemented by devui_static +extern void RegisterImGuiWindowFactory( IImguiWindow *pWindow ); + +//--------------------------------------------------------------------------------// +// Purpose: Interface implemented by each window. Use with DEFINE_DEVUI_WINDOW. +//--------------------------------------------------------------------------------// +class IImguiWindow +{ +public: + IImguiWindow() = delete; + + IImguiWindow( const char *pszName, const char* pszTitle ) : + m_pName( pszName ), + m_pTitle( pszTitle ) + { + RegisterImGuiWindowFactory( this ); + } + + virtual ~IImguiWindow() = default; + + // Must be implemented by subclasses + // Returns true to indicate if this window should continue to stay open + virtual bool Draw() = 0; + + // Default implementations provided + virtual bool ShouldDraw() { return m_bEnabled; } + + virtual void ToggleDraw() + { + m_bEnabled = !m_bEnabled; + OnChangeVisibility(); + } + + virtual void SetDraw( bool bState ) + { + bool old = m_bEnabled; + m_bEnabled = bState; + if ( old != m_bEnabled ) + OnChangeVisibility(); + } + + // Returns window flags for this window, override this if you want (Or use DECLARE_DEVUI_WINDOW_F) + virtual ImGuiWindowFlags GetFlags() const { return ImGuiWindowFlags_None; } + + // Returns the internal reference name of the window + const char *GetName() const { return m_pName; } + + // Returns the window title. This will be passed to ImGui::Begin and displayed in the menu + virtual const char *GetWindowTitle() const { return m_pTitle; } + + virtual void OnChangeVisibility() {} + +protected: + bool m_bEnabled = false; + const char *m_pName; + const char* m_pTitle; +}; + +// Defines an imgui window +#define DEFINE_IMGUI_WINDOW( _className ) \ + static _className __s_##_className##_registrar; + + +// Helper macro for simpler windows +#define DECLARE_IMGUI_WINDOW_F( name, _title, _flags ) \ + class CDearImGuiWindow##name : public IImguiWindow \ + { \ + public: \ + CDearImGuiWindow##name() : IImguiWindow( #name, _title ) \ + { \ + } \ + bool Draw() override; \ + ImGuiWindowFlags GetFlags() const override { return _flags; } \ + }; \ + static CDearImGuiWindow##name _s_dearimguiwindow##name; \ + bool CDearImGuiWindow##name::Draw() + + +#define DECLARE_IMGUI_WINDOW( _name, _title ) DECLARE_IMGUI_WINDOW_F( _name, _title, ImGuiWindowFlags_None ) diff --git a/game/client/wscript b/game/client/wscript index 99e4e03b..ae9b0367 100755 --- a/game/client/wscript +++ b/game/client/wscript @@ -12,14 +12,14 @@ def options(opt): return games = { - 'hl2': ['client_base.vpc', 'client_hl2.vpc'], - 'hl2mp': ['client_base.vpc', 'client_hl2mp.vpc'], - 'hl1': ['client_base.vpc', 'client_hl1.vpc'], - 'episodic': ['client_base.vpc', 'client_episodic.vpc'], - 'portal': ['client_base.vpc', 'client_portal.vpc'], - 'hl1mp': ['client_base.vpc', 'client_hl1mp.vpc'], - 'cstrike': ['client_base.vpc', 'client_cstrike.vpc'], - 'dod': ['client_base.vpc', 'client_dod.vpc'] + 'hl2': ['client_base.vpc', 'client_hl2.vpc', 'imgui/imgui_system.vpc'], + 'hl2mp': ['client_base.vpc', 'client_hl2mp.vpc', 'imgui/imgui_system.vpc'], + 'hl1': ['client_base.vpc', 'client_hl1.vpc', 'imgui/imgui_system.vpc'], + 'episodic': ['client_base.vpc', 'client_episodic.vpc', 'imgui/imgui_system.vpc'], + 'portal': ['client_base.vpc', 'client_portal.vpc', 'imgui/imgui_system.vpc'], + 'hl1mp': ['client_base.vpc', 'client_hl1mp.vpc', 'imgui/imgui_system.vpc'], + 'cstrike': ['client_base.vpc', 'client_cstrike.vpc', 'imgui/imgui_system.vpc'], + 'dod': ['client_base.vpc', 'client_dod.vpc', 'imgui/imgui_system.vpc'] } def configure(conf):