diff options
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r-- | src/ui/inputwidget.c | 563 |
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 | ||
181 | enum iInputWidgetFlag { | 181 | enum 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 | ||
463 | static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { | 471 | static 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 | ||
479 | static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { | ||
480 | return relativeCoord_InputWidget_(d, d->cursor); | ||
481 | } | ||
482 | |||
471 | static void updateVisible_InputWidget_(iInputWidget *d) { | 483 | static 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 | ||
1448 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 1463 | enum 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. */ | 1469 | static 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); | 1476 | static 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 | |||
1486 | static 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)) { | 1591 | static 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 | |||
1599 | static iBool isInsideMark_InputWidget_(const iInputWidget *d, size_t pos) { | ||
1600 | const iRanges mark = mark_InputWidget_(d); | ||
1601 | return contains_Range(&mark, pos); | ||
1602 | } | ||
1603 | |||
1604 | static 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 | |||
1610 | static 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 | |||
1855 | static 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 | ||
1889 | static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, | 2195 | static 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 = ▮ | 2293 | wrapText.context = ▮ |
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 | ||