diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-31 07:12:57 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-31 07:12:57 +0300 |
commit | 49d98b968281fb26cb14586e3e781585f74e506f (patch) | |
tree | cbdb751c83f3bdf3c1c916ab73a4ae5465ef8ac4 /src | |
parent | e43891459ff4640cdc97ca7cb71891a54c0d1989 (diff) |
InputWidget: Move cursor, select with mouse
Diffstat (limited to 'src')
-rw-r--r-- | src/ui/inputwidget.c | 151 |
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 | ||
351 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
352 | |||
353 | static 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 | |||
367 | iLocalDef iInt2 padding_(void) { | ||
368 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
369 | } | ||
370 | |||
371 | static 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 | |||
392 | static 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 | |||
348 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 415 | static 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 | ||
538 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
539 | |||
540 | static iBool isWhite_(const iString *str) { | 613 | static 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) { | |||
549 | static void draw_InputWidget_(const iInputWidget *d) { | 622 | static 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 | ||
640 | iBeginDefineSubclass(InputWidget, Widget) | 687 | iBeginDefineSubclass(InputWidget, Widget) |