213 lines
6.1 KiB
C++
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 ? "_" : "");
|
|
}
|