summaryrefslogtreecommitdiff
path: root/src/ui/inputwidget.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-21 15:06:52 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-21 15:07:38 +0300
commitd773b499e595a43b9b1ae449262dcf13cabf2d02 (patch)
treeb1baeb12025a04f8316636b5d0ab18e30ceedb2c /src/ui/inputwidget.c
Initial commit
Borrowing the app skeleton from Bitwise Harmony.
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r--src/ui/inputwidget.c280
1 files changed, 280 insertions, 0 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
new file mode 100644
index 00000000..7a2b7d1c
--- /dev/null
+++ b/src/ui/inputwidget.c
@@ -0,0 +1,280 @@
1#include "inputwidget.h"
2#include "paint.h"
3#include "util.h"
4
5#include <the_Foundation/array.h>
6#include <SDL_timer.h>
7
8struct Impl_InputWidget {
9 iWidget widget;
10 enum iInputMode mode;
11 size_t maxLen;
12 iArray text; /* iChar[] */
13 iArray oldText; /* iChar[] */
14 size_t cursor;
15 int font;
16 iClick click;
17};
18
19iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen)
20
21void init_InputWidget(iInputWidget *d, size_t maxLen) {
22 iWidget *w = &d->widget;
23 init_Widget(w);
24 setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag, iTrue);
25 init_Array(&d->text, sizeof(iChar));
26 init_Array(&d->oldText, sizeof(iChar));
27 d->font = uiInput_FontId;
28 d->cursor = 0;
29 setMaxLen_InputWidget(d, maxLen);
30 if (maxLen == 0) {
31 /* Caller must arrange the width. */
32 w->rect.size.y = lineHeight_Text(d->font) + 2 * gap_UI;
33 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue);
34 }
35 init_Click(&d->click, d, SDL_BUTTON_LEFT);
36}
37
38void deinit_InputWidget(iInputWidget *d) {
39 deinit_Array(&d->oldText);
40 deinit_Array(&d->text);
41}
42
43void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) {
44 d->mode = mode;
45}
46
47const iString *text_InputWidget(const iInputWidget *d) {
48 return collect_String(newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text)));
49}
50
51void setMaxLen_InputWidget(iInputWidget *d, size_t maxLen) {
52 d->maxLen = maxLen;
53 d->mode = (maxLen == 0 ? insert_InputMode : overwrite_InputMode);
54 resize_Array(&d->text, maxLen);
55 if (maxLen) {
56 /* Set a fixed size. */
57 iBlock *content = new_Block(maxLen);
58 fill_Block(content, 'M');
59 setSize_Widget(
60 as_Widget(d),
61 add_I2(measure_Text(d->font, cstr_Block(content)), init_I2(6 * gap_UI, 2 * gap_UI)));
62 delete_Block(content);
63 }
64}
65
66void setText_InputWidget(iInputWidget *d, const iString *text) {
67 clear_Array(&d->text);
68 iConstForEach(String, i, text) {
69 pushBack_Array(&d->text, &i.value);
70 }
71}
72
73void setCursor_InputWidget(iInputWidget *d, size_t pos) {
74 d->cursor = iMin(pos, size_Array(&d->text));
75}
76
77void begin_InputWidget(iInputWidget *d) {
78 iWidget *w = as_Widget(d);
79 if (flags_Widget(w) & selected_WidgetFlag) {
80 /* Already active. */
81 return;
82 }
83 setFlags_Widget(w, hidden_WidgetFlag | disabled_WidgetFlag, iFalse);
84 setCopy_Array(&d->oldText, &d->text);
85 if (d->mode == overwrite_InputMode) {
86 d->cursor = 0;
87 }
88 else {
89 d->cursor = iMin(size_Array(&d->text), d->maxLen - 1);
90 }
91 SDL_StartTextInput();
92 setFlags_Widget(w, selected_WidgetFlag, iTrue);
93}
94
95void end_InputWidget(iInputWidget *d, iBool accept) {
96 iWidget *w = as_Widget(d);
97 if (~flags_Widget(w) & selected_WidgetFlag) {
98 /* Was not active. */
99 return;
100 }
101 if (!accept) {
102 setCopy_Array(&d->text, &d->oldText);
103 }
104 SDL_StopTextInput();
105 setFlags_Widget(w, selected_WidgetFlag, iFalse);
106 const char *id = cstr_String(id_Widget(as_Widget(d)));
107 if (!*id) id = "_";
108 postCommand_Widget(w, "input.ended id:%s arg:%d", id, accept ? 1 : 0);
109}
110
111static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
112 if (isCommand_Widget(as_Widget(d), ev, "focus.gained")) {
113 begin_InputWidget(d);
114 return iTrue;
115 }
116 else if (isCommand_Widget(as_Widget(d), ev, "focus.lost")) {
117 end_InputWidget(d, iTrue);
118 return iTrue;
119 }
120 switch (processEvent_Click(&d->click, ev)) {
121 case none_ClickResult:
122 break;
123 case started_ClickResult:
124 case drag_ClickResult:
125 case double_ClickResult:
126 case aborted_ClickResult:
127 return iTrue;
128 case finished_ClickResult:
129 setFocus_Widget(as_Widget(d));
130 return iTrue;
131 }
132 if (ev->type == SDL_KEYUP) {
133 return iTrue;
134 }
135 const size_t curMax = iMin(size_Array(&d->text), d->maxLen - 1);
136 if (ev->type == SDL_KEYDOWN && isFocused_Widget(as_Widget(d))) {
137 const int key = ev->key.keysym.sym;
138 const int mods = keyMods_Sym(ev->key.keysym.mod);
139 switch (key) {
140 case SDLK_RETURN:
141 case SDLK_KP_ENTER:
142 setFocus_Widget(NULL);
143 return iTrue;
144 case SDLK_ESCAPE:
145 end_InputWidget(d, iFalse);
146 setFocus_Widget(NULL);
147 return iTrue;
148 case SDLK_BACKSPACE:
149 if (mods & KMOD_ALT) {
150 clear_Array(&d->text);
151 d->cursor = 0;
152 }
153 else if (d->cursor > 0) {
154 remove_Array(&d->text, --d->cursor);
155 }
156 return iTrue;
157 case SDLK_d:
158 if (mods != KMOD_CTRL) break;
159 case SDLK_DELETE:
160 if (d->cursor < size_Array(&d->text)) {
161 remove_Array(&d->text, d->cursor);
162 }
163 return iTrue;
164 case SDLK_k:
165 if (mods == KMOD_CTRL) {
166 removeN_Array(&d->text, d->cursor, size_Array(&d->text) - d->cursor);
167 return iTrue;
168 }
169 break;
170 case SDLK_HOME:
171 case SDLK_END:
172 d->cursor = (key == SDLK_HOME ? 0 : curMax);
173 return iTrue;
174 case SDLK_a:
175 case SDLK_e:
176 if (mods == KMOD_CTRL) {
177 d->cursor = (key == 'a' ? 0 : curMax);
178 return iTrue;
179 }
180 break;
181 case SDLK_LEFT:
182 if (mods & KMOD_PRIMARY) {
183 d->cursor = 0;
184 }
185 else if (d->cursor > 0) {
186 d->cursor--;
187 }
188 return iTrue;
189 case SDLK_RIGHT:
190 if (mods & KMOD_PRIMARY) {
191 d->cursor = curMax;
192 }
193 else if (d->cursor < curMax) {
194 d->cursor++;
195 }
196 return iTrue;
197 case SDLK_TAB:
198 /* Allow focus switching. */
199 return processEvent_Widget(as_Widget(d), ev);
200 }
201 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) {
202 return iFalse;
203 }
204 return iTrue;
205 }
206 else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(as_Widget(d))) {
207 const iString *uni = collectNewCStr_String(ev->text.text);
208 const iChar chr = first_String(uni);
209 if (d->mode == insert_InputMode) {
210 insert_Array(&d->text, d->cursor, &chr);
211 d->cursor++;
212 }
213 else {
214 if (d->cursor >= size_Array(&d->text)) {
215 resize_Array(&d->text, d->cursor + 1);
216 }
217 set_Array(&d->text, d->cursor++, &chr);
218 if (d->maxLen && d->cursor == d->maxLen) {
219 setFocus_Widget(NULL);
220 }
221 }
222 return iTrue;
223 }
224 return processEvent_Widget(as_Widget(d), ev);
225}
226
227static void draw_InputWidget_(const iInputWidget *d) {
228 const uint32_t time = frameTime_Window(get_Window());
229 const iInt2 padding = init_I2(3 * gap_UI, gap_UI);
230 iRect bounds = adjusted_Rect(bounds_Widget(constAs_Widget(d)), padding, neg_I2(padding));
231 const iBool isFocused = isFocused_Widget(constAs_Widget(d));
232 const iBool isHover = isHover_Widget(constAs_Widget(d)) &&
233 contains_Widget(constAs_Widget(d), mouseCoord_Window(get_Window()));
234 iPaint p;
235 init_Paint(&p);
236 iString text;
237 initUnicodeN_String(&text, constData_Array(&d->text), size_Array(&d->text));
238 fillRect_Paint(&p, bounds, black_ColorId);
239 drawRect_Paint(&p,
240 adjusted_Rect(bounds, neg_I2(one_I2()), zero_I2()),
241 isFocused ? orange_ColorId : isHover ? cyan_ColorId : gray50_ColorId);
242 setClip_Paint(&p, bounds);
243 const iInt2 emSize = advance_Text(d->font, "M");
244 const int textWidth = advance_Text(d->font, cstr_String(&text)).x;
245 const int cursorX = advanceN_Text(d->font, cstr_String(&text), d->cursor).x;
246 int xOff = 0;
247 if (d->maxLen == 0) {
248 if (textWidth > width_Rect(bounds) - emSize.x) {
249 xOff = width_Rect(bounds) - emSize.x - textWidth;
250 }
251 if (cursorX + xOff < width_Rect(bounds) / 2) {
252 xOff = width_Rect(bounds) / 2 - cursorX;
253 }
254 xOff = iMin(xOff, 0);
255 }
256 draw_Text(d->font, addX_I2(topLeft_Rect(bounds), xOff), white_ColorId, cstr_String(&text));
257 clearClip_Paint(&p);
258 /* Cursor blinking. */
259 if (isFocused && (time & 256)) {
260 const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(&text), d->cursor);
261 const iInt2 curPos = init_I2(xOff + left_Rect(bounds) + prefixSize.x, top_Rect(bounds));
262 const iRect curRect = { curPos, addX_I2(emSize, 1) };
263 iString cur;
264 if (d->cursor < size_Array(&d->text)) {
265 initUnicodeN_String(&cur, constAt_Array(&d->text, d->cursor), 1);
266 }
267 else {
268 initCStr_String(&cur, " ");
269 }
270 fillRect_Paint(&p, curRect, orange_ColorId);
271 draw_Text(d->font, curPos, black_ColorId, cstr_String(&cur));
272 deinit_String(&cur);
273 }
274 deinit_String(&text);
275}
276
277iBeginDefineSubclass(InputWidget, Widget)
278 .processEvent = (iAny *) processEvent_InputWidget_,
279 .draw = (iAny *) draw_InputWidget_,
280iEndDefineSubclass(InputWidget)