ASW Lib
A.D.S. Games SDL Wrapper Library. A library targeted at Allegro4 users who want to switch to SDL3 and use modern c++.
Loading...
Searching...
No Matches
input_box.cpp
Go to the documentation of this file.
2
3#include <SDL3/SDL.h>
4#include <algorithm>
5
9
10namespace {
11// UTF-8 continuation bytes look like 10xxxxxx
12bool is_continuation(char c)
13{
14 return (static_cast<unsigned char>(c) & 0xC0) == 0x80;
15}
16
17// Start of the character before pos
18std::size_t prev_char(const std::string& s, std::size_t pos)
19{
20 if (pos == 0) {
21 return 0;
22 }
23 --pos;
24 while (pos > 0 && is_continuation(s[pos])) {
25 --pos;
26 }
27 return pos;
28}
29
30// Start of the character after the one at pos
31std::size_t next_char(const std::string& s, std::size_t pos)
32{
33 if (pos >= s.size()) {
34 return s.size();
35 }
36 ++pos;
37 while (pos < s.size() && is_continuation(s[pos])) {
38 ++pos;
39 }
40 return pos;
41}
42} // namespace
43
45{
46 if (_focused) {
47 SDL_StopTextInput(asw::display::get_window());
48 }
49}
50
52{
53 (void)ctx;
54
55 if (focused) {
56 SDL_StartTextInput(asw::display::get_window());
57 _cursor_pos = value.size();
58 } else {
59 SDL_StopTextInput(asw::display::get_window());
60 }
61}
62
64{
65 (void)ctx;
66
67 // value is public and may have been shortened since the last event
68 _cursor_pos = std::min(_cursor_pos, value.size());
69
70 if (!enabled) {
71 return false;
72 }
73
74 switch (e.type) {
76 // Root has already focused the box
78 return false;
79 }
80 _cursor_pos = value.size();
81 return true;
82 }
84 value.insert(_cursor_pos, e.text);
85 _cursor_pos += e.text.size();
86 if (on_change) {
87 on_change(value);
88 }
89 return true;
90 }
93 if (_cursor_pos > 0) {
94 // Whole characters, not single bytes of a UTF-8 sequence
95 const auto start = prev_char(value, _cursor_pos);
96 value.erase(start, _cursor_pos - start);
97 _cursor_pos = start;
98 if (on_change) {
99 on_change(value);
100 }
101 }
102 return true;
103 }
104 if (e.key == asw::input::Key::Delete) {
105 if (_cursor_pos < value.size()) {
106 value.erase(_cursor_pos, next_char(value, _cursor_pos) - _cursor_pos);
107 if (on_change) {
108 on_change(value);
109 }
110 }
111 return true;
112 }
113 // At either end left and right fall through to move focus
114 if (e.key == asw::input::Key::Left) {
115 if (_cursor_pos == 0) {
116 return false;
117 }
118 _cursor_pos = prev_char(value, _cursor_pos);
119 return true;
120 }
121 if (e.key == asw::input::Key::Right) {
122 if (_cursor_pos >= value.size()) {
123 return false;
124 }
125 _cursor_pos = next_char(value, _cursor_pos);
126 return true;
127 }
128 if (e.key == asw::input::Key::Home) {
129 _cursor_pos = 0;
130 return true;
131 }
132 if (e.key == asw::input::Key::End) {
133 _cursor_pos = value.size();
134 return true;
135 }
136 return false;
137 }
138 default:
139 break;
140 }
141 return false;
142}
143
145{
146 constexpr float text_padding = 4.0F;
147
148 const auto& s = get_style(ctx);
149 const auto& f = pick_font(font, ctx.theme);
150
151 // Background
152 asw::draw::rect_fill(transform, enabled ? s.bg : s.bg_disabled);
153
154 // Border
155 if (s.border_width > 0.0F) {
157 transform, (_hovered && enabled) ? s.border_hover : s.border, s.border_width);
158 }
159
160 // Clip text to input bounds, inside any clip already set, which is put
161 // back after
162 SDL_Renderer* renderer = asw::display::get_renderer();
163 const bool had_clip = SDL_RenderClipEnabled(renderer);
164 SDL_Rect old_clip {};
165 if (had_clip) {
166 SDL_GetRenderClipRect(renderer, &old_clip);
167 }
168
169 const SDL_Rect box_clip {
170 static_cast<int>(transform.position.x + text_padding),
171 static_cast<int>(transform.position.y),
172 static_cast<int>(transform.size.x - (text_padding * 2)),
173 static_cast<int>(transform.size.y),
174 };
175 SDL_Rect clip = box_clip;
176 const bool text_visible = !had_clip || SDL_GetRectIntersection(&box_clip, &old_clip, &clip);
177 SDL_SetRenderClipRect(renderer, &clip);
178
179 // Caret offset from the start of the text
180 _cursor_pos = std::min(_cursor_pos, value.size());
181 float caret_offset = 0.0F;
182 if (_focused && _cursor_pos > 0 && f != nullptr) {
183 // A view of the text before the caret, not a copy
184 const std::string_view before_caret = std::string_view(value).substr(0, _cursor_pos);
185 caret_offset = static_cast<float>(asw::util::get_text_size(f, before_caret).x);
186 }
187
188 // Scroll so the caret stays inside the box. The caret is 1 pixel wide, so
189 // it needs that much room at the right edge.
190 const float visible_width = std::max(transform.size.x - (text_padding * 2) - 1.0F, 0.0F);
191 if (!_focused || value.empty() || f == nullptr) {
192 _scroll = 0.0F;
193 } else {
194 const auto text_width = static_cast<float>(asw::util::get_text_size(f, value).x);
195 _scroll = std::clamp(_scroll, caret_offset - visible_width, caret_offset);
196 _scroll = std::clamp(_scroll, 0.0F, std::max(text_width - visible_width, 0.0F));
197 }
198
199 const float text_x = transform.position.x + text_padding - _scroll;
200
201 // Text position (vertically centered)
202 const auto& display_text = value.empty() ? placeholder : value;
203 const auto display_color = value.empty() ? s.placeholder : s.text;
204
205 if (text_visible && !display_text.empty() && f != nullptr) {
206 const auto text_size = asw::util::get_text_size(f, display_text);
207 const float text_y = transform.position.y + ((transform.size.y - text_size.y) / 2.0F);
208 const asw::Vec2 text_pos { text_x, text_y };
209
210 asw::draw::text(f, display_text, text_pos, display_color);
211 }
212
213 // Cursor
214 if (text_visible && _focused && f != nullptr) {
215 const float cursor_x = text_x + caret_offset;
216
217 const auto text_height = asw::util::get_text_size(f, "|");
218 const float cursor_y = transform.position.y
219 + ((transform.size.y - static_cast<float>(text_height.y)) / 2.0F);
220
221 asw::draw::line({ cursor_x, cursor_y },
222 { cursor_x, cursor_y + static_cast<float>(text_height.y) }, s.caret);
223 }
224
225 // Put back the clip from before
226 SDL_SetRenderClipRect(renderer, had_clip ? &old_clip : nullptr);
227
228 Widget::draw(ctx);
229}
A 2D vector in space.
Definition geometry.h:27
T x
The x component of the vector.
Definition geometry.h:277
Shared state for the UI system.
Definition context.h:97
Theme theme
The current UI theme.
Definition context.h:104
void draw(Context &ctx) override
Draw the input box.
void on_focus_changed(Context &ctx, bool focused) override
Called when focus state changes.
Definition input_box.cpp:51
~InputBox() override
Stops text input if the box is destroyed while focused.
Definition input_box.cpp:44
bool on_event(Context &ctx, const UIEvent &e) override
Handle a UI event.
Definition input_box.cpp:63
virtual void draw(Context &ctx)
Draw this widget and its children.
Definition widget.cpp:163
Display and window routines for the ASW library.
Routines for drawing sprites and primitives to the screen.
Input box widget for the ASW UI module.
std::size_t next_char(const std::string &s, std::size_t pos)
Definition input_box.cpp:31
std::size_t prev_char(const std::string &s, std::size_t pos)
Definition input_box.cpp:18
asw::Renderer * get_renderer()
Get the SDL renderer.
Definition display.cpp:139
asw::Window * get_window()
Get the SDL window.
Definition display.cpp:144
void rect(const asw::Quad< float > &position, asw::Color color, float thickness=1.0F)
Draw a rectangle.
Definition draw.cpp:504
void line(const asw::Vec2< float > &position1, const asw::Vec2< float > &position2, asw::Color color)
Draw a line.
Definition draw.cpp:492
void text(const asw::Font &font, const std::string &text, const asw::Vec2< float > &position, asw::Color color, asw::TextJustify justify=asw::TextJustify::Left)
Draw text. The position is rounded to whole pixels after justification so glyphs stay sharp....
Definition draw.cpp:357
void rect_fill(const asw::Quad< float > &position, asw::Color color)
Draw a filled rectangle.
Definition draw.cpp:536
const asw::Font & pick_font(const asw::Font &own, const Theme &theme)
The font a widget draws with: its own, or the theme font.
Definition theme.h:304
asw::Vec2< int > get_text_size(const asw::Font &font, std::string_view text)
Get text size.
Definition util.cpp:81
Event structure for UI interactions.
Definition event.h:21
asw::input::MouseButton mouse_button
The mouse button associated with the event.
Definition event.h:48
std::string text
The text associated with a TextInput event.
Definition event.h:51
asw::input::Key key
The key associated with the event.
Definition event.h:39
Type type
The type of the event.
Definition event.h:36
General utility functions.