summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/color.h1
-rw-r--r--src/ui/documentwidget.c21
-rw-r--r--src/ui/inputwidget.c563
-rw-r--r--src/ui/paint.c19
-rw-r--r--src/ui/paint.h2
-rw-r--r--src/ui/root.c33
-rw-r--r--src/ui/touch.c9
-rw-r--r--src/ui/util.c70
-rw-r--r--src/ui/widget.c40
-rw-r--r--src/ui/window.c10
-rw-r--r--src/ui/window.h2
11 files changed, 569 insertions, 201 deletions
diff --git a/src/ui/color.h b/src/ui/color.h
index 37ec49eb..a1d863dc 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -183,6 +183,7 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) {
183#define mask_ColorId 0x7f 183#define mask_ColorId 0x7f
184#define permanent_ColorId 0x80 /* cannot be changed via escapes */ 184#define permanent_ColorId 0x80 /* cannot be changed via escapes */
185#define fillBackground_ColorId 0x100 /* fill background with same color, but alpha 0 */ 185#define fillBackground_ColorId 0x100 /* fill background with same color, but alpha 0 */
186#define opaque_ColorId 0x200
186 187
187#define asciiBase_ColorEscape 33 188#define asciiBase_ColorEscape 33
188#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) 189#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape)
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 4b3c2db0..8ea695d5 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -4597,23 +4597,6 @@ static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) {
4597 } 4597 }
4598} 4598}
4599 4599
4600static void drawPin_(iPaint *p, iRect rangeRect, int dir) {
4601 const int pinColor = tmQuote_ColorId;
4602 const int height = height_Rect(rangeRect);
4603 iRect pin;
4604 if (dir == 0) {
4605 pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)),
4606 init_I2(gap_UI / 2, height + gap_UI) };
4607 }
4608 else {
4609 pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4),
4610 init_I2(gap_UI / 2, height + gap_UI) };
4611 }
4612 fillRect_Paint(p, pin, pinColor);
4613 fillRect_Paint(p, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin),
4614 init1_I2(gap_UI * 2)), pinColor);
4615}
4616
4617static void extend_GmRunRange_(iGmRunRange *runs) { 4600static void extend_GmRunRange_(iGmRunRange *runs) {
4618 if (runs->start) { 4601 if (runs->start) {
4619 runs->start--; 4602 runs->start--;
@@ -4857,8 +4840,8 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4857 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); 4840 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE);
4858 /* Selection range pins. */ 4841 /* Selection range pins. */
4859 if (isTouchSelecting) { 4842 if (isTouchSelecting) {
4860 drawPin_(&ctx.paint, ctx.firstMarkRect, 0); 4843 drawPin_Paint(&ctx.paint, ctx.firstMarkRect, 0, tmQuote_ColorId);
4861 drawPin_(&ctx.paint, ctx.lastMarkRect, 1); 4844 drawPin_Paint(&ctx.paint, ctx.lastMarkRect, 1, tmQuote_ColorId);
4862 } 4845 }
4863 } 4846 }
4864 drawMedia_DocumentWidget_(d, &ctx.paint); 4847 drawMedia_DocumentWidget_(d, &ctx.paint);
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
diff --git a/src/ui/paint.c b/src/ui/paint.c
index 71ebb81d..89de47d4 100644
--- a/src/ui/paint.c
+++ b/src/ui/paint.c
@@ -33,7 +33,8 @@ iLocalDef SDL_Renderer *renderer_Paint_(const iPaint *d) {
33 33
34static void setColor_Paint_(const iPaint *d, int color) { 34static void setColor_Paint_(const iPaint *d, int color) {
35 const iColor clr = get_Color(color & mask_ColorId); 35 const iColor clr = get_Color(color & mask_ColorId);
36 SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b, clr.a * d->alpha / 255); 36 SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b,
37 (color & opaque_ColorId ? 255 : clr.a) * d->alpha / 255);
37} 38}
38 39
39void init_Paint(iPaint *d) { 40void init_Paint(iPaint *d) {
@@ -186,6 +187,22 @@ void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t n, int color)
186 free(offsetPoints); 187 free(offsetPoints);
187} 188}
188 189
190void drawPin_Paint(iPaint *d, iRect rangeRect, int dir, int pinColor) {
191 const int height = height_Rect(rangeRect);
192 iRect pin;
193 if (dir == 0) {
194 pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)),
195 init_I2(gap_UI / 2, height + gap_UI) };
196 }
197 else {
198 pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4),
199 init_I2(gap_UI / 2, height + gap_UI) };
200 }
201 fillRect_Paint(d, pin, pinColor);
202 fillRect_Paint(d, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin),
203 init1_I2(gap_UI * 2)), pinColor);
204}
205
189iInt2 size_SDLTexture(SDL_Texture *d) { 206iInt2 size_SDLTexture(SDL_Texture *d) {
190 iInt2 size; 207 iInt2 size;
191 SDL_QueryTexture(d, NULL, NULL, &size.x, &size.y); 208 SDL_QueryTexture(d, NULL, NULL, &size.x, &size.y);
diff --git a/src/ui/paint.h b/src/ui/paint.h
index e6701635..e894b62f 100644
--- a/src/ui/paint.h
+++ b/src/ui/paint.h
@@ -63,4 +63,6 @@ iLocalDef void drawVLine_Paint(const iPaint *d, iInt2 pos, int len, int color) {
63 drawLine_Paint(d, pos, addY_I2(pos, len), color); 63 drawLine_Paint(d, pos, addY_I2(pos, len), color);
64} 64}
65 65
66void drawPin_Paint (iPaint *, iRect rangeRect, int dir, int pinColor);
67
66iInt2 size_SDLTexture (SDL_Texture *); 68iInt2 size_SDLTexture (SDL_Texture *);
diff --git a/src/ui/root.c b/src/ui/root.c
index 59f98aa4..91f9fbb3 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -1401,23 +1401,38 @@ void createUserInterface_Root(iRoot *d) {
1401 { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" }, 1401 { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" },
1402 { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" }, 1402 { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" },
1403 { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" }, 1403 { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" },
1404 }, 1404 },
1405 6); 1405 6);
1406 iWidget *barMenu = 1406 iWidget *barMenu =
1407 makeMenu_Widget(root, 1407 makeMenu_Widget(root,
1408 (iMenuItem[]){ 1408 (iMenuItem[]){
1409 { leftHalf_Icon " ${menu.sidebar.left}", 0, 0, "sidebar.toggle" }, 1409 { leftHalf_Icon " ${menu.sidebar.left}", 0, 0, "sidebar.toggle" },
1410 { rightHalf_Icon " ${menu.sidebar.right}", 0, 0, "sidebar2.toggle" }, 1410 { rightHalf_Icon " ${menu.sidebar.right}", 0, 0, "sidebar2.toggle" },
1411 }, 1411 },
1412 deviceType_App() == phone_AppDeviceType ? 1 : 2); 1412 deviceType_App() == phone_AppDeviceType ? 1 : 2);
1413 iWidget *clipMenu = makeMenu_Widget(root, 1413 iWidget *clipMenu = makeMenu_Widget(root,
1414 (iMenuItem[]){ 1414#if defined (iPlatformMobile)
1415 { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, 1415 (iMenuItem[]){
1416 { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, 1416 { ">>>" scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" },
1417 { "---" }, 1417 { ">>>" clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" },
1418 { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, 1418 { ">>>" clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" },
1419 }, 1419 { "---" },
1420 4); 1420 { ">>>" delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" },
1421 { ">>>" select_Icon " ${menu.selectall}", 0, 0, "input.selectall" },
1422 { ">>>" undo_Icon " ${menu.undo}", 0, 0, "input.undo" },
1423 }, 7);
1424#else
1425 (iMenuItem[]){
1426 { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" },
1427 { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" },
1428 { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" },
1429 { "---" },
1430 { delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" },
1431 { undo_Icon " ${menu.undo}", 0, 0, "input.undo" },
1432 { "---" },
1433 { select_Icon " ${menu.selectall}", 0, 0, "input.selectall" },
1434 }, 8);
1435#endif
1421 iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){ 1436 iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){
1422 { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, 1437 { "${menu.split.merge}", '1', 0, "ui.split arg:0" },
1423 { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, 1438 { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" },
diff --git a/src/ui/touch.c b/src/ui/touch.c
index 5fc8f245..61882739 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -293,6 +293,7 @@ static void update_TouchState_(void *ptr) {
293 } 293 }
294 if (elapsed > 50 && !touch->isTapBegun) { 294 if (elapsed > 50 && !touch->isTapBegun) {
295 /* Looks like a possible tap. */ 295 /* Looks like a possible tap. */
296 touchState_()->currentTouchPos = initF3_I2(touch->pos[0]);
296 dispatchNotification_Touch_(touch, widgetTapBegins_UserEventCode); 297 dispatchNotification_Touch_(touch, widgetTapBegins_UserEventCode);
297 dispatchMotion_Touch_(touch->pos[0], 0); 298 dispatchMotion_Touch_(touch->pos[0], 0);
298 refresh_Widget(touch->affinity); 299 refresh_Widget(touch->affinity);
@@ -471,13 +472,13 @@ iBool processEvent_Touch(const SDL_Event *ev) {
471 } 472 }
472 iTouchState *d = touchState_(); 473 iTouchState *d = touchState_();
473 iWindow *window = get_Window(); 474 iWindow *window = get_Window();
474 if (!isFinished_Anim(&window->rootOffset)) { 475// if (!isFinished_Anim(&window->rootOffset)) {
475 return iFalse; 476// return iFalse;
476 } 477// }
477 const iInt2 rootSize = size_Window(window); 478 const iInt2 rootSize = size_Window(window);
478 const SDL_TouchFingerEvent *fing = &ev->tfinger; 479 const SDL_TouchFingerEvent *fing = &ev->tfinger;
479 const iFloat3 pos = add_F3(init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0), /* pixels */ 480 const iFloat3 pos = add_F3(init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0), /* pixels */
480 init_F3(0, -value_Anim(&window->rootOffset), 0)); 481 init_F3(0, 0 /*-value_Anim(&window->rootOffset)*/, 0));
481 const uint32_t nowTime = SDL_GetTicks(); 482 const uint32_t nowTime = SDL_GetTicks();
482 if (ev->type == SDL_FINGERDOWN) { 483 if (ev->type == SDL_FINGERDOWN) {
483 /* Register the new touch. */ 484 /* Register the new touch. */
diff --git a/src/ui/util.c b/src/ui/util.c
index 48ed41a6..cfa8152c 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -706,23 +706,36 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
706 setFrameColor_Widget(menu, uiSeparator_ColorId); 706 setFrameColor_Widget(menu, uiSeparator_ColorId);
707 } 707 }
708 iBool haveIcons = iFalse; 708 iBool haveIcons = iFalse;
709 iWidget *horizGroup = NULL;
709 for (size_t i = 0; i < n; ++i) { 710 for (size_t i = 0; i < n; ++i) {
710 const iMenuItem *item = &items[i]; 711 const iMenuItem *item = &items[i];
711 if (!item->label) { 712 if (!item->label) {
712 break; 713 break;
713 } 714 }
714 if (equal_CStr(item->label, "---")) { 715 const char *labelText = item->label;
716 if (!startsWith_CStr(labelText, ">>>")) {
717 horizGroup = NULL;
718 }
719 if (equal_CStr(labelText, "---")) {
715 addChild_Widget(menu, iClob(makeMenuSeparator_())); 720 addChild_Widget(menu, iClob(makeMenuSeparator_()));
716 } 721 }
717 else { 722 else {
718 iBool isInfo = iFalse; 723 iBool isInfo = iFalse;
719 const char *labelText = item->label; 724 if (startsWith_CStr(labelText, ">>>")) {
725 labelText += 3;
726 if (!horizGroup) {
727 horizGroup = makeHDiv_Widget();
728 setFlags_Widget(horizGroup, resizeHeightOfChildren_WidgetFlag, iFalse);
729 setFlags_Widget(horizGroup, arrangeHeight_WidgetFlag, iTrue);
730 addChild_Widget(menu, iClob(horizGroup));
731 }
732 }
720 if (startsWith_CStr(labelText, "```")) { 733 if (startsWith_CStr(labelText, "```")) {
721 labelText += 3; 734 labelText += 3;
722 isInfo = iTrue; 735 isInfo = iTrue;
723 } 736 }
724 iLabelWidget *label = addChildFlags_Widget( 737 iLabelWidget *label = addChildFlags_Widget(
725 menu, 738 horizGroup ? horizGroup : menu,
726 iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), 739 iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)),
727 noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | 740 noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag |
728 drawKey_WidgetFlag | itemFlags); 741 drawKey_WidgetFlag | itemFlags);
@@ -766,6 +779,34 @@ void openMenu_Widget(iWidget *d, iInt2 windowCoord) {
766 openMenuFlags_Widget(d, windowCoord, iTrue); 779 openMenuFlags_Widget(d, windowCoord, iTrue);
767} 780}
768 781
782static void updateMenuItemFonts_Widget_(iWidget *d) {
783 const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App());
784 const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0;
785 iForEach(ObjectList, i, children_Widget(d)) {
786 if (isInstance_Object(i.object, &Class_LabelWidget)) {
787 iLabelWidget *label = i.object;
788 const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape);
789 if (isWrapped_LabelWidget(label)) {
790 continue;
791 }
792 if (deviceType_App() == desktop_AppDeviceType) {
793 setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId);
794 }
795 else if (isPortraitPhone) {
796 if (!isSlidePanel) {
797 setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId);
798 }
799 }
800 else {
801 setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId);
802 }
803 }
804 else if (childCount_Widget(i.object)) {
805 updateMenuItemFonts_Widget_(i.object);
806 }
807 }
808}
809
769void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { 810void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
770 const iRect rootRect = rect_Root(d->root); 811 const iRect rootRect = rect_Root(d->root);
771 const iInt2 rootSize = rootRect.size; 812 const iInt2 rootSize = rootRect.size;
@@ -788,28 +829,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
788 } 829 }
789 d->rect.size.x = rootSize.x; 830 d->rect.size.x = rootSize.x;
790 } 831 }
791 /* Update item fonts. */ { 832 updateMenuItemFonts_Widget_(d);
792 iForEach(ObjectList, i, children_Widget(d)) {
793 if (isInstance_Object(i.object, &Class_LabelWidget)) {
794 iLabelWidget *label = i.object;
795 const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape);
796 if (isWrapped_LabelWidget(label)) {
797 continue;
798 }
799 if (deviceType_App() == desktop_AppDeviceType) {
800 setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId);
801 }
802 else if (isPortraitPhone) {
803 if (!isSlidePanel) {
804 setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId);
805 }
806 }
807 else {
808 setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId);
809 }
810 }
811 }
812 }
813 arrange_Widget(d); 833 arrange_Widget(d);
814 if (isPortraitPhone) { 834 if (isPortraitPhone) {
815 if (isSlidePanel) { 835 if (isSlidePanel) {
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 0765bf9f..1c0fb271 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -880,9 +880,9 @@ iInt2 localToWindow_Widget(const iWidget *d, iInt2 localCoord) {
880 applyVisualOffset_Widget_(w, &pos); 880 applyVisualOffset_Widget_(w, &pos);
881 addv_I2(&window, pos); 881 addv_I2(&window, pos);
882 } 882 }
883#if defined (iPlatformMobile) 883//#if defined (iPlatformMobile)
884 window.y += value_Anim(&get_Window()->rootOffset); 884// window.y += value_Anim(&get_Window()->rootOffset);
885#endif 885//#endif
886 return window; 886 return window;
887} 887}
888 888
@@ -1072,23 +1072,33 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
1072} 1072}
1073 1073
1074iBool scrollOverflow_Widget(iWidget *d, int delta) { 1074iBool scrollOverflow_Widget(iWidget *d, int delta) {
1075 iRect bounds = boundsWithoutVisualOffset_Widget(d); 1075 iRect bounds = boundsWithoutVisualOffset_Widget(d);
1076 const iInt2 rootSize = size_Root(d->root); 1076// const iInt2 rootSize = size_Root(d->root);
1077 const iRect winRect = safeRect_Root(d->root); 1077 const iRect winRect = adjusted_Rect(safeRect_Root(d->root),
1078 const int yTop = top_Rect(winRect); 1078 zero_I2(),
1079 const int yBottom = bottom_Rect(winRect); 1079 init_I2(0, -get_Window()->keyboardHeight));
1080 const int yTop = top_Rect(winRect);
1081 const int yBottom = bottom_Rect(winRect);
1080 if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { 1082 if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) {
1081 return iFalse; /* fits inside just fine */ 1083 return iFalse; /* fits inside just fine */
1082 } 1084 }
1083 //const int safeBottom = rootSize.y - yBottom; 1085 //const int safeBottom = rootSize.y - yBottom;
1084 bounds.pos.y += delta; 1086 iRangei validPosRange = { bottom_Rect(winRect) - height_Rect(bounds), yTop };
1085 const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; 1087 if (validPosRange.start > validPosRange.end) {
1088 validPosRange.start = validPosRange.end; /* no room to scroll */
1089 }
1090 if (delta) {
1091 if (delta < 0 && bounds.pos.y < validPosRange.start) {
1092 delta = 0;
1093 }
1094 if (delta > 0 && bounds.pos.y > validPosRange.end) {
1095 delta = 0;
1096 }
1097 bounds.pos.y += delta;
1086// printf("range: %d ... %d\n", range.start, range.end); 1098// printf("range: %d ... %d\n", range.start, range.end);
1087 if (range.start >= range.end) {
1088 bounds.pos.y = range.end;
1089 } 1099 }
1090 else { 1100 else {
1091 bounds.pos.y = iClamp(bounds.pos.y, range.start, range.end); 1101 bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end);
1092 } 1102 }
1093// if (delta >= 0) { 1103// if (delta >= 0) {
1094// bounds.pos.y = iMin(bounds.pos.y, yTop); 1104// bounds.pos.y = iMin(bounds.pos.y, yTop);
@@ -1454,7 +1464,7 @@ void setDrawBufferEnabled_Widget(iWidget *d, iBool enable) {
1454 1464
1455static void beginBufferDraw_Widget_(const iWidget *d) { 1465static void beginBufferDraw_Widget_(const iWidget *d) {
1456 if (d->drawBuf) { 1466 if (d->drawBuf) {
1457 printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); 1467// printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid);
1458 if (d->drawBuf->isValid) { 1468 if (d->drawBuf->isValid) {
1459 iAssert(!isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size)); 1469 iAssert(!isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size));
1460// printf(" drawBuf:%dx%d boundsForDraw:%dx%d\n", 1470// printf(" drawBuf:%dx%d boundsForDraw:%dx%d\n",
@@ -1503,7 +1513,7 @@ void draw_Widget(const iWidget *d) {
1503 endBufferDraw_Widget_(d); 1513 endBufferDraw_Widget_(d);
1504 } 1514 }
1505 if (d->drawBuf) { 1515 if (d->drawBuf) {
1506 iAssert(d->drawBuf->isValid); 1516 //iAssert(d->drawBuf->isValid);
1507 const iRect bounds = bounds_Widget(d); 1517 const iRect bounds = bounds_Widget(d);
1508 SDL_RenderCopy(renderer_Window(get_Window()), d->drawBuf->texture, NULL, 1518 SDL_RenderCopy(renderer_Window(get_Window()), d->drawBuf->texture, NULL,
1509 &(SDL_Rect){ bounds.pos.x, bounds.pos.y, 1519 &(SDL_Rect){ bounds.pos.x, bounds.pos.y,
diff --git a/src/ui/window.c b/src/ui/window.c
index 8034d858..ed2ec024 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -421,7 +421,7 @@ void init_Window(iWindow *d, iRect rect) {
421 d->ignoreClick = iFalse; 421 d->ignoreClick = iFalse;
422 d->focusGainedAt = 0; 422 d->focusGainedAt = 0;
423 d->keyboardHeight = 0; 423 d->keyboardHeight = 0;
424 init_Anim(&d->rootOffset, 0.0f); 424// init_Anim(&d->rootOffset, 0.0f);
425 uint32_t flags = 0; 425 uint32_t flags = 0;
426#if defined (iPlatformAppleDesktop) 426#if defined (iPlatformAppleDesktop)
427 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); 427 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl");
@@ -1215,10 +1215,10 @@ iBool isOpenGLRenderer_Window(void) {
1215void setKeyboardHeight_Window(iWindow *d, int height) { 1215void setKeyboardHeight_Window(iWindow *d, int height) {
1216 if (d->keyboardHeight != height) { 1216 if (d->keyboardHeight != height) {
1217 d->keyboardHeight = height; 1217 d->keyboardHeight = height;
1218 if (height == 0) { 1218// if (height == 0) {
1219 setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue); 1219// setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue);
1220 setValue_Anim(&d->rootOffset, 0, 250); 1220// setValue_Anim(&d->rootOffset, 0, 250);
1221 } 1221// }
1222 postCommandf_App("keyboard.changed arg:%d", height); 1222 postCommandf_App("keyboard.changed arg:%d", height);
1223 postRefresh_App(); 1223 postRefresh_App();
1224 } 1224 }
diff --git a/src/ui/window.h b/src/ui/window.h
index 63f7e5f2..a5b8f137 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -98,7 +98,7 @@ struct Impl_Window {
98 SDL_Cursor * cursors[SDL_NUM_SYSTEM_CURSORS]; 98 SDL_Cursor * cursors[SDL_NUM_SYSTEM_CURSORS];
99 SDL_Cursor * pendingCursor; 99 SDL_Cursor * pendingCursor;
100 int loadAnimTimer; 100 int loadAnimTimer;
101 iAnim rootOffset; 101// iAnim rootOffset;
102 int keyboardHeight; /* mobile software keyboards */ 102 int keyboardHeight; /* mobile software keyboards */
103}; 103};
104 104