Files
combattest/Console.cpp
T
2026-11-08 07:45:01 -05:00

213 lines
6.1 KiB
C++

#include "Console.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <sstream>
#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 ? "_" : "");
}