From bc52202b01dcf72f8daf179d95fa72f201a029cf Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 21 Dec 2021 02:00:22 +0100 Subject: [PATCH] Console window progress --- deps/imgui | 2 +- src/client/component/console.cpp | 2 +- src/client/component/game_console.cpp | 142 ++++++++++-------- src/client/component/game_console.hpp | 6 + src/client/component/gui.cpp | 3 + src/client/component/gui_asset_list.cpp | 10 +- src/client/component/gui_console.cpp | 192 ++++++++++++++++++++++++ 7 files changed, 284 insertions(+), 73 deletions(-) create mode 100644 src/client/component/gui_console.cpp diff --git a/deps/imgui b/deps/imgui index f791ff33..e55dce84 160000 --- a/deps/imgui +++ b/deps/imgui @@ -1 +1 @@ -Subproject commit f791ff33702d55603d182b586a2850418ec49e3d +Subproject commit e55dce841b4a5a738ae41745b534248596df7f5e diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp index 50944cb1..05a5d125 100644 --- a/src/client/component/console.cpp +++ b/src/client/component/console.cpp @@ -31,7 +31,7 @@ namespace console while (true) { std::getline(std::cin, cmd); - command::execute(cmd.data(), false); + game_console::add(cmd.data(), false); } }); } diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp index 0bdcb3b3..97c9681d 100644 --- a/src/client/component/game_console.cpp +++ b/src/client/component/game_console.cpp @@ -69,7 +69,7 @@ namespace game_console matches.clear(); } - void print(const std::string& data) + void print(const std::string& data, bool print_ = true) { if (con.visible_line_count > 0 && con.display_line_offset == (con.output.size() - con.visible_line_count)) { @@ -78,7 +78,10 @@ namespace game_console con.output.push_back(data); - printf("%s\n", data.data()); + if (print_) + { + printf("%s\n", data.data()); + } if (con.output.size() > 1024) { @@ -182,50 +185,6 @@ namespace game_console return false; } - void find_matches(std::string input, std::vector& suggestions, const bool exact) - { - input = utils::string::to_lower(input); - - for (const auto& dvar : dvars::dvar_list) - { - auto name = utils::string::to_lower(dvar); - if (match_compare(input, name, exact)) - { - suggestions.push_back(dvar); - } - - if (exact && suggestions.size() > 1) - { - return; - } - } - - if (suggestions.size() == 0 && game::Dvar_FindVar(input.data())) - { - suggestions.push_back(input.data()); - } - - game::cmd_function_s* cmd = (*game::cmd_functions); - while (cmd) - { - if (cmd->name) - { - std::string name = utils::string::to_lower(cmd->name); - - if (match_compare(input, name, exact)) - { - suggestions.push_back(cmd->name); - } - - if (exact && suggestions.size() > 1) - { - return; - } - } - cmd = cmd->next; - } - } - void draw_input() { con.globals.font_height = static_cast(console_font->pixelHeight); @@ -534,21 +493,16 @@ namespace game_console void execute(const char* cmd) { - const auto sv_running = game::Dvar_FindVar("sv_running")->current.enabled; - - if (sv_running) - { - std::string command = cmd; - - scheduler::once([command]() - { - scripting::notify(*game::levelEntityId, "console_command", {command}); - }, scheduler::pipeline::server); - } - game::Cbuf_AddText(0, utils::string::va("%s \n", cmd)); } + void add(const std::string& cmd, bool print_) + { + print(cmd, print_); + history.push_front(cmd); + game::Cbuf_AddText(0, utils::string::va("%s \n", cmd.data())); + } + bool console_key_event(const int localClientNum, const int key, const int down) { if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE) @@ -678,6 +632,69 @@ namespace game_console return true; } + void find_matches(std::string input, std::vector& suggestions, const bool exact) + { + input = utils::string::to_lower(input); + + for (const auto& dvar : dvars::dvar_list) + { + auto name = utils::string::to_lower(dvar); + if (match_compare(input, name, exact)) + { + suggestions.push_back(dvar); + } + + if (exact && suggestions.size() > 1) + { + return; + } + } + + if (suggestions.size() == 0 && game::Dvar_FindVar(input.data())) + { + suggestions.push_back(input.data()); + } + + game::cmd_function_s* cmd = (*game::cmd_functions); + while (cmd) + { + if (cmd->name) + { + std::string name = utils::string::to_lower(cmd->name); + + if (match_compare(input, name, exact)) + { + suggestions.push_back(cmd->name); + } + + if (exact && suggestions.size() > 1) + { + return; + } + } + cmd = cmd->next; + } + } + + std::deque& get_output() + { + return con.output; + } + + std::deque& get_history() + { + return history; + } + + void clear_console() + { + clear(); + con.line_count = 0; + con.output.clear(); + history_index = -1; + history.clear(); + } + class component final : public component_interface { public: @@ -699,14 +716,7 @@ namespace game_console strncpy_s(con.globals.auto_complete_choice, "", 64); // add clear command - command::add("clear", [&]() - { - clear(); - con.line_count = 0; - con.output.clear(); - history_index = -1; - history.clear(); - }); + command::add("clear", clear_console); // add our dvars dvars::con_inputBoxColor = dvars::register_vec4( diff --git a/src/client/component/game_console.hpp b/src/client/component/game_console.hpp index 4a9bec7c..ebc8adab 100644 --- a/src/client/component/game_console.hpp +++ b/src/client/component/game_console.hpp @@ -16,5 +16,11 @@ namespace game_console bool console_char_event(int local_client_num, int key); bool console_key_event(int local_client_num, int key, int down); + void find_matches(std::string input, std::vector& suggestions, const bool exact); void execute(const char* cmd); + void clear_console(); + void add(const std::string& cmd, bool print_ = true); + + std::deque& get_output(); + std::deque& get_history(); } \ No newline at end of file diff --git a/src/client/component/gui.cpp b/src/client/component/gui.cpp index b20aa0cb..eed43ef0 100644 --- a/src/client/component/gui.cpp +++ b/src/client/component/gui.cpp @@ -113,12 +113,15 @@ namespace gui } }); + ImGui::ShowDemoWindow(); + if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Windows")) { ImGui::Checkbox("Asset list", &enabled_menus["asset_list"]); ImGui::Checkbox("Entity list", &enabled_menus["entity_list"]); + ImGui::Checkbox("Console", &enabled_menus["console"]); ImGui::EndMenu(); } diff --git a/src/client/component/gui_asset_list.cpp b/src/client/component/gui_asset_list.cpp index 19c7756f..4732068a 100644 --- a/src/client/component/gui_asset_list.cpp +++ b/src/client/component/gui_asset_list.cpp @@ -15,6 +15,9 @@ namespace asset_list { namespace { + bool shown_assets[game::XAssetType::ASSET_TYPE_COUNT]; + std::string filter_buffer{}; + void enum_assets(const game::XAssetType type, const std::function& callback, const bool includeOverride) { game::DB_EnumXAssets_Internal(type, static_cast([](game::XAssetHeader header, void* data) @@ -31,19 +34,16 @@ namespace asset_list return; } - static bool shown_assets[game::XAssetType::ASSET_TYPE_COUNT]; - { ImGui::Begin("Asset list", &gui::enabled_menus["asset_list"]); - static char filter[0x200]{}; - ImGui::InputText("asset type", filter, IM_ARRAYSIZE(filter)); + ImGui::InputText("asset type", &filter_buffer); for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++) { const auto name = game::g_assetNames[i]; const auto type = static_cast(i); - if (strstr(name, filter)) + if (strstr(name, filter_buffer.data())) { ImGui::Checkbox(name, &shown_assets[type]); } diff --git a/src/client/component/gui_console.cpp b/src/client/component/gui_console.cpp new file mode 100644 index 00000000..3495a53c --- /dev/null +++ b/src/client/component/gui_console.cpp @@ -0,0 +1,192 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "scheduler.hpp" +#include "command.hpp" +#include "game_console.hpp" +#include "gui.hpp" + +#include +#include +#include + +namespace gui_console +{ + namespace + { + bool auto_scroll = true; + int history_index = -1; + std::string input{}; + std::string filter{}; + std::vector matches{}; + + int input_text_edit(ImGuiInputTextCallbackData* data) + { + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + matches.clear(); + const std::string text = data->Buf; + + if (text.find(" ") != std::string::npos) + { + game_console::find_matches(text.substr(0, text.find(" ")), matches, true); + } + else + { + game_console::find_matches(text, matches, false); + } + + if (matches.size() == 1) + { + const auto match = matches[0].data(); + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, match, match + strlen(match)); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + const auto history = game_console::get_history(); + + if (data->EventKey == ImGuiKey_UpArrow) + { + if (++history_index >= history.size()) + { + history_index = static_cast(history.size()) - 1; + } + + data->DeleteChars(0, data->BufTextLen); + + if (history_index != -1) + { + const auto text = history.at(history_index).data(); + data->InsertChars(0, text, text + strlen(text)); + } + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (--history_index < -1) + { + history_index = -1; + } + + data->DeleteChars(0, data->BufTextLen); + + if (history_index != -1) + { + const auto text = history.at(history_index).data(); + data->InsertChars(0, text, text + strlen(text)); + } + } + + break; + } + } + + return 0; + } + + std::string get_filtered_text() + { + std::string text{}; + + const auto history = game_console::get_output(); + for (const auto& line : history) + { + if (strstr(line.data(), filter.data())) + { + text.append(line.data()); + text.append("\n"); + } + } + + if (text[text.size() - 1] == '\n') + { + text.pop_back(); + } + + return text; + } + + void on_frame() + { + auto* open = &gui::enabled_menus["console"]; + if (!*open) + { + return; + } + + auto filtered_text = get_filtered_text(); + static auto input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | + ImGuiInputTextFlags_CallbackHistory; + + ImGui::Begin("Console", open); + + if (ImGui::BeginPopup("Options")) + { + ImGui::Checkbox("Auto-scroll", &auto_scroll); + ImGui::EndPopup(); + } + + if (ImGui::Button("Clear")) + { + game_console::clear_console(); + } + + ImGui::SameLine(); + + if (ImGui::Button("Copy")) + { + utils::string::set_clipboard_data(filtered_text); + } + + ImGui::Separator(); + + if (ImGui::Button("Options")) + { + ImGui::OpenPopup("Options"); + } + + ImGui::SameLine(); + ImGui::InputText("Filter", &filter); + + const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + ImGui::BeginChild("console_scroll", ImVec2(0, -footer_height_to_reserve), false); + + ImGui::Text(filtered_text.data(), ImVec2(-1, -1)); + + if (auto_scroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + { + ImGui::SetScrollHereY(1.0f); + } + + ImGui::EndChild(); + + if (ImGui::InputText("Input", &input, input_text_flags, input_text_edit)) + { + game_console::add(input.data()); + input.clear(); + ImGui::SetKeyboardFocusHere(-1); + } + + ImGui::End(); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + gui::on_frame(on_frame); + } + }; +} + +REGISTER_COMPONENT(gui_console::component)