
1. Introduction
In the previous post, we implemented a UIButton
and mastered the basics of drawing and interacting with SDL UI controls. In this post, we’ll build a single-line text input box, UITextEdit
. This is a fundamental component of many UI systems, used for things like login forms, nickname inputs, and search bars.
Since SDL doesn’t have a built-in text input box, we need to handle the details ourselves, such as the cursor, text selection, IME (Input Method Editor), and focus management.
2. Functional Requirements Analysis
Core Features
- Keyboard character input
- Cursor movement (arrow keys, Home/End)
- Deletion and backspace
- Copy, cut, and paste (to be implemented in the next chapter)
- Placeholder display
- Blinking cursor
- Password display
- IME support (for Chinese, Japanese, etc., to be implemented in the next chapter)
Interaction Details
- Mouse click to gain focus
- Clicking outside the control to lose focus
- Highlighting selected text
- Rendering temporary input text (Composition) (to be implemented in the next chapter)
3. SDL Prerequisites
SDL_StartTextInput / SDL_StopTextInput
: Enable/disable text input.SDL_TEXTINPUT
: Character input event.SDL_TEXTEDITING
: IME editing event.SDL_KEYDOWN
: Key presses for arrow keys, backspace, delete, etc.SDL_SetClipboardText / SDL_GetClipboardText
: Clipboard operations.SDL_ttf
: Rendering single-line text.
4. Class Design
We’ll have UITextEdit
inherit from UIControl
(or your custom base class) to maintain a consistent interface style with UIButton
.
UITextEdit.h
C++
#pragma once
#include "UIControl.h"
class UITextEdit : public UIControl
{
public:
UITextEdit();
~UITextEdit() = default;
void Update(uint32_t dt) override;
void Render() override;
bool Handle(SDL_Event* e) override;
void OnTextChanged(const std::string& old) override;
void OnMouseOver() override;
void OnMouseOut() override;
void OnFocusChanged() override;
std::string GetSelectionText() const;
bool IsReadOnly() const { return m_isreadonly; }
inline void SetReadOnly(bool _readonly) { m_isreadonly = _readonly; }
bool IsPassword() const { return m_ispassword; }
void SetPassowrd(bool _password);
inline void SetLength(int len) { m_length = len; }
inline int GetLength() const { return m_length; }
void SelectAll();
void SetNumOnly(bool _numOnly = true) { m_numonly = _numOnly; }
protected:
void calcTextOffset();
void calcCharacterWidths();
SDL_Rect getSelectionRect();
void setCursorPos(int x);
void clearSelection();
SDL_Rect calcCursorRect(int pos);
//SDL_Rect calcImeListRect();
//SDL_Rect calcImeListRect(const SDL_Rect& rcCursor);
void removeCursorCharater();
void removeSelectedText();
void replaceSelectionText(const std::string& _utf8);
std::string getRectText() const;
std::string getDisplayText() const;
bool isCursorRight() const;
bool isAllDigits(const std::string& str);
bool isAllDigits(const std::wstring& str);
protected:
struct
{
bool visiable{ false };
int position{ 0 };
} cursor;
struct
{
int start{ 0 };
int end{ 0 };
int pos1{ 0 };
int pos2{ 2 };
}selection;
struct
{
std::string text;
int width;
int height;
int index{ -1 };
}editing;
bool IsTextSelected() const { return selection.end - selection.start > 0; }
std::wstring m_wtext;
std::vector<int> m_chw;
bool m_isreadonly{ false };
bool m_ispassword{ false };
bool m_numonly{ false };
std::vector<std::wstring> m_ims;
uint32_t m_ticks{ 0 };
int m_textwidth{ 0 };
int m_textheight{ 0 };
int m_textoffset{ 0 };
int m_length{ 32 };
const int IME_WAIT_SIZE = 8;
};
5. Design Highlights
Focus and Placeholder
- Clicking inside the control gains focus.
- Losing focus means no longer accepting keyboard input.
- A gray placeholder is displayed when the text is empty.
Input and Deletion
- Normal character input is handled by
SDL_TEXTINPUT
. - Backspace / Delete removes characters at the corresponding position.
- When replacing a selection, first delete the selected text, then insert the new characters.
Cursor and Selection
- Cursor movement: Use arrow keys, Home, or End.
- Text selection: Use Shift + arrow keys to update
m_selectStart
andm_selectEnd
. - When rendering, first draw the selection background, then the text and cursor.
Paste, Copy, Cut
- Copy:
SDL_SetClipboardText
. - Paste:
SDL_GetClipboardText
. - Cut: Copy + delete the selected text.
IME Support
SDL_TEXTEDITING
provides a temporary input string and cursor position.- Render this temporary string at the cursor’s position.
Blinking Cursor
- Toggle the
m_cursorVisible
state at regular intervals.
6. Usage Example
In the init
method of your scene:
C++
auto tb = std::shared_ptr<UITextEdit>(new UITextEdit);
tb->SetPosition({ 190, 200 });
tb->SetSize({120,25});
AddNode(tb);
7. Conclusion
In this post, we designed the header file for a single-line UITextEdit
control and analyzed its features and implementation ideas.
In the next part, we will continue to implement:
- Copy, paste, and cut functionality.
- IME support.
Sample code for this chapter: https://github.com/84378996/simple_sdl_gui_tutorials/tree/main/tutorials04
Demo video for this chapter: