diff --git a/src/Components/Modules/TextRenderer.cpp b/src/Components/Modules/TextRenderer.cpp index 8c41e8ab..eda123c6 100644 --- a/src/Components/Modules/TextRenderer.cpp +++ b/src/Components/Modules/TextRenderer.cpp @@ -43,6 +43,8 @@ namespace Components TextRenderer::FontIconAutocompleteContext TextRenderer::autocompleteContextArray[FONT_ICON_ACI_COUNT]{}; Dvar::Var TextRenderer::cg_newColors; + Dvar::Var TextRenderer::cg_fontIconAutocomplete; + Dvar::Var TextRenderer::cg_fontIconAutocompleteHint; Game::dvar_t* TextRenderer::sv_customTextColor; Dvar::Var TextRenderer::r_colorBlind; Game::dvar_t* TextRenderer::g_ColorBlind_MyTeam; @@ -190,20 +192,41 @@ namespace Components void TextRenderer::UpdateAutocompleteContext(FontIconAutocompleteContext& context, Game::field_t* edit, Game::Font_s* font) { int fontIconStart = -1; - for(auto i = edit->cursor - 1; i >= 0; i--) + auto inModifiers = false; + + for(auto i = 0; i < edit->cursor; i++) { const auto c = static_cast(edit->buffer[i]); if (c == ':') { - fontIconStart = i + 1; - break; + if(fontIconStart < 0) + { + fontIconStart = i + 1; + inModifiers = false; + } + else + { + fontIconStart = -1; + inModifiers = false; + } + } + else if(isspace(c)) + { + fontIconStart = -1; + inModifiers = false; + } + else if(c == '+') + { + if (fontIconStart >= 0 && !inModifiers) + { + inModifiers = true; + } + else + { + fontIconStart = -1; + inModifiers = false; + } } - - if (isspace(c)) - break; - - if (c == '+') - break; } if(fontIconStart < 0 || fontIconStart == edit->cursor) @@ -214,6 +237,8 @@ namespace Components return; } + context.inModifiers = inModifiers; + // Update scroll if(context.selectedOffset < context.resultOffset) context.resultOffset = context.selectedOffset; @@ -222,6 +247,10 @@ namespace Components context.autocompleteActive = true; + // No need to update results when in modifiers + if (context.inModifiers) + return; + // Check if results need updates const auto currentFontIconHash = Game::R_HashString(&edit->buffer[fontIconStart], edit->cursor - fontIconStart); if (currentFontIconHash == context.lastHash && context.lastResultOffset == context.resultOffset) @@ -240,13 +269,32 @@ namespace Components UpdateAutocompleteContextResults(context, font); } - void TextRenderer::DrawAutocomplete(const FontIconAutocompleteContext& context, const float x, const float y, Game::Font_s* font) + void TextRenderer::DrawAutocompleteModifiers(const FontIconAutocompleteContext& context, float x, float y, Game::Font_s* font) + { + const auto* text = "The following modifiers are available:\n" + "^2h ^7Flip icon horizontally\n" + "^2v ^7Flip icon vertically\n" + "^2b ^7Bigger icon"; + const auto boxWidth = static_cast(Game::R_TextWidth(text, INT_MAX, font)); + constexpr auto totalLines = 4u; + DrawAutocompleteBox(context, + x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, + static_cast(font->pixelHeight * totalLines) + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, + (*con_inputBoxColor)->current.vector); + const auto currentY = y + static_cast(font->pixelHeight); + Game::R_AddCmdDrawText(text, INT_MAX, font, x, currentY, 1.0f, 1.0f, 0.0, TEXT_COLOR, 0); + } + + void TextRenderer::DrawAutocompleteResults(const FontIconAutocompleteContext& context, const float x, const float y, Game::Font_s* font) { const auto* text = Utils::String::VA("Font icons starting with ^2%s^7:", context.lastQuery.c_str()); const auto boxWidth = std::max(context.maxFontIconWidth + context.maxMaterialNameWidth + FONT_ICON_AUTOCOMPLETE_COL_SPACING, static_cast(Game::R_TextWidth(text, INT_MAX, font))); - const auto totalLines = 1u + context.resultCount; + const auto hintEnabled = cg_fontIconAutocompleteHint.get(); + const auto totalLines = 1u + context.resultCount + (hintEnabled ? 2u : 0u); const auto arrowPadding = context.resultOffset > 0 || context.hasMoreResults ? FONT_ICON_AUTOCOMPLETE_ARROW_SIZE : 0.0f; DrawAutocompleteBox(context, x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, @@ -254,31 +302,38 @@ namespace Components boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2 + arrowPadding, static_cast(font->pixelHeight * totalLines) + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, (*con_inputBoxColor)->current.vector); - - const float textColor[4] - { - 1.0f, - 1.0f, - 0.8f, - 1.0f - }; - + auto currentY = y + static_cast(font->pixelHeight); - Game::R_AddCmdDrawText(text, INT_MAX, font, x, currentY, 1.0f, 1.0f, 0.0, textColor, 0); + Game::R_AddCmdDrawText(text, INT_MAX, font, x, currentY, 1.0f, 1.0f, 0.0, TEXT_COLOR, 0); currentY += static_cast(font->pixelHeight); const auto selectedIndex = context.selectedOffset - context.resultOffset; for(auto resultIndex = 0u; resultIndex < context.resultCount; resultIndex++) { const auto& result = context.results[resultIndex]; - Game::R_AddCmdDrawText(result.fontIconName.c_str(), INT_MAX, font, x, currentY, 1.0f, 1.0f, 0.0, textColor, 0); + Game::R_AddCmdDrawText(result.fontIconName.c_str(), INT_MAX, font, x, currentY, 1.0f, 1.0f, 0.0, TEXT_COLOR, 0); if(selectedIndex == resultIndex) - Game::R_AddCmdDrawText(Utils::String::VA("^2%s", result.materialName.c_str()), INT_MAX, font, x + context.maxFontIconWidth + FONT_ICON_AUTOCOMPLETE_COL_SPACING, currentY, 1.0f, 1.0f, 0.0, textColor, 0); + Game::R_AddCmdDrawText(Utils::String::VA("^2%s", result.materialName.c_str()), INT_MAX, font, x + context.maxFontIconWidth + FONT_ICON_AUTOCOMPLETE_COL_SPACING, currentY, 1.0f, 1.0f, 0.0, TEXT_COLOR, 0); else - Game::R_AddCmdDrawText(result.materialName.c_str(), INT_MAX, font, x + context.maxFontIconWidth + FONT_ICON_AUTOCOMPLETE_COL_SPACING, currentY, 1.0f, 1.0f, 0.0, textColor, 0); + Game::R_AddCmdDrawText(result.materialName.c_str(), INT_MAX, font, x + context.maxFontIconWidth + FONT_ICON_AUTOCOMPLETE_COL_SPACING, currentY, 1.0f, 1.0f, 0.0, TEXT_COLOR, 0); currentY += static_cast(font->pixelHeight); } + + if(hintEnabled) + { + Game::R_AddCmdDrawText("Press ^3TAB ^7for autocomplete", INT_MAX, font, x, currentY, 1.0f, 1.0f, 0.0, HINT_COLOR, 0); + currentY += static_cast(font->pixelHeight); + Game::R_AddCmdDrawText("Use ^3+ ^7for modifiers", INT_MAX, font, x, currentY, 1.0f, 1.0f, 0.0, HINT_COLOR, 0); + } + } + + void TextRenderer::DrawAutocomplete(const FontIconAutocompleteContext& context, float x, float y, Game::Font_s* font) + { + if (context.inModifiers) + DrawAutocompleteModifiers(context, x, y, font); + else + DrawAutocompleteResults(context, x, y, font); } void TextRenderer::Con_DrawInput_Hk(const int localClientNum) @@ -287,6 +342,12 @@ namespace Components Utils::Hook::Call(0x5A4480)(localClientNum); auto& autocompleteContext = autocompleteContextArray[FONT_ICON_ACI_CONSOLE]; + if (cg_fontIconAutocomplete.get() == false) + { + autocompleteContext.autocompleteActive = false; + return; + } + UpdateAutocompleteContext(autocompleteContext, Game::g_consoleField, Game::cls->consoleFont); if (autocompleteContext.autocompleteActive) { @@ -301,6 +362,12 @@ namespace Components Game::Field_Draw(localClientNum, edit, x, y, horzAlign, vertAlign); auto& autocompleteContext = autocompleteContextArray[FONT_ICON_ACI_CHAT]; + if (cg_fontIconAutocomplete.get() == false) + { + autocompleteContext.autocompleteActive = false; + return; + } + UpdateAutocompleteContext(autocompleteContext, edit, Game::cls->consoleFont); if (autocompleteContext.autocompleteActive) { @@ -1257,6 +1324,8 @@ namespace Components currentColorTable = &colorTableDefault; cg_newColors = Dvar::Register("cg_newColors", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Use Warfare 2 color code style."); + cg_fontIconAutocomplete = Dvar::Register("cg_fontIconAutocomplete", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Show autocomplete for fonticons when typing."); + cg_fontIconAutocompleteHint = Dvar::Register("cg_fontIconAutocompleteHint", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Show hint text in autocomplete for fonticons."); sv_customTextColor = Game::Dvar_RegisterColor("sv_customTextColor", 1, 0.7f, 0, 1, Game::dvar_flag::DVAR_FLAG_REPLICATED, "Color for the extended color code."); // Replace vanilla text drawing function with a reimplementation with extensions diff --git a/src/Components/Modules/TextRenderer.hpp b/src/Components/Modules/TextRenderer.hpp index d7d70e7f..5f64ba5f 100644 --- a/src/Components/Modules/TextRenderer.hpp +++ b/src/Components/Modules/TextRenderer.hpp @@ -78,6 +78,7 @@ namespace Components static constexpr auto MAX_RESULTS = 10; bool autocompleteActive; + bool inModifiers; unsigned int lastHash; std::string lastQuery; FontIconAutocompleteResult results[MAX_RESULTS]; @@ -105,6 +106,20 @@ namespace Components {1.0f, -1.0f}, {1.0f, 1.0f}, }; + static constexpr float TEXT_COLOR[4] + { + 1.0f, + 1.0f, + 0.8f, + 1.0f + }; + static constexpr float HINT_COLOR[4] + { + 0.6f, + 0.6f, + 0.6f, + 1.0f + }; static unsigned colorTableDefault[TEXT_COLOR_COUNT]; static unsigned colorTableNew[TEXT_COLOR_COUNT]; @@ -112,6 +127,8 @@ namespace Components static FontIconAutocompleteContext autocompleteContextArray[FONT_ICON_ACI_COUNT]; static Dvar::Var cg_newColors; + static Dvar::Var cg_fontIconAutocomplete; + static Dvar::Var cg_fontIconAutocompleteHint; static Game::dvar_t* sv_customTextColor; static Dvar::Var r_colorBlind; static Game::dvar_t* g_ColorBlind_MyTeam; @@ -135,6 +152,8 @@ namespace Components static unsigned HsvToRgb(HsvColor hsv); static void DrawAutocompleteBox(const FontIconAutocompleteContext& context, float x, float y, float w, float h, const float* color); + static void DrawAutocompleteModifiers(const FontIconAutocompleteContext& context, float x, float y, Game::Font_s* font); + static void DrawAutocompleteResults(const FontIconAutocompleteContext& context, float x, float y, Game::Font_s* font); static void DrawAutocomplete(const FontIconAutocompleteContext& context, float x, float y, Game::Font_s* font); static void UpdateAutocompleteContextResults(FontIconAutocompleteContext& context, Game::Font_s* font); static void UpdateAutocompleteContext(FontIconAutocompleteContext& context, Game::field_t* edit, Game::Font_s* font);