From c29efc7573233d03ce3a39707d2c027228362997 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 11 Mar 2018 19:55:52 +0000 Subject: [PATCH] Separate consoles to different sources --- src/openrct2/interface/Console.cpp | 454 --------------------- src/openrct2/interface/Console.h | 1 + src/openrct2/interface/InGameConsole.cpp | 374 +++++++++++++++++ src/openrct2/interface/StdInOutConsole.cpp | 91 +++++ 4 files changed, 466 insertions(+), 454 deletions(-) create mode 100644 src/openrct2/interface/InGameConsole.cpp create mode 100644 src/openrct2/interface/StdInOutConsole.cpp diff --git a/src/openrct2/interface/Console.cpp b/src/openrct2/interface/Console.cpp index 7566a624c0..5b3cafcc55 100644 --- a/src/openrct2/interface/Console.cpp +++ b/src/openrct2/interface/Console.cpp @@ -21,8 +21,6 @@ #include #include -#include "../thirdparty/linenoise.hpp" - #include "../config/Config.h" #include "../Context.h" #include "../core/Guard.hpp" @@ -62,29 +60,6 @@ #endif -#define CONSOLE_MAX_LINES 300 -#define CONSOLE_HISTORY_SIZE 64 -#define CONSOLE_INPUT_SIZE 256 -#define CONSOLE_CARET_FLASH_THRESHOLD 15 -#define CONSOLE_EDGE_PADDING 4 -#define CONSOLE_CARET_WIDTH 6 - -bool gConsoleOpen = false; - -static sint32 _consoleLeft, _consoleTop, _consoleRight, _consoleBottom; -static sint32 _lastMainViewportX, _lastMainViewportY; -static std::deque _consoleLines; -static utf8 _consoleCurrentLine[CONSOLE_INPUT_SIZE]; -static sint32 _consoleCaretTicks; -static sint32 _consoleScrollPos = 0; -static TextInputSession * _consoleTextInputSession; - -static utf8 _consoleHistory[CONSOLE_HISTORY_SIZE][CONSOLE_INPUT_SIZE]; -static sint32 _consoleHistoryIndex = 0; -static sint32 _consoleHistoryCount = 0; - -static InGameConsole _inGameConsole; - static void console_write_all_commands(InteractiveConsole &console); static sint32 console_parse_int(const utf8 *src, bool *valid); static double console_parse_double(const utf8 *src, bool *valid); @@ -95,50 +70,8 @@ static sint32 cc_help(InteractiveConsole &console, const utf8 **argv, sint32 arg static bool invalidArguments(bool *invalid, bool arguments); -static sint32 console_get_num_visible_lines(); - #define SET_FLAG(variable, flag, value) {if (value) variable |= flag; else variable &= ~flag;} -void console_open() -{ - _inGameConsole.Open(); -} - -void console_close() -{ - _inGameConsole.Close(); -} - -void console_toggle() -{ - _inGameConsole.Toggle(); -} - -void console_update() -{ - _inGameConsole.Update(); -} - -void console_draw(rct_drawpixelinfo *dpi) -{ - _inGameConsole.Draw(dpi); -} - -void console_input(CONSOLE_INPUT input) -{ - _inGameConsole.Input(input); -} - -void console_writeline(const utf8 * src, uint32 colourFormat) -{ - _inGameConsole.WriteLine(src, colourFormat); -} - -void console_scroll(sint32 linesToScroll) -{ - _inGameConsole.Scroll(linesToScroll); -} - sint32 console_parse_int(const utf8 *src, bool *valid) { utf8 *end; sint32 value; @@ -153,20 +86,6 @@ double console_parse_double(const utf8 *src, bool *valid) { return value; } -// Calculates the amount of visible lines, based on the console size, excluding the input line. -static sint32 console_get_num_visible_lines() -{ - const sint32 lineHeight = font_get_line_height(gCurrentFontSpriteBase); - const sint32 consoleHeight = _consoleBottom - _consoleTop; - const sint32 drawableHeight = consoleHeight - 2 * lineHeight - 4; // input line, separator - padding - return drawableHeight / lineHeight; -} - -void console_refresh_caret() -{ - _inGameConsole.RefreshCaret(); -} - static sint32 cc_clear(InteractiveConsole &console, const utf8 **argv, sint32 argc) { console.Clear(); @@ -1326,376 +1245,3 @@ void InteractiveConsole::WriteFormatLine(const std::string &format, ...) std::free(buffer); WriteLine(s); } - -InGameConsole::InGameConsole() -{ - InteractiveConsole::WriteLine(OPENRCT2_NAME " " OPENRCT2_VERSION); - InteractiveConsole::WriteLine("Type 'help' for a list of available commands. Type 'hide' to hide the console."); - InteractiveConsole::WriteLine(""); - WritePrompt(); -} - -void InGameConsole::WritePrompt() -{ - InteractiveConsole::WriteLine("> "); -} - -void InGameConsole::Input(CONSOLE_INPUT input) -{ - switch (input) { - case CONSOLE_INPUT_LINE_CLEAR: - ClearInput(); - RefreshCaret(); - break; - case CONSOLE_INPUT_LINE_EXECUTE: - if (_consoleCurrentLine[0] != '\0') { - HistoryAdd(_consoleCurrentLine); - Execute(_consoleCurrentLine); - WritePrompt(); - ClearInput(); - RefreshCaret(); - } - ScrollToEnd(); - break; - case CONSOLE_INPUT_HISTORY_PREVIOUS: - if (_consoleHistoryIndex > 0) { - _consoleHistoryIndex--; - memcpy(_consoleCurrentLine, _consoleHistory[_consoleHistoryIndex], 256); - } - _consoleTextInputSession->Size = strlen(_consoleTextInputSession->Buffer); - _consoleTextInputSession->Length = utf8_length(_consoleTextInputSession->Buffer); - _consoleTextInputSession->SelectionStart = strlen(_consoleCurrentLine); - break; - case CONSOLE_INPUT_HISTORY_NEXT: - if (_consoleHistoryIndex < _consoleHistoryCount - 1) { - _consoleHistoryIndex++; - memcpy(_consoleCurrentLine, _consoleHistory[_consoleHistoryIndex], 256); - _consoleTextInputSession->Size = strlen(_consoleTextInputSession->Buffer); - _consoleTextInputSession->Length = utf8_length(_consoleTextInputSession->Buffer); - _consoleTextInputSession->SelectionStart = strlen(_consoleCurrentLine); - } else { - _consoleHistoryIndex = _consoleHistoryCount; - ClearInput(); - } - break; - case CONSOLE_INPUT_SCROLL_PREVIOUS: - { - sint32 scrollAmt = console_get_num_visible_lines() - 1; - Scroll(scrollAmt); - break; - } - case CONSOLE_INPUT_SCROLL_NEXT: - { - sint32 scrollAmt = console_get_num_visible_lines() - 1; - Scroll(-scrollAmt); - break; - } - default: - break; - } -} - -void InGameConsole::ClearInput() -{ - _consoleCurrentLine[0] = 0; - if (gConsoleOpen) { - context_start_text_input(_consoleCurrentLine, sizeof(_consoleCurrentLine)); - } -} - -void InGameConsole::HistoryAdd(const utf8 * src) -{ - if (_consoleHistoryCount >= CONSOLE_HISTORY_SIZE) { - for (sint32 i = 0; i < _consoleHistoryCount - 1; i++) - memcpy(_consoleHistory[i], _consoleHistory[i + 1], CONSOLE_INPUT_SIZE); - _consoleHistoryCount--; - } - memcpy(_consoleHistory[_consoleHistoryCount++], src, CONSOLE_INPUT_SIZE); - _consoleHistoryIndex = _consoleHistoryCount; -} - -void InGameConsole::ScrollToEnd() -{ - _consoleScrollPos = Math::Max(0, (sint32)_consoleLines.size() - console_get_num_visible_lines()); -} - -void InGameConsole::RefreshCaret() -{ - _consoleCaretTicks = 0; -} - -void InGameConsole::Scroll(sint32 linesToScroll) -{ - const sint32 maxVisibleLines = console_get_num_visible_lines(); - const sint32 numLines = (sint32)_consoleLines.size(); - if (numLines > maxVisibleLines) - { - sint32 maxScrollValue = numLines - maxVisibleLines; - _consoleScrollPos = Math::Clamp(0, _consoleScrollPos - linesToScroll, maxScrollValue); - } -} - -void InGameConsole::Clear() -{ - _consoleLines.clear(); - ScrollToEnd(); -} - -void InGameConsole::ClearLine() -{ - _consoleCurrentLine[0] = 0; - RefreshCaret(); -} - -void InGameConsole::Open() -{ - gConsoleOpen = true; - ScrollToEnd(); - RefreshCaret(); - _consoleTextInputSession = context_start_text_input(_consoleCurrentLine, sizeof(_consoleCurrentLine)); -} - -void InGameConsole::Close() -{ - gConsoleOpen = false; - Invalidate(); - context_stop_text_input(); -} - -void InGameConsole::Toggle() -{ - if (gConsoleOpen) - console_close(); - else - console_open(); -} - -void InGameConsole::WriteLine(const std::string &input, uint32 colourFormat) -{ - // Include text colour format only for special cases - // The draw function handles the default text colour differently - utf8 colourCodepoint[4]{}; - if (colourFormat != FORMAT_WINDOW_COLOUR_2) - utf8_write_codepoint(colourCodepoint, colourFormat); - - std::string line; - std::size_t splitPos = 0; - std::size_t stringOffset = 0; - while (splitPos != std::string::npos) - { - splitPos = input.find('\n', stringOffset); - line = input.substr(stringOffset, splitPos - stringOffset); - _consoleLines.push_back(colourCodepoint + line); - stringOffset = splitPos + 1; - } - - if (_consoleLines.size() > CONSOLE_MAX_LINES) - { - const std::size_t linesToErase = _consoleLines.size() - CONSOLE_MAX_LINES; - _consoleLines.erase(_consoleLines.begin(), _consoleLines.begin() + linesToErase); - } -} - -void InGameConsole::Invalidate() -{ - gfx_set_dirty_blocks(_consoleLeft, _consoleTop, _consoleRight, _consoleBottom); -} - -void InGameConsole::Update() -{ - _consoleLeft = 0; - _consoleTop = 0; - _consoleRight = context_get_width(); - _consoleBottom = 322; - - if (gConsoleOpen) { - // When scrolling the map, the console pixels get copied... therefore invalidate the screen - rct_window *mainWindow = window_get_main(); - if (mainWindow != nullptr) { - rct_viewport *mainViewport = window_get_viewport(mainWindow); - if (mainViewport != nullptr) { - if (_lastMainViewportX != mainViewport->view_x || _lastMainViewportY != mainViewport->view_y) { - _lastMainViewportX = mainViewport->view_x; - _lastMainViewportY = mainViewport->view_y; - - gfx_invalidate_screen(); - } - } - } - - // Remove unwanted characters in console input - utf8_remove_format_codes(_consoleCurrentLine, false); - } - - // Flash the caret - _consoleCaretTicks = (_consoleCaretTicks + 1) % 30; -} - -void InGameConsole::Draw(rct_drawpixelinfo * dpi) -{ - if (!gConsoleOpen) - return; - - // Set font - gCurrentFontSpriteBase = (gConfigInterface.console_small_font ? FONT_SPRITE_BASE_SMALL : FONT_SPRITE_BASE_MEDIUM); - gCurrentFontFlags = 0; - uint8 textColour = NOT_TRANSLUCENT(theme_get_colour(WC_CONSOLE, 1)); - const sint32 lineHeight = font_get_line_height(gCurrentFontSpriteBase); - const sint32 maxLines = console_get_num_visible_lines(); - - // This is something of a hack to ensure the text is actually black - // as opposed to a desaturated grey - std::string colourFormatStr; - if (textColour == COLOUR_BLACK) - { - utf8 extraTextFormatCode[4]{}; - utf8_write_codepoint(extraTextFormatCode, FORMAT_BLACK); - colourFormatStr = extraTextFormatCode; - } - - // TTF looks far better without the outlines - if (!gUseTrueTypeFont) - { - textColour |= COLOUR_FLAG_OUTLINE; - } - - Invalidate(); - - // Give console area a translucent effect. - gfx_filter_rect(dpi, _consoleLeft, _consoleTop, _consoleRight, _consoleBottom, PALETTE_51); - - // Make input area more opaque. - gfx_filter_rect(dpi, _consoleLeft, _consoleBottom - lineHeight - 10, _consoleRight, _consoleBottom - 1, PALETTE_51); - - // Paint background colour. - uint8 backgroundColour = theme_get_colour(WC_CONSOLE, 0); - gfx_fill_rect_inset(dpi, _consoleLeft, _consoleTop, _consoleRight, _consoleBottom, backgroundColour, INSET_RECT_FLAG_FILL_NONE); - gfx_fill_rect_inset(dpi, _consoleLeft + 1, _consoleTop + 1, _consoleRight - 1, _consoleBottom - 1, backgroundColour, INSET_RECT_FLAG_BORDER_INSET); - - std::string lineBuffer; - sint32 x = _consoleLeft + CONSOLE_EDGE_PADDING; - sint32 y = _consoleTop + CONSOLE_EDGE_PADDING; - - // Draw text inside console - for (std::size_t i = 0; i < _consoleLines.size() && i < (size_t)maxLines; i++) { - const size_t index = i + _consoleScrollPos; - lineBuffer = colourFormatStr + _consoleLines[index]; - gfx_draw_string(dpi, lineBuffer.c_str(), textColour, x, y); - y += lineHeight; - } - - y = _consoleBottom - lineHeight - CONSOLE_EDGE_PADDING - 1; - - // Draw current line - lineBuffer = colourFormatStr + _consoleCurrentLine; - gfx_draw_string(dpi, lineBuffer.c_str(), TEXT_COLOUR_255, x, y); - - // Draw caret - if (_consoleCaretTicks < CONSOLE_CARET_FLASH_THRESHOLD) { - sint32 caretX = x + gfx_get_string_width(_consoleCurrentLine); - sint32 caretY = y + lineHeight; - - uint8 caretColour = ColourMapA[BASE_COLOUR(textColour)].lightest; - gfx_fill_rect(dpi, caretX, caretY, caretX + CONSOLE_CARET_WIDTH, caretY, caretColour); - } - - // What about border colours? - uint8 borderColour1 = ColourMapA[BASE_COLOUR(backgroundColour)].light; - uint8 borderColour2 = ColourMapA[BASE_COLOUR(backgroundColour)].mid_dark; - - // Input area top border - gfx_fill_rect(dpi, _consoleLeft, _consoleBottom - lineHeight - 11, _consoleRight, _consoleBottom - lineHeight - 11, borderColour1); - gfx_fill_rect(dpi, _consoleLeft, _consoleBottom - lineHeight - 10, _consoleRight, _consoleBottom - lineHeight - 10, borderColour2); - - // Input area bottom border - gfx_fill_rect(dpi, _consoleLeft, _consoleBottom - 1, _consoleRight, _consoleBottom - 1, borderColour1); - gfx_fill_rect(dpi, _consoleLeft, _consoleBottom - 0, _consoleRight, _consoleBottom - 0, borderColour2); -} - -void StdInOutConsole::Start() -{ - std::thread replThread ([this]() -> void - { - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - - linenoise::SetMultiLine(true); - linenoise::SetHistoryMaxLen(32); - - std::string prompt = "\033[32mopenrct2 $\x1b[0m "; - while (true) - { - std::string line; - std::string left = prompt; - auto quit = linenoise::Readline(left.c_str(), line); - if (quit) { - openrct2_finish(); - break; - } - linenoise::AddHistory(line.c_str()); - Eval(line).wait(); - } - }); - replThread.detach(); -} - -std::future StdInOutConsole::Eval(const std::string &s) -{ - // Push on-demand evaluations onto a queue so that it can be processed deterministically - // on the main thead at the right time. - std::promise barrier; - auto future = barrier.get_future(); - _evalQueue.emplace(std::move(barrier), s); - return future; -} - -void StdInOutConsole::ProcessEvalQueue() -{ - while (_evalQueue.size() > 0) - { - auto item = std::move(_evalQueue.front()); - _evalQueue.pop(); - auto promise = std::move(std::get<0>(item)); - auto command = std::move(std::get<1>(item)); - - Execute(command); - - // Signal the promise so caller can continue - promise.set_value(); - } -} - -void StdInOutConsole::Clear() -{ - linenoise::linenoiseClearScreen(); -} - -void StdInOutConsole::Close() -{ - openrct2_finish(); -} - -void StdInOutConsole::WriteLine(const std::string &s, uint32 colourFormat) -{ - std::string formatBegin; - if (colourFormat != FORMAT_WINDOW_COLOUR_2) - { - switch (colourFormat) - { - case FORMAT_RED: - formatBegin = "\033[31m"; - break; - case FORMAT_YELLOW: - formatBegin = "\033[33m"; - break; - } - } - - if (formatBegin.empty()) - { - std::printf("%s\n", s.c_str()); - } - else - { - std::printf("%s%s%s\n", formatBegin.c_str(), s.c_str(), "\x1b[0m"); - } -} diff --git a/src/openrct2/interface/Console.h b/src/openrct2/interface/Console.h index 66ac20f641..1b0a189f88 100644 --- a/src/openrct2/interface/Console.h +++ b/src/openrct2/interface/Console.h @@ -79,6 +79,7 @@ private: void WritePrompt(); void ScrollToEnd(); void Invalidate(); + sint32 GetNumVisibleLines(); }; class StdInOutConsole : public InteractiveConsole diff --git a/src/openrct2/interface/InGameConsole.cpp b/src/openrct2/interface/InGameConsole.cpp new file mode 100644 index 0000000000..2c2cfa6799 --- /dev/null +++ b/src/openrct2/interface/InGameConsole.cpp @@ -0,0 +1,374 @@ +#include +#include "Colour.h" +#include "Console.h" +#include "themes.h" +#include "Window.h" +#include "../config/Config.h" +#include "../Context.h" +#include "../core/Math.hpp" +#include "../localisation/Language.h" +#include "../Version.h" + +#define CONSOLE_MAX_LINES 300 +#define CONSOLE_HISTORY_SIZE 64 +#define CONSOLE_INPUT_SIZE 256 +#define CONSOLE_CARET_FLASH_THRESHOLD 15 +#define CONSOLE_EDGE_PADDING 4 +#define CONSOLE_CARET_WIDTH 6 + +bool gConsoleOpen = false; + +static sint32 _consoleLeft, _consoleTop, _consoleRight, _consoleBottom; +static sint32 _lastMainViewportX, _lastMainViewportY; +static std::deque _consoleLines; +static utf8 _consoleCurrentLine[CONSOLE_INPUT_SIZE]; +static sint32 _consoleCaretTicks; +static sint32 _consoleScrollPos = 0; +static TextInputSession * _consoleTextInputSession; + +static utf8 _consoleHistory[CONSOLE_HISTORY_SIZE][CONSOLE_INPUT_SIZE]; +static sint32 _consoleHistoryIndex = 0; +static sint32 _consoleHistoryCount = 0; + +static InGameConsole _inGameConsole; + +InGameConsole::InGameConsole() +{ + InteractiveConsole::WriteLine(OPENRCT2_NAME " " OPENRCT2_VERSION); + InteractiveConsole::WriteLine("Type 'help' for a list of available commands. Type 'hide' to hide the console."); + InteractiveConsole::WriteLine(""); + WritePrompt(); +} + +void InGameConsole::WritePrompt() +{ + InteractiveConsole::WriteLine("> "); +} + +void InGameConsole::Input(CONSOLE_INPUT input) +{ + switch (input) { + case CONSOLE_INPUT_LINE_CLEAR: + ClearInput(); + RefreshCaret(); + break; + case CONSOLE_INPUT_LINE_EXECUTE: + if (_consoleCurrentLine[0] != '\0') { + HistoryAdd(_consoleCurrentLine); + Execute(_consoleCurrentLine); + WritePrompt(); + ClearInput(); + RefreshCaret(); + } + ScrollToEnd(); + break; + case CONSOLE_INPUT_HISTORY_PREVIOUS: + if (_consoleHistoryIndex > 0) { + _consoleHistoryIndex--; + memcpy(_consoleCurrentLine, _consoleHistory[_consoleHistoryIndex], 256); + } + _consoleTextInputSession->Size = strlen(_consoleTextInputSession->Buffer); + _consoleTextInputSession->Length = utf8_length(_consoleTextInputSession->Buffer); + _consoleTextInputSession->SelectionStart = strlen(_consoleCurrentLine); + break; + case CONSOLE_INPUT_HISTORY_NEXT: + if (_consoleHistoryIndex < _consoleHistoryCount - 1) { + _consoleHistoryIndex++; + memcpy(_consoleCurrentLine, _consoleHistory[_consoleHistoryIndex], 256); + _consoleTextInputSession->Size = strlen(_consoleTextInputSession->Buffer); + _consoleTextInputSession->Length = utf8_length(_consoleTextInputSession->Buffer); + _consoleTextInputSession->SelectionStart = strlen(_consoleCurrentLine); + } else { + _consoleHistoryIndex = _consoleHistoryCount; + ClearInput(); + } + break; + case CONSOLE_INPUT_SCROLL_PREVIOUS: + { + sint32 scrollAmt = GetNumVisibleLines() - 1; + Scroll(scrollAmt); + break; + } + case CONSOLE_INPUT_SCROLL_NEXT: + { + sint32 scrollAmt = GetNumVisibleLines() - 1; + Scroll(-scrollAmt); + break; + } + default: + break; + } +} + +void InGameConsole::ClearInput() +{ + _consoleCurrentLine[0] = 0; + if (gConsoleOpen) { + context_start_text_input(_consoleCurrentLine, sizeof(_consoleCurrentLine)); + } +} + +void InGameConsole::HistoryAdd(const utf8 * src) +{ + if (_consoleHistoryCount >= CONSOLE_HISTORY_SIZE) { + for (sint32 i = 0; i < _consoleHistoryCount - 1; i++) + memcpy(_consoleHistory[i], _consoleHistory[i + 1], CONSOLE_INPUT_SIZE); + _consoleHistoryCount--; + } + memcpy(_consoleHistory[_consoleHistoryCount++], src, CONSOLE_INPUT_SIZE); + _consoleHistoryIndex = _consoleHistoryCount; +} + +void InGameConsole::ScrollToEnd() +{ + _consoleScrollPos = std::max(0, (sint32)_consoleLines.size() - GetNumVisibleLines()); +} + +void InGameConsole::RefreshCaret() +{ + _consoleCaretTicks = 0; +} + +void InGameConsole::Scroll(sint32 linesToScroll) +{ + const sint32 maxVisibleLines = GetNumVisibleLines(); + const sint32 numLines = (sint32)_consoleLines.size(); + if (numLines > maxVisibleLines) + { + sint32 maxScrollValue = numLines - maxVisibleLines; + _consoleScrollPos = Math::Clamp(0, _consoleScrollPos - linesToScroll, maxScrollValue); + } +} + +void InGameConsole::Clear() +{ + _consoleLines.clear(); + ScrollToEnd(); +} + +void InGameConsole::ClearLine() +{ + _consoleCurrentLine[0] = 0; + RefreshCaret(); +} + +void InGameConsole::Open() +{ + gConsoleOpen = true; + ScrollToEnd(); + RefreshCaret(); + _consoleTextInputSession = context_start_text_input(_consoleCurrentLine, sizeof(_consoleCurrentLine)); +} + +void InGameConsole::Close() +{ + gConsoleOpen = false; + Invalidate(); + context_stop_text_input(); +} + +void InGameConsole::Toggle() +{ + if (gConsoleOpen) + console_close(); + else + console_open(); +} + +void InGameConsole::WriteLine(const std::string &input, uint32 colourFormat) +{ + // Include text colour format only for special cases + // The draw function handles the default text colour differently + utf8 colourCodepoint[4]{}; + if (colourFormat != FORMAT_WINDOW_COLOUR_2) + utf8_write_codepoint(colourCodepoint, colourFormat); + + std::string line; + std::size_t splitPos = 0; + std::size_t stringOffset = 0; + while (splitPos != std::string::npos) + { + splitPos = input.find('\n', stringOffset); + line = input.substr(stringOffset, splitPos - stringOffset); + _consoleLines.push_back(colourCodepoint + line); + stringOffset = splitPos + 1; + } + + if (_consoleLines.size() > CONSOLE_MAX_LINES) + { + const std::size_t linesToErase = _consoleLines.size() - CONSOLE_MAX_LINES; + _consoleLines.erase(_consoleLines.begin(), _consoleLines.begin() + linesToErase); + } +} + +void InGameConsole::Invalidate() +{ + gfx_set_dirty_blocks(_consoleLeft, _consoleTop, _consoleRight, _consoleBottom); +} + +void InGameConsole::Update() +{ + _consoleLeft = 0; + _consoleTop = 0; + _consoleRight = context_get_width(); + _consoleBottom = 322; + + if (gConsoleOpen) { + // When scrolling the map, the console pixels get copied... therefore invalidate the screen + rct_window *mainWindow = window_get_main(); + if (mainWindow != nullptr) { + rct_viewport *mainViewport = window_get_viewport(mainWindow); + if (mainViewport != nullptr) { + if (_lastMainViewportX != mainViewport->view_x || _lastMainViewportY != mainViewport->view_y) { + _lastMainViewportX = mainViewport->view_x; + _lastMainViewportY = mainViewport->view_y; + + gfx_invalidate_screen(); + } + } + } + + // Remove unwanted characters in console input + utf8_remove_format_codes(_consoleCurrentLine, false); + } + + // Flash the caret + _consoleCaretTicks = (_consoleCaretTicks + 1) % 30; +} + +void InGameConsole::Draw(rct_drawpixelinfo * dpi) +{ + if (!gConsoleOpen) + return; + + // Set font + gCurrentFontSpriteBase = (gConfigInterface.console_small_font ? FONT_SPRITE_BASE_SMALL : FONT_SPRITE_BASE_MEDIUM); + gCurrentFontFlags = 0; + uint8 textColour = NOT_TRANSLUCENT(theme_get_colour(WC_CONSOLE, 1)); + const sint32 lineHeight = font_get_line_height(gCurrentFontSpriteBase); + const sint32 maxLines = GetNumVisibleLines(); + + // This is something of a hack to ensure the text is actually black + // as opposed to a desaturated grey + std::string colourFormatStr; + if (textColour == COLOUR_BLACK) + { + utf8 extraTextFormatCode[4]{}; + utf8_write_codepoint(extraTextFormatCode, FORMAT_BLACK); + colourFormatStr = extraTextFormatCode; + } + + // TTF looks far better without the outlines + if (!gUseTrueTypeFont) + { + textColour |= COLOUR_FLAG_OUTLINE; + } + + Invalidate(); + + // Give console area a translucent effect. + gfx_filter_rect(dpi, _consoleLeft, _consoleTop, _consoleRight, _consoleBottom, PALETTE_51); + + // Make input area more opaque. + gfx_filter_rect(dpi, _consoleLeft, _consoleBottom - lineHeight - 10, _consoleRight, _consoleBottom - 1, PALETTE_51); + + // Paint background colour. + uint8 backgroundColour = theme_get_colour(WC_CONSOLE, 0); + gfx_fill_rect_inset(dpi, _consoleLeft, _consoleTop, _consoleRight, _consoleBottom, backgroundColour, INSET_RECT_FLAG_FILL_NONE); + gfx_fill_rect_inset(dpi, _consoleLeft + 1, _consoleTop + 1, _consoleRight - 1, _consoleBottom - 1, backgroundColour, INSET_RECT_FLAG_BORDER_INSET); + + std::string lineBuffer; + sint32 x = _consoleLeft + CONSOLE_EDGE_PADDING; + sint32 y = _consoleTop + CONSOLE_EDGE_PADDING; + + // Draw text inside console + for (std::size_t i = 0; i < _consoleLines.size() && i < (size_t)maxLines; i++) { + const size_t index = i + _consoleScrollPos; + lineBuffer = colourFormatStr + _consoleLines[index]; + gfx_draw_string(dpi, lineBuffer.c_str(), textColour, x, y); + y += lineHeight; + } + + y = _consoleBottom - lineHeight - CONSOLE_EDGE_PADDING - 1; + + // Draw current line + lineBuffer = colourFormatStr + _consoleCurrentLine; + gfx_draw_string(dpi, lineBuffer.c_str(), TEXT_COLOUR_255, x, y); + + // Draw caret + if (_consoleCaretTicks < CONSOLE_CARET_FLASH_THRESHOLD) { + sint32 caretX = x + gfx_get_string_width(_consoleCurrentLine); + sint32 caretY = y + lineHeight; + + uint8 caretColour = ColourMapA[BASE_COLOUR(textColour)].lightest; + gfx_fill_rect(dpi, caretX, caretY, caretX + CONSOLE_CARET_WIDTH, caretY, caretColour); + } + + // What about border colours? + uint8 borderColour1 = ColourMapA[BASE_COLOUR(backgroundColour)].light; + uint8 borderColour2 = ColourMapA[BASE_COLOUR(backgroundColour)].mid_dark; + + // Input area top border + gfx_fill_rect(dpi, _consoleLeft, _consoleBottom - lineHeight - 11, _consoleRight, _consoleBottom - lineHeight - 11, borderColour1); + gfx_fill_rect(dpi, _consoleLeft, _consoleBottom - lineHeight - 10, _consoleRight, _consoleBottom - lineHeight - 10, borderColour2); + + // Input area bottom border + gfx_fill_rect(dpi, _consoleLeft, _consoleBottom - 1, _consoleRight, _consoleBottom - 1, borderColour1); + gfx_fill_rect(dpi, _consoleLeft, _consoleBottom - 0, _consoleRight, _consoleBottom - 0, borderColour2); +} + +// Calculates the amount of visible lines, based on the console size, excluding the input line. +sint32 InGameConsole::GetNumVisibleLines() +{ + const sint32 lineHeight = font_get_line_height(gCurrentFontSpriteBase); + const sint32 consoleHeight = _consoleBottom - _consoleTop; + const sint32 drawableHeight = consoleHeight - 2 * lineHeight - 4; // input line, separator - padding + return drawableHeight / lineHeight; +} + +// Old pass-through functions + +void console_open() +{ + _inGameConsole.Open(); +} + +void console_close() +{ + _inGameConsole.Close(); +} + +void console_toggle() +{ + _inGameConsole.Toggle(); +} + +void console_update() +{ + _inGameConsole.Update(); +} + +void console_draw(rct_drawpixelinfo *dpi) +{ + _inGameConsole.Draw(dpi); +} + +void console_input(CONSOLE_INPUT input) +{ + _inGameConsole.Input(input); +} + +void console_writeline(const utf8 * src, uint32 colourFormat) +{ + _inGameConsole.WriteLine(src, colourFormat); +} + +void console_scroll(sint32 linesToScroll) +{ + _inGameConsole.Scroll(linesToScroll); +} + +void console_refresh_caret() +{ + _inGameConsole.RefreshCaret(); +} diff --git a/src/openrct2/interface/StdInOutConsole.cpp b/src/openrct2/interface/StdInOutConsole.cpp new file mode 100644 index 0000000000..e75aac3c24 --- /dev/null +++ b/src/openrct2/interface/StdInOutConsole.cpp @@ -0,0 +1,91 @@ +#include "../OpenRCT2.h" +#include "../thirdparty/linenoise.hpp" +#include "Console.h" + +void StdInOutConsole::Start() +{ + std::thread replThread ([this]() -> void + { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + linenoise::SetMultiLine(true); + linenoise::SetHistoryMaxLen(32); + + std::string prompt = "\033[32mopenrct2 $\x1b[0m "; + while (true) + { + std::string line; + std::string left = prompt; + auto quit = linenoise::Readline(left.c_str(), line); + if (quit) { + openrct2_finish(); + break; + } + linenoise::AddHistory(line.c_str()); + Eval(line).wait(); + } + }); + replThread.detach(); +} + +std::future StdInOutConsole::Eval(const std::string &s) +{ + // Push on-demand evaluations onto a queue so that it can be processed deterministically + // on the main thead at the right time. + std::promise barrier; + auto future = barrier.get_future(); + _evalQueue.emplace(std::move(barrier), s); + return future; +} + +void StdInOutConsole::ProcessEvalQueue() +{ + while (_evalQueue.size() > 0) + { + auto item = std::move(_evalQueue.front()); + _evalQueue.pop(); + auto promise = std::move(std::get<0>(item)); + auto command = std::move(std::get<1>(item)); + + Execute(command); + + // Signal the promise so caller can continue + promise.set_value(); + } +} + +void StdInOutConsole::Clear() +{ + linenoise::linenoiseClearScreen(); +} + +void StdInOutConsole::Close() +{ + openrct2_finish(); +} + +void StdInOutConsole::WriteLine(const std::string &s, uint32 colourFormat) +{ + std::string formatBegin; + if (colourFormat != FORMAT_WINDOW_COLOUR_2) + { + switch (colourFormat) + { + case FORMAT_RED: + formatBegin = "\033[31m"; + break; + case FORMAT_YELLOW: + formatBegin = "\033[33m"; + break; + } + } + + if (formatBegin.empty()) + { + std::printf("%s\n", s.c_str()); + } + else + { + std::printf("%s%s%s\n", formatBegin.c_str(), s.c_str(), "\x1b[0m"); + } +}