#include "Console.h" #include #include #include #include #include "Gui.h" static constexpr float PANEL_HEIGHT = 360.0f; static constexpr float INPUT_HEIGHT = 36.0f; static constexpr float MARGIN = 16.0f; static constexpr int VISIBLE_LINES = 13; static constexpr int MAX_LOG_LINES = 300; static constexpr int MAX_INPUT_LENGTH = 200; static constexpr int SCROLL_STEP = 5; static std::string Lowercase(std::string text) { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return (char)std::tolower(c); }); return text; } Console::Console(Font* newFont) : font(newFont) { Register("help", "help", "lists every command", [this](const Arguments&) { for (const auto& [name, entry] : commands) Print(" " + entry.usage + " - " + entry.description); }); Register("clear", "clear", "empties the console", [this](const Arguments&) { log.clear(); scroll = 0; }); } // GUI draws in the order things were made, so this waits until the first Update to land on top of everything void Console::CreateDisplay() { background = new Box(); background->Color = { 0.02f, 0.03f, 0.05f }; background->Opacity = 0.0f; background->t2d.position = { 0.0f, 0.0f, 0.0f, 0.0f }; background->t2d.size = { 1.0f, 0.0f, 0.0f, PANEL_HEIGHT }; background->BindToRenderSystem(); inputBackground = new Box(); inputBackground->Color = { 0.10f, 0.12f, 0.16f }; inputBackground->Opacity = 0.0f; inputBackground->t2d.position = { 0.0f, 0.0f, 0.0f, PANEL_HEIGHT - INPUT_HEIGHT }; inputBackground->t2d.size = { 1.0f, 0.0f, 0.0f, INPUT_HEIGHT }; inputBackground->BindToRenderSystem(); // Bottom aligned, so the newest line sits just above the input and old ones get cut off at the top logText = new TextBox(); logText->font = font; logText->fontsize = 0.42f; logText->Color = { 0.85f, 0.88f, 0.93f }; logText->Opacity = 0.0f; logText->t2d.position = { 0.0f, 0.0f, MARGIN, 8.0f }; logText->t2d.size = { 1.0f, 0.0f, -2.0f * MARGIN, PANEL_HEIGHT - INPUT_HEIGHT - 12.0f }; logText->textCenter = { 0.0f, 1.0f }; logText->BindToRenderSystem(); inputText = new TextBox(); inputText->font = font; inputText->fontsize = 0.45f; inputText->Color = { 0.96f, 0.58f, 0.16f }; inputText->Opacity = 0.0f; inputText->t2d.position = { 0.0f, 0.0f, MARGIN, PANEL_HEIGHT - INPUT_HEIGHT }; inputText->t2d.size = { 1.0f, 0.0f, -2.0f * MARGIN, INPUT_HEIGHT }; inputText->textCenter = { 0.0f, 0.5f }; inputText->BindToRenderSystem(); } void Console::Register(const std::string& name, const std::string& usage, const std::string& description, Command run) { commands[Lowercase(name)] = { usage, description, std::move(run) }; } void Console::Execute(const std::string& line) { std::istringstream words(line); std::string name; if (!(words >> name)) return; Arguments arguments; for (std::string word; words >> word; ) arguments.push_back(word); auto command = commands.find(Lowercase(name)); if (command == commands.end()) { Print("Unknown command '" + name + "', type help for a list"); return; } command->second.run(arguments); } void Console::Print(const std::string& message) { std::istringstream lines(message); for (std::string line; std::getline(lines, line); ) { log.push_back(line); if ((int)log.size() > MAX_LOG_LINES) log.pop_front(); } } void Console::Open() { open = true; historyIndex = (int)history.size(); caretTimer = 0.0f; } void Console::Close() { open = false; } void Console::OnChar(unsigned int codepoint) { // The font only has printable ASCII, and the toggle key shouldn't end up in the line if (codepoint < 32 || codepoint > 126 || codepoint == '`' || codepoint == '~') return; if ((int)input.size() < MAX_INPUT_LENGTH) input += (char)codepoint; caretTimer = 0.0f; } void Console::OnKey(int key) { caretTimer = 0.0f; switch (key) { case GLFW_KEY_ENTER: case GLFW_KEY_KP_ENTER: if (input.empty()) break; Print("> " + input); if (history.empty() || history.back() != input) history.push_back(input); historyIndex = (int)history.size(); scroll = 0; { // Copied first, a command is allowed to touch the console's own state const std::string line = input; input.clear(); Execute(line); } break; case GLFW_KEY_BACKSPACE: if (!input.empty()) input.pop_back(); break; case GLFW_KEY_UP: if (historyIndex > 0) { historyIndex--; input = history[historyIndex]; } break; case GLFW_KEY_DOWN: if (historyIndex < (int)history.size()) { historyIndex++; input = historyIndex < (int)history.size() ? history[historyIndex] : std::string(); } break; case GLFW_KEY_PAGE_UP: Scroll(SCROLL_STEP); break; case GLFW_KEY_PAGE_DOWN: Scroll(-SCROLL_STEP); break; case GLFW_KEY_ESCAPE: Close(); break; default: break; } } void Console::Scroll(int lines) { scroll = glm::clamp(scroll + lines, 0, std::max((int)log.size() - VISIBLE_LINES, 0)); } void Console::Update(float dt) { if (!background) CreateDisplay(); const float opacity = open ? 1.0f : 0.0f; background->Opacity = open ? 0.88f : 0.0f; inputBackground->Opacity = open ? 0.9f : 0.0f; logText->Opacity = opacity; inputText->Opacity = opacity; if (!open) return; const int newest = (int)log.size() - scroll; const int oldest = std::max(newest - VISIBLE_LINES, 0); std::string visible; for (int i = oldest; i < newest; i++) { visible += log[i]; if (i + 1 < newest) visible += '\n'; } logText->text = visible; caretTimer += dt; const bool caretOn = std::fmod(caretTimer, 1.0f) < 0.5f; inputText->text = "> " + input + (caretOn ? "_" : ""); }