Browse Source

Add a basic Quake-style console

Enabled only in Debug mode.

Runs Lua similar to the `lua` CLI.
Supports multiline input with Shift+Enter.

Missing features:
1. Scrollback.
2. Input history on up/down.

Open with backtick, close with Esc.
pull/6757/head
Gleb Mazovetskiy 2 years ago
parent
commit
3ea4996367
  1. 2
      CMake/Assets.cmake
  2. BIN
      Packaging/resources/assets/fonts/gamedialogred.trn
  3. BIN
      Packaging/resources/assets/fonts/gamedialogwhite.trn
  4. 2
      Source/CMakeLists.txt
  5. 3
      Source/DiabloUI/text_input.cpp
  6. 45
      Source/DiabloUI/ui_flags.hpp
  7. 10
      Source/debug.cpp
  8. 13
      Source/diablo.cpp
  9. 5
      Source/engine/render/scrollrt.cpp
  10. 61
      Source/engine/render/text_render.cpp
  11. 22
      Source/engine/render/text_render.hpp
  12. 15
      Source/lua/lua.cpp
  13. 6
      Source/lua/lua.hpp
  14. 59
      Source/lua/repl.cpp
  15. 14
      Source/lua/repl.hpp
  16. 275
      Source/panels/console.cpp
  17. 15
      Source/panels/console.hpp
  18. 2
      Source/utils/sdl2_to_1_2_backports.h
  19. 14
      Source/utils/str_split.hpp

2
CMake/Assets.cmake

@ -114,6 +114,8 @@ set(devilutionx_assets
fonts/blue.trn
fonts/buttonface.trn
fonts/buttonpushed.trn
fonts/gamedialogwhite.trn
fonts/gamedialogred.trn
fonts/golduis.trn
fonts/goldui.trn
fonts/grayuis.trn

BIN
Packaging/resources/assets/fonts/gamedialogred.trn

Binary file not shown.

BIN
Packaging/resources/assets/fonts/gamedialogwhite.trn

Binary file not shown.

2
Source/CMakeLists.txt

@ -133,10 +133,12 @@ set(libdevilutionx_SRCS
levels/trigs.cpp
lua/lua.cpp
lua/repl.cpp
lua/modules/render.cpp
lua/modules/log.cpp
panels/charpanel.cpp
panels/console.cpp
panels/info_box.cpp
panels/mainpanel.cpp
panels/spell_book.cpp

3
Source/DiabloUI/text_input.cpp

@ -103,7 +103,7 @@ bool HandleInputEvent(const SDL_Event &event, TextInputState &state,
return !isCtrl && !isAlt
&& event.key.keysym.sym >= SDLK_SPACE && event.key.keysym.sym <= SDLK_z;
#endif
}
} break;
#ifndef USE_SDL1
case SDL_TEXTINPUT:
#ifdef __vita__
@ -118,6 +118,7 @@ bool HandleInputEvent(const SDL_Event &event, TextInputState &state,
default:
return false;
}
return false;
}
} // namespace

45
Source/DiabloUI/ui_flags.hpp

@ -22,28 +22,29 @@ enum class UiFlags : uint32_t {
ColorUiGoldDark = 1 << 8,
ColorUiSilverDark = 1 << 9,
ColorDialogWhite = 1 << 10,
ColorYellow = 1 << 11,
ColorGold = 1 << 12,
ColorBlack = 1 << 13,
ColorWhite = 1 << 14,
ColorWhitegold = 1 << 15,
ColorRed = 1 << 16,
ColorBlue = 1 << 17,
ColorOrange = 1 << 18,
ColorButtonface = 1 << 19,
ColorButtonpushed = 1 << 20,
AlignCenter = 1 << 21,
AlignRight = 1 << 22,
VerticalCenter = 1 << 23,
KerningFitSpacing = 1 << 24,
ElementDisabled = 1 << 25,
ElementHidden = 1 << 26,
PentaCursor = 1 << 27,
Outlined = 1 << 28,
ColorDialogRed = 1 << 11,
ColorYellow = 1 << 12,
ColorGold = 1 << 13,
ColorBlack = 1 << 14,
ColorWhite = 1 << 15,
ColorWhitegold = 1 << 16,
ColorRed = 1 << 17,
ColorBlue = 1 << 18,
ColorOrange = 1 << 19,
ColorButtonface = 1 << 20,
ColorButtonpushed = 1 << 21,
AlignCenter = 1 << 22,
AlignRight = 1 << 23,
VerticalCenter = 1 << 24,
KerningFitSpacing = 1 << 25,
ElementDisabled = 1 << 26,
ElementHidden = 1 << 27,
PentaCursor = 1 << 28,
Outlined = 1 << 29,
/** @brief Ensures that the if current element is active that the next element is also visible. */
NeedsNextElement = 1 << 30,

10
Source/debug.cpp

@ -153,15 +153,6 @@ std::string DebugCmdHelp(const std::string_view parameter)
return StrCat("Description: ", dbgCmdItem.description, "\nParameters: ", dbgCmdItem.requiredParameter);
}
std::string DebugCmdLua(std::string_view code)
{
tl::expected<std::string, std::string> result = RunLua(code);
if (!result.has_value()) {
return StrCat("> ", code, "\n") + std::move(result).error();
}
return StrCat("> ", code, "\n") + std::move(result).value();
}
std::string DebugCmdGiveGoldCheat(const std::string_view parameter)
{
Player &myPlayer = *MyPlayer;
@ -1285,7 +1276,6 @@ std::vector<DebugCmdItem> DebugCmdList = {
{ "searchitem", "Searches the automap for {item}", "{item}", &DebugCmdSearchItem },
{ "searchobject", "Searches the automap for {object}", "{object}", &DebugCmdSearchObject },
{ "clearsearch", "Search in the auto map is cleared", "", &DebugCmdClearSearch },
{ "lua", "Run Lua code", "{code}", &DebugCmdLua },
};
} // namespace

13
Source/diablo.cpp

@ -61,6 +61,7 @@
#include "nthread.h"
#include "objects.h"
#include "options.h"
#include "panels/console.hpp"
#include "panels/info_box.hpp"
#include "panels/spell_book.hpp"
#include "panels/spell_list.hpp"
@ -706,6 +707,12 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState)
return;
}
#ifdef _DEBUG
if (ConsoleHandleEvent(event)) {
return;
}
#endif
if (IsTalkActive() && HandleTalkTextInputEvent(event)) {
return;
}
@ -1864,6 +1871,12 @@ void InitKeymapActions()
ToggleChatLog();
});
#ifdef _DEBUG
sgOptions.Keymapper.AddAction(
"OpenConsole",
N_("Console"),
N_("Opens Lua console."),
SDLK_BACKQUOTE,
OpenConsole);
sgOptions.Keymapper.AddAction(
"DebugToggle",
"Debug toggle",

5
Source/engine/render/scrollrt.cpp

@ -34,6 +34,7 @@
#include "nthread.h"
#include "options.h"
#include "panels/charpanel.hpp"
#include "panels/console.hpp"
#include "plrmsg.h"
#include "qol/chatlog.h"
#include "qol/floatingnumbers.h"
@ -1660,6 +1661,10 @@ void DrawAndBlit()
DrawMain(out, hgt, drawInfoBox, drawHealth, drawMana, drawBelt, drawControlButtons);
#ifdef _DEBUG
DrawConsole(out);
#endif
RedrawComplete();
for (PanelDrawComponent component : enum_values<PanelDrawComponent>()) {
if (IsRedrawComponent(component)) {

61
Source/engine/render/text_render.cpp

@ -46,13 +46,14 @@ constexpr std::array<int, 6> LineHeights = { 12, 26, 38, 42, 50, 22 };
constexpr int SmallFontTallLineHeight = 16;
std::array<int, 6> BaseLineOffset = { -3, -2, -3, -6, -7, 3 };
std::array<const char *, 15> ColorTranslations = {
std::array<const char *, 18> ColorTranslations = {
"fonts\\goldui.trn",
"fonts\\grayui.trn",
"fonts\\golduis.trn",
"fonts\\grayuis.trn",
nullptr,
nullptr, // ColorDialogWhite
nullptr, // ColorDialogRed
"fonts\\yellow.trn",
nullptr,
@ -66,55 +67,43 @@ std::array<const char *, 15> ColorTranslations = {
"fonts\\buttonface.trn",
"fonts\\buttonpushed.trn",
"fonts\\gamedialogwhite.trn",
"fonts\\gamedialogred.trn",
};
std::array<std::optional<std::array<uint8_t, 256>>, 15> ColorTranslationsData;
GameFontTables GetSizeFromFlags(UiFlags flags)
{
if (HasAnyOf(flags, UiFlags::FontSize24))
return GameFont24;
else if (HasAnyOf(flags, UiFlags::FontSize30))
return GameFont30;
else if (HasAnyOf(flags, UiFlags::FontSize42))
return GameFont42;
else if (HasAnyOf(flags, UiFlags::FontSize46))
return GameFont46;
else if (HasAnyOf(flags, UiFlags::FontSizeDialog))
return FontSizeDialog;
return GameFont12;
}
std::array<std::optional<std::array<uint8_t, 256>>, 18> ColorTranslationsData;
text_color GetColorFromFlags(UiFlags flags)
{
if (HasAnyOf(flags, UiFlags::ColorWhite))
return ColorWhite;
else if (HasAnyOf(flags, UiFlags::ColorBlue))
if (HasAnyOf(flags, UiFlags::ColorBlue))
return ColorBlue;
else if (HasAnyOf(flags, UiFlags::ColorOrange))
if (HasAnyOf(flags, UiFlags::ColorOrange))
return ColorOrange;
else if (HasAnyOf(flags, UiFlags::ColorRed))
if (HasAnyOf(flags, UiFlags::ColorRed))
return ColorRed;
else if (HasAnyOf(flags, UiFlags::ColorBlack))
if (HasAnyOf(flags, UiFlags::ColorBlack))
return ColorBlack;
else if (HasAnyOf(flags, UiFlags::ColorGold))
if (HasAnyOf(flags, UiFlags::ColorGold))
return ColorGold;
else if (HasAnyOf(flags, UiFlags::ColorUiGold))
if (HasAnyOf(flags, UiFlags::ColorUiGold))
return ColorUiGold;
else if (HasAnyOf(flags, UiFlags::ColorUiSilver))
if (HasAnyOf(flags, UiFlags::ColorUiSilver))
return ColorUiSilver;
else if (HasAnyOf(flags, UiFlags::ColorUiGoldDark))
if (HasAnyOf(flags, UiFlags::ColorUiGoldDark))
return ColorUiGoldDark;
else if (HasAnyOf(flags, UiFlags::ColorUiSilverDark))
if (HasAnyOf(flags, UiFlags::ColorUiSilverDark))
return ColorUiSilverDark;
else if (HasAnyOf(flags, UiFlags::ColorDialogWhite))
return ColorDialogWhite;
else if (HasAnyOf(flags, UiFlags::ColorYellow))
if (HasAnyOf(flags, UiFlags::ColorDialogWhite))
return gbRunGame ? ColorInGameDialogWhite : ColorDialogWhite;
if (HasAnyOf(flags, UiFlags::ColorDialogRed))
return ColorInGameDialogRed;
if (HasAnyOf(flags, UiFlags::ColorYellow))
return ColorYellow;
else if (HasAnyOf(flags, UiFlags::ColorButtonface))
if (HasAnyOf(flags, UiFlags::ColorButtonface))
return ColorButtonface;
else if (HasAnyOf(flags, UiFlags::ColorButtonpushed))
if (HasAnyOf(flags, UiFlags::ColorButtonpushed))
return ColorButtonpushed;
return ColorWhitegold;
@ -443,6 +432,8 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect,
const uint8_t frame = next & 0xFF;
const uint16_t width = (*currentFont.sprite)[frame].width();
if (next == U'\n' || characterPosition.x + width > rightMargin) {
if (next == '\n')
maybeDrawCursor();
const int nextLineY = characterPosition.y + opts.lineHeight;
if (nextLineY >= bottomMargin)
break;
@ -683,7 +674,7 @@ std::string WordWrapString(std::string_view text, unsigned width, GameFontTables
*/
uint32_t DrawString(const Surface &out, std::string_view text, const Rectangle &rect, TextRenderOptions opts)
{
const GameFontTables size = GetSizeFromFlags(opts.flags);
const GameFontTables size = GetFontSizeFromUiFlags(opts.flags);
const text_color color = GetColorFromFlags(opts.flags);
int charactersInLine = 0;
@ -734,7 +725,7 @@ uint32_t DrawString(const Surface &out, std::string_view text, const Rectangle &
void DrawStringWithColors(const Surface &out, std::string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, const Rectangle &rect, TextRenderOptions opts)
{
const GameFontTables size = GetSizeFromFlags(opts.flags);
const GameFontTables size = GetFontSizeFromUiFlags(opts.flags);
const text_color color = GetColorFromFlags(opts.flags);
int charactersInLine = 0;

22
Source/engine/render/text_render.hpp

@ -20,6 +20,7 @@
#include "engine/clx_sprite.hpp"
#include "engine/palette.h"
#include "engine/rectangle.hpp"
#include "utils/enum_traits.h"
namespace devilution {
@ -38,7 +39,8 @@ enum text_color : uint8_t {
ColorUiGoldDark,
ColorUiSilverDark,
ColorDialogWhite,
ColorDialogWhite, // Dialog white in main menu
ColorDialogRed,
ColorYellow,
ColorGold,
@ -52,8 +54,26 @@ enum text_color : uint8_t {
ColorButtonface,
ColorButtonpushed,
ColorInGameDialogWhite, // Dialog white in-game
ColorInGameDialogRed, // Dialog red in-game
};
constexpr GameFontTables GetFontSizeFromUiFlags(UiFlags flags)
{
if (HasAnyOf(flags, UiFlags::FontSize24))
return GameFont24;
if (HasAnyOf(flags, UiFlags::FontSize30))
return GameFont30;
if (HasAnyOf(flags, UiFlags::FontSize42))
return GameFont42;
if (HasAnyOf(flags, UiFlags::FontSize46))
return GameFont46;
if (HasAnyOf(flags, UiFlags::FontSizeDialog))
return FontSizeDialog;
return GameFont12;
}
/**
* @brief A format argument for `DrawStringWithColors`.
*/

15
Source/lua/lua.cpp

@ -4,7 +4,6 @@
#include <string_view>
#include <sol/sol.hpp>
#include <sol/utility/to_string.hpp>
#include "engine/assets.hpp"
#include "lua/modules/log.hpp"
@ -137,19 +136,9 @@ void LuaEvent(std::string_view name)
CheckResult(fn());
}
tl::expected<std::string, std::string> RunLua(std::string_view code)
sol::state &LuaState()
{
sol::state &lua = *luaState;
const sol::protected_function_result result = lua.safe_script(code);
const bool valid = result.valid();
if (!valid) {
if (result.get_type() == sol::type::string) {
return tl::make_unexpected(result.get<std::string>());
}
return tl::make_unexpected("Unknown Lua error");
}
return sol::utility::to_string(sol::stack_object(result));
return *luaState;
}
} // namespace devilution

6
Source/lua/lua.hpp

@ -4,11 +4,15 @@
#include <expected.hpp>
namespace sol {
class state;
} // namespace sol
namespace devilution {
void LuaInitialize();
void LuaShutdown();
void LuaEvent(std::string_view name);
tl::expected<std::string, std::string> RunLua(std::string_view code);
sol::state &LuaState();
} // namespace devilution

59
Source/lua/repl.cpp

@ -0,0 +1,59 @@
#ifdef _DEBUG
#include "lua/repl.hpp"
#include <string>
#include <string_view>
#include <expected.hpp>
#include <sol/sol.hpp>
#include <sol/utility/to_string.hpp>
#include "lua/lua.hpp"
#include "utils/str_cat.hpp"
namespace devilution {
namespace {
sol::protected_function_result TryRunLuaAsExpressionThenStatement(std::string_view code)
{
// Try to compile as an expression first. This also how the `lua` repl is implemented.
const sol::state &lua = LuaState();
std::string expression = StrCat("return ", code, ";");
sol::detail::typical_chunk_name_t basechunkname = {};
sol::load_status status = static_cast<sol::load_status>(
luaL_loadbufferx(lua.lua_state(), expression.data(), expression.size(),
sol::detail::make_chunk_name(expression, sol::detail::default_chunk_name(), basechunkname), "text"));
if (status != sol::load_status::ok) {
// Try as a statement:
status = static_cast<sol::load_status>(
luaL_loadbufferx(lua.lua_state(), code.data(), code.size(),
sol::detail::make_chunk_name(code, sol::detail::default_chunk_name(), basechunkname), "text"));
if (status != sol::load_status::ok) {
return sol::protected_function_result(
lua.lua_state(), sol::absolute_index(lua.lua_state(), -1), 0, 1, static_cast<sol::call_status>(status));
}
}
sol::stack_aligned_protected_function fn(lua.lua_state(), -1);
return fn();
}
} // namespace
tl::expected<std::string, std::string> RunLuaReplLine(std::string_view code)
{
const sol::protected_function_result result = TryRunLuaAsExpressionThenStatement(code);
if (!result.valid()) {
if (result.get_type() == sol::type::string) {
return tl::make_unexpected(result.get<std::string>());
}
return tl::make_unexpected("Unknown Lua error");
}
if (result.get_type() == sol::type::none) {
return std::string {};
}
return sol::utility::to_string(sol::stack_object(result));
}
} // namespace devilution
#endif // _DEBUG

14
Source/lua/repl.hpp

@ -0,0 +1,14 @@
#pragma once
#ifdef _DEBUG
#include <string>
#include <string_view>
#include <expected.hpp>
namespace devilution {
tl::expected<std::string, std::string> RunLuaReplLine(std::string_view code);
} // namespace devilution
#endif // _DEBUG

275
Source/panels/console.cpp

@ -0,0 +1,275 @@
#ifdef _DEBUG
#include "panels/console.hpp"
#include <cstdint>
#include <string_view>
#include <SDL.h>
#ifdef USE_SDL1
#include "utils/sdl2_to_1_2_backports.h"
#endif
#include "DiabloUI/text_input.hpp"
#include "control.h"
#include "engine.h"
#include "engine/displacement.hpp"
#include "engine/dx.h"
#include "engine/palette.h"
#include "engine/rectangle.hpp"
#include "engine/render/text_render.hpp"
#include "engine/size.hpp"
#include "engine/surface.hpp"
#include "lua/repl.hpp"
#include "utils/algorithm/container.hpp"
#include "utils/sdl_geometry.h"
#include "utils/str_cat.hpp"
#include "utils/str_split.hpp"
namespace devilution {
namespace {
constexpr std::string_view Prompt = "> ";
bool IsConsoleVisible;
char ConsoleInputBuffer[4096];
TextInputCursorState ConsoleInputCursor;
TextInputState ConsoleInputState {
TextInputState::Options {
.value = ConsoleInputBuffer,
.cursor = &ConsoleInputCursor,
.maxLength = sizeof(ConsoleInputBuffer) - 1,
}
};
bool InputTextChanged = false;
std::string WrappedInputText { Prompt };
struct ConsoleLine {
enum Type : uint8_t {
Input,
Output,
Error
};
Type type;
std::string text;
std::string wrapped = {};
};
std::vector<ConsoleLine> ConsoleLines;
Rectangle OuterRect;
Rectangle InputRect;
int InputRectHeight;
constexpr int LineHeight = 20;
constexpr int TextPaddingYTop = 0;
constexpr int TextPaddingYBottom = 4;
constexpr int TextPaddingX = 4;
constexpr uint8_t BorderColor = PAL8_YELLOW;
bool FirstRender;
constexpr UiFlags TextUiFlags = UiFlags::FontSizeDialog;
constexpr UiFlags InputTextUiFlags = TextUiFlags | UiFlags::ColorDialogWhite;
constexpr UiFlags OutputTextUiFlags = TextUiFlags | UiFlags::ColorDialogWhite;
constexpr UiFlags ErrorTextUiFlags = TextUiFlags | UiFlags::ColorDialogRed;
constexpr int TextSpacing = 0;
constexpr GameFontTables TextFontSize = GetFontSizeFromUiFlags(InputTextUiFlags);
void CloseConsole()
{
IsConsoleVisible = false;
SDL_StopTextInput();
}
void SendInput()
{
const std::string_view input = ConsoleInputState.value();
tl::expected<std::string, std::string> result = RunLuaReplLine(input);
ConsoleLines.emplace_back(ConsoleLine { .type = ConsoleLine::Input, .text = StrCat("> ", input) });
if (result.has_value()) {
if (!result->empty()) {
ConsoleLines.emplace_back(ConsoleLine { .type = ConsoleLine::Output, .text = *std::move(result) });
}
} else {
if (!result.error().empty()) {
ConsoleLines.emplace_back(ConsoleLine { .type = ConsoleLine::Error, .text = std::move(result).error() });
} else {
ConsoleLines.emplace_back(ConsoleLine { .type = ConsoleLine::Error, .text = "Unknown error" });
}
}
ConsoleInputState.clear();
}
void DrawInputText(const Surface &inputTextSurface, std::string_view originalInputText, std::string_view wrappedInputText)
{
int lineY = 0;
int numRendered = -static_cast<int>(Prompt.size());
bool prevIsOriginalNewline = false;
for (const std::string_view line : SplitByChar(wrappedInputText, '\n')) {
const int lineCursorPosition = static_cast<int>(ConsoleInputCursor.position) - numRendered;
const bool isCursorOnPrevLine = lineCursorPosition == 0 && !prevIsOriginalNewline && numRendered > 0;
DrawString(
inputTextSurface, line, { 0, lineY },
TextRenderOptions {
.flags = InputTextUiFlags,
.spacing = TextSpacing,
.cursorPosition = isCursorOnPrevLine ? -1 : lineCursorPosition,
.highlightRange = { static_cast<int>(ConsoleInputCursor.selection.begin) - numRendered, static_cast<int>(ConsoleInputCursor.selection.end) - numRendered },
});
lineY += LineHeight;
numRendered += static_cast<int>(line.size());
prevIsOriginalNewline = static_cast<size_t>(numRendered) < originalInputText.size()
&& originalInputText[static_cast<size_t>(numRendered)] == '\n';
if (prevIsOriginalNewline)
++numRendered;
}
}
void DrawConsoleLines(const Surface &out)
{
int lineYEnd = out.h() - 4; // Extra space for letters like g.
// NOLINTNEXTLINE(modernize-loop-convert)
for (auto it = ConsoleLines.rbegin(), itEnd = ConsoleLines.rend(); it != itEnd; ++it) {
ConsoleLine &consoleLine = *it;
if (consoleLine.wrapped.empty()) {
consoleLine.wrapped = WordWrapString(consoleLine.text, out.w(), TextFontSize, TextSpacing);
}
size_t end = consoleLine.wrapped.size();
while (true) {
const size_t begin = consoleLine.wrapped.rfind('\n', end - 1) + 1;
const std::string_view line = std::string_view(consoleLine.wrapped.data() + begin, end - begin);
lineYEnd -= LineHeight;
switch (consoleLine.type) {
case ConsoleLine::Input:
DrawString(out, line, { 0, lineYEnd },
TextRenderOptions { .flags = InputTextUiFlags, .spacing = TextSpacing });
break;
case ConsoleLine::Output:
DrawString(out, line, { 0, lineYEnd },
TextRenderOptions { .flags = OutputTextUiFlags, .spacing = TextSpacing });
break;
case ConsoleLine::Error:
DrawString(out, line, { 0, lineYEnd },
TextRenderOptions { .flags = ErrorTextUiFlags, .spacing = TextSpacing });
break;
}
if (lineYEnd < 0 || begin == 0)
break;
end = begin - 1;
}
}
}
} // namespace
void OpenConsole()
{
IsConsoleVisible = true;
FirstRender = true;
}
bool ConsoleHandleEvent(const SDL_Event &event)
{
if (!IsConsoleVisible) {
// Make console open on the top-left keyboard key even if it is not a backtick.
if (event.type == SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_GRAVE) {
OpenConsole();
return true;
}
return false;
}
if (HandleTextInputEvent(event, ConsoleInputState)) {
InputTextChanged = true;
return true;
}
const auto modState = SDL_GetModState();
const bool isShift = (modState & KMOD_SHIFT) != 0;
switch (event.type) {
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
CloseConsole();
return true;
case SDLK_RETURN:
case SDLK_KP_ENTER:
if (isShift) {
ConsoleInputState.type("\n");
} else {
SendInput();
}
InputTextChanged = true;
return true;
default:
return false;
}
break;
default:
return false;
}
return false;
}
void DrawConsole(const Surface &out)
{
if (!IsConsoleVisible)
return;
OuterRect.position = { 0, 0 };
OuterRect.size = { out.w(), out.h() - GetMainPanel().size.height - 2 };
const std::string_view originalInputText = ConsoleInputState.value();
if (InputTextChanged) {
WrappedInputText = WordWrapString(StrCat(Prompt, originalInputText), OuterRect.size.width - 2 * TextPaddingX, TextFontSize, TextSpacing);
InputTextChanged = false;
}
const int numLines = static_cast<int>(c_count(WrappedInputText, '\n')) + 1;
const int inputTextHeight = numLines * LineHeight;
InputRectHeight = inputTextHeight + TextPaddingYTop + TextPaddingYBottom;
InputRect.position = { 0, OuterRect.size.height - InputRectHeight };
InputRect.size = { OuterRect.size.width, InputRectHeight };
const Rectangle inputTextRect {
{ InputRect.position.x + TextPaddingX, InputRect.position.y + TextPaddingYTop },
{ InputRect.size.width - 2 * TextPaddingX, inputTextHeight }
};
if (FirstRender) {
const SDL_Rect sdlInputRect = MakeSdlRect(InputRect);
SDL_SetTextInputRect(&sdlInputRect);
SDL_StartTextInput();
FirstRender = false;
}
const Rectangle bgRect = OuterRect;
DrawHalfTransparentRectTo(out, bgRect.position.x, bgRect.position.y, bgRect.size.width, bgRect.size.height);
DrawConsoleLines(
out.subregion(
TextPaddingX,
TextPaddingYTop,
OuterRect.size.width - 2 * TextPaddingX,
OuterRect.size.height - inputTextRect.size.height - 8));
DrawHorizontalLine(out, InputRect.position - Displacement { 0, 1 }, InputRect.size.width, BorderColor);
DrawInputText(
out.subregion(
inputTextRect.position.x,
inputTextRect.position.y,
// Extra space for the cursor on the right:
inputTextRect.size.width + TextPaddingX,
// Extra space for letters like g.
inputTextRect.size.height + TextPaddingYBottom),
originalInputText,
WrappedInputText);
SDL_Rect sdlRect = MakeSdlRect(OuterRect);
BltFast(&sdlRect, &sdlRect);
}
} // namespace devilution
#endif // _DEBUG

15
Source/panels/console.hpp

@ -0,0 +1,15 @@
#ifdef _DEBUG
#pragma once
#include <SDL.h>
#include "engine/surface.hpp"
namespace devilution {
void OpenConsole();
bool ConsoleHandleEvent(const SDL_Event &event);
void DrawConsole(const Surface &out);
} // namespace devilution
#endif // _DEBUG

2
Source/utils/sdl2_to_1_2_backports.h

@ -51,6 +51,8 @@
#define SDLK_LGUI SDLK_LSUPER
#define SDLK_RGUI SDLK_RSUPER
#define SDL_SCANCODE_GRAVE 53
// Haptic events are not supported in SDL1.
#define SDL_INIT_HAPTIC 0

14
Source/utils/str_split.hpp

@ -17,10 +17,8 @@ public:
return SplitByCharIterator(split_by, text, text.substr(0, text.find(split_by)));
}
static SplitByCharIterator end(std::string_view text, char split_by) // NOLINT(readability-identifier-naming)
{
return SplitByCharIterator(split_by, text, text.substr(text.size()));
}
// End iterator
SplitByCharIterator() = default;
[[nodiscard]] std::string_view operator*() const
{
@ -34,6 +32,10 @@ public:
SplitByCharIterator &operator++()
{
if (slice_.data() + slice_.size() == text_.data() + text_.size()) {
slice_ = {};
return *this;
}
slice_ = text_.substr(slice_.data() - text_.data() + slice_.size());
if (!slice_.empty())
slice_.remove_prefix(1); // skip the split_by char
@ -66,7 +68,7 @@ private:
{
}
const char split_by_;
const char split_by_ = '\0';
const std::string_view text_;
std::string_view slice_;
};
@ -86,7 +88,7 @@ public:
[[nodiscard]] SplitByCharIterator end() const // NOLINT(readability-identifier-naming)
{
return SplitByCharIterator::end(text_, split_by_);
return {};
}
private:

Loading…
Cancel
Save