summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-08-30 14:19:11 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-08-30 14:19:11 +0300
commit3eb85b3d0fdd007ad18c3b522795b0ba3a038d1f (patch)
treeac08f58747b408950b7958ef5cb9f7d2ccf2f77e /src/ui
parent2f24e9404c2c01251826e98286dab5df7d9e5ce7 (diff)
InputWidget: Marking, deleting, word skipping
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/inputwidget.c198
-rw-r--r--src/ui/inputwidget.h1
-rw-r--r--src/ui/window.c1
3 files changed, 169 insertions, 31 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 8e9f0423..3daecfa8 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -36,11 +36,14 @@ struct Impl_InputWidget {
36 enum iInputMode mode; 36 enum iInputMode mode;
37 iBool isSensitive; 37 iBool isSensitive;
38 iBool enterPressed; 38 iBool enterPressed;
39 iBool selectAllOnFocus;
39 size_t maxLen; 40 size_t maxLen;
40 iArray text; /* iChar[] */ 41 iArray text; /* iChar[] */
41 iArray oldText; /* iChar[] */ 42 iArray oldText; /* iChar[] */
42 iString hint; 43 iString hint;
43 size_t cursor; 44 size_t cursor;
45 size_t lastCursor;
46 iRanges mark;
44 int font; 47 int font;
45 iClick click; 48 iClick click;
46 uint32_t timer; 49 uint32_t timer;
@@ -57,8 +60,10 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
57 init_String(&d->hint); 60 init_String(&d->hint);
58 d->font = uiInput_FontId; 61 d->font = uiInput_FontId;
59 d->cursor = 0; 62 d->cursor = 0;
60 d->isSensitive = iFalse; 63 iZap(d->mark);
61 d->enterPressed = iFalse; 64 d->isSensitive = iFalse;
65 d->enterPressed = iFalse;
66 d->selectAllOnFocus = iFalse;
62 setMaxLen_InputWidget(d, maxLen); 67 setMaxLen_InputWidget(d, maxLen);
63 /* Caller must arrange the width, but the height is fixed. */ 68 /* Caller must arrange the width, but the height is fixed. */
64 w->rect.size.y = lineHeight_Text(default_FontId) + 2 * gap_UI; 69 w->rect.size.y = lineHeight_Text(default_FontId) + 2 * gap_UI;
@@ -121,10 +126,6 @@ void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) {
121 delete_String(str); 126 delete_String(str);
122} 127}
123 128
124void setCursor_InputWidget(iInputWidget *d, size_t pos) {
125 d->cursor = iMin(pos, size_Array(&d->text));
126}
127
128static uint32_t refreshTimer_(uint32_t interval, void *d) { 129static uint32_t refreshTimer_(uint32_t interval, void *d) {
129 refresh_Widget(d); 130 refresh_Widget(d);
130 return interval; 131 return interval;
@@ -149,6 +150,12 @@ void begin_InputWidget(iInputWidget *d) {
149 refresh_Widget(w); 150 refresh_Widget(w);
150 d->timer = SDL_AddTimer(REFRESH_INTERVAL, refreshTimer_, d); 151 d->timer = SDL_AddTimer(REFRESH_INTERVAL, refreshTimer_, d);
151 d->enterPressed = iFalse; 152 d->enterPressed = iFalse;
153 if (d->selectAllOnFocus) {
154 d->mark = (iRanges){ 0, size_Array(&d->text) };
155 }
156 else {
157 iZap(d->mark);
158 }
152} 159}
153 160
154void end_InputWidget(iInputWidget *d, iBool accept) { 161void end_InputWidget(iInputWidget *d, iBool accept) {
@@ -188,6 +195,100 @@ static void insertChar_InputWidget_(iInputWidget *d, iChar chr) {
188 refresh_Widget(as_Widget(d)); 195 refresh_Widget(as_Widget(d));
189} 196}
190 197
198iLocalDef size_t cursorMax_InputWidget_(const iInputWidget *d) {
199 return iMin(size_Array(&d->text), d->maxLen - 1);
200}
201
202iLocalDef iBool isMarking_(void) {
203 return (SDL_GetModState() & KMOD_SHIFT) != 0;
204}
205
206void setCursor_InputWidget(iInputWidget *d, size_t pos) {
207 if (isEmpty_Array(&d->text)) {
208 d->cursor = 0;
209 }
210 else {
211 d->cursor = iClamp(pos, 0, cursorMax_InputWidget_(d));
212 }
213 /* Update selection. */
214 if (isMarking_()) {
215 if (isEmpty_Range(&d->mark)) {
216 d->mark.start = d->lastCursor;
217 d->mark.end = d->cursor;
218 }
219 else {
220 d->mark.end = d->cursor;
221 }
222 }
223 else {
224 iZap(d->mark);
225 }
226}
227
228void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) {
229 d->selectAllOnFocus = selectAllOnFocus;
230}
231
232static iRanges mark_InputWidget_(const iInputWidget *d) {
233 return (iRanges){ iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) };
234}
235
236static void deleteMarked_InputWidget_(iInputWidget *d) {
237 const iRanges m = mark_InputWidget_(d);
238 removeRange_Array(&d->text, m);
239 setCursor_InputWidget(d, m.start);
240 iZap(d->mark);
241}
242
243static iBool isWordChar_InputWidget_(const iInputWidget *d, size_t pos) {
244 const iChar ch = pos < size_Array(&d->text) ? constValue_Array(&d->text, pos, iChar) : ' ';
245 return isAlphaNumeric_Char(ch);
246}
247
248iLocalDef iBool movePos_InputWidget_(const iInputWidget *d, size_t *pos, int dir) {
249 if (dir < 0) {
250 if (*pos > 0) (*pos)--; else return iFalse;
251 }
252 else {
253 if (*pos < cursorMax_InputWidget_(d)) (*pos)++; else return iFalse;
254 }
255 return iTrue;
256}
257
258static size_t skipWord_InputWidget_(const iInputWidget *d, size_t pos, int dir) {
259 const iBool startedAtNonWord = !isWordChar_InputWidget_(d, pos);
260 if (!movePos_InputWidget_(d, &pos, dir)) {
261 return pos;
262 }
263 /* Skip any non-word characters at start position. */
264 while (!isWordChar_InputWidget_(d, pos)) {
265 if (!movePos_InputWidget_(d, &pos, dir)) {
266 return pos;
267 }
268 }
269 if (startedAtNonWord && dir > 0) {
270 return pos; /* Found the start of a word. */
271 }
272 /* Skip the word. */
273 while (isWordChar_InputWidget_(d, pos)) {
274 if (!movePos_InputWidget_(d, &pos, dir)) {
275 return pos;
276 }
277 }
278 if (dir > 0) {
279 /* Skip to the beginning of the word. */
280 while (!isWordChar_InputWidget_(d, pos)) {
281 if (!movePos_InputWidget_(d, &pos, dir)) {
282 return pos;
283 }
284 }
285 }
286 else {
287 movePos_InputWidget_(d, &pos, +1);
288 }
289 return pos;
290}
291
191static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 292static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
192 iWidget *w = as_Widget(d); 293 iWidget *w = as_Widget(d);
193 if (isCommand_Widget(w, ev, "focus.gained")) { 294 if (isCommand_Widget(w, ev, "focus.gained")) {
@@ -207,13 +308,18 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
207 case aborted_ClickResult: 308 case aborted_ClickResult:
208 return iTrue; 309 return iTrue;
209 case finished_ClickResult: 310 case finished_ClickResult:
210 setFocus_Widget(as_Widget(d)); 311 if (isFocused_Widget(w)) {
312
313 }
314 else {
315 setFocus_Widget(w);
316 }
211 return iTrue; 317 return iTrue;
212 } 318 }
213 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { 319 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) {
214 return iTrue; 320 return iTrue;
215 } 321 }
216 const size_t curMax = iMin(size_Array(&d->text), d->maxLen - 1); 322 const size_t curMax = cursorMax_InputWidget_(d);
217 if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) { 323 if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) {
218 const int key = ev->key.keysym.sym; 324 const int key = ev->key.keysym.sym;
219 const int mods = keyMods_Sym(ev->key.keysym.mod); 325 const int mods = keyMods_Sym(ev->key.keysym.mod);
@@ -231,6 +337,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
231 return iTrue; 337 return iTrue;
232 } 338 }
233 } 339 }
340 d->lastCursor = d->cursor;
234 switch (key) { 341 switch (key) {
235 case SDLK_RETURN: 342 case SDLK_RETURN:
236 case SDLK_KP_ENTER: 343 case SDLK_KP_ENTER:
@@ -242,9 +349,13 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
242 setFocus_Widget(NULL); 349 setFocus_Widget(NULL);
243 return iTrue; 350 return iTrue;
244 case SDLK_BACKSPACE: 351 case SDLK_BACKSPACE:
245 if (mods & KMOD_ALT) { 352 if (!isEmpty_Range(&d->mark)) {
246 clear_Array(&d->text); 353 deleteMarked_InputWidget_(d);
247 d->cursor = 0; 354 }
355 else if (mods & KMOD_ALT) {
356 d->mark.start = d->cursor;
357 d->mark.end = skipWord_InputWidget_(d, d->cursor, -1);
358 deleteMarked_InputWidget_(d);
248 } 359 }
249 else if (d->cursor > 0) { 360 else if (d->cursor > 0) {
250 remove_Array(&d->text, --d->cursor); 361 remove_Array(&d->text, --d->cursor);
@@ -254,49 +365,64 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
254 case SDLK_d: 365 case SDLK_d:
255 if (mods != KMOD_CTRL) break; 366 if (mods != KMOD_CTRL) break;
256 case SDLK_DELETE: 367 case SDLK_DELETE:
257 if (d->cursor < size_Array(&d->text)) { 368 if (!isEmpty_Range(&d->mark)) {
369 deleteMarked_InputWidget_(d);
370 }
371 else if (mods & KMOD_ALT) {
372 d->mark.start = d->cursor;
373 d->mark.end = skipWord_InputWidget_(d, d->cursor, +1);
374 deleteMarked_InputWidget_(d);
375 }
376 else if (d->cursor < size_Array(&d->text)) {
258 remove_Array(&d->text, d->cursor); 377 remove_Array(&d->text, d->cursor);
259 refresh_Widget(w); 378 refresh_Widget(w);
260 } 379 }
261 return iTrue; 380 return iTrue;
262 case SDLK_k: 381 case SDLK_k:
263 if (mods == KMOD_CTRL) { 382 if (mods == KMOD_CTRL) {
264 removeN_Array(&d->text, d->cursor, size_Array(&d->text) - d->cursor); 383 if (!isEmpty_Range(&d->mark)) {
265 refresh_Widget(w); 384 deleteMarked_InputWidget_(d);
385 }
386 else {
387 removeN_Array(&d->text, d->cursor, size_Array(&d->text) - d->cursor);
388 refresh_Widget(w);
389 }
266 return iTrue; 390 return iTrue;
267 } 391 }
268 break; 392 break;
269 case SDLK_HOME: 393 case SDLK_HOME:
270 case SDLK_END: 394 case SDLK_END:
271 d->cursor = (key == SDLK_HOME ? 0 : curMax); 395 setCursor_InputWidget(d, key == SDLK_HOME ? 0 : curMax);
272 refresh_Widget(w); 396 refresh_Widget(w);
273 return iTrue; 397 return iTrue;
274 case SDLK_a: 398 case SDLK_a:
275 case SDLK_e: 399 case SDLK_e:
276 if (mods == KMOD_CTRL) { 400 if (!(mods & ~(KMOD_CTRL | KMOD_SHIFT))) {
277 d->cursor = (key == 'a' ? 0 : curMax); 401 setCursor_InputWidget(d, key == 'a' ? 0 : curMax);
278 refresh_Widget(w); 402 refresh_Widget(w);
279 return iTrue; 403 return iTrue;
280 } 404 }
281 break; 405 break;
282 case SDLK_LEFT: 406 case SDLK_LEFT:
407 case SDLK_RIGHT: {
408 const int dir = (key == SDLK_LEFT ? -1 : +1);
283 if (mods & KMOD_PRIMARY) { 409 if (mods & KMOD_PRIMARY) {
284 d->cursor = 0; 410 setCursor_InputWidget(d, dir < 0 ? 0 : curMax);
285 } 411 }
286 else if (d->cursor > 0) { 412 else if (mods & KMOD_ALT) {
287 d->cursor--; 413 setCursor_InputWidget(d, skipWord_InputWidget_(d, d->cursor, dir));
288 } 414 }
289 refresh_Widget(w); 415 else if (!isMarking_() && !isEmpty_Range(&d->mark)) {
290 return iTrue; 416 const iRanges m = mark_InputWidget_(d);
291 case SDLK_RIGHT: 417 setCursor_InputWidget(d, dir < 0 ? m.start : m.end);
292 if (mods & KMOD_PRIMARY) { 418 iZap(d->mark);
293 d->cursor = curMax;
294 } 419 }
295 else if (d->cursor < curMax) { 420 else if ((dir < 0 && d->cursor > 0) || (dir > 0 && d->cursor < curMax)) {
296 d->cursor++; 421 setCursor_InputWidget(d, d->cursor + dir);
297 } 422 }
298 refresh_Widget(w); 423 refresh_Widget(w);
299 return iTrue; 424 return iTrue;
425 }
300 case SDLK_TAB: 426 case SDLK_TAB:
301 /* Allow focus switching. */ 427 /* Allow focus switching. */
302 return processEvent_Widget(as_Widget(d), ev); 428 return processEvent_Widget(as_Widget(d), ev);
@@ -332,10 +458,10 @@ static void draw_InputWidget_(const iInputWidget *d) {
332 const uint32_t time = frameTime_Window(get_Window()); 458 const uint32_t time = frameTime_Window(get_Window());
333 const iInt2 padding = init_I2(gap_UI / 2, gap_UI / 2); 459 const iInt2 padding = init_I2(gap_UI / 2, gap_UI / 2);
334 iRect bounds = adjusted_Rect(bounds_Widget(w), padding, neg_I2(padding)); 460 iRect bounds = adjusted_Rect(bounds_Widget(w), padding, neg_I2(padding));
461 iBool isHint = iFalse;
335 const iBool isFocused = isFocused_Widget(w); 462 const iBool isFocused = isFocused_Widget(w);
336 const iBool isHover = isHover_Widget(w) && 463 const iBool isHover = isHover_Widget(w) &&
337 contains_Widget(w, mouseCoord_Window(get_Window())); 464 contains_Widget(w, mouseCoord_Window(get_Window()));
338 iBool isHint = iFalse;
339 iPaint p; 465 iPaint p;
340 init_Paint(&p); 466 init_Paint(&p);
341 iString text; 467 iString text;
@@ -375,8 +501,18 @@ static void draw_InputWidget_(const iInputWidget *d) {
375 xOff = iMin(xOff, 0); 501 xOff = iMin(xOff, 0);
376 } 502 }
377 const int yOff = (height_Rect(bounds) - lineHeight_Text(d->font)) / 2; 503 const int yOff = (height_Rect(bounds) - lineHeight_Text(d->font)) / 2;
504 const iInt2 textOrigin = add_I2(topLeft_Rect(bounds), init_I2(xOff, yOff));
505 if (isFocused && !isEmpty_Range(&d->mark)) {
506 /* Draw the selected range. */
507 const int m1 = advanceN_Text(d->font, cstr_String(&text), d->mark.start).x;
508 const int m2 = advanceN_Text(d->font, cstr_String(&text), d->mark.end).x;
509 fillRect_Paint(
510 &p,
511 (iRect){ addX_I2(textOrigin, iMin(m1, m2)), init_I2(iAbs(m2 - m1), lineHeight_Text(d->font)) },
512 red_ColorId);
513 }
378 draw_Text(d->font, 514 draw_Text(d->font,
379 add_I2(topLeft_Rect(bounds), init_I2(xOff, yOff)), 515 textOrigin,
380 isHint ? uiAnnotation_ColorId 516 isHint ? uiAnnotation_ColorId
381 : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId 517 : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId
382 : uiInputText_ColorId, 518 : uiInputText_ColorId,
@@ -386,8 +522,8 @@ static void draw_InputWidget_(const iInputWidget *d) {
386 /* Cursor blinking. */ 522 /* Cursor blinking. */
387 if (isFocused && (time & 256)) { 523 if (isFocused && (time & 256)) {
388 const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(&text), d->cursor); 524 const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(&text), d->cursor);
389 const iInt2 curPos = init_I2(xOff + left_Rect(bounds) + prefixSize.x, 525 const iInt2 curPos = addX_I2(textOrigin, prefixSize.x); /* init_I2(xOff + left_Rect(bounds) + prefixSize.x,
390 yOff + top_Rect(bounds)); 526 yOff + top_Rect(bounds));*/
391 const iRect curRect = { curPos, addX_I2(emSize, 1) }; 527 const iRect curRect = { curPos, addX_I2(emSize, 1) };
392 iString cur; 528 iString cur;
393 if (d->cursor < size_Array(&d->text)) { 529 if (d->cursor < size_Array(&d->text)) {
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index 01f4efc3..fed9c3d9 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -39,6 +39,7 @@ void setMaxLen_InputWidget (iInputWidget *, size_t maxLen);
39void setText_InputWidget (iInputWidget *, const iString *text); 39void setText_InputWidget (iInputWidget *, const iString *text);
40void setTextCStr_InputWidget (iInputWidget *, const char *cstr); 40void setTextCStr_InputWidget (iInputWidget *, const char *cstr);
41void setCursor_InputWidget (iInputWidget *, size_t pos); 41void setCursor_InputWidget (iInputWidget *, size_t pos);
42void setSelectAllOnFocus_InputWidget(iInputWidget *, iBool selectAllOnFocus);
42void begin_InputWidget (iInputWidget *); 43void begin_InputWidget (iInputWidget *);
43void end_InputWidget (iInputWidget *, iBool accept); 44void end_InputWidget (iInputWidget *, iBool accept);
44 45
diff --git a/src/ui/window.c b/src/ui/window.c
index fdc60423..650bc9ee 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -344,6 +344,7 @@ static void setupUserInterface_Window(iWindow *d) {
344 setFont_LabelWidget(lock, defaultSymbols_FontId); 344 setFont_LabelWidget(lock, defaultSymbols_FontId);
345 updateTextCStr_LabelWidget(lock, "\U0001f512"); 345 updateTextCStr_LabelWidget(lock, "\U0001f512");
346 iInputWidget *url = new_InputWidget(0); 346 iInputWidget *url = new_InputWidget(0);
347 setSelectAllOnFocus_InputWidget(url, iTrue);
347 setId_Widget(as_Widget(url), "url"); 348 setId_Widget(as_Widget(url), "url");
348 setTextCStr_InputWidget(url, "gemini://"); 349 setTextCStr_InputWidget(url, "gemini://");
349 addChildFlags_Widget(navBar, iClob(url), expand_WidgetFlag); 350 addChildFlags_Widget(navBar, iClob(url), expand_WidgetFlag);