summaryrefslogtreecommitdiff
path: root/src/ui/inputwidget.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-08-31 07:12:57 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-08-31 07:12:57 +0300
commit49d98b968281fb26cb14586e3e781585f74e506f (patch)
treecbdb751c83f3bdf3c1c916ab73a4ae5465ef8ac4 /src/ui/inputwidget.c
parente43891459ff4640cdc97ca7cb71891a54c0d1989 (diff)
InputWidget: Move cursor, select with mouse
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r--src/ui/inputwidget.c151
1 files changed, 99 insertions, 52 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 9884ff7d..eca5dbdd 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -60,6 +60,7 @@ struct Impl_InputWidget {
60 iString hint; 60 iString hint;
61 size_t cursor; 61 size_t cursor;
62 size_t lastCursor; 62 size_t lastCursor;
63 iBool isMarking;
63 iRanges mark; 64 iRanges mark;
64 iArray undoStack; 65 iArray undoStack;
65 int font; 66 int font;
@@ -86,6 +87,8 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
86 init_Array(&d->undoStack, sizeof(iInputUndo)); 87 init_Array(&d->undoStack, sizeof(iInputUndo));
87 d->font = uiInput_FontId; 88 d->font = uiInput_FontId;
88 d->cursor = 0; 89 d->cursor = 0;
90 d->lastCursor = 0;
91 d->isMarking = iFalse;
89 iZap(d->mark); 92 iZap(d->mark);
90 d->isSensitive = iFalse; 93 d->isSensitive = iFalse;
91 d->enterPressed = iFalse; 94 d->enterPressed = iFalse;
@@ -345,6 +348,70 @@ static size_t skipWord_InputWidget_(const iInputWidget *d, size_t pos, int dir)
345 return pos; 348 return pos;
346} 349}
347 350
351static const iChar sensitiveChar_ = 0x25cf; /* black circle */
352
353static iString *visText_InputWidget_(const iInputWidget *d) {
354 iString *text;
355 if (!d->isSensitive) {
356 text = newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text));
357 }
358 else {
359 text = new_String();
360 for (size_t i = 0; i < size_Array(&d->text); ++i) {
361 appendChar_String(text, sensitiveChar_);
362 }
363 }
364 return text;
365}
366
367iLocalDef iInt2 padding_(void) {
368 return init_I2(gap_UI / 2, gap_UI / 2);
369}
370
371static iInt2 textOrigin_InputWidget_(const iInputWidget *d, const char *visText) {
372 const iWidget *w = constAs_Widget(d);
373 iRect bounds = adjusted_Rect(bounds_Widget(w), padding_(), neg_I2(padding_()));
374 const iInt2 emSize = advance_Text(d->font, "M");
375 const int textWidth = advance_Text(d->font, visText).x;
376 const int cursorX = advanceN_Text(d->font, visText, d->cursor).x;
377 int xOff = 0;
378 shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0));
379 if (d->maxLen == 0) {
380 if (textWidth > width_Rect(bounds) - emSize.x) {
381 xOff = width_Rect(bounds) - emSize.x - textWidth;
382 }
383 if (cursorX + xOff < width_Rect(bounds) / 2) {
384 xOff = width_Rect(bounds) / 2 - cursorX;
385 }
386 xOff = iMin(xOff, 0);
387 }
388 const int yOff = (height_Rect(bounds) - lineHeight_Text(d->font)) / 2;
389 return add_I2(topLeft_Rect(bounds), init_I2(xOff, yOff));
390}
391
392static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) {
393 iString *visText = visText_InputWidget_(d);
394 iInt2 pos = sub_I2(coord, textOrigin_InputWidget_(d, cstr_String(visText)));
395 size_t index = 0;
396 if (pos.x > 0) {
397 const char *endPos;
398 tryAdvanceNoWrap_Text(d->font, range_String(visText), pos.x, &endPos);
399 if (endPos == constEnd_String(visText)) {
400 index = cursorMax_InputWidget_(d);
401 }
402 else {
403 /* Need to know the actual character index. */
404 /* TODO: tryAdvance could tell us this directly with an extra return value */
405 iConstForEach(String, i, visText) {
406 if (i.pos >= endPos) break;
407 index++;
408 }
409 }
410 }
411 delete_String(visText);
412 return index;
413}
414
348static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 415static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
349 iWidget *w = as_Widget(d); 416 iWidget *w = as_Widget(d);
350 if (isCommand_Widget(w, ev, "focus.gained")) { 417 if (isCommand_Widget(w, ev, "focus.gained")) {
@@ -359,17 +426,24 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
359 case none_ClickResult: 426 case none_ClickResult:
360 break; 427 break;
361 case started_ClickResult: 428 case started_ClickResult:
362 case drag_ClickResult: 429 setFocus_Widget(w);
430 setCursor_InputWidget(d, coordIndex_InputWidget_(d, pos_Click(&d->click)));
431 iZap(d->mark);
432 d->isMarking = iFalse;
433 return iTrue;
363 case double_ClickResult: 434 case double_ClickResult:
364 case aborted_ClickResult: 435 case aborted_ClickResult:
365 return iTrue; 436 return iTrue;
366 case finished_ClickResult: 437 case drag_ClickResult:
367 if (isFocused_Widget(w)) { 438 d->cursor = coordIndex_InputWidget_(d, pos_Click(&d->click));
368 439 if (!d->isMarking) {
369 } 440 d->isMarking = iTrue;
370 else { 441 d->mark.start = d->cursor;
371 setFocus_Widget(w);
372 } 442 }
443 d->mark.end = d->cursor;
444 refresh_Widget(w);
445 return iTrue;
446 case finished_ClickResult:
373 return iTrue; 447 return iTrue;
374 } 448 }
375 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { 449 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) {
@@ -483,6 +557,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
483 d->mark.start = 0; 557 d->mark.start = 0;
484 d->mark.end = curMax; 558 d->mark.end = curMax;
485 d->cursor = curMax; 559 d->cursor = curMax;
560 refresh_Widget(w);
486 return iTrue; 561 return iTrue;
487 } 562 }
488#endif 563#endif
@@ -535,8 +610,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
535 return processEvent_Widget(w, ev); 610 return processEvent_Widget(w, ev);
536} 611}
537 612
538static const iChar sensitiveChar_ = 0x25cf; /* black circle */
539
540static iBool isWhite_(const iString *str) { 613static iBool isWhite_(const iString *str) {
541 iConstForEach(String, i, str) { 614 iConstForEach(String, i, str) {
542 if (!isSpace_Char(i.value)) { 615 if (!isSpace_Char(i.value)) {
@@ -549,26 +622,16 @@ static iBool isWhite_(const iString *str) {
549static void draw_InputWidget_(const iInputWidget *d) { 622static void draw_InputWidget_(const iInputWidget *d) {
550 const iWidget *w = constAs_Widget(d); 623 const iWidget *w = constAs_Widget(d);
551 const uint32_t time = frameTime_Window(get_Window()); 624 const uint32_t time = frameTime_Window(get_Window());
552 const iInt2 padding = init_I2(gap_UI / 2, gap_UI / 2); 625 iRect bounds = adjusted_Rect(bounds_Widget(w), padding_(), neg_I2(padding_()));
553 iRect bounds = adjusted_Rect(bounds_Widget(w), padding, neg_I2(padding));
554 iBool isHint = iFalse; 626 iBool isHint = iFalse;
555 const iBool isFocused = isFocused_Widget(w); 627 const iBool isFocused = isFocused_Widget(w);
556 const iBool isHover = isHover_Widget(w) && 628 const iBool isHover = isHover_Widget(w) &&
557 contains_Widget(w, mouseCoord_Window(get_Window())); 629 contains_Widget(w, mouseCoord_Window(get_Window()));
558 iPaint p; 630 iPaint p;
559 init_Paint(&p); 631 init_Paint(&p);
560 iString text; 632 iString *text = visText_InputWidget_(d);
561 if (!d->isSensitive) { 633 if (isWhite_(text) && !isEmpty_String(&d->hint)) {
562 initUnicodeN_String(&text, constData_Array(&d->text), size_Array(&d->text)); 634 set_String(text, &d->hint);
563 }
564 else {
565 init_String(&text);
566 for (size_t i = 0; i < size_Array(&d->text); ++i) {
567 appendChar_String(&text, sensitiveChar_);
568 }
569 }
570 if (isWhite_(&text) && !isEmpty_String(&d->hint)) {
571 set_String(&text, &d->hint);
572 isHint = iTrue; 635 isHint = iTrue;
573 } 636 }
574 fillRect_Paint( 637 fillRect_Paint(
@@ -579,30 +642,15 @@ static void draw_InputWidget_(const iInputWidget *d) {
579 isFocused ? uiInputFrameFocused_ColorId 642 isFocused ? uiInputFrameFocused_ColorId
580 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); 643 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId);
581 setClip_Paint(&p, bounds); 644 setClip_Paint(&p, bounds);
582 shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); 645 const iInt2 textOrigin = textOrigin_InputWidget_(d, cstr_String(text));
583 const iInt2 emSize = advance_Text(d->font, "M");
584 const int textWidth = advance_Text(d->font, cstr_String(&text)).x;
585 const int cursorX = advanceN_Text(d->font, cstr_String(&text), d->cursor).x;
586 int xOff = 0;
587 if (d->maxLen == 0) {
588 if (textWidth > width_Rect(bounds) - emSize.x) {
589 xOff = width_Rect(bounds) - emSize.x - textWidth;
590 }
591 if (cursorX + xOff < width_Rect(bounds) / 2) {
592 xOff = width_Rect(bounds) / 2 - cursorX;
593 }
594 xOff = iMin(xOff, 0);
595 }
596 const int yOff = (height_Rect(bounds) - lineHeight_Text(d->font)) / 2;
597 const iInt2 textOrigin = add_I2(topLeft_Rect(bounds), init_I2(xOff, yOff));
598 if (isFocused && !isEmpty_Range(&d->mark)) { 646 if (isFocused && !isEmpty_Range(&d->mark)) {
599 /* Draw the selected range. */ 647 /* Draw the selected range. */
600 const int m1 = advanceN_Text(d->font, cstr_String(&text), d->mark.start).x; 648 const int m1 = advanceN_Text(d->font, cstr_String(text), d->mark.start).x;
601 const int m2 = advanceN_Text(d->font, cstr_String(&text), d->mark.end).x; 649 const int m2 = advanceN_Text(d->font, cstr_String(text), d->mark.end).x;
602 fillRect_Paint( 650 fillRect_Paint(&p,
603 &p, 651 (iRect){ addX_I2(textOrigin, iMin(m1, m2)),
604 (iRect){ addX_I2(textOrigin, iMin(m1, m2)), init_I2(iAbs(m2 - m1), lineHeight_Text(d->font)) }, 652 init_I2(iAbs(m2 - m1), lineHeight_Text(d->font)) },
605 red_ColorId); 653 uiMarked_ColorId);
606 } 654 }
607 draw_Text(d->font, 655 draw_Text(d->font,
608 textOrigin, 656 textOrigin,
@@ -610,15 +658,11 @@ static void draw_InputWidget_(const iInputWidget *d) {
610 : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId 658 : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId
611 : uiInputText_ColorId, 659 : uiInputText_ColorId,
612 "%s", 660 "%s",
613 cstr_String(&text)); 661 cstr_String(text));
614 unsetClip_Paint(&p); 662 unsetClip_Paint(&p);
615 /* Cursor blinking. */ 663 /* Cursor blinking. */
616 if (isFocused && (time & 256)) { 664 if (isFocused && (time & 256)) {
617 const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(&text), d->cursor); 665 iString cur;
618 const iInt2 curPos = addX_I2(textOrigin, prefixSize.x); /* init_I2(xOff + left_Rect(bounds) + prefixSize.x,
619 yOff + top_Rect(bounds));*/
620 const iRect curRect = { curPos, addX_I2(emSize, 1) };
621 iString cur;
622 if (d->cursor < size_Array(&d->text)) { 666 if (d->cursor < size_Array(&d->text)) {
623 if (!d->isSensitive) { 667 if (!d->isSensitive) {
624 initUnicodeN_String(&cur, constAt_Array(&d->text, d->cursor), 1); 668 initUnicodeN_String(&cur, constAt_Array(&d->text, d->cursor), 1);
@@ -630,11 +674,14 @@ static void draw_InputWidget_(const iInputWidget *d) {
630 else { 674 else {
631 initCStr_String(&cur, " "); 675 initCStr_String(&cur, " ");
632 } 676 }
677 const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(text), d->cursor);
678 const iInt2 curPos = addX_I2(textOrigin, prefixSize.x);
679 const iRect curRect = { curPos, addX_I2(advance_Text(d->font, cstr_String(&cur)), 1) };
633 fillRect_Paint(&p, curRect, uiInputCursor_ColorId); 680 fillRect_Paint(&p, curRect, uiInputCursor_ColorId);
634 draw_Text(d->font, curPos, uiInputCursorText_ColorId, cstr_String(&cur)); 681 draw_Text(d->font, curPos, uiInputCursorText_ColorId, cstr_String(&cur));
635 deinit_String(&cur); 682 deinit_String(&cur);
636 } 683 }
637 deinit_String(&text); 684 delete_String(text);
638} 685}
639 686
640iBeginDefineSubclass(InputWidget, Widget) 687iBeginDefineSubclass(InputWidget, Widget)