summaryrefslogtreecommitdiff
path: root/src/ui/inputwidget.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r--src/ui/inputwidget.c665
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
180enum iInputWidgetFlag { 181enum 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
462static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { 471static 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
488static 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
496static 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
515static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) {
516 return relativeCoordOnLine_InputWidget_(d, d->cursor);
517}
518
470static void updateVisible_InputWidget_(iInputWidget *d) { 519static 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
497static void showCursor_InputWidget_(iInputWidget *d) { 548static void showCursor_InputWidget_(iInputWidget *d) {
@@ -542,8 +593,10 @@ static int contentHeight_InputWidget_(const iInputWidget *d) {
542} 593}
543 594
544static void updateTextInputRect_InputWidget_(const iInputWidget *d) { 595static 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
549static void updateMetrics_InputWidget_(iInputWidget *d) { 602static 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
811int font_InputWidget(const iInputWidget *d) {
812 return d->font;
813}
814
756iInputWidgetContentPadding contentPadding_InputWidget(const iInputWidget *d) { 815iInputWidgetContentPadding 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
766void setLineLimits_InputWidget(iInputWidget *d, int minLines, int maxLines) { 825void 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
825static 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
842static void updateBuffered_InputWidget_(iInputWidget *d) { 885static 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
1490enum 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
1496static 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
1503static 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
1513static 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
1590static 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
1598static iBool isInsideMark_InputWidget_(const iInputWidget *d, size_t pos) {
1599 const iRanges mark = mark_InputWidget_(d);
1600 return contains_Range(&mark, pos);
1601}
1602
1603static 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
1612static 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
1445static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 1858static 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
1883static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, 2248static 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 = &marker; 2346 wrapText.context = &marker;
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