diff options
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r-- | src/ui/inputwidget.c | 665 |
1 files changed, 522 insertions, 143 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 690107a2..874cf2b5 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
27 | #include "keys.h" | 27 | #include "keys.h" |
28 | #include "prefs.h" | 28 | #include "prefs.h" |
29 | #include "lang.h" | 29 | #include "lang.h" |
30 | #include "touch.h" | ||
30 | #include "app.h" | 31 | #include "app.h" |
31 | 32 | ||
32 | #include <the_Foundation/array.h> | 33 | #include <the_Foundation/array.h> |
@@ -178,19 +179,23 @@ static void deinit_InputUndo_(iInputUndo *d) { | |||
178 | } | 179 | } |
179 | 180 | ||
180 | enum iInputWidgetFlag { | 181 | enum iInputWidgetFlag { |
181 | isSensitive_InputWidgetFlag = iBit(1), | 182 | isSensitive_InputWidgetFlag = iBit(1), |
182 | isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ | 183 | isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ |
183 | enterPressed_InputWidgetFlag = iBit(3), | 184 | enterPressed_InputWidgetFlag = iBit(3), |
184 | selectAllOnFocus_InputWidgetFlag = iBit(4), | 185 | selectAllOnFocus_InputWidgetFlag = iBit(4), |
185 | notifyEdits_InputWidgetFlag = iBit(5), | 186 | notifyEdits_InputWidgetFlag = iBit(5), |
186 | eatEscape_InputWidgetFlag = iBit(6), | 187 | eatEscape_InputWidgetFlag = iBit(6), |
187 | isMarking_InputWidgetFlag = iBit(7), | 188 | isMarking_InputWidgetFlag = iBit(7), |
188 | markWords_InputWidgetFlag = iBit(8), | 189 | markWords_InputWidgetFlag = iBit(8), |
189 | needUpdateBuffer_InputWidgetFlag = iBit(9), | 190 | needUpdateBuffer_InputWidgetFlag = iBit(9), |
190 | enterKeyEnabled_InputWidgetFlag = iBit(10), | 191 | enterKeyEnabled_InputWidgetFlag = iBit(10), |
191 | lineBreaksEnabled_InputWidgetFlag= iBit(11), | 192 | lineBreaksEnabled_InputWidgetFlag = iBit(11), |
192 | needBackup_InputWidgetFlag = iBit(12), | 193 | needBackup_InputWidgetFlag = iBit(12), |
193 | useReturnKeyBehavior_InputWidgetFlag = iBit(13), | 194 | useReturnKeyBehavior_InputWidgetFlag = iBit(13), |
195 | //touchBehavior_InputWidgetFlag = iBit(14), /* different behavior depending on interaction method */ | ||
196 | dragCursor_InputWidgetFlag = iBit(14), | ||
197 | dragMarkerStart_InputWidgetFlag = iBit(15), | ||
198 | dragMarkerEnd_InputWidgetFlag = iBit(16), | ||
194 | }; | 199 | }; |
195 | 200 | ||
196 | /*----------------------------------------------------------------------------------------------*/ | 201 | /*----------------------------------------------------------------------------------------------*/ |
@@ -216,6 +221,10 @@ struct Impl_InputWidget { | |||
216 | iArray undoStack; | 221 | iArray undoStack; |
217 | int font; | 222 | int font; |
218 | iClick click; | 223 | iClick click; |
224 | uint32_t tapStartTime; | ||
225 | uint32_t lastTapTime; | ||
226 | iInt2 lastTapPos; | ||
227 | int tapCount; | ||
219 | int wheelAccum; | 228 | int wheelAccum; |
220 | int cursorVis; | 229 | int cursorVis; |
221 | uint32_t timer; | 230 | uint32_t timer; |
@@ -459,14 +468,54 @@ static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) { | |||
459 | }; | 468 | }; |
460 | } | 469 | } |
461 | 470 | ||
462 | static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { | 471 | static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) { |
463 | /* Relative to the start of the line on which the cursor is. */ | 472 | iRangei vis = { -1, -1 }; |
464 | iWrapText wt = wrap_InputWidget_(d, d->cursor.y); | 473 | /* Determine which lines are in the potentially visible range. */ |
465 | wt.hitChar = wt.text.start + d->cursor.x; | 474 | for (int i = 0; i < size_Array(&d->lines); i++) { |
475 | const iInputLine *line = constAt_Array(&d->lines, i); | ||
476 | if (vis.start < 0 && line->wrapLines.end > d->visWrapLines.start) { | ||
477 | vis.start = vis.end = i; | ||
478 | } | ||
479 | if (line->wrapLines.start < d->visWrapLines.end) { | ||
480 | vis.end = i + 1; | ||
481 | } | ||
482 | else break; | ||
483 | } | ||
484 | iAssert(isEmpty_Range(&vis) || (vis.start >= 0 && vis.end >= vis.start)); | ||
485 | return vis; | ||
486 | } | ||
487 | |||
488 | static iInt2 relativeCoordOnLine_InputWidget_(const iInputWidget *d, iInt2 pos) { | ||
489 | /* Relative to the start of the line on which the position is. */ | ||
490 | iWrapText wt = wrap_InputWidget_(d, pos.y); | ||
491 | wt.hitChar = wt.text.start + pos.x; | ||
466 | measure_WrapText(&wt, d->font); | 492 | measure_WrapText(&wt, d->font); |
467 | return wt.hitAdvance_out; | 493 | return wt.hitAdvance_out; |
468 | } | 494 | } |
469 | 495 | ||
496 | static iInt2 cursorToWindowCoord_InputWidget_(const iInputWidget *d, iInt2 pos, iBool *isInsideBounds) { | ||
497 | /* Maps a cursor XY position to a window coordinate. */ | ||
498 | const iRect bounds = contentBounds_InputWidget_(d); | ||
499 | iInt2 wc = addY_I2(topLeft_Rect(bounds), visLineOffsetY_InputWidget_(d)); | ||
500 | iRangei visLines = visibleLineRange_InputWidget_(d); | ||
501 | if (!contains_Range(&visLines, pos.y)) { | ||
502 | /* This line is not visible. */ | ||
503 | *isInsideBounds = iFalse; | ||
504 | return zero_I2(); | ||
505 | } | ||
506 | for (int i = visLines.start; i < pos.y; i++) { | ||
507 | wc.y += lineHeight_Text(d->font) * numWrapLines_InputLine_(line_InputWidget_(d, i)); | ||
508 | } | ||
509 | const iInputLine *line = line_InputWidget_(d, pos.y); | ||
510 | addv_I2(&wc, relativeCoordOnLine_InputWidget_(d, pos)); | ||
511 | *isInsideBounds = contains_Rect(bounds, wc); | ||
512 | return wc; | ||
513 | } | ||
514 | |||
515 | static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { | ||
516 | return relativeCoordOnLine_InputWidget_(d, d->cursor); | ||
517 | } | ||
518 | |||
470 | static void updateVisible_InputWidget_(iInputWidget *d) { | 519 | static void updateVisible_InputWidget_(iInputWidget *d) { |
471 | const int totalWraps = numWrapLines_InputWidget_(d); | 520 | const int totalWraps = numWrapLines_InputWidget_(d); |
472 | const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); | 521 | const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); |
@@ -492,6 +541,8 @@ static void updateVisible_InputWidget_(iInputWidget *d) { | |||
492 | d->visWrapLines.start = 0; | 541 | d->visWrapLines.start = 0; |
493 | d->visWrapLines.end = 1; | 542 | d->visWrapLines.end = 1; |
494 | } | 543 | } |
544 | // printf("[InputWidget %p] total:%d viswrp:%d cur:%d vis:%d..%d\n", | ||
545 | // d, totalWraps, visWraps, d->cursor.y, d->visWrapLines.start, d->visWrapLines.end); | ||
495 | } | 546 | } |
496 | 547 | ||
497 | static void showCursor_InputWidget_(iInputWidget *d) { | 548 | static void showCursor_InputWidget_(iInputWidget *d) { |
@@ -542,8 +593,10 @@ static int contentHeight_InputWidget_(const iInputWidget *d) { | |||
542 | } | 593 | } |
543 | 594 | ||
544 | static void updateTextInputRect_InputWidget_(const iInputWidget *d) { | 595 | static void updateTextInputRect_InputWidget_(const iInputWidget *d) { |
596 | #if !defined (iPlatformAppleMobile) | ||
545 | const iRect bounds = bounds_Widget(constAs_Widget(d)); | 597 | const iRect bounds = bounds_Widget(constAs_Widget(d)); |
546 | SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y }); | 598 | SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y }); |
599 | #endif | ||
547 | } | 600 | } |
548 | 601 | ||
549 | static void updateMetrics_InputWidget_(iInputWidget *d) { | 602 | static void updateMetrics_InputWidget_(iInputWidget *d) { |
@@ -629,7 +682,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
629 | init_Widget(w); | 682 | init_Widget(w); |
630 | d->validator = NULL; | 683 | d->validator = NULL; |
631 | d->validatorContext = NULL; | 684 | d->validatorContext = NULL; |
632 | setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag | touchDrag_WidgetFlag, iTrue); | 685 | setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag, iTrue); |
633 | #if defined (iPlatformMobile) | 686 | #if defined (iPlatformMobile) |
634 | setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); | 687 | setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); |
635 | #endif | 688 | #endif |
@@ -659,6 +712,8 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
659 | splitToLines_(&iStringLiteral(""), &d->lines); | 712 | splitToLines_(&iStringLiteral(""), &d->lines); |
660 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ | 713 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ |
661 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 714 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
715 | d->lastTapTime = 0; | ||
716 | d->tapCount = 0; | ||
662 | d->wheelAccum = 0; | 717 | d->wheelAccum = 0; |
663 | d->timer = 0; | 718 | d->timer = 0; |
664 | d->cursorVis = 0; | 719 | d->cursorVis = 0; |
@@ -753,6 +808,10 @@ const iString *text_InputWidget(const iInputWidget *d) { | |||
753 | return collectNew_String(); | 808 | return collectNew_String(); |
754 | } | 809 | } |
755 | 810 | ||
811 | int font_InputWidget(const iInputWidget *d) { | ||
812 | return d->font; | ||
813 | } | ||
814 | |||
756 | iInputWidgetContentPadding contentPadding_InputWidget(const iInputWidget *d) { | 815 | iInputWidgetContentPadding contentPadding_InputWidget(const iInputWidget *d) { |
757 | return (iInputWidgetContentPadding){ d->leftPadding, d->rightPadding }; | 816 | return (iInputWidgetContentPadding){ d->leftPadding, d->rightPadding }; |
758 | } | 817 | } |
@@ -764,6 +823,7 @@ void setMaxLen_InputWidget(iInputWidget *d, size_t maxLen) { | |||
764 | } | 823 | } |
765 | 824 | ||
766 | void setLineLimits_InputWidget(iInputWidget *d, int minLines, int maxLines) { | 825 | void setLineLimits_InputWidget(iInputWidget *d, int minLines, int maxLines) { |
826 | maxLines = iMax(minLines, maxLines); | ||
767 | if (d->minWrapLines != minLines || d->maxWrapLines != maxLines) { | 827 | if (d->minWrapLines != minLines || d->maxWrapLines != maxLines) { |
768 | d->minWrapLines = minLines; | 828 | d->minWrapLines = minLines; |
769 | d->maxWrapLines = maxLines; | 829 | d->maxWrapLines = maxLines; |
@@ -822,23 +882,6 @@ static iBool isHintVisible_InputWidget_(const iInputWidget *d) { | |||
822 | return !isEmpty_String(&d->hint) && isEmpty_InputWidget_(d); | 882 | return !isEmpty_String(&d->hint) && isEmpty_InputWidget_(d); |
823 | } | 883 | } |
824 | 884 | ||
825 | static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) { | ||
826 | iRangei vis = { -1, -1 }; | ||
827 | /* Determine which lines are in the potentially visible range. */ | ||
828 | for (int i = 0; i < size_Array(&d->lines); i++) { | ||
829 | const iInputLine *line = constAt_Array(&d->lines, i); | ||
830 | if (vis.start < 0 && line->wrapLines.end > d->visWrapLines.start) { | ||
831 | vis.start = vis.end = i; | ||
832 | } | ||
833 | if (line->wrapLines.start < d->visWrapLines.end) { | ||
834 | vis.end = i + 1; | ||
835 | } | ||
836 | else break; | ||
837 | } | ||
838 | iAssert(isEmpty_Range(&vis) || (vis.start >= 0 && vis.end >= vis.start)); | ||
839 | return vis; | ||
840 | } | ||
841 | |||
842 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 885 | static void updateBuffered_InputWidget_(iInputWidget *d) { |
843 | invalidateBuffered_InputWidget_(d); | 886 | invalidateBuffered_InputWidget_(d); |
844 | if (isHintVisible_InputWidget_(d)) { | 887 | if (isHintVisible_InputWidget_(d)) { |
@@ -990,7 +1033,7 @@ void begin_InputWidget(iInputWidget *d) { | |||
990 | d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; | 1033 | d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; |
991 | d->cursor = cursorMax_InputWidget_(d); | 1034 | d->cursor = cursorMax_InputWidget_(d); |
992 | } | 1035 | } |
993 | else { | 1036 | else if (~d->inFlags & isMarking_InputWidgetFlag) { |
994 | iZap(d->mark); | 1037 | iZap(d->mark); |
995 | } | 1038 | } |
996 | enableEditorKeysInMenus_(iFalse); | 1039 | enableEditorKeysInMenus_(iFalse); |
@@ -1010,9 +1053,10 @@ void end_InputWidget(iInputWidget *d, iBool accept) { | |||
1010 | splitToLines_(&d->oldText, &d->lines); | 1053 | splitToLines_(&d->oldText, &d->lines); |
1011 | } | 1054 | } |
1012 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 1055 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
1056 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1013 | startOrStopCursorTimer_InputWidget_(d, iFalse); | 1057 | startOrStopCursorTimer_InputWidget_(d, iFalse); |
1014 | SDL_StopTextInput(); | 1058 | SDL_StopTextInput(); |
1015 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); | 1059 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse); |
1016 | const char *id = cstr_String(id_Widget(as_Widget(d))); | 1060 | const char *id = cstr_String(id_Widget(as_Widget(d))); |
1017 | if (!*id) id = "_"; | 1061 | if (!*id) id = "_"; |
1018 | refresh_Widget(w); | 1062 | refresh_Widget(w); |
@@ -1314,9 +1358,10 @@ static iInt2 coordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { | |||
1314 | if (relCoord.y < 0) { | 1358 | if (relCoord.y < 0) { |
1315 | return zero_I2(); | 1359 | return zero_I2(); |
1316 | } | 1360 | } |
1317 | if (relCoord.y >= height_Rect(bounds)) { | 1361 | // if (relCoord.y >= height_Rect(bounds)) { |
1318 | return cursorMax_InputWidget_(d); | 1362 | // printf("relCoord > bounds.h\n"); fflush(stdout); |
1319 | } | 1363 | // return cursorMax_InputWidget_(d); |
1364 | // } | ||
1320 | iWrapText wrapText = { | 1365 | iWrapText wrapText = { |
1321 | .maxWidth = d->maxLen == 0 ? width_Rect(bounds) : unlimitedWidth_InputWidget_, | 1366 | .maxWidth = d->maxLen == 0 ? width_Rect(bounds) : unlimitedWidth_InputWidget_, |
1322 | .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode), | 1367 | .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode), |
@@ -1442,6 +1487,374 @@ static iBool checkAcceptMods_InputWidget_(const iInputWidget *d, int mods) { | |||
1442 | return mods == 0; | 1487 | return mods == 0; |
1443 | } | 1488 | } |
1444 | 1489 | ||
1490 | enum iEventResult { | ||
1491 | ignored_EventResult = 0, /* event was not processed */ | ||
1492 | false_EventResult = 1, /* event was processed but other widgets can still process it, too*/ | ||
1493 | true_EventResult = 2, /* event was processed and should not be passed on */ | ||
1494 | }; | ||
1495 | |||
1496 | static void markWordAtCursor_InputWidget_(iInputWidget *d) { | ||
1497 | d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); | ||
1498 | extendRange_InputWidget_(d, &d->mark.start, -1); | ||
1499 | extendRange_InputWidget_(d, &d->mark.end, +1); | ||
1500 | d->initialMark = d->mark; | ||
1501 | } | ||
1502 | |||
1503 | static void showClipMenu_(iInt2 coord) { | ||
1504 | iWidget *clipMenu = findWidget_App("clipmenu"); | ||
1505 | if (isVisible_Widget(clipMenu)) { | ||
1506 | closeMenu_Widget(clipMenu); | ||
1507 | } | ||
1508 | else { | ||
1509 | openMenuFlags_Widget(clipMenu, coord, iFalse); | ||
1510 | } | ||
1511 | } | ||
1512 | |||
1513 | static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | ||
1514 | iWidget *w = as_Widget(d); | ||
1515 | if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { | ||
1516 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | ||
1517 | const iInt2 inner = windowToInner_Widget(w, coord); | ||
1518 | setCursor_Window(get_Window(), | ||
1519 | inner.x >= 2 * gap_UI + d->leftPadding && | ||
1520 | inner.x < width_Widget(w) - d->rightPadding | ||
1521 | ? SDL_SYSTEM_CURSOR_IBEAM | ||
1522 | : SDL_SYSTEM_CURSOR_ARROW); | ||
1523 | } | ||
1524 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && | ||
1525 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | ||
1526 | showClipMenu_(mouseCoord_Window(get_Window(), ev->button.which)); | ||
1527 | return iTrue; | ||
1528 | } | ||
1529 | switch (processEvent_Click(&d->click, ev)) { | ||
1530 | case none_ClickResult: | ||
1531 | break; | ||
1532 | case started_ClickResult: { | ||
1533 | setFocus_Widget(w); | ||
1534 | const iInt2 oldCursor = d->cursor; | ||
1535 | setCursor_InputWidget(d, coordCursor_InputWidget_(d, pos_Click(&d->click))); | ||
1536 | if (keyMods_Sym(modState_Keys()) == KMOD_SHIFT) { | ||
1537 | d->mark = d->initialMark = (iRanges){ | ||
1538 | cursorToIndex_InputWidget_(d, oldCursor), | ||
1539 | cursorToIndex_InputWidget_(d, d->cursor) | ||
1540 | }; | ||
1541 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1542 | } | ||
1543 | else { | ||
1544 | iZap(d->mark); | ||
1545 | iZap(d->initialMark); | ||
1546 | d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); | ||
1547 | if (d->click.count == 2) { | ||
1548 | d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; | ||
1549 | markWordAtCursor_InputWidget_(d); | ||
1550 | refresh_Widget(w); | ||
1551 | } | ||
1552 | if (d->click.count == 3) { | ||
1553 | selectAll_InputWidget(d); | ||
1554 | } | ||
1555 | } | ||
1556 | refresh_Widget(d); | ||
1557 | return true_EventResult; | ||
1558 | } | ||
1559 | case aborted_ClickResult: | ||
1560 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1561 | return true_EventResult; | ||
1562 | case drag_ClickResult: | ||
1563 | d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1564 | showCursor_InputWidget_(d); | ||
1565 | if (~d->inFlags & isMarking_InputWidgetFlag) { | ||
1566 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1567 | d->mark.start = cursorToIndex_InputWidget_(d, d->cursor); | ||
1568 | } | ||
1569 | d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); | ||
1570 | if (d->inFlags & markWords_InputWidgetFlag) { | ||
1571 | const iBool isFwd = d->mark.end >= d->mark.start; | ||
1572 | extendRange_InputWidget_(d, &d->mark.end, isFwd ? +1 : -1); | ||
1573 | d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; | ||
1574 | } | ||
1575 | refresh_Widget(w); | ||
1576 | return true_EventResult; | ||
1577 | case finished_ClickResult: | ||
1578 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1579 | return true_EventResult; | ||
1580 | } | ||
1581 | if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { | ||
1582 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | ||
1583 | if (contains_Click(&d->click, coord)) { | ||
1584 | return true_EventResult; | ||
1585 | } | ||
1586 | } | ||
1587 | return ignored_EventResult; | ||
1588 | } | ||
1589 | |||
1590 | static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { | ||
1591 | /* Clamp to the bounds so the cursor doesn't wrap at the ends. */ | ||
1592 | iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); | ||
1593 | bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; | ||
1594 | return coordCursor_InputWidget_(d, min_I2(bottomRight_Rect(bounds), | ||
1595 | max_I2(coord, topLeft_Rect(bounds)))); | ||
1596 | } | ||
1597 | |||
1598 | static iBool isInsideMark_InputWidget_(const iInputWidget *d, size_t pos) { | ||
1599 | const iRanges mark = mark_InputWidget_(d); | ||
1600 | return contains_Range(&mark, pos); | ||
1601 | } | ||
1602 | |||
1603 | static int distanceToPos_InputWidget_(const iInputWidget *d, iInt2 uiCoord, iInt2 textPos) { | ||
1604 | iBool isInside; | ||
1605 | const iInt2 winCoord = cursorToWindowCoord_InputWidget_(d, textPos, &isInside); | ||
1606 | if (!isInside) { | ||
1607 | return INT_MAX; | ||
1608 | } | ||
1609 | return dist_I2(addY_I2(winCoord, lineHeight_Text(d->font) / 2), uiCoord); | ||
1610 | } | ||
1611 | |||
1612 | static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | ||
1613 | iWidget *w = as_Widget(d); | ||
1614 | /* | ||
1615 | + first tap to focus & select all/place cursor | ||
1616 | + focused tap to place cursor | ||
1617 | - drag cursor to move it | ||
1618 | - double-click to select a word | ||
1619 | - drag to move selection handles | ||
1620 | - long-press for context menu: copy, paste, delete, select all, deselect | ||
1621 | - double-click and hold to select words | ||
1622 | - triple-click to select all | ||
1623 | - drag/wheel elsewhere to scroll (contents or overflow), no change in focus | ||
1624 | */ | ||
1625 | // if (ev->type != SDL_MOUSEBUTTONUP && ev->type != SDL_MOUSEBUTTONDOWN && | ||
1626 | // ev->type != SDL_MOUSEWHEEL && ev->type != SDL_MOUSEMOTION && | ||
1627 | // !(ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) && | ||
1628 | // !(ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { | ||
1629 | // return ignored_EventResult; | ||
1630 | // } | ||
1631 | if (isFocused_Widget(w)) { | ||
1632 | if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) { | ||
1633 | d->lastTapTime = d->tapStartTime; | ||
1634 | d->tapStartTime = SDL_GetTicks(); | ||
1635 | const int tapDist = dist_I2(latestPosition_Touch(), d->lastTapPos); | ||
1636 | d->lastTapPos = latestPosition_Touch(); | ||
1637 | // printf("[%p] tap start time: %u (%u) %d\n", w, d->tapStartTime, d->tapStartTime - d->lastTapTime, tapDist); | ||
1638 | if (d->tapStartTime - d->lastTapTime < 400 && tapDist < gap_UI * 4) { | ||
1639 | d->tapCount++; | ||
1640 | // printf("[%p] >> tap count: %d\n", w, d->tapCount); | ||
1641 | } | ||
1642 | else { | ||
1643 | d->tapCount = 0; | ||
1644 | } | ||
1645 | if (!isEmpty_Range(&d->mark)) { | ||
1646 | const int dist[2] = { | ||
1647 | distanceToPos_InputWidget_(d, latestPosition_Touch(), | ||
1648 | indexToCursor_InputWidget_(d, d->mark.start)), | ||
1649 | distanceToPos_InputWidget_(d, latestPosition_Touch(), | ||
1650 | indexToCursor_InputWidget_(d, d->mark.end)) | ||
1651 | }; | ||
1652 | if (dist[0] < dist[1]) { | ||
1653 | // printf("[%p] begin marker start drag\n", w); | ||
1654 | d->inFlags |= dragMarkerStart_InputWidgetFlag; | ||
1655 | } | ||
1656 | else { | ||
1657 | // printf("[%p] begin marker end drag\n", w); | ||
1658 | d->inFlags |= dragMarkerEnd_InputWidgetFlag; | ||
1659 | } | ||
1660 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1661 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
1662 | } | ||
1663 | else { | ||
1664 | const int dist = distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor); | ||
1665 | // printf("[%p] tap dist: %d\n", w, dist); | ||
1666 | if (dist < gap_UI * 10) { | ||
1667 | // printf("[%p] begin cursor drag\n", w); | ||
1668 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
1669 | d->inFlags |= dragCursor_InputWidgetFlag; | ||
1670 | // d->inFlags |= touchBehavior_InputWidgetFlag; | ||
1671 | // setMouseGrab_Widget(w); | ||
1672 | // return iTrue; | ||
1673 | } | ||
1674 | } | ||
1675 | // if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { | ||
1676 | // d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1677 | // showCursor_InputWidget_(d); | ||
1678 | // } | ||
1679 | return true_EventResult; | ||
1680 | } | ||
1681 | } | ||
1682 | #if 0 | ||
1683 | else if (isFocused_Widget(w)) { | ||
1684 | if (ev->type == SDL_MOUSEMOTION) { | ||
1685 | if (~d->inFlags & touchBehavior_InputWidgetFlag) { | ||
1686 | const iInt2 curPos = relativeCursorCoord_InputWidget_(d); | ||
1687 | const iInt2 relClick = sub_I2(pos_Click(&d->click), | ||
1688 | topLeft_Rect(contentBounds_InputWidget_(d))); | ||
1689 | if (dist_I2(curPos, relClick) < gap_UI * 8) { | ||
1690 | // printf("tap on cursor!\n"); | ||
1691 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
1692 | d->inFlags |= touchBehavior_InputWidgetFlag; | ||
1693 | // printf("[Input] begin cursor drag\n"); | ||
1694 | setMouseGrab_Widget(w); | ||
1695 | return iTrue; | ||
1696 | } | ||
1697 | } | ||
1698 | else if (ev->motion.x > 0 && ev->motion.y > 0) { | ||
1699 | // printf("[Input] cursor being dragged\n"); | ||
1700 | iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); | ||
1701 | bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; | ||
1702 | iInt2 mpos = init_I2(ev->motion.x, ev->motion.y); | ||
1703 | mpos = min_I2(bottomRight_Rect(bounds), max_I2(mpos, topLeft_Rect(bounds))); | ||
1704 | d->cursor = coordCursor_InputWidget_(d, mpos); | ||
1705 | showCursor_InputWidget_(d); | ||
1706 | refresh_Widget(w); | ||
1707 | return iTrue; | ||
1708 | } | ||
1709 | } | ||
1710 | if (d->inFlags & touchBehavior_InputWidgetFlag) { | ||
1711 | if (ev->type == SDL_MOUSEBUTTONUP || | ||
1712 | (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { | ||
1713 | d->inFlags &= ~touchBehavior_InputWidgetFlag; | ||
1714 | setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); | ||
1715 | setMouseGrab_Widget(NULL); | ||
1716 | // printf("[Input] touch ends\n"); | ||
1717 | return iFalse; | ||
1718 | } | ||
1719 | } | ||
1720 | } | ||
1721 | #endif | ||
1722 | #if 1 | ||
1723 | if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && | ||
1724 | ev->button.button == SDL_BUTTON_RIGHT && contains_Widget(w, latestPosition_Touch())) { | ||
1725 | if (ev->type == SDL_MOUSEBUTTONDOWN) { | ||
1726 | /*if (isFocused_Widget(w)) { | ||
1727 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1728 | d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); | ||
1729 | markWordAtCursor_InputWidget_(d); | ||
1730 | refresh_Widget(d); | ||
1731 | return true_EventResult; | ||
1732 | }*/ | ||
1733 | setFocus_Widget(w); | ||
1734 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1735 | d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); | ||
1736 | markWordAtCursor_InputWidget_(d); | ||
1737 | d->cursor = indexToCursor_InputWidget_(d, d->mark.end); | ||
1738 | refresh_Widget(d); | ||
1739 | } | ||
1740 | return true_EventResult; | ||
1741 | } | ||
1742 | switch (processEvent_Click(&d->click, ev)) { | ||
1743 | case none_ClickResult: | ||
1744 | break; | ||
1745 | case started_ClickResult: { | ||
1746 | // printf("[%p] started\n", w); | ||
1747 | /* | ||
1748 | const iInt2 curPos = relativeCursorCoord_InputWidget_(d); | ||
1749 | const iInt2 relClick = sub_I2(pos_Click(&d->click), | ||
1750 | topLeft_Rect(contentBounds_InputWidget_(d))); | ||
1751 | if (dist_I2(curPos, relClick) < gap_UI * 8) { | ||
1752 | printf("tap on cursor!\n"); | ||
1753 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
1754 | } | ||
1755 | else { | ||
1756 | printf("tap elsewhere\n"); | ||
1757 | }*/ | ||
1758 | return true_EventResult; | ||
1759 | } | ||
1760 | case drag_ClickResult: | ||
1761 | // printf("[%p] drag %d,%d\n", w, pos_Click(&d->click).x, pos_Click(&d->click).y); | ||
1762 | if (d->inFlags & dragCursor_InputWidgetFlag) { | ||
1763 | iZap(d->mark); | ||
1764 | d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1765 | showCursor_InputWidget_(d); | ||
1766 | refresh_Widget(w); | ||
1767 | } | ||
1768 | else if (d->inFlags & dragMarkerStart_InputWidgetFlag) { | ||
1769 | d->mark.start = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); | ||
1770 | refresh_Widget(w); | ||
1771 | } | ||
1772 | else if (d->inFlags & dragMarkerEnd_InputWidgetFlag) { | ||
1773 | d->mark.end = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); | ||
1774 | refresh_Widget(w); | ||
1775 | } | ||
1776 | return true_EventResult; | ||
1777 | // printf("[%p] aborted\n", w); | ||
1778 | // d->inFlags &= ~touchBehavior_InputWidgetFlag; | ||
1779 | // setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); | ||
1780 | // return true_EventResult; | ||
1781 | case finished_ClickResult: | ||
1782 | case aborted_ClickResult: { | ||
1783 | // printf("[%p] ended\n", w); | ||
1784 | uint32_t tapElapsed = SDL_GetTicks() - d->tapStartTime; | ||
1785 | // printf("tapElapsed: %u\n", tapElapsed); | ||
1786 | if (!isFocused_Widget(w)) { | ||
1787 | setFocus_Widget(w); | ||
1788 | d->lastTapPos = latestPosition_Touch(); | ||
1789 | d->tapStartTime = SDL_GetTicks(); | ||
1790 | d->tapCount = 0; | ||
1791 | d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1792 | showCursor_InputWidget_(d); | ||
1793 | } | ||
1794 | else if (!isEmpty_Range(&d->mark) && !isMoved_Click(&d->click)) { | ||
1795 | if (isInsideMark_InputWidget_(d, cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, latestPosition_Touch())))) { | ||
1796 | showClipMenu_(latestPosition_Touch()); | ||
1797 | } | ||
1798 | else { | ||
1799 | iZap(d->mark); | ||
1800 | d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1801 | } | ||
1802 | } | ||
1803 | else if (SDL_GetTicks() - d->lastTapTime > 1000 && | ||
1804 | d->tapCount == 0 && isEmpty_Range(&d->mark) && !isMoved_Click(&d->click) && | ||
1805 | distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor) < gap_UI * 5) { | ||
1806 | showClipMenu_(latestPosition_Touch()); | ||
1807 | } | ||
1808 | else { | ||
1809 | if (~d->inFlags & isMarking_InputWidgetFlag) { | ||
1810 | iZap(d->mark); | ||
1811 | d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1812 | } | ||
1813 | } | ||
1814 | if (d->inFlags & (dragCursor_InputWidgetFlag | dragMarkerStart_InputWidgetFlag | | ||
1815 | dragMarkerEnd_InputWidgetFlag)) { | ||
1816 | // printf("[%p] finished cursor/marker drag\n", w); | ||
1817 | d->inFlags &= ~(dragCursor_InputWidgetFlag | | ||
1818 | dragMarkerStart_InputWidgetFlag | | ||
1819 | dragMarkerEnd_InputWidgetFlag); | ||
1820 | setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); | ||
1821 | } | ||
1822 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1823 | showCursor_InputWidget_(d); | ||
1824 | refresh_Widget(w); | ||
1825 | #if 0 | ||
1826 | d->inFlags &= ~touchBehavior_InputWidgetFlag; | ||
1827 | if (flags_Widget(w) & touchDrag_WidgetFlag) { | ||
1828 | setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); | ||
1829 | return true_EventResult; | ||
1830 | } | ||
1831 | if (!isMoved_Click(&d->click)) { | ||
1832 | if (!isFocused_Widget(w)) { | ||
1833 | setFocus_Widget(w); | ||
1834 | if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { | ||
1835 | d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1836 | showCursor_InputWidget_(d); | ||
1837 | } | ||
1838 | } | ||
1839 | else { | ||
1840 | iZap(d->mark); | ||
1841 | d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1842 | showCursor_InputWidget_(d); | ||
1843 | } | ||
1844 | } | ||
1845 | #endif | ||
1846 | return true_EventResult; | ||
1847 | } | ||
1848 | } | ||
1849 | #endif | ||
1850 | // if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && | ||
1851 | // contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | ||
1852 | // /* Eat all mouse clicks on the widget. */ | ||
1853 | // return true_EventResult; | ||
1854 | // } | ||
1855 | return ignored_EventResult; | ||
1856 | } | ||
1857 | |||
1445 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 1858 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { |
1446 | iWidget *w = as_Widget(d); | 1859 | iWidget *w = as_Widget(d); |
1447 | /* Resize according to width immediately. */ | 1860 | /* Resize according to width immediately. */ |
@@ -1486,23 +1899,35 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1486 | paste_InputWidget_(d); | 1899 | paste_InputWidget_(d); |
1487 | return iTrue; | 1900 | return iTrue; |
1488 | } | 1901 | } |
1902 | else if (isCommand_UserEvent(ev, "input.undo") && isEditing_InputWidget_(d)) { | ||
1903 | if (popUndo_InputWidget_(d)) { | ||
1904 | refresh_Widget(w); | ||
1905 | contentsWereChanged_InputWidget_(d); | ||
1906 | } | ||
1907 | return iTrue; | ||
1908 | } | ||
1909 | else if (isCommand_UserEvent(ev, "input.selectall") && isEditing_InputWidget_(d)) { | ||
1910 | selectAll_InputWidget(d); | ||
1911 | return iTrue; | ||
1912 | } | ||
1489 | else if (isCommand_UserEvent(ev, "theme.changed")) { | 1913 | else if (isCommand_UserEvent(ev, "theme.changed")) { |
1490 | if (d->buffered) { | 1914 | if (d->buffered) { |
1491 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 1915 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
1492 | } | 1916 | } |
1493 | return iFalse; | 1917 | return iFalse; |
1494 | } | 1918 | } |
1495 | else if (isCommand_UserEvent(ev, "keyboard.changed")) { | 1919 | /* TODO: Scroll to keep widget visible when keyboard appears. */ |
1496 | if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { | 1920 | // else if (isCommand_UserEvent(ev, "keyboard.changed")) { |
1497 | iRect rect = bounds_Widget(w); | 1921 | // if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { |
1498 | rect.pos.y -= value_Anim(&get_Window()->rootOffset); | 1922 | // iRect rect = bounds_Widget(w); |
1499 | const iInt2 visRoot = visibleSize_Root(w->root); | 1923 | // rect.pos.y -= value_Anim(&get_Window()->rootOffset); |
1500 | if (bottom_Rect(rect) > visRoot.y) { | 1924 | // const iInt2 visRoot = visibleSize_Root(w->root); |
1501 | setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); | 1925 | // if (bottom_Rect(rect) > visRoot.y) { |
1502 | } | 1926 | // setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); |
1503 | } | 1927 | // } |
1504 | return iFalse; | 1928 | // } |
1505 | } | 1929 | // return iFalse; |
1930 | // } | ||
1506 | else if (isCommand_UserEvent(ev, "text.insert")) { | 1931 | else if (isCommand_UserEvent(ev, "text.insert")) { |
1507 | pushUndo_InputWidget_(d); | 1932 | pushUndo_InputWidget_(d); |
1508 | deleteMarked_InputWidget_(d); | 1933 | deleteMarked_InputWidget_(d); |
@@ -1524,16 +1949,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1524 | copy_InputWidget_(d, iFalse); | 1949 | copy_InputWidget_(d, iFalse); |
1525 | return iTrue; | 1950 | return iTrue; |
1526 | } | 1951 | } |
1527 | if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { | ||
1528 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | ||
1529 | const iInt2 inner = windowToInner_Widget(w, coord); | ||
1530 | setCursor_Window(get_Window(), | ||
1531 | inner.x >= 2 * gap_UI + d->leftPadding && | ||
1532 | inner.x < width_Widget(w) - d->rightPadding | ||
1533 | ? SDL_SYSTEM_CURSOR_IBEAM | ||
1534 | : SDL_SYSTEM_CURSOR_ARROW); | ||
1535 | } | ||
1536 | if (ev->type == SDL_MOUSEWHEEL && contains_Widget(w, coord_MouseWheelEvent(&ev->wheel))) { | 1952 | if (ev->type == SDL_MOUSEWHEEL && contains_Widget(w, coord_MouseWheelEvent(&ev->wheel))) { |
1953 | if (numWrapLines_InputWidget_(d) <= size_Range(&d->visWrapLines)) { | ||
1954 | return ignored_EventResult; | ||
1955 | } | ||
1537 | const int lineHeight = lineHeight_Text(d->font); | 1956 | const int lineHeight = lineHeight_Text(d->font); |
1538 | if (isPerPixel_MouseWheelEvent(&ev->wheel)) { | 1957 | if (isPerPixel_MouseWheelEvent(&ev->wheel)) { |
1539 | d->wheelAccum -= ev->wheel.y; | 1958 | d->wheelAccum -= ev->wheel.y; |
@@ -1551,87 +1970,24 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1551 | lastLine_InputWidget_(d)->wrapLines.end - d->visWrapLines.end); | 1970 | lastLine_InputWidget_(d)->wrapLines.end - d->visWrapLines.end); |
1552 | if (!lineDelta) d->wheelAccum = 0; | 1971 | if (!lineDelta) d->wheelAccum = 0; |
1553 | } | 1972 | } |
1554 | d->wheelAccum -= lineDelta * lineHeight; | 1973 | if (lineDelta) { |
1555 | d->visWrapLines.start += lineDelta; | 1974 | d->wheelAccum -= lineDelta * lineHeight; |
1556 | d->visWrapLines.end += lineDelta; | 1975 | d->visWrapLines.start += lineDelta; |
1557 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 1976 | d->visWrapLines.end += lineDelta; |
1558 | refresh_Widget(d); | 1977 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
1559 | return iTrue; | ||
1560 | } | ||
1561 | switch (processEvent_Click(&d->click, ev)) { | ||
1562 | case none_ClickResult: | ||
1563 | break; | ||
1564 | case started_ClickResult: { | ||
1565 | setFocus_Widget(w); | ||
1566 | const iInt2 oldCursor = d->cursor; | ||
1567 | setCursor_InputWidget(d, coordCursor_InputWidget_(d, pos_Click(&d->click))); | ||
1568 | if (keyMods_Sym(modState_Keys()) == KMOD_SHIFT) { | ||
1569 | d->mark = d->initialMark = (iRanges){ | ||
1570 | cursorToIndex_InputWidget_(d, oldCursor), | ||
1571 | cursorToIndex_InputWidget_(d, d->cursor) | ||
1572 | }; | ||
1573 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1574 | } | ||
1575 | else { | ||
1576 | iZap(d->mark); | ||
1577 | iZap(d->initialMark); | ||
1578 | d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); | ||
1579 | if (d->click.count == 2) { | ||
1580 | d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; | ||
1581 | d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); | ||
1582 | extendRange_InputWidget_(d, &d->mark.start, -1); | ||
1583 | extendRange_InputWidget_(d, &d->mark.end, +1); | ||
1584 | d->initialMark = d->mark; | ||
1585 | refresh_Widget(w); | ||
1586 | } | ||
1587 | if (d->click.count == 3) { | ||
1588 | selectAll_InputWidget(d); | ||
1589 | } | ||
1590 | } | ||
1591 | refresh_Widget(d); | 1978 | refresh_Widget(d); |
1592 | return iTrue; | 1979 | return true_EventResult; |
1593 | } | 1980 | } |
1594 | case aborted_ClickResult: | 1981 | return false_EventResult; |
1595 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1596 | return iTrue; | ||
1597 | case drag_ClickResult: | ||
1598 | d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1599 | showCursor_InputWidget_(d); | ||
1600 | if (~d->inFlags & isMarking_InputWidgetFlag) { | ||
1601 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1602 | d->mark.start = cursorToIndex_InputWidget_(d, d->cursor); | ||
1603 | } | ||
1604 | d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); | ||
1605 | if (d->inFlags & markWords_InputWidgetFlag) { | ||
1606 | const iBool isFwd = d->mark.end >= d->mark.start; | ||
1607 | extendRange_InputWidget_(d, &d->mark.end, isFwd ? +1 : -1); | ||
1608 | d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; | ||
1609 | } | ||
1610 | refresh_Widget(w); | ||
1611 | return iTrue; | ||
1612 | case finished_ClickResult: | ||
1613 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1614 | return iTrue; | ||
1615 | } | 1982 | } |
1616 | if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { | 1983 | /* Click behavior depends on device type. */ { |
1617 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | 1984 | const int mbResult = (deviceType_App() == desktop_AppDeviceType |
1618 | if (contains_Click(&d->click, coord)) { | 1985 | ? processPointerEvents_InputWidget_(d, ev) |
1619 | return iTrue; | 1986 | : processTouchEvents_InputWidget_(d, ev)); |
1987 | if (mbResult) { | ||
1988 | return mbResult >> 1; | ||
1620 | } | 1989 | } |
1621 | } | 1990 | } |
1622 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && | ||
1623 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | ||
1624 | iWidget *clipMenu = findWidget_App("clipmenu"); | ||
1625 | if (isVisible_Widget(clipMenu)) { | ||
1626 | closeMenu_Widget(clipMenu); | ||
1627 | } | ||
1628 | else { | ||
1629 | openMenuFlags_Widget(clipMenu, | ||
1630 | mouseCoord_Window(get_Window(), ev->button.which), | ||
1631 | iFalse); | ||
1632 | } | ||
1633 | return iTrue; | ||
1634 | } | ||
1635 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { | 1991 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { |
1636 | return iTrue; | 1992 | return iTrue; |
1637 | } | 1993 | } |
@@ -1833,6 +2189,13 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1833 | return iTrue; | 2189 | return iTrue; |
1834 | } | 2190 | } |
1835 | case SDLK_TAB: | 2191 | case SDLK_TAB: |
2192 | if (mods == (KMOD_ALT | KMOD_SHIFT)) { | ||
2193 | pushUndo_InputWidget_(d); | ||
2194 | deleteMarked_InputWidget_(d); | ||
2195 | insertChar_InputWidget_(d, '\t'); | ||
2196 | contentsWereChanged_InputWidget_(d); | ||
2197 | return iTrue; | ||
2198 | } | ||
1836 | /* Allow focus switching. */ | 2199 | /* Allow focus switching. */ |
1837 | return processEvent_Widget(as_Widget(d), ev); | 2200 | return processEvent_Widget(as_Widget(d), ev); |
1838 | case SDLK_UP: | 2201 | case SDLK_UP: |
@@ -1878,6 +2241,8 @@ struct Impl_MarkPainter { | |||
1878 | const iInputLine * line; | 2241 | const iInputLine * line; |
1879 | iInt2 pos; | 2242 | iInt2 pos; |
1880 | iRanges mark; | 2243 | iRanges mark; |
2244 | iRect firstMarkRect; | ||
2245 | iRect lastMarkRect; | ||
1881 | }; | 2246 | }; |
1882 | 2247 | ||
1883 | static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, | 2248 | static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, |
@@ -1916,7 +2281,11 @@ static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int or | |||
1916 | } | 2281 | } |
1917 | rect.size.x = iMax(gap_UI / 3, rect.size.x); | 2282 | rect.size.x = iMax(gap_UI / 3, rect.size.x); |
1918 | mp->pos.y += lineHeight_Text(mp->d->font); | 2283 | mp->pos.y += lineHeight_Text(mp->d->font); |
1919 | fillRect_Paint(mp->paint, rect, uiMarked_ColorId); | 2284 | fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId); |
2285 | if (deviceType_App() != desktop_AppDeviceType) { | ||
2286 | if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect; | ||
2287 | mp->lastMarkRect = rect; | ||
2288 | } | ||
1920 | return iTrue; | 2289 | return iTrue; |
1921 | } | 2290 | } |
1922 | 2291 | ||
@@ -1924,8 +2293,9 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1924 | const iWidget *w = constAs_Widget(d); | 2293 | const iWidget *w = constAs_Widget(d); |
1925 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); | 2294 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); |
1926 | iBool isHint = isHintVisible_InputWidget_(d); | 2295 | iBool isHint = isHintVisible_InputWidget_(d); |
1927 | const iBool isFocused = isFocused_Widget(w); | 2296 | const iBool isFocused = isFocused_Widget(w); |
1928 | const iBool isHover = isHover_Widget(w) && | 2297 | const iBool isHover = deviceType_App() == desktop_AppDeviceType && |
2298 | isHover_Widget(w) && | ||
1929 | contains_InputWidget_(d, mouseCoord_Window(get_Window(), 0)); | 2299 | contains_InputWidget_(d, mouseCoord_Window(get_Window(), 0)); |
1930 | if (d->inFlags & needUpdateBuffer_InputWidgetFlag) { | 2300 | if (d->inFlags & needUpdateBuffer_InputWidgetFlag) { |
1931 | updateBuffered_InputWidget_(iConstCast(iInputWidget *, d)); | 2301 | updateBuffered_InputWidget_(iConstCast(iInputWidget *, d)); |
@@ -1955,6 +2325,7 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1955 | }; | 2325 | }; |
1956 | const iRangei visLines = visibleLineRange_InputWidget_(d); | 2326 | const iRangei visLines = visibleLineRange_InputWidget_(d); |
1957 | const int visLineOffsetY = visLineOffsetY_InputWidget_(d); | 2327 | const int visLineOffsetY = visLineOffsetY_InputWidget_(d); |
2328 | iRect markerRects[2] = { zero_Rect(), zero_Rect() }; | ||
1958 | /* If buffered, just draw the buffered copy. */ | 2329 | /* If buffered, just draw the buffered copy. */ |
1959 | if (d->buffered && !isFocused) { | 2330 | if (d->buffered && !isFocused) { |
1960 | /* Most input widgets will use this, since only one is focused at a time. */ | 2331 | /* Most input widgets will use this, since only one is focused at a time. */ |
@@ -1970,7 +2341,7 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1970 | .paint = &p, | 2341 | .paint = &p, |
1971 | .d = d, | 2342 | .d = d, |
1972 | .contentBounds = contentBounds, | 2343 | .contentBounds = contentBounds, |
1973 | .mark = mark_InputWidget_(d) | 2344 | .mark = mark_InputWidget_(d), |
1974 | }; | 2345 | }; |
1975 | wrapText.context = ▮ | 2346 | wrapText.context = ▮ |
1976 | wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */ | 2347 | wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */ |
@@ -1981,11 +2352,14 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1981 | marker.pos = drawPos; | 2352 | marker.pos = drawPos; |
1982 | addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */ | 2353 | addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */ |
1983 | } | 2354 | } |
2355 | markerRects[0] = marker.firstMarkRect; | ||
2356 | markerRects[1] = marker.lastMarkRect; | ||
1984 | wrapText.wrapFunc = NULL; | 2357 | wrapText.wrapFunc = NULL; |
1985 | wrapText.context = NULL; | 2358 | wrapText.context = NULL; |
1986 | } | 2359 | } |
1987 | /* Draw the insertion point. */ | 2360 | /* Draw the insertion point. */ |
1988 | if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y)) { | 2361 | if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) && |
2362 | (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) { | ||
1989 | iInt2 curSize; | 2363 | iInt2 curSize; |
1990 | iRangecc cursorChar = iNullRange; | 2364 | iRangecc cursorChar = iNullRange; |
1991 | int visWrapsAbove = 0; | 2365 | int visWrapsAbove = 0; |
@@ -1998,8 +2372,8 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1998 | cursorChar.start = charPos_InputWidget_(d, d->cursor); | 2372 | cursorChar.start = charPos_InputWidget_(d, d->cursor); |
1999 | iChar ch = 0; | 2373 | iChar ch = 0; |
2000 | int n = decodeBytes_MultibyteChar(cursorChar.start, | 2374 | int n = decodeBytes_MultibyteChar(cursorChar.start, |
2001 | constEnd_String(&constCursorLine_InputWidget_(d)->text), | 2375 | constEnd_String(&constCursorLine_InputWidget_(d)->text), |
2002 | &ch); | 2376 | &ch); |
2003 | cursorChar.end = cursorChar.start + iMax(n, 0); | 2377 | cursorChar.end = cursorChar.start + iMax(n, 0); |
2004 | if (ch) { | 2378 | if (ch) { |
2005 | if (d->inFlags & isSensitive_InputWidgetFlag) { | 2379 | if (d->inFlags & isSensitive_InputWidgetFlag) { |
@@ -2033,6 +2407,11 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
2033 | } | 2407 | } |
2034 | } | 2408 | } |
2035 | unsetClip_Paint(&p); | 2409 | unsetClip_Paint(&p); |
2410 | if (!isEmpty_Rect(markerRects[0])) { | ||
2411 | for (int i = 0; i < 2; ++i) { | ||
2412 | drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId); | ||
2413 | } | ||
2414 | } | ||
2036 | drawChildren_Widget(w); | 2415 | drawChildren_Widget(w); |
2037 | } | 2416 | } |
2038 | 2417 | ||