Emu?
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

328 lines
9.0 KiB

/* Console.cpp
*
* A Quake-style console implementation.
*/
#include <list>
#include <sstream>
#include <utility>
#include <SDL.h>
#include <SDL_image.h>
#include "Console.h"
#include "StringUtil.h"
namespace PlipSdl {
Console::Console(SdlWindow *wnd) {
m_video = wnd;
m_renderer = wnd->GetRenderer();
m_vidFmt = Plip::PlipVideo::GetFormatInfo(m_video->GetFormat());
IMG_Init(0);
RegisterInternalCommands();
}
Console::~Console() {
IMG_Quit();
}
void Console::Clear() {
for(auto i = 0; i < m_conWidth * m_conHeight; i++)
m_conBuffer[i] = 0x20;
m_cursor = 0;
}
void Console::Draw() {
if(m_renderer == nullptr) return;
// Background
SDL_SetRenderDrawColor(m_renderer, 0x00, 0x00, 0x00, 0xC0);
SDL_RenderClear(m_renderer);
// Foreground
SDL_Rect src, dest;
src.w = dest.w = m_charWidth;
src.h = dest.h = m_charHeight;
for(auto i = 0; i < m_conWidth * m_conHeight; i++) {
src.x = (m_conBuffer[i] % m_charCountX) * m_charWidth;
src.y = (m_conBuffer[i] / m_charCountY) * m_charHeight;
dest.x = (i % m_conWidth) * m_charWidth;
dest.y = (i / m_conWidth) * m_charHeight;
SDL_RenderCopy(m_renderer, m_fontTex, &src, &dest);
}
}
void Console::EnterPressed() {
Write("\n");
if(m_input.empty()) {
NewCommand();
return;
}
std::string str(m_input.cbegin(), m_input.cend());
if(StringUtil::Trim(str).empty()) {
NewCommand();
return;
}
std::vector<std::string> cmdLine(StringUtil::Split(str, ' '));
auto cmd = StringUtil::ToLower(cmdLine[0]);
// Build a list of candidates.
std::list<Command> candidates;
auto len = cmd.length();
for(const auto &it : m_commandList) {
if(it.name.substr(0, len) != cmd) continue;
candidates.push_back(it);
}
if(candidates.empty()) {
// No candidates.
WriteError("command not found");
} else if(candidates.size() == 1) {
// Only one candidate. Execute the attached function.
candidates.cbegin()->callback(this, cmdLine);
} else {
std::stringstream ss;
bool first = true;
ss << "Ambiguous command. Possible candidates: ";
// Check for an exact match.
for(const auto &it : candidates) {
if(it.name != cmd) {
if(first) first = false;
else ss << ", ";
ss << it.name;
continue;
}
it.callback(this, cmdLine);
goto done;
}
// Print the list of candidates.
WriteLine(ss.str());
}
done:
NewCommand();
}
bool Console::GetConsoleEnabled() const {
return m_consoleEnabled;
}
bool Console::LoadFont(const std::string &filename) {
auto newFont = IMG_Load(filename.c_str());
if(!newFont) return false;
if(m_fontSurf != nullptr) SDL_FreeSurface(m_fontSurf);
m_fontSurf = newFont;
if(m_fontTex != nullptr) SDL_DestroyTexture(m_fontTex);
m_fontTex = SDL_CreateTextureFromSurface(m_renderer, m_fontSurf);
SDL_SetTextureBlendMode(m_fontTex, SDL_BLENDMODE_ADD);
m_charWidth = m_fontSurf->w / m_charCountX;
m_charHeight = m_fontSurf->h / m_charCountY;
Resize();
return true;
}
bool Console::ParseInt(const std::string &str, int *val) {
if(str.empty()) return false;
try {
*val = std::stoi(str, nullptr, 0);
} catch(std::invalid_argument&) {
return false;
} catch(std::out_of_range&) {
return false;
}
return true;
}
bool Console::ParseLong(const std::string &str, long *val) {
if(str.empty()) return false;
try {
*val = std::stol(str, nullptr, 0);
} catch(std::invalid_argument&) {
return false;
} catch(std::out_of_range&) {
return false;
}
return true;
}
bool Console::ParseULong(const std::string &str, unsigned long *val) {
if(str.empty()) return false;
try {
*val = std::stol(str, nullptr, 0);
} catch(std::invalid_argument&) {
return false;
} catch(std::out_of_range&) {
return false;
}
return true;
}
SdlUiEvent Console::ProcessEvents() {
SDL_Event ev;
auto uiEvent = SdlUiEvent::None;
while(SDL_PollEvent(&ev)) {
int idx;
char ch;
switch(ev.type) {
case SDL_KEYDOWN:
if(ev.key.keysym.scancode == m_consoleKey)
ToggleConsole();
if(ev.key.keysym.scancode == SDL_SCANCODE_BACKSPACE) {
if(m_input.empty()) break;
m_input.pop_back();
m_conBuffer[--m_cursor] = 0x20;
}
if(ev.key.keysym.scancode == SDL_SCANCODE_KP_ENTER ||
ev.key.keysym.scancode == SDL_SCANCODE_RETURN ||
ev.key.keysym.scancode == SDL_SCANCODE_RETURN2) {
EnterPressed();
}
break;
case SDL_TEXTINPUT:
idx = 0;
while((ch = ev.text.text[idx++]) != 0) {
m_input.push_back(ch);
Write(ch);
}
break;
case SDL_QUIT:
uiEvent = SdlUiEvent::Quit;
break;
}
}
return uiEvent;
}
void Console::RegisterCommand(const std::string &commandName,
std::function<void(Console*, const std::vector<std::string> &args)> callback) {
auto lowerName = StringUtil::ToLower(commandName);
m_commandList.push_back({ lowerName, std::move(callback) });
m_commandList.sort([](const Command &first, const Command &second) {
return first.name < second.name;
});
}
void Console::RegisterInternalCommands() {
RegisterCommand("help", [](Console *console, const std::vector<std::string>&) {
console->Write("valid commands: ");
bool first = true;
for(auto &it : console->m_commandList) {
if(first) first = false;
else console->Write(", ");
console->Write(it.name);
}
console->WriteLine();
});
}
void Console::Resize() {
if(m_charWidth == 0 || m_charHeight == 0) return;
delete m_conBuffer;
delete m_videoBuffer;
m_wndWidth = m_video->GetWidth() * m_video->GetGameScale();
m_wndHeight = m_video->GetHeight() * m_video->GetGameScale();
m_conWidth = m_wndWidth / m_charWidth;
m_conHeight = m_wndHeight / m_charHeight;
m_conBuffer = new uint8_t[m_conWidth * m_conHeight];
m_videoBuffer = new uint8_t[m_wndWidth * m_wndHeight * m_vidFmt.pixelWidth];
Clear();
}
void Console::Run() {
m_video->BeginDrawConsole();
Draw();
m_video->EndDrawConsole();
m_video->Render();
}
void Console::Scroll(int lines) {
if(lines <= 0) return;
if(lines >= m_conHeight) {
Clear();
m_cursor = m_conWidth * (m_conHeight - 1);
}
memcpy(m_conBuffer,
m_conBuffer + (m_conWidth * lines),
m_conWidth * m_conHeight - (m_conWidth * lines));
memset(m_conBuffer + (m_conWidth * (m_conHeight - lines)),
0x20, lines * m_conWidth);
m_cursor -= m_conWidth;
}
void Console::SetConsoleEnabled(bool enabled) {
m_consoleEnabled = enabled;
m_video->SetConsoleEnabled(m_consoleEnabled);
}
void Console::SetConsoleKey(SDL_Scancode scancode) {
m_consoleKey = scancode;
}
void Console::SetConsoleKey(const std::string &binding) {
auto scancode = SDL_GetScancodeFromName(binding.c_str());
SetConsoleKey(scancode);
}
void Console::ToggleConsole() {
Initialize();
SetConsoleEnabled(!m_consoleEnabled);
if(m_consoleEnabled)
SDL_StartTextInput();
else
SDL_StopTextInput();
}
void Console::Write(const char ch) {
Initialize();
if(ch == '\n') {
auto row = m_cursor / m_conWidth;
m_cursor = (row + 1) * m_conWidth;
} else {
m_conBuffer[m_cursor++] = ch;
}
if(m_cursor >= m_conWidth * m_conHeight)
Scroll();
}
void Console::Write(const std::string &str) {
for(auto ch : str)
Write(ch);
}
}