Skip to content

Commit

Permalink
Add cursor support to DiabloUI and chat
Browse files Browse the repository at this point in the history
Supports move left/right/home/end, backspace, delete, and Ctrl+V.
  • Loading branch information
glebm committed Oct 24, 2023
1 parent 1c1704d commit fdb5738
Show file tree
Hide file tree
Showing 15 changed files with 391 additions and 141 deletions.
1 change: 1 addition & 0 deletions Source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ set(libdevilutionx_SRCS
DiabloUI/settingsmenu.cpp
DiabloUI/support_lines.cpp
DiabloUI/title.cpp
DiabloUI/text_input.cpp

dvlnet/abstract_net.cpp
dvlnet/base.cpp
Expand Down
94 changes: 22 additions & 72 deletions Source/DiabloUI/diabloui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>

#include "DiabloUI/button.h"
#include "DiabloUI/dialogs.h"
#include "DiabloUI/scrollbar.h"
#include "DiabloUI/text_input.hpp"
#include "controls/controller.h"
#include "controls/input.h"
#include "controls/menu_controls.h"
Expand Down Expand Up @@ -58,7 +60,6 @@ OptionalOwnedClxSpriteList ArtBackgroundWidescreen;
OptionalOwnedClxSpriteList ArtBackground;
OptionalOwnedClxSpriteList ArtCursor;

bool textInputActive = true;
std::size_t SelectedItem = 0;

namespace {
Expand All @@ -79,8 +80,8 @@ bool (*gfnListYesNo)();
std::vector<UiItemBase *> gUiItems;
UiList *gUiList = nullptr;
bool UiItemsWraps;
char *UiTextInput;
int UiTextInputLen;

std::optional<TextInputState> UiTextInputState;
bool allowEmptyTextInput = false;

uint32_t fadeTc;
Expand All @@ -107,6 +108,11 @@ void AdjustListOffset(std::size_t itemIndex)

} // namespace

bool IsTextInputActive()
{
return UiTextInputState.has_value();
}

void UiInitList(void (*fnFocus)(int value), void (*fnSelect)(int value), void (*fnEsc)(), const std::vector<std::unique_ptr<UiItemBase>> &items, bool itemsWraps, void (*fnFullscreen)(), bool (*fnYesNo)(), size_t selectedItem /*= 0*/)
{
SelectedItem = selectedItem;
Expand All @@ -128,13 +134,11 @@ void UiInitList(void (*fnFocus)(int value), void (*fnSelect)(int value), void (*
#ifndef __SWITCH__
SDL_StopTextInput(); // input is enabled by default
#endif
textInputActive = false;
UiScrollbar *uiScrollbar = nullptr;
for (const auto &item : items) {
if (item->IsType(UiType::Edit)) {
auto *pItemUIEdit = static_cast<UiEdit *>(item.get());
SDL_SetTextInputRect(&item->m_rect);
textInputActive = true;
allowEmptyTextInput = pItemUIEdit->m_allowEmpty;
#ifdef __SWITCH__
switch_start_text_input(pItemUIEdit->m_hint, pItemUIEdit->m_value, pItemUIEdit->m_max_length);
Expand All @@ -145,8 +149,11 @@ void UiInitList(void (*fnFocus)(int value), void (*fnSelect)(int value), void (*
#else
SDL_StartTextInput();
#endif
UiTextInput = pItemUIEdit->m_value;
UiTextInputLen = pItemUIEdit->m_max_length;
UiTextInputState.emplace(TextInputState::Options {
.value = pItemUIEdit->m_value,
.cursorPosition = &pItemUIEdit->m_cursor,
.maxLength = pItemUIEdit->m_max_length,
});
} else if (item->IsType(UiType::List)) {
auto *uiList = static_cast<UiList *>(item.get());
SelectedItemMax = std::max(uiList->m_vecItems.size() - 1, static_cast<size_t>(0));
Expand Down Expand Up @@ -293,14 +300,6 @@ void UiFocusPageDown()
}
}

void SelheroCatToName(const char *inBuf, char *outBuf, int cnt)
{
size_t outLen = strlen(outBuf);
char *dest = outBuf + outLen;
size_t destCount = cnt - outLen;
CopyUtf8(dest, inBuf, destCount);
}

bool HandleMenuAction(MenuAction menuAction)
{
switch (menuAction) {
Expand Down Expand Up @@ -404,55 +403,8 @@ void UiFocusNavigation(SDL_Event *event)
}
#endif

if (textInputActive) {
switch (event->type) {
case SDL_KEYDOWN: {
switch (event->key.keysym.sym) {
#ifndef USE_SDL1
case SDLK_v:
if ((SDL_GetModState() & KMOD_CTRL) != 0) {
if (SDL_HasClipboardText() == SDL_TRUE) {
std::unique_ptr<char, SDLFreeDeleter<char>> clipboard { SDL_GetClipboardText() };
if (clipboard == nullptr || *clipboard == '\0') {
Log("{}", SDL_GetError());
} else {
SelheroCatToName(clipboard.get(), UiTextInput, UiTextInputLen);
}
}
}
return;
#endif
case SDLK_BACKSPACE:
case SDLK_LEFT: {
UiTextInput[FindLastUtf8Symbols(UiTextInput)] = '\0';
return;
}
default:
break;
}
#ifdef USE_SDL1
if ((event->key.keysym.mod & KMOD_CTRL) == 0) {
std::string utf8;
AppendUtf8(event->key.keysym.unicode, utf8);
SelheroCatToName(utf8.c_str(), UiTextInput, UiTextInputLen);
}
#endif
break;
}
#ifndef USE_SDL1
case SDL_TEXTINPUT:
if (textInputActive) {
#ifdef __vita__
CopyUtf8(UiTextInput, event->text.text, UiTextInputLen);
#else
SelheroCatToName(event->text.text, UiTextInput, UiTextInputLen);
#endif
}
return;
#endif
default:
break;
}
if (UiTextInputState.has_value() && HandleTextInputEvent(*event, *UiTextInputState)) {
return;
}

if (event->type == SDL_MOUSEBUTTONDOWN || event->type == SDL_MOUSEBUTTONUP) {
Expand Down Expand Up @@ -519,15 +471,14 @@ void UiHandleEvents(SDL_Event *event)
void UiFocusNavigationSelect()
{
UiPlaySelectSound();
if (textInputActive) {
if (!allowEmptyTextInput && strlen(UiTextInput) == 0) {
if (UiTextInputState.has_value()) {
if (!allowEmptyTextInput && UiTextInputState->empty()) {
return;
}
#ifndef __SWITCH__
SDL_StopTextInput();
#endif
UiTextInput = nullptr;
UiTextInputLen = 0;
UiTextInputState = std::nullopt;
}
if (gfnListSelect != nullptr)
gfnListSelect(SelectedItem);
Expand All @@ -536,12 +487,11 @@ void UiFocusNavigationSelect()
void UiFocusNavigationEsc()
{
UiPlaySelectSound();
if (textInputActive) {
if (UiTextInputState.has_value()) {
#ifndef __SWITCH__
SDL_StopTextInput();
#endif
UiTextInput = nullptr;
UiTextInputLen = 0;
UiTextInputState = std::nullopt;
}
if (gfnListEsc != nullptr)
gfnListEsc();
Expand Down Expand Up @@ -914,7 +864,7 @@ void Render(const UiEdit &uiEdit)
Rectangle rect = MakeRectangle(uiEdit.m_rect).inset({ 43, 1 });

const Surface &out = Surface(DiabloUiSurface());
DrawString(out, uiEdit.m_value, rect, uiEdit.GetFlags() | UiFlags::TextCursor);
DrawString(out, uiEdit.m_value, rect, uiEdit.GetFlags(), /*spacing=*/1, /*lineHeight=*/-1, uiEdit.m_cursor);
}

bool HandleMouseEventArtTextButton(const SDL_Event &event, const UiArtTextButton *uiButton)
Expand Down
3 changes: 2 additions & 1 deletion Source/DiabloUI/diabloui.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
namespace devilution {

extern std::size_t SelectedItem;
extern bool textInputActive;

bool IsTextInputActive();

enum _artFocus : uint8_t {
FOCUS_SMALL,
Expand Down
81 changes: 81 additions & 0 deletions Source/DiabloUI/text_input.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "DiabloUI/text_input.hpp"

#include <memory>

#include <SDL.h>

#ifdef USE_SDL1
#include "utils/sdl2_to_1_2_backports.h"
#endif

#include "utils/log.hpp"
#include "utils/sdl_ptrs.h"
#include "utils/utf8.hpp"

namespace devilution {

bool HandleTextInputEvent(const SDL_Event &event, TextInputState &state)
{
switch (event.type) {
case SDL_KEYDOWN: {
switch (event.key.keysym.sym) {
#ifndef USE_SDL1
case SDLK_v:
if ((SDL_GetModState() & KMOD_CTRL) != 0) {
if (SDL_HasClipboardText() == SDL_TRUE) {
std::unique_ptr<char, SDLFreeDeleter<char>> clipboard { SDL_GetClipboardText() };
if (clipboard == nullptr || *clipboard == '\0') {
Log("{}", SDL_GetError());
} else {
state.type(clipboard.get());
}
}
}
return true;
#endif
case SDLK_BACKSPACE:
state.backspace();
return true;
case SDLK_DELETE:
state.del();
return true;
case SDLK_LEFT:
state.moveCursorLeft();
return true;
case SDLK_RIGHT:
state.moveCursorRight();
return true;
case SDLK_HOME:
state.setCursorToStart();
return true;
case SDLK_END:
state.setCursorToEnd();
return true;
default:
break;
}
#ifdef USE_SDL1
if ((event.key.keysym.mod & KMOD_CTRL) == 0 && event.key.keysym.unicode >= ' ') {
std::string utf8;
AppendUtf8(event.key.keysym.unicode, utf8);
state.type(utf8);
return true;
}
#endif
return false;
}
#ifndef USE_SDL1
case SDL_TEXTINPUT:
#ifdef __vita__
state.assign(event.text.text);
#else
state.type(event.text.text);
#endif
return true;
#endif
default:
return false;
}
}

} // namespace devilution
Loading

0 comments on commit fdb5738

Please sign in to comment.