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.c563
1 files changed, 441 insertions, 122 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index ad630223..12eb490d 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -179,19 +179,23 @@ static void deinit_InputUndo_(iInputUndo *d) {
179} 179}
180 180
181enum iInputWidgetFlag { 181enum iInputWidgetFlag {
182 isSensitive_InputWidgetFlag = iBit(1), 182 isSensitive_InputWidgetFlag = iBit(1),
183 isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ 183 isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */
184 enterPressed_InputWidgetFlag = iBit(3), 184 enterPressed_InputWidgetFlag = iBit(3),
185 selectAllOnFocus_InputWidgetFlag = iBit(4), 185 selectAllOnFocus_InputWidgetFlag = iBit(4),
186 notifyEdits_InputWidgetFlag = iBit(5), 186 notifyEdits_InputWidgetFlag = iBit(5),
187 eatEscape_InputWidgetFlag = iBit(6), 187 eatEscape_InputWidgetFlag = iBit(6),
188 isMarking_InputWidgetFlag = iBit(7), 188 isMarking_InputWidgetFlag = iBit(7),
189 markWords_InputWidgetFlag = iBit(8), 189 markWords_InputWidgetFlag = iBit(8),
190 needUpdateBuffer_InputWidgetFlag = iBit(9), 190 needUpdateBuffer_InputWidgetFlag = iBit(9),
191 enterKeyEnabled_InputWidgetFlag = iBit(10), 191 enterKeyEnabled_InputWidgetFlag = iBit(10),
192 lineBreaksEnabled_InputWidgetFlag= iBit(11), 192 lineBreaksEnabled_InputWidgetFlag = iBit(11),
193 needBackup_InputWidgetFlag = iBit(12), 193 needBackup_InputWidgetFlag = iBit(12),
194 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),
195}; 199};
196 200
197/*----------------------------------------------------------------------------------------------*/ 201/*----------------------------------------------------------------------------------------------*/
@@ -217,6 +221,10 @@ struct Impl_InputWidget {
217 iArray undoStack; 221 iArray undoStack;
218 int font; 222 int font;
219 iClick click; 223 iClick click;
224 uint32_t tapStartTime;
225 uint32_t lastTapTime;
226 iInt2 lastTapPos;
227 int tapCount;
220 int wheelAccum; 228 int wheelAccum;
221 int cursorVis; 229 int cursorVis;
222 uint32_t timer; 230 uint32_t timer;
@@ -460,14 +468,18 @@ static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) {
460 }; 468 };
461} 469}
462 470
463static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { 471static iInt2 relativeCoord_InputWidget_(const iInputWidget *d, iInt2 pos) {
464 /* Relative to the start of the line on which the cursor is. */ 472 /* Relative to the start of the line on which the cursor is. */
465 iWrapText wt = wrap_InputWidget_(d, d->cursor.y); 473 iWrapText wt = wrap_InputWidget_(d, pos.y);
466 wt.hitChar = wt.text.start + d->cursor.x; 474 wt.hitChar = wt.text.start + pos.x;
467 measure_WrapText(&wt, d->font); 475 measure_WrapText(&wt, d->font);
468 return wt.hitAdvance_out; 476 return wt.hitAdvance_out;
469} 477}
470 478
479static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) {
480 return relativeCoord_InputWidget_(d, d->cursor);
481}
482
471static void updateVisible_InputWidget_(iInputWidget *d) { 483static void updateVisible_InputWidget_(iInputWidget *d) {
472 const int totalWraps = numWrapLines_InputWidget_(d); 484 const int totalWraps = numWrapLines_InputWidget_(d);
473 const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); 485 const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines);
@@ -632,7 +644,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
632 init_Widget(w); 644 init_Widget(w);
633 d->validator = NULL; 645 d->validator = NULL;
634 d->validatorContext = NULL; 646 d->validatorContext = NULL;
635 setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag | touchDrag_WidgetFlag, iTrue); 647 setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag, iTrue);
636#if defined (iPlatformMobile) 648#if defined (iPlatformMobile)
637 setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); 649 setFlags_Widget(w, extraPadding_WidgetFlag, iTrue);
638#endif 650#endif
@@ -662,6 +674,8 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
662 splitToLines_(&iStringLiteral(""), &d->lines); 674 splitToLines_(&iStringLiteral(""), &d->lines);
663 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ 675 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */
664 init_Click(&d->click, d, SDL_BUTTON_LEFT); 676 init_Click(&d->click, d, SDL_BUTTON_LEFT);
677 d->lastTapTime = 0;
678 d->tapCount = 0;
665 d->wheelAccum = 0; 679 d->wheelAccum = 0;
666 d->timer = 0; 680 d->timer = 0;
667 d->cursorVis = 0; 681 d->cursorVis = 0;
@@ -993,7 +1007,7 @@ void begin_InputWidget(iInputWidget *d) {
993 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; 1007 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end };
994 d->cursor = cursorMax_InputWidget_(d); 1008 d->cursor = cursorMax_InputWidget_(d);
995 } 1009 }
996 else { 1010 else if (~d->inFlags & isMarking_InputWidgetFlag) {
997 iZap(d->mark); 1011 iZap(d->mark);
998 } 1012 }
999 enableEditorKeysInMenus_(iFalse); 1013 enableEditorKeysInMenus_(iFalse);
@@ -1013,9 +1027,10 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
1013 splitToLines_(&d->oldText, &d->lines); 1027 splitToLines_(&d->oldText, &d->lines);
1014 } 1028 }
1015 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 1029 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1030 d->inFlags &= ~isMarking_InputWidgetFlag;
1016 startOrStopCursorTimer_InputWidget_(d, iFalse); 1031 startOrStopCursorTimer_InputWidget_(d, iFalse);
1017 SDL_StopTextInput(); 1032 SDL_StopTextInput();
1018 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); 1033 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse);
1019 const char *id = cstr_String(id_Widget(as_Widget(d))); 1034 const char *id = cstr_String(id_Widget(as_Widget(d)));
1020 if (!*id) id = "_"; 1035 if (!*id) id = "_";
1021 refresh_Widget(w); 1036 refresh_Widget(w);
@@ -1445,88 +1460,31 @@ static iBool checkAcceptMods_InputWidget_(const iInputWidget *d, int mods) {
1445 return mods == 0; 1460 return mods == 0;
1446} 1461}
1447 1462
1448static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 1463enum iEventResult {
1449 iWidget *w = as_Widget(d); 1464 ignored_EventResult = 0, /* event was not processed */
1450 /* Resize according to width immediately. */ 1465 false_EventResult = 1, /* event was processed but other widgets can still process it, too*/
1451 if (d->lastUpdateWidth != w->rect.size.x) { 1466 true_EventResult = 2, /* event was processed and should not be passed on */
1452 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 1467};
1453 if (d->inFlags & isUrl_InputWidgetFlag) { 1468
1454 /* Restore/omit the default scheme if necessary. */ 1469static void markWordAtCursor_InputWidget_(iInputWidget *d) {
1455 setText_InputWidget(d, text_InputWidget(d)); 1470 d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor);
1456 } 1471 extendRange_InputWidget_(d, &d->mark.start, -1);
1457 updateAllLinesAndResizeHeight_InputWidget_(d); 1472 extendRange_InputWidget_(d, &d->mark.end, +1);
1458 d->lastUpdateWidth = w->rect.size.x; 1473 d->initialMark = d->mark;
1459 } 1474}
1460 if (isCommand_Widget(w, ev, "focus.gained")) { 1475
1461 begin_InputWidget(d); 1476static void showClipMenu_(iInt2 coord) {
1462 return iFalse; 1477 iWidget *clipMenu = findWidget_App("clipmenu");
1463 } 1478 if (isVisible_Widget(clipMenu)) {
1464 else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") || 1479 closeMenu_Widget(clipMenu);
1465 isCommand_UserEvent(ev, "window.focus.gained"))) {
1466 startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained"));
1467 d->cursorVis = 1;
1468 refresh_Widget(d);
1469 return iFalse;
1470 }
1471 else if (isCommand_UserEvent(ev, "keyroot.changed")) {
1472 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1473 }
1474 else if (isCommand_UserEvent(ev, "lang.changed")) {
1475 set_String(&d->hint, &d->srcHint);
1476 translate_Lang(&d->hint);
1477 return iFalse;
1478 }
1479 else if (isCommand_Widget(w, ev, "focus.lost")) {
1480 end_InputWidget(d, iTrue);
1481 return iFalse;
1482 }
1483 else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) &&
1484 isEditing_InputWidget_(d)) {
1485 copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut"));
1486 return iTrue;
1487 }
1488 else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) {
1489 paste_InputWidget_(d);
1490 return iTrue;
1491 }
1492 else if (isCommand_UserEvent(ev, "theme.changed")) {
1493 if (d->buffered) {
1494 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1495 }
1496 return iFalse;
1497 }
1498 else if (isCommand_UserEvent(ev, "keyboard.changed")) {
1499 if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) {
1500 iRect rect = bounds_Widget(w);
1501 rect.pos.y -= value_Anim(&get_Window()->rootOffset);
1502 const iInt2 visRoot = visibleSize_Root(w->root);
1503 if (bottom_Rect(rect) > visRoot.y) {
1504 setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250);
1505 }
1506 }
1507 return iFalse;
1508 }
1509 else if (isCommand_UserEvent(ev, "text.insert")) {
1510 pushUndo_InputWidget_(d);
1511 deleteMarked_InputWidget_(d);
1512 insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev)));
1513 contentsWereChanged_InputWidget_(d);
1514 return iTrue;
1515 }
1516 else if (isCommand_Widget(w, ev, "input.backup")) {
1517 if (d->inFlags & needBackup_InputWidgetFlag) {
1518 saveBackup_InputWidget_(d);
1519 }
1520 return iTrue;
1521 }
1522 else if (isMetricsChange_UserEvent(ev)) {
1523 updateMetrics_InputWidget_(d);
1524 // updateLinesAndResize_InputWidget_(d);
1525 } 1480 }
1526 else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { 1481 else {
1527 copy_InputWidget_(d, iFalse); 1482 openMenuFlags_Widget(clipMenu, coord, iFalse);
1528 return iTrue;
1529 } 1483 }
1484}
1485
1486static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1487 iWidget *w = as_Widget(d);
1530 if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { 1488 if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) {
1531 const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); 1489 const iInt2 coord = init_I2(ev->motion.x, ev->motion.y);
1532 const iInt2 inner = windowToInner_Widget(w, coord); 1490 const iInt2 inner = windowToInner_Widget(w, coord);
@@ -1559,10 +1517,15 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1559 d->visWrapLines.start += lineDelta; 1517 d->visWrapLines.start += lineDelta;
1560 d->visWrapLines.end += lineDelta; 1518 d->visWrapLines.end += lineDelta;
1561 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 1519 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1562 refresh_Widget(d); 1520 refresh_Widget(d);
1563 return iTrue; 1521 return true_EventResult;
1564 } 1522 }
1565 return iFalse; 1523 return false_EventResult;
1524 }
1525 if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT &&
1526 contains_Widget(w, init_I2(ev->button.x, ev->button.y))) {
1527 showClipMenu_(mouseCoord_Window(get_Window(), ev->button.which));
1528 return iTrue;
1566 } 1529 }
1567 switch (processEvent_Click(&d->click, ev)) { 1530 switch (processEvent_Click(&d->click, ev)) {
1568 case none_ClickResult: 1531 case none_ClickResult:
@@ -1584,10 +1547,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1584 d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); 1547 d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag);
1585 if (d->click.count == 2) { 1548 if (d->click.count == 2) {
1586 d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; 1549 d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag;
1587 d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); 1550 markWordAtCursor_InputWidget_(d);
1588 extendRange_InputWidget_(d, &d->mark.start, -1);
1589 extendRange_InputWidget_(d, &d->mark.end, +1);
1590 d->initialMark = d->mark;
1591 refresh_Widget(w); 1551 refresh_Widget(w);
1592 } 1552 }
1593 if (d->click.count == 3) { 1553 if (d->click.count == 3) {
@@ -1595,11 +1555,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1595 } 1555 }
1596 } 1556 }
1597 refresh_Widget(d); 1557 refresh_Widget(d);
1598 return iTrue; 1558 return true_EventResult;
1599 } 1559 }
1600 case aborted_ClickResult: 1560 case aborted_ClickResult:
1601 d->inFlags &= ~isMarking_InputWidgetFlag; 1561 d->inFlags &= ~isMarking_InputWidgetFlag;
1602 return iTrue; 1562 return true_EventResult;
1603 case drag_ClickResult: 1563 case drag_ClickResult:
1604 d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); 1564 d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click));
1605 showCursor_InputWidget_(d); 1565 showCursor_InputWidget_(d);
@@ -1614,30 +1574,374 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1614 d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; 1574 d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end;
1615 } 1575 }
1616 refresh_Widget(w); 1576 refresh_Widget(w);
1617 return iTrue; 1577 return true_EventResult;
1618 case finished_ClickResult: 1578 case finished_ClickResult:
1619 d->inFlags &= ~isMarking_InputWidgetFlag; 1579 d->inFlags &= ~isMarking_InputWidgetFlag;
1620 return iTrue; 1580 return true_EventResult;
1621 } 1581 }
1622 if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { 1582 if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) {
1623 const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); 1583 const iInt2 coord = init_I2(ev->motion.x, ev->motion.y);
1624 if (contains_Click(&d->click, coord)) { 1584 if (contains_Click(&d->click, coord)) {
1625 return iTrue; 1585 return true_EventResult;
1626 } 1586 }
1627 } 1587 }
1628 if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && 1588 return ignored_EventResult;
1629 contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { 1589}
1630 iWidget *clipMenu = findWidget_App("clipmenu"); 1590
1631 if (isVisible_Widget(clipMenu)) { 1591static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) {
1632 closeMenu_Widget(clipMenu); 1592 /* Clamp to the bounds so the cursor doesn't wrap at the ends. */
1593 iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2());
1594 bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2;
1595 return coordCursor_InputWidget_(d, min_I2(bottomRight_Rect(bounds),
1596 max_I2(coord, topLeft_Rect(bounds))));
1597}
1598
1599static iBool isInsideMark_InputWidget_(const iInputWidget *d, size_t pos) {
1600 const iRanges mark = mark_InputWidget_(d);
1601 return contains_Range(&mark, pos);
1602}
1603
1604static int distanceToPos_InputWidget_(const iInputWidget *d, iInt2 uiCoord, iInt2 textPos) {
1605 const iInt2 a = addY_I2(relativeCoord_InputWidget_(d, textPos), lineHeight_Text(d->font) / 2);
1606 const iInt2 b = sub_I2(uiCoord, topLeft_Rect(contentBounds_InputWidget_(d)));
1607 return dist_I2(a, b);
1608}
1609
1610static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1611 iWidget *w = as_Widget(d);
1612 /*
1613 + first tap to focus & select all/place cursor
1614 + focused tap to place cursor
1615 - drag cursor to move it
1616 - double-click to select a word
1617 - drag to move selection handles
1618 - long-press for context menu: copy, paste, delete, select all, deselect
1619 - double-click and hold to select words
1620 - triple-click to select all
1621 - drag/wheel elsewhere to scroll (contents or overflow), no change in focus
1622 */
1623// if (ev->type != SDL_MOUSEBUTTONUP && ev->type != SDL_MOUSEBUTTONDOWN &&
1624// ev->type != SDL_MOUSEWHEEL && ev->type != SDL_MOUSEMOTION &&
1625// !(ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) &&
1626// !(ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) {
1627// return ignored_EventResult;
1628// }
1629 if (isFocused_Widget(w)) {
1630 if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) {
1631 d->lastTapTime = d->tapStartTime;
1632 d->tapStartTime = SDL_GetTicks();
1633 const int tapDist = dist_I2(latestPosition_Touch(), d->lastTapPos);
1634 d->lastTapPos = latestPosition_Touch();
1635 printf("[%p] tap start time: %u (%u) %d\n", w, d->tapStartTime, d->tapStartTime - d->lastTapTime, tapDist);
1636 if (d->tapStartTime - d->lastTapTime < 400 && tapDist < gap_UI * 4) {
1637 d->tapCount++;
1638 printf("[%p] >> tap count: %d\n", w, d->tapCount);
1639 }
1640 else {
1641 d->tapCount = 0;
1642 }
1643 if (!isEmpty_Range(&d->mark)) {
1644 const int dist[2] = {
1645 distanceToPos_InputWidget_(d, latestPosition_Touch(),
1646 indexToCursor_InputWidget_(d, d->mark.start)),
1647 distanceToPos_InputWidget_(d, latestPosition_Touch(),
1648 indexToCursor_InputWidget_(d, d->mark.end))
1649 };
1650 if (dist[0] < dist[1]) {
1651 printf("[%p] begin marker start drag\n", w);
1652 d->inFlags |= dragMarkerStart_InputWidgetFlag;
1653 }
1654 else {
1655 printf("[%p] begin marker end drag\n", w);
1656 d->inFlags |= dragMarkerEnd_InputWidgetFlag;
1657 }
1658 d->inFlags |= isMarking_InputWidgetFlag;
1659 setFlags_Widget(w, touchDrag_WidgetFlag, iTrue);
1660 }
1661 else {
1662 const int dist = distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor);
1663 printf("[%p] tap dist: %d\n", w, dist);
1664 if (dist < gap_UI * 10) {
1665 printf("[%p] begin cursor drag\n", w);
1666 setFlags_Widget(w, touchDrag_WidgetFlag, iTrue);
1667 d->inFlags |= dragCursor_InputWidgetFlag;
1668// d->inFlags |= touchBehavior_InputWidgetFlag;
1669// setMouseGrab_Widget(w);
1670// return iTrue;
1671 }
1672 }
1673// if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) {
1674// d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click));
1675// showCursor_InputWidget_(d);
1676// }
1677 return true_EventResult;
1678 }
1679 }
1680#if 0
1681 else if (isFocused_Widget(w)) {
1682 if (ev->type == SDL_MOUSEMOTION) {
1683 if (~d->inFlags & touchBehavior_InputWidgetFlag) {
1684 const iInt2 curPos = relativeCursorCoord_InputWidget_(d);
1685 const iInt2 relClick = sub_I2(pos_Click(&d->click),
1686 topLeft_Rect(contentBounds_InputWidget_(d)));
1687 if (dist_I2(curPos, relClick) < gap_UI * 8) {
1688 printf("tap on cursor!\n");
1689 setFlags_Widget(w, touchDrag_WidgetFlag, iTrue);
1690 d->inFlags |= touchBehavior_InputWidgetFlag;
1691 printf("[Input] begin cursor drag\n");
1692 setMouseGrab_Widget(w);
1693 return iTrue;
1694 }
1695 }
1696 else if (ev->motion.x > 0 && ev->motion.y > 0) {
1697 printf("[Input] cursor being dragged\n");
1698 iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2());
1699 bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2;
1700 iInt2 mpos = init_I2(ev->motion.x, ev->motion.y);
1701 mpos = min_I2(bottomRight_Rect(bounds), max_I2(mpos, topLeft_Rect(bounds)));
1702 d->cursor = coordCursor_InputWidget_(d, mpos);
1703 showCursor_InputWidget_(d);
1704 refresh_Widget(w);
1705 return iTrue;
1706 }
1633 } 1707 }
1634 else { 1708 if (d->inFlags & touchBehavior_InputWidgetFlag) {
1635 openMenuFlags_Widget(clipMenu, 1709 if (ev->type == SDL_MOUSEBUTTONUP ||
1636 mouseCoord_Window(get_Window(), ev->button.which), 1710 (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) {
1637 iFalse); 1711 d->inFlags &= ~touchBehavior_InputWidgetFlag;
1712 setFlags_Widget(w, touchDrag_WidgetFlag, iFalse);
1713 setMouseGrab_Widget(NULL);
1714 printf("[Input] touch ends\n");
1715 return iFalse;
1716 }
1717 }
1718 }
1719#endif
1720#if 1
1721 if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) &&
1722 ev->button.button == SDL_BUTTON_RIGHT && contains_Widget(w, latestPosition_Touch())) {
1723 if (ev->type == SDL_MOUSEBUTTONDOWN) {
1724 /*if (isFocused_Widget(w)) {
1725 d->inFlags |= isMarking_InputWidgetFlag;
1726 d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch());
1727 markWordAtCursor_InputWidget_(d);
1728 refresh_Widget(d);
1729 return true_EventResult;
1730 }*/
1731 setFocus_Widget(w);
1732 d->inFlags |= isMarking_InputWidgetFlag;
1733 d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch());
1734 markWordAtCursor_InputWidget_(d);
1735 d->cursor = indexToCursor_InputWidget_(d, d->mark.end);
1736 refresh_Widget(d);
1737 }
1738 return true_EventResult;
1739 }
1740 switch (processEvent_Click(&d->click, ev)) {
1741 case none_ClickResult:
1742 break;
1743 case started_ClickResult: {
1744 printf("[%p] started\n", w);
1745 /*
1746 const iInt2 curPos = relativeCursorCoord_InputWidget_(d);
1747 const iInt2 relClick = sub_I2(pos_Click(&d->click),
1748 topLeft_Rect(contentBounds_InputWidget_(d)));
1749 if (dist_I2(curPos, relClick) < gap_UI * 8) {
1750 printf("tap on cursor!\n");
1751 setFlags_Widget(w, touchDrag_WidgetFlag, iTrue);
1752 }
1753 else {
1754 printf("tap elsewhere\n");
1755 }*/
1756 return true_EventResult;
1757 }
1758 case drag_ClickResult:
1759 printf("[%p] drag %d,%d\n", w, pos_Click(&d->click).x, pos_Click(&d->click).y);
1760 if (d->inFlags & dragCursor_InputWidgetFlag) {
1761 iZap(d->mark);
1762 d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click));
1763 showCursor_InputWidget_(d);
1764 refresh_Widget(w);
1765 }
1766 else if (d->inFlags & dragMarkerStart_InputWidgetFlag) {
1767 d->mark.start = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click)));
1768 refresh_Widget(w);
1769 }
1770 else if (d->inFlags & dragMarkerEnd_InputWidgetFlag) {
1771 d->mark.end = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click)));
1772 refresh_Widget(w);
1773 }
1774 return true_EventResult;
1775 // printf("[%p] aborted\n", w);
1776// d->inFlags &= ~touchBehavior_InputWidgetFlag;
1777// setFlags_Widget(w, touchDrag_WidgetFlag, iFalse);
1778// return true_EventResult;
1779 case finished_ClickResult:
1780 case aborted_ClickResult:
1781 printf("[%p] ended\n", w);
1782 uint32_t tapElapsed = SDL_GetTicks() - d->tapStartTime;
1783 printf("tapElapsed: %u\n", tapElapsed);
1784 if (!isFocused_Widget(w)) {
1785 setFocus_Widget(w);
1786 d->lastTapPos = latestPosition_Touch();
1787 d->tapStartTime = SDL_GetTicks();
1788 d->tapCount = 0;
1789 d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click));
1790 showCursor_InputWidget_(d);
1791 }
1792 else if (!isEmpty_Range(&d->mark) && !isMoved_Click(&d->click)) {
1793 if (isInsideMark_InputWidget_(d, cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, latestPosition_Touch())))) {
1794 showClipMenu_(latestPosition_Touch());
1795 }
1796 else {
1797 iZap(d->mark);
1798 d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click));
1799 }
1800 }
1801 else if (SDL_GetTicks() - d->lastTapTime > 1000 &&
1802 d->tapCount == 0 && isEmpty_Range(&d->mark) && !isMoved_Click(&d->click) &&
1803 distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor) < gap_UI * 5) {
1804 showClipMenu_(latestPosition_Touch());
1805 }
1806 else {
1807 if (~d->inFlags & isMarking_InputWidgetFlag) {
1808 iZap(d->mark);
1809 d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click));
1810 }
1811 }
1812 if (d->inFlags & (dragCursor_InputWidgetFlag | dragMarkerStart_InputWidgetFlag |
1813 dragMarkerEnd_InputWidgetFlag)) {
1814 printf("[%p] finished cursor/marker drag\n", w);
1815 d->inFlags &= ~(dragCursor_InputWidgetFlag |
1816 dragMarkerStart_InputWidgetFlag |
1817 dragMarkerEnd_InputWidgetFlag);
1818 setFlags_Widget(w, touchDrag_WidgetFlag, iFalse);
1819 }
1820 d->inFlags &= ~isMarking_InputWidgetFlag;
1821 showCursor_InputWidget_(d);
1822 refresh_Widget(w);
1823#if 0
1824 d->inFlags &= ~touchBehavior_InputWidgetFlag;
1825 if (flags_Widget(w) & touchDrag_WidgetFlag) {
1826 setFlags_Widget(w, touchDrag_WidgetFlag, iFalse);
1827 return true_EventResult;
1828 }
1829 if (!isMoved_Click(&d->click)) {
1830 if (!isFocused_Widget(w)) {
1831 setFocus_Widget(w);
1832 if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) {
1833 d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click));
1834 showCursor_InputWidget_(d);
1835 }
1836 }
1837 else {
1838 iZap(d->mark);
1839 d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click));
1840 showCursor_InputWidget_(d);
1841 }
1842 }
1843#endif
1844 return true_EventResult;
1845 }
1846#endif
1847// if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) &&
1848// contains_Widget(w, init_I2(ev->button.x, ev->button.y))) {
1849// /* Eat all mouse clicks on the widget. */
1850// return true_EventResult;
1851// }
1852 return ignored_EventResult;
1853}
1854
1855static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1856 iWidget *w = as_Widget(d);
1857 /* Resize according to width immediately. */
1858 if (d->lastUpdateWidth != w->rect.size.x) {
1859 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1860 if (d->inFlags & isUrl_InputWidgetFlag) {
1861 /* Restore/omit the default scheme if necessary. */
1862 setText_InputWidget(d, text_InputWidget(d));
1638 } 1863 }
1864 updateAllLinesAndResizeHeight_InputWidget_(d);
1865 d->lastUpdateWidth = w->rect.size.x;
1866 }
1867 if (isCommand_Widget(w, ev, "focus.gained")) {
1868 begin_InputWidget(d);
1869 return iFalse;
1870 }
1871 else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") ||
1872 isCommand_UserEvent(ev, "window.focus.gained"))) {
1873 startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained"));
1874 d->cursorVis = 1;
1875 refresh_Widget(d);
1876 return iFalse;
1877 }
1878 else if (isCommand_UserEvent(ev, "keyroot.changed")) {
1879 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1880 }
1881 else if (isCommand_UserEvent(ev, "lang.changed")) {
1882 set_String(&d->hint, &d->srcHint);
1883 translate_Lang(&d->hint);
1884 return iFalse;
1885 }
1886 else if (isCommand_Widget(w, ev, "focus.lost")) {
1887 end_InputWidget(d, iTrue);
1888 return iFalse;
1889 }
1890 else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) &&
1891 isEditing_InputWidget_(d)) {
1892 copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut"));
1893 return iTrue;
1894 }
1895 else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) {
1896 paste_InputWidget_(d);
1639 return iTrue; 1897 return iTrue;
1640 } 1898 }
1899 else if (isCommand_UserEvent(ev, "theme.changed")) {
1900 if (d->buffered) {
1901 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1902 }
1903 return iFalse;
1904 }
1905// else if (isCommand_UserEvent(ev, "keyboard.changed")) {
1906// if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) {
1907// iRect rect = bounds_Widget(w);
1908// rect.pos.y -= value_Anim(&get_Window()->rootOffset);
1909// const iInt2 visRoot = visibleSize_Root(w->root);
1910// if (bottom_Rect(rect) > visRoot.y) {
1911// setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250);
1912// }
1913// }
1914// return iFalse;
1915// }
1916 else if (isCommand_UserEvent(ev, "text.insert")) {
1917 pushUndo_InputWidget_(d);
1918 deleteMarked_InputWidget_(d);
1919 insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev)));
1920 contentsWereChanged_InputWidget_(d);
1921 return iTrue;
1922 }
1923 else if (isCommand_Widget(w, ev, "input.backup")) {
1924 if (d->inFlags & needBackup_InputWidgetFlag) {
1925 saveBackup_InputWidget_(d);
1926 }
1927 return iTrue;
1928 }
1929 else if (isMetricsChange_UserEvent(ev)) {
1930 updateMetrics_InputWidget_(d);
1931 // updateLinesAndResize_InputWidget_(d);
1932 }
1933 else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) {
1934 copy_InputWidget_(d, iFalse);
1935 return iTrue;
1936 }
1937 /* Click behavior depends on device type. */ {
1938 const int mbResult = (deviceType_App() == desktop_AppDeviceType
1939 ? processPointerEvents_InputWidget_(d, ev)
1940 : processTouchEvents_InputWidget_(d, ev));
1941 if (mbResult) {
1942 return mbResult >> 1;
1943 }
1944 }
1641 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { 1945 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) {
1642 return iTrue; 1946 return iTrue;
1643 } 1947 }
@@ -1884,6 +2188,8 @@ struct Impl_MarkPainter {
1884 const iInputLine * line; 2188 const iInputLine * line;
1885 iInt2 pos; 2189 iInt2 pos;
1886 iRanges mark; 2190 iRanges mark;
2191 iRect firstMarkRect;
2192 iRect lastMarkRect;
1887}; 2193};
1888 2194
1889static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, 2195static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance,
@@ -1922,7 +2228,11 @@ static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int or
1922 } 2228 }
1923 rect.size.x = iMax(gap_UI / 3, rect.size.x); 2229 rect.size.x = iMax(gap_UI / 3, rect.size.x);
1924 mp->pos.y += lineHeight_Text(mp->d->font); 2230 mp->pos.y += lineHeight_Text(mp->d->font);
1925 fillRect_Paint(mp->paint, rect, uiMarked_ColorId); 2231 fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId);
2232 if (deviceType_App() != desktop_AppDeviceType) {
2233 if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect;
2234 mp->lastMarkRect = rect;
2235 }
1926 return iTrue; 2236 return iTrue;
1927} 2237}
1928 2238
@@ -1962,6 +2272,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
1962 }; 2272 };
1963 const iRangei visLines = visibleLineRange_InputWidget_(d); 2273 const iRangei visLines = visibleLineRange_InputWidget_(d);
1964 const int visLineOffsetY = visLineOffsetY_InputWidget_(d); 2274 const int visLineOffsetY = visLineOffsetY_InputWidget_(d);
2275 iRect markerRects[2];
1965 /* If buffered, just draw the buffered copy. */ 2276 /* If buffered, just draw the buffered copy. */
1966 if (d->buffered && !isFocused) { 2277 if (d->buffered && !isFocused) {
1967 /* Most input widgets will use this, since only one is focused at a time. */ 2278 /* Most input widgets will use this, since only one is focused at a time. */
@@ -1977,7 +2288,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
1977 .paint = &p, 2288 .paint = &p,
1978 .d = d, 2289 .d = d,
1979 .contentBounds = contentBounds, 2290 .contentBounds = contentBounds,
1980 .mark = mark_InputWidget_(d) 2291 .mark = mark_InputWidget_(d),
1981 }; 2292 };
1982 wrapText.context = &marker; 2293 wrapText.context = &marker;
1983 wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */ 2294 wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */
@@ -1988,11 +2299,14 @@ static void draw_InputWidget_(const iInputWidget *d) {
1988 marker.pos = drawPos; 2299 marker.pos = drawPos;
1989 addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */ 2300 addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */
1990 } 2301 }
2302 markerRects[0] = marker.firstMarkRect;
2303 markerRects[1] = marker.lastMarkRect;
1991 wrapText.wrapFunc = NULL; 2304 wrapText.wrapFunc = NULL;
1992 wrapText.context = NULL; 2305 wrapText.context = NULL;
1993 } 2306 }
1994 /* Draw the insertion point. */ 2307 /* Draw the insertion point. */
1995 if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y)) { 2308 if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) &&
2309 isEmpty_Range(&d->mark)) {
1996 iInt2 curSize; 2310 iInt2 curSize;
1997 iRangecc cursorChar = iNullRange; 2311 iRangecc cursorChar = iNullRange;
1998 int visWrapsAbove = 0; 2312 int visWrapsAbove = 0;
@@ -2040,6 +2354,11 @@ static void draw_InputWidget_(const iInputWidget *d) {
2040 } 2354 }
2041 } 2355 }
2042 unsetClip_Paint(&p); 2356 unsetClip_Paint(&p);
2357 if (!isEmpty_Rect(markerRects[0])) {
2358 for (int i = 0; i < 2; ++i) {
2359 drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId);
2360 }
2361 }
2043 drawChildren_Widget(w); 2362 drawChildren_Widget(w);
2044} 2363}
2045 2364