Use SDL text input correctly, add basic composition support

This commit is contained in:
Tamás Bálint Misius 2021-03-12 21:02:02 +01:00
parent da2ccc70fe
commit 3dbf6d7810
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
12 changed files with 291 additions and 57 deletions

View File

@ -7,3 +7,6 @@ void ClipboardPush(ByteString text);
ByteString ClipboardPull();
int GetModifiers();
unsigned int GetTicks();
void StartTextInput();
void StopTextInput();
void SetTextInputRect(int x, int y, int w, int h);

View File

@ -60,6 +60,26 @@ bool forceIntegerScaling = true;
bool resizable = false;
void StartTextInput()
{
SDL_StartTextInput();
}
void StopTextInput()
{
SDL_StopTextInput();
}
void SetTextInputRect(int x, int y, int w, int h)
{
SDL_Rect rect;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_SetTextInputRect(&rect);
}
void ClipboardPush(ByteString text)
{
SDL_SetClipboardText(text.c_str());
@ -267,6 +287,13 @@ void EventProcess(SDL_Event event)
}
engine->onTextInput(ByteString(event.text.text).FromUtf8());
break;
case SDL_TEXTEDITING:
if (SDL_GetModState() & KMOD_GUI)
{
break;
}
engine->onTextEditing(ByteString(event.edit.text).FromUtf8(), event.edit.start);
break;
case SDL_MOUSEWHEEL:
{
int x = event.wheel.x;

View File

@ -69,6 +69,25 @@ bool forceIntegerScaling = true;
bool resizable = false;
bool momentumScroll = true;
void StartTextInput()
{
SDL_StartTextInput();
}
void StopTextInput()
{
SDL_StopTextInput();
}
void SetTextInputRect(int x, int y, int w, int h)
{
SDL_Rect rect;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_SetTextInputRect(&rect);
}
void ClipboardPush(ByteString text)
{
@ -425,6 +444,13 @@ void EventProcess(SDL_Event event)
}
engine->onTextInput(ByteString(event.text.text).FromUtf8());
break;
case SDL_TEXTEDITING:
if (SDL_GetModState() & KMOD_GUI)
{
break;
}
engine->onTextEditing(ByteString(event.edit.text).FromUtf8(), event.edit.start);
break;
case SDL_MOUSEWHEEL:
{
int x = event.wheel.x;

View File

@ -1282,8 +1282,6 @@ void GameView::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl,
switch(scan)
{
case SDL_SCANCODE_GRAVE:
SDL_StopTextInput();
SDL_StartTextInput();
c->ShowConsole();
break;
case SDL_SCANCODE_SPACE: //Space

View File

@ -19,7 +19,8 @@ Component::Component(Window* parent_state):
Position(Point(0,0)),
Size(Point(0,0)),
Enabled(true),
Visible(true)
Visible(true),
DoesTextInput(false)
{
}
@ -35,7 +36,8 @@ Component::Component(Point position, Point size):
Position(position),
Size(size),
Enabled(true),
Visible(true)
Visible(true),
DoesTextInput(false)
{
}
@ -51,7 +53,8 @@ Component::Component():
Position(Point(0,0)),
Size(Point(0,0)),
Enabled(true),
Visible(true)
Visible(true),
DoesTextInput(false)
{
}
@ -201,6 +204,10 @@ void Component::OnTextInput(String text)
{
}
void Component::OnTextEditing(String text)
{
}
void Component::OnMouseClick(int localx, int localy, unsigned button)
{
}

View File

@ -29,6 +29,7 @@ namespace ui
ui::Point iconPosition;
ui::ContextMenu * menu;
Graphics * GetGraphics();
public:
Component(Window* parent_state);
Component(Point position, Point size);
@ -45,6 +46,7 @@ namespace ui
Point Size;
bool Enabled;
bool Visible;
bool DoesTextInput;
ui::Appearance Appearance;
//virtual void SetAppearance(ui::Appearance);
@ -204,5 +206,6 @@ namespace ui
virtual void OnKeyRelease(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt);
virtual void OnTextInput(String text);
virtual void OnTextEditing(String text);
};
}

View File

@ -260,8 +260,38 @@ void Engine::onKeyRelease(int key, int scan, bool repeat, bool shift, bool ctrl,
void Engine::onTextInput(String text)
{
if (state_ && !ignoreEvents)
state_->DoTextInput(text);
if (textInput)
{
if (state_ && !ignoreEvents)
state_->DoTextInput(text);
}
}
void Engine::onTextEditing(String text, int start)
{
if (textInput)
{
// * SDL sends the candidate string in packets of some arbitrary size,
// leaving it up to the user to assemble these packets into the
// complete candidate string. The start parameter tells us which
// portion of the candidate string the current packet spans.
// * Sadly, there's no documented way to tell the first or last packet
// apart from the rest. While there's also no documented guarantee
// that the packets come in order and that there are no gaps or
// overlaps between them, the implementation on the SDL side seems to
// ensure this. So what we do is just append whatever packet we get
// to a buffer, which we reset every time a "first-y looking" packet
// arrives. We also forward a textediting event on every packet,
// which is redundant, but should be okay, as textediting events are
// not supposed to have an effect on the actual text being edited.
if (start == 0)
{
textEditingBuf.clear();
}
textEditingBuf.append(text);
if (state_ && !ignoreEvents)
state_->DoTextEditing(textEditingBuf);
}
}
void Engine::onMouseClick(int x, int y, unsigned button)
@ -318,3 +348,28 @@ void Engine::onFileDrop(ByteString filename)
if (state_)
state_->DoFileDrop(filename);
}
void Engine::StartTextInput()
{
if (textInput)
{
return;
}
textInput = true;
::StartTextInput();
}
void Engine::StopTextInput()
{
if (!textInput)
{
return;
}
::StopTextInput();
textInput = false;
}
void Engine::TextInputRect(Point position, Point size)
{
::SetTextInputRect(position.X * Scale, position.Y * Scale, size.X * Scale, size.Y * Scale);
}

View File

@ -33,6 +33,7 @@ namespace ui
void onKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt);
void onKeyRelease(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt);
void onTextInput(String text);
void onTextEditing(String text, int start);
void onResize(int newWidth, int newHeight);
void onClose();
void onFileDrop(ByteString filename);
@ -80,6 +81,10 @@ namespace ui
inline void SetSize(int width, int height);
void StartTextInput();
void StopTextInput();
void TextInputRect(Point position, Point size);
//void SetState(Window* state);
//inline State* GetState() { return state_; }
inline Window* GetWindow() { return state_; }
@ -95,6 +100,8 @@ namespace ui
bool forceIntegerScaling = true;
bool resizable;
bool textInput = false;
float dt;
float fps;
pixel * lastBuffer;
@ -125,6 +132,8 @@ namespace ui
bool momentumScroll;
String textEditingBuf;
public:
inline void SetMomentumScroll(bool newMomentumScroll)
{

View File

@ -6,10 +6,12 @@
#include "PowderToy.h"
#include "graphics/Graphics.h"
#include "graphics/FontReader.h"
#include "gui/interface/Point.h"
#include "gui/interface/Keys.h"
#include "gui/interface/Mouse.h"
#include "gui/interface/Engine.h"
#include "ContextMenu.h"
@ -24,8 +26,11 @@ Textbox::Textbox(Point position, Point size, String textboxText, String textboxP
characterDown(0),
mouseDown(false),
masked(false),
border(true)
border(true),
inputRectPosition(0, 0),
textEditing(false)
{
DoesTextInput = true;
placeHolder = textboxPlaceholder;
SetText(textboxText);
@ -56,6 +61,8 @@ void Textbox::SetPlaceholder(String text)
void Textbox::SetText(String newText)
{
StopTextEditing();
backingText = newText;
if(masked)
@ -69,14 +76,7 @@ void Textbox::SetText(String newText)
cursor = newText.length();
if(cursor)
{
textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
}
else
{
cursorPositionY = cursorPositionX = 0;
}
resetCursorPosition();
}
Textbox::ValidInput Textbox::GetInputType()
@ -106,6 +106,8 @@ String Textbox::GetText()
void Textbox::OnContextMenuAction(int item)
{
StopTextEditing();
switch(item)
{
case 0:
@ -122,7 +124,14 @@ void Textbox::OnContextMenuAction(int item)
void Textbox::resetCursorPosition()
{
textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
if(cursor)
{
textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
}
else
{
cursorPositionY = cursorPositionX = 0;
}
}
void Textbox::TabFocus()
@ -133,6 +142,8 @@ void Textbox::TabFocus()
void Textbox::cutSelection()
{
StopTextEditing();
if (HasSelection())
{
if (getLowerSelectionBound() < 0 || getHigherSelectionBound() > (int)backingText.length())
@ -167,20 +178,15 @@ void Textbox::cutSelection()
updateSelection();
TextPosition(displayTextWrapper.WrappedText());
if(cursor)
{
textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
}
else
{
cursorPositionY = cursorPositionX = 0;
}
resetCursorPosition();
if (actionCallback.change)
actionCallback.change();
}
void Textbox::pasteIntoSelection()
{
StopTextEditing();
String newText = format::CleanString(ClipboardPull().FromUtf8(), false, true, inputType != Multiline, inputType == Number || inputType == Numeric);
if (HasSelection())
{
@ -237,14 +243,7 @@ void Textbox::pasteIntoSelection()
updateSelection();
TextPosition(displayTextWrapper.WrappedText());
if(cursor)
{
textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
}
else
{
cursorPositionY = cursorPositionX = 0;
}
resetCursorPosition();
if (actionCallback.change)
actionCallback.change();
}
@ -280,6 +279,10 @@ bool Textbox::StringValid(String text)
void Textbox::Tick(float dt)
{
Label::Tick(dt);
if (GetParentWindow() && Visible && Enabled && IsFocused())
{
ui::Engine::Ref().TextInputRect(GetScreenPos() + textPosition + inputRectPosition - Point(1, 3), Point(Size.X - textPosition.X - inputRectPosition.X, FONT_H + 2));
}
if (!IsFocused())
{
keyDown = 0;
@ -336,19 +339,23 @@ void Textbox::OnVKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl,
switch(key)
{
case SDLK_HOME:
StopTextEditing();
cursor = 0;
ClearSelection();
break;
case SDLK_END:
StopTextEditing();
cursor = backingText.length();
ClearSelection();
break;
case SDLK_LEFT:
StopTextEditing();
if(cursor > 0)
cursor--;
ClearSelection();
break;
case SDLK_RIGHT:
StopTextEditing();
if (cursor < (int)backingText.length())
cursor++;
ClearSelection();
@ -356,6 +363,7 @@ void Textbox::OnVKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl,
case SDLK_DELETE:
if(ReadOnly)
break;
StopTextEditing();
if (HasSelection())
{
if (getLowerSelectionBound() < 0 || getHigherSelectionBound() > (int)backingText.length())
@ -382,6 +390,7 @@ void Textbox::OnVKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl,
case SDLK_BACKSPACE:
if (ReadOnly)
break;
StopTextEditing();
if (HasSelection())
{
if (getLowerSelectionBound() < 0 || getHigherSelectionBound() > (int)backingText.length())
@ -455,19 +464,18 @@ void Textbox::AfterTextChange(bool changed)
updateSelection();
TextPosition(displayTextWrapper.WrappedText());
if(cursor)
{
textWrapper.Index2Point(textWrapper.Clear2Index(cursor), cursorPositionX, cursorPositionY);
}
else
{
cursorPositionY = cursorPositionX = 0;
}
resetCursorPosition();
if (changed && actionCallback.change)
actionCallback.change();
}
void Textbox::OnTextInput(String text)
{
StopTextEditing();
InsertText(text);
}
void Textbox::InsertText(String text)
{
if (StringValid(text) && !ReadOnly)
{
@ -501,21 +509,80 @@ void Textbox::OnTextInput(String text)
}
}
void Textbox::StartTextEditing()
{
if (ReadOnly || textEditing)
{
return;
}
textEditing = true;
selectionIndexLSave1 = selectionIndexL.clear_index;
selectionIndexHSave1 = selectionIndexH.clear_index;
backingTextSave1 = backingText;
cursorSave1 = cursor;
InsertText(String(""));
selectionIndexLSave2 = selectionIndexL.clear_index;
selectionIndexHSave2 = selectionIndexH.clear_index;
backingTextSave2 = backingText;
cursorSave2 = cursor;
inputRectPosition.X = cursorPositionX;
inputRectPosition.Y = cursorPositionY;
}
void Textbox::StopTextEditing()
{
if (ReadOnly || !textEditing)
{
return;
}
textEditing = false;
backingText = backingTextSave1;
AfterTextChange(true);
selectionIndexL = textWrapper.Clear2Index(selectionIndexLSave1);
selectionIndexH = textWrapper.Clear2Index(selectionIndexHSave1);
selectionIndex0 = selectionIndexL;
selectionIndex1 = selectionIndexH;
cursor = cursorSave1;
updateSelection();
}
void Textbox::OnTextEditing(String text)
{
if (!StringValid(text) || ReadOnly)
{
return;
}
if (!text.size())
{
StopTextEditing();
return;
}
StartTextEditing();
backingText = backingTextSave2;
AfterTextChange(true);
selectionIndexL = textWrapper.Clear2Index(selectionIndexLSave2);
selectionIndexH = textWrapper.Clear2Index(selectionIndexHSave2);
selectionIndex0 = selectionIndexL;
selectionIndex1 = selectionIndexH;
cursor = cursorSave2;
updateSelection();
InsertText(text);
selectionIndex1 = textWrapper.Clear2Index(cursor);
selectionIndex0 = textWrapper.Clear2Index(cursor - int(text.size()));
selectionIndexL = selectionIndex0;
selectionIndexH = selectionIndex1;
updateSelection();
}
void Textbox::OnMouseClick(int x, int y, unsigned button)
{
if (button != SDL_BUTTON_RIGHT)
{
StopTextEditing();
mouseDown = true;
auto index = textWrapper.Point2Index(x-textPosition.X, y-textPosition.Y);
cursor = index.raw_index;
if(cursor)
{
textWrapper.Index2Point(index, cursorPositionX, cursorPositionY);
}
else
{
cursorPositionY = cursorPositionX = 0;
}
resetCursorPosition();
}
Label::OnMouseClick(x, y, button);
}
@ -532,14 +599,7 @@ void Textbox::OnMouseMoved(int localx, int localy, int dx, int dy)
{
auto index = textWrapper.Point2Index(localx-textPosition.X, localy-textPosition.Y);
cursor = index.raw_index;
if(cursor)
{
textWrapper.Index2Point(index, cursorPositionX, cursorPositionY);
}
else
{
cursorPositionY = cursorPositionX = 0;
}
resetCursorPosition();
}
Label::OnMouseMoved(localx, localy, dx, dy);
}

View File

@ -15,6 +15,9 @@ struct TextboxAction
class Textbox : public Label
{
void AfterTextChange(bool changed);
void InsertText(String text);
void StartTextEditing();
void StopTextEditing();
public:
bool ReadOnly;
@ -53,6 +56,7 @@ public:
void OnVKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt);
void OnKeyRelease(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) override;
void OnTextInput(String text) override;
void OnTextEditing(String text) override;
void Draw(const Point& screenPos) override;
protected:
@ -68,6 +72,21 @@ protected:
String backingText;
String placeHolder;
// * Cursor state to reset to before inserting actual input in StopTextEditing.
int selectionIndexLSave1;
int selectionIndexHSave1;
String backingTextSave1;
int cursorSave1;
// * Cursor state to reset to before inserting a candidate string in OnTextEditing.
int selectionIndexLSave2;
int selectionIndexHSave2;
String backingTextSave2;
int cursorSave2;
Point inputRectPosition;
bool textEditing;
virtual void cutSelection();
virtual void pasteIntoSelection();
};

View File

@ -261,6 +261,16 @@ void Window::DoTick(float dt)
if (debugMode)
return;
#endif
if (focusedComponent_ && focusedComponent_->Visible && focusedComponent_->Enabled && focusedComponent_->DoesTextInput)
{
ui::Engine::Ref().StartTextInput();
}
else
{
ui::Engine::Ref().StopTextInput();
}
//on mouse hover
for (int i = Components.size() - 1; i >= 0 && !halt; --i)
{
@ -435,6 +445,21 @@ void Window::DoTextInput(String text)
finalise();
}
void Window::DoTextEditing(String text)
{
if (focusedComponent_ != NULL)
{
if (focusedComponent_->Enabled && focusedComponent_->Visible)
focusedComponent_->OnTextEditing(text);
}
if (!stop)
OnTextEditing(text);
if (destruct)
finalise();
}
void Window::DoMouseDown(int x_, int y_, unsigned button)
{
//on mouse click

View File

@ -68,6 +68,7 @@ namespace ui
virtual void DoKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt);
virtual void DoKeyRelease(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt);
virtual void DoTextInput(String text);
virtual void DoTextEditing(String text);
// Sets halt and destroy, this causes the Windows to stop sending events and remove itself.
void SelfDestruct();
@ -107,6 +108,7 @@ namespace ui
virtual void OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) {}
virtual void OnKeyRelease(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) {}
virtual void OnTextInput(String text) {}
virtual void OnTextEditing(String text) {}
std::vector<Component*> Components;
Component *focusedComponent_;
Component *hoverComponent;