From 33620846cca5678fbd662ea1a48fad302727dae7 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 12 Sep 2021 13:49:38 +0300 Subject: Mobile: Draw optimizations; focus handling Widgets can now be marked for buffering their contents, which is useful if their contents change seldom but they are drawn often. For example, the navbar is always visible but doesn't change very often, and during animations menu contents are static but there is a moving animation so everything gets drawn 60 FPS. Focus handling was also improved so the lookup results can be scrolled while entering text, and one can tap outside an input field to unfocus it. --- src/ui/paint.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/ui/paint.h') diff --git a/src/ui/paint.h b/src/ui/paint.h index 90cc2aef..e6701635 100644 --- a/src/ui/paint.h +++ b/src/ui/paint.h @@ -36,6 +36,8 @@ struct Impl_Paint { uint8_t alpha; }; +extern iInt2 origin_Paint; /* add this to all drawn positions so buffered graphics are correctly offset */ + void init_Paint (iPaint *); void beginTarget_Paint (iPaint *, SDL_Texture *target); -- cgit v1.2.3 From 4cf52f29b926a924d838a3158d5c78b3337ee0ee Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 13 Sep 2021 19:52:21 +0300 Subject: Mobile: New selection logic for InputWidget Touch-based interaction requires a different kind of selection and copy/paste behavior. This isn't done yet; especially multi-line text still needs work. --- po/en.po | 12 ++ res/lang/de.bin | Bin 24162 -> 24222 bytes res/lang/en.bin | Bin 22763 -> 22823 bytes res/lang/es.bin | Bin 25286 -> 25346 bytes res/lang/fi.bin | Bin 25291 -> 25351 bytes res/lang/fr.bin | Bin 26260 -> 26320 bytes res/lang/ia.bin | Bin 24890 -> 24950 bytes res/lang/ie.bin | Bin 24645 -> 24705 bytes res/lang/pl.bin | Bin 25823 -> 25883 bytes res/lang/ru.bin | Bin 37542 -> 37602 bytes res/lang/sr.bin | Bin 37199 -> 37259 bytes res/lang/tok.bin | Bin 23081 -> 23141 bytes res/lang/zh_Hans.bin | Bin 21842 -> 21902 bytes res/lang/zh_Hant.bin | Bin 22027 -> 22087 bytes src/app.c | 16 +- src/defs.h | 2 + src/ui/color.h | 1 + src/ui/documentwidget.c | 21 +- src/ui/inputwidget.c | 563 +++++++++++++++++++++++++++++++++++++----------- src/ui/paint.c | 19 +- src/ui/paint.h | 2 + src/ui/root.c | 33 ++- src/ui/touch.c | 9 +- src/ui/util.c | 70 +++--- src/ui/widget.c | 40 ++-- src/ui/window.c | 10 +- src/ui/window.h | 2 +- 27 files changed, 591 insertions(+), 209 deletions(-) (limited to 'src/ui/paint.h') diff --git a/po/en.po b/po/en.po index 546a9489..96026528 100644 --- a/po/en.po +++ b/po/en.po @@ -273,6 +273,18 @@ msgstr "Copy" msgid "menu.paste" msgstr "Paste" +# keep this short (3x1 horiz layout) +msgid "menu.selectall" +msgstr "Select All" + +# keep this short (3x1 horiz layout) +msgid "menu.delete" +msgstr "Delete" + +# keep this short (3x1 horiz layout) +msgid "menu.undo" +msgstr "Undo" + msgid "menu.select.clear" msgstr "Clear Selection" diff --git a/res/lang/de.bin b/res/lang/de.bin index ba87d002..f5a6b07e 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index a7dfb866..df3c4025 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 7e7398d6..9f5a169f 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 607e52fd..ac3b99ef 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 955695ed..7d40e32c 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 61a18efc..d10ab85e 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 06ea7979..14349637 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 5fc5e24a..9ddd137f 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 1718f647..366c6ee5 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 60b7b600..870b0950 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 3298f0e8..dac5fb33 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 8c32a0c5..3407c485 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index 68f7d3bc..b9001e2d 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index fa601ac3..37f9c804 100644 --- a/src/app.c +++ b/src/app.c @@ -1069,11 +1069,11 @@ iLocalDef iBool isWaitingAllowed_App_(iApp *d) { return iFalse; } #endif -#if defined (iPlatformMobile) - if (!isFinished_Anim(&d->window->rootOffset)) { - return iFalse; - } -#endif +//#if defined (iPlatformMobile) +// if (!isFinished_Anim(&d->window->rootOffset)) { +// return iFalse; +// } +//#endif return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers); } @@ -1318,7 +1318,7 @@ void processEvents_App(enum iAppEventMode eventMode) { } } #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) - if (d->isIdling && !gotEvents && isFinished_Anim(&d->window->rootOffset)) { + if (d->isIdling && !gotEvents /*&& isFinished_Anim(&d->window->rootOffset)*/) { /* This is where we spend most of our time when idle. 60 Hz still quite a lot but we can't wait too long after the user tries to interact again with the app. In any case, on macOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping. */ @@ -1411,9 +1411,9 @@ void refresh_App(void) { #endif if (!exchange_Atomic(&d->pendingRefresh, iFalse)) { /* Refreshing wasn't pending. */ - if (isFinished_Anim(&d->window->rootOffset)) { +// if (isFinished_Anim(&d->window->rootOffset)) { return; - } +// } } // iTime draw; // initCurrent_Time(&draw); diff --git a/src/defs.h b/src/defs.h index 6b12c86c..c3a23596 100644 --- a/src/defs.h +++ b/src/defs.h @@ -151,6 +151,8 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define magnifyingGlass_Icon "\U0001f50d" #define midEllipsis_Icon "\u00b7\u00b7\u00b7" #define return_Icon "\u23ce" +#define undo_Icon "\u23ea" +#define select_Icon "\u2b1a" #if defined (iPlatformApple) # define shift_Icon "\u21e7" 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) { #define mask_ColorId 0x7f #define permanent_ColorId 0x80 /* cannot be changed via escapes */ #define fillBackground_ColorId 0x100 /* fill background with same color, but alpha 0 */ +#define opaque_ColorId 0x200 #define asciiBase_ColorEscape 33 #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) { } } -static void drawPin_(iPaint *p, iRect rangeRect, int dir) { - const int pinColor = tmQuote_ColorId; - const int height = height_Rect(rangeRect); - iRect pin; - if (dir == 0) { - pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)), - init_I2(gap_UI / 2, height + gap_UI) }; - } - else { - pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4), - init_I2(gap_UI / 2, height + gap_UI) }; - } - fillRect_Paint(p, pin, pinColor); - fillRect_Paint(p, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin), - init1_I2(gap_UI * 2)), pinColor); -} - static void extend_GmRunRange_(iGmRunRange *runs) { if (runs->start) { runs->start--; @@ -4857,8 +4840,8 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); /* Selection range pins. */ if (isTouchSelecting) { - drawPin_(&ctx.paint, ctx.firstMarkRect, 0); - drawPin_(&ctx.paint, ctx.lastMarkRect, 1); + drawPin_Paint(&ctx.paint, ctx.firstMarkRect, 0, tmQuote_ColorId); + drawPin_Paint(&ctx.paint, ctx.lastMarkRect, 1, tmQuote_ColorId); } } 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) { } enum iInputWidgetFlag { - isSensitive_InputWidgetFlag = iBit(1), - isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ - enterPressed_InputWidgetFlag = iBit(3), - selectAllOnFocus_InputWidgetFlag = iBit(4), - notifyEdits_InputWidgetFlag = iBit(5), - eatEscape_InputWidgetFlag = iBit(6), - isMarking_InputWidgetFlag = iBit(7), - markWords_InputWidgetFlag = iBit(8), - needUpdateBuffer_InputWidgetFlag = iBit(9), - enterKeyEnabled_InputWidgetFlag = iBit(10), - lineBreaksEnabled_InputWidgetFlag= iBit(11), - needBackup_InputWidgetFlag = iBit(12), + isSensitive_InputWidgetFlag = iBit(1), + isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ + enterPressed_InputWidgetFlag = iBit(3), + selectAllOnFocus_InputWidgetFlag = iBit(4), + notifyEdits_InputWidgetFlag = iBit(5), + eatEscape_InputWidgetFlag = iBit(6), + isMarking_InputWidgetFlag = iBit(7), + markWords_InputWidgetFlag = iBit(8), + needUpdateBuffer_InputWidgetFlag = iBit(9), + enterKeyEnabled_InputWidgetFlag = iBit(10), + lineBreaksEnabled_InputWidgetFlag = iBit(11), + needBackup_InputWidgetFlag = iBit(12), useReturnKeyBehavior_InputWidgetFlag = iBit(13), + //touchBehavior_InputWidgetFlag = iBit(14), /* different behavior depending on interaction method */ + dragCursor_InputWidgetFlag = iBit(14), + dragMarkerStart_InputWidgetFlag = iBit(15), + dragMarkerEnd_InputWidgetFlag = iBit(16), }; /*----------------------------------------------------------------------------------------------*/ @@ -217,6 +221,10 @@ struct Impl_InputWidget { iArray undoStack; int font; iClick click; + uint32_t tapStartTime; + uint32_t lastTapTime; + iInt2 lastTapPos; + int tapCount; int wheelAccum; int cursorVis; uint32_t timer; @@ -460,14 +468,18 @@ static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) { }; } -static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { +static iInt2 relativeCoord_InputWidget_(const iInputWidget *d, iInt2 pos) { /* Relative to the start of the line on which the cursor is. */ - iWrapText wt = wrap_InputWidget_(d, d->cursor.y); - wt.hitChar = wt.text.start + d->cursor.x; + iWrapText wt = wrap_InputWidget_(d, pos.y); + wt.hitChar = wt.text.start + pos.x; measure_WrapText(&wt, d->font); return wt.hitAdvance_out; } +static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { + return relativeCoord_InputWidget_(d, d->cursor); +} + static void updateVisible_InputWidget_(iInputWidget *d) { const int totalWraps = numWrapLines_InputWidget_(d); const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); @@ -632,7 +644,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { init_Widget(w); d->validator = NULL; d->validatorContext = NULL; - setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag | touchDrag_WidgetFlag, iTrue); + setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag, iTrue); #if defined (iPlatformMobile) setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); #endif @@ -662,6 +674,8 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { splitToLines_(&iStringLiteral(""), &d->lines); setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ init_Click(&d->click, d, SDL_BUTTON_LEFT); + d->lastTapTime = 0; + d->tapCount = 0; d->wheelAccum = 0; d->timer = 0; d->cursorVis = 0; @@ -993,7 +1007,7 @@ void begin_InputWidget(iInputWidget *d) { d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; d->cursor = cursorMax_InputWidget_(d); } - else { + else if (~d->inFlags & isMarking_InputWidgetFlag) { iZap(d->mark); } enableEditorKeysInMenus_(iFalse); @@ -1013,9 +1027,10 @@ void end_InputWidget(iInputWidget *d, iBool accept) { splitToLines_(&d->oldText, &d->lines); } d->inFlags |= needUpdateBuffer_InputWidgetFlag; + d->inFlags &= ~isMarking_InputWidgetFlag; startOrStopCursorTimer_InputWidget_(d, iFalse); SDL_StopTextInput(); - setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); + setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse); const char *id = cstr_String(id_Widget(as_Widget(d))); if (!*id) id = "_"; refresh_Widget(w); @@ -1445,88 +1460,31 @@ static iBool checkAcceptMods_InputWidget_(const iInputWidget *d, int mods) { return mods == 0; } -static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { - iWidget *w = as_Widget(d); - /* Resize according to width immediately. */ - if (d->lastUpdateWidth != w->rect.size.x) { - d->inFlags |= needUpdateBuffer_InputWidgetFlag; - if (d->inFlags & isUrl_InputWidgetFlag) { - /* Restore/omit the default scheme if necessary. */ - setText_InputWidget(d, text_InputWidget(d)); - } - updateAllLinesAndResizeHeight_InputWidget_(d); - d->lastUpdateWidth = w->rect.size.x; - } - if (isCommand_Widget(w, ev, "focus.gained")) { - begin_InputWidget(d); - return iFalse; - } - else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") || - isCommand_UserEvent(ev, "window.focus.gained"))) { - startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained")); - d->cursorVis = 1; - refresh_Widget(d); - return iFalse; - } - else if (isCommand_UserEvent(ev, "keyroot.changed")) { - d->inFlags |= needUpdateBuffer_InputWidgetFlag; - } - else if (isCommand_UserEvent(ev, "lang.changed")) { - set_String(&d->hint, &d->srcHint); - translate_Lang(&d->hint); - return iFalse; - } - else if (isCommand_Widget(w, ev, "focus.lost")) { - end_InputWidget(d, iTrue); - return iFalse; - } - else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) && - isEditing_InputWidget_(d)) { - copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut")); - return iTrue; - } - else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) { - paste_InputWidget_(d); - return iTrue; - } - else if (isCommand_UserEvent(ev, "theme.changed")) { - if (d->buffered) { - d->inFlags |= needUpdateBuffer_InputWidgetFlag; - } - return iFalse; - } - else if (isCommand_UserEvent(ev, "keyboard.changed")) { - if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { - iRect rect = bounds_Widget(w); - rect.pos.y -= value_Anim(&get_Window()->rootOffset); - const iInt2 visRoot = visibleSize_Root(w->root); - if (bottom_Rect(rect) > visRoot.y) { - setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); - } - } - return iFalse; - } - else if (isCommand_UserEvent(ev, "text.insert")) { - pushUndo_InputWidget_(d); - deleteMarked_InputWidget_(d); - insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); - contentsWereChanged_InputWidget_(d); - return iTrue; - } - else if (isCommand_Widget(w, ev, "input.backup")) { - if (d->inFlags & needBackup_InputWidgetFlag) { - saveBackup_InputWidget_(d); - } - return iTrue; - } - else if (isMetricsChange_UserEvent(ev)) { - updateMetrics_InputWidget_(d); - // updateLinesAndResize_InputWidget_(d); +enum iEventResult { + ignored_EventResult = 0, /* event was not processed */ + false_EventResult = 1, /* event was processed but other widgets can still process it, too*/ + true_EventResult = 2, /* event was processed and should not be passed on */ +}; + +static void markWordAtCursor_InputWidget_(iInputWidget *d) { + d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); + extendRange_InputWidget_(d, &d->mark.start, -1); + extendRange_InputWidget_(d, &d->mark.end, +1); + d->initialMark = d->mark; +} + +static void showClipMenu_(iInt2 coord) { + iWidget *clipMenu = findWidget_App("clipmenu"); + if (isVisible_Widget(clipMenu)) { + closeMenu_Widget(clipMenu); } - else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { - copy_InputWidget_(d, iFalse); - return iTrue; + else { + openMenuFlags_Widget(clipMenu, coord, iFalse); } +} + +static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); const iInt2 inner = windowToInner_Widget(w, coord); @@ -1559,10 +1517,15 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { d->visWrapLines.start += lineDelta; d->visWrapLines.end += lineDelta; d->inFlags |= needUpdateBuffer_InputWidgetFlag; - refresh_Widget(d); - return iTrue; + refresh_Widget(d); + return true_EventResult; } - return iFalse; + return false_EventResult; + } + if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && + contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { + showClipMenu_(mouseCoord_Window(get_Window(), ev->button.which)); + return iTrue; } switch (processEvent_Click(&d->click, ev)) { case none_ClickResult: @@ -1584,10 +1547,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); if (d->click.count == 2) { d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; - d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); - extendRange_InputWidget_(d, &d->mark.start, -1); - extendRange_InputWidget_(d, &d->mark.end, +1); - d->initialMark = d->mark; + markWordAtCursor_InputWidget_(d); refresh_Widget(w); } if (d->click.count == 3) { @@ -1595,11 +1555,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { } } refresh_Widget(d); - return iTrue; + return true_EventResult; } case aborted_ClickResult: d->inFlags &= ~isMarking_InputWidgetFlag; - return iTrue; + return true_EventResult; case drag_ClickResult: d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); showCursor_InputWidget_(d); @@ -1614,30 +1574,374 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; } refresh_Widget(w); - return iTrue; + return true_EventResult; case finished_ClickResult: d->inFlags &= ~isMarking_InputWidgetFlag; - return iTrue; + return true_EventResult; } if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); if (contains_Click(&d->click, coord)) { - return iTrue; + return true_EventResult; } } - if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && - contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { - iWidget *clipMenu = findWidget_App("clipmenu"); - if (isVisible_Widget(clipMenu)) { - closeMenu_Widget(clipMenu); + return ignored_EventResult; +} + +static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { + /* Clamp to the bounds so the cursor doesn't wrap at the ends. */ + iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); + bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; + return coordCursor_InputWidget_(d, min_I2(bottomRight_Rect(bounds), + max_I2(coord, topLeft_Rect(bounds)))); +} + +static iBool isInsideMark_InputWidget_(const iInputWidget *d, size_t pos) { + const iRanges mark = mark_InputWidget_(d); + return contains_Range(&mark, pos); +} + +static int distanceToPos_InputWidget_(const iInputWidget *d, iInt2 uiCoord, iInt2 textPos) { + const iInt2 a = addY_I2(relativeCoord_InputWidget_(d, textPos), lineHeight_Text(d->font) / 2); + const iInt2 b = sub_I2(uiCoord, topLeft_Rect(contentBounds_InputWidget_(d))); + return dist_I2(a, b); +} + +static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); + /* + + first tap to focus & select all/place cursor + + focused tap to place cursor + - drag cursor to move it + - double-click to select a word + - drag to move selection handles + - long-press for context menu: copy, paste, delete, select all, deselect + - double-click and hold to select words + - triple-click to select all + - drag/wheel elsewhere to scroll (contents or overflow), no change in focus + */ +// if (ev->type != SDL_MOUSEBUTTONUP && ev->type != SDL_MOUSEBUTTONDOWN && +// ev->type != SDL_MOUSEWHEEL && ev->type != SDL_MOUSEMOTION && +// !(ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) && +// !(ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { +// return ignored_EventResult; +// } + if (isFocused_Widget(w)) { + if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) { + d->lastTapTime = d->tapStartTime; + d->tapStartTime = SDL_GetTicks(); + const int tapDist = dist_I2(latestPosition_Touch(), d->lastTapPos); + d->lastTapPos = latestPosition_Touch(); + printf("[%p] tap start time: %u (%u) %d\n", w, d->tapStartTime, d->tapStartTime - d->lastTapTime, tapDist); + if (d->tapStartTime - d->lastTapTime < 400 && tapDist < gap_UI * 4) { + d->tapCount++; + printf("[%p] >> tap count: %d\n", w, d->tapCount); + } + else { + d->tapCount = 0; + } + if (!isEmpty_Range(&d->mark)) { + const int dist[2] = { + distanceToPos_InputWidget_(d, latestPosition_Touch(), + indexToCursor_InputWidget_(d, d->mark.start)), + distanceToPos_InputWidget_(d, latestPosition_Touch(), + indexToCursor_InputWidget_(d, d->mark.end)) + }; + if (dist[0] < dist[1]) { + printf("[%p] begin marker start drag\n", w); + d->inFlags |= dragMarkerStart_InputWidgetFlag; + } + else { + printf("[%p] begin marker end drag\n", w); + d->inFlags |= dragMarkerEnd_InputWidgetFlag; + } + d->inFlags |= isMarking_InputWidgetFlag; + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + } + else { + const int dist = distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor); + printf("[%p] tap dist: %d\n", w, dist); + if (dist < gap_UI * 10) { + printf("[%p] begin cursor drag\n", w); + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + d->inFlags |= dragCursor_InputWidgetFlag; +// d->inFlags |= touchBehavior_InputWidgetFlag; +// setMouseGrab_Widget(w); +// return iTrue; + } + } +// if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { +// d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); +// showCursor_InputWidget_(d); +// } + return true_EventResult; + } + } +#if 0 + else if (isFocused_Widget(w)) { + if (ev->type == SDL_MOUSEMOTION) { + if (~d->inFlags & touchBehavior_InputWidgetFlag) { + const iInt2 curPos = relativeCursorCoord_InputWidget_(d); + const iInt2 relClick = sub_I2(pos_Click(&d->click), + topLeft_Rect(contentBounds_InputWidget_(d))); + if (dist_I2(curPos, relClick) < gap_UI * 8) { + printf("tap on cursor!\n"); + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + d->inFlags |= touchBehavior_InputWidgetFlag; + printf("[Input] begin cursor drag\n"); + setMouseGrab_Widget(w); + return iTrue; + } + } + else if (ev->motion.x > 0 && ev->motion.y > 0) { + printf("[Input] cursor being dragged\n"); + iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); + bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; + iInt2 mpos = init_I2(ev->motion.x, ev->motion.y); + mpos = min_I2(bottomRight_Rect(bounds), max_I2(mpos, topLeft_Rect(bounds))); + d->cursor = coordCursor_InputWidget_(d, mpos); + showCursor_InputWidget_(d); + refresh_Widget(w); + return iTrue; + } } - else { - openMenuFlags_Widget(clipMenu, - mouseCoord_Window(get_Window(), ev->button.which), - iFalse); + if (d->inFlags & touchBehavior_InputWidgetFlag) { + if (ev->type == SDL_MOUSEBUTTONUP || + (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { + d->inFlags &= ~touchBehavior_InputWidgetFlag; + setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); + setMouseGrab_Widget(NULL); + printf("[Input] touch ends\n"); + return iFalse; + } + } + } +#endif +#if 1 + if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && + ev->button.button == SDL_BUTTON_RIGHT && contains_Widget(w, latestPosition_Touch())) { + if (ev->type == SDL_MOUSEBUTTONDOWN) { + /*if (isFocused_Widget(w)) { + d->inFlags |= isMarking_InputWidgetFlag; + d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); + markWordAtCursor_InputWidget_(d); + refresh_Widget(d); + return true_EventResult; + }*/ + setFocus_Widget(w); + d->inFlags |= isMarking_InputWidgetFlag; + d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); + markWordAtCursor_InputWidget_(d); + d->cursor = indexToCursor_InputWidget_(d, d->mark.end); + refresh_Widget(d); + } + return true_EventResult; + } + switch (processEvent_Click(&d->click, ev)) { + case none_ClickResult: + break; + case started_ClickResult: { + printf("[%p] started\n", w); + /* + const iInt2 curPos = relativeCursorCoord_InputWidget_(d); + const iInt2 relClick = sub_I2(pos_Click(&d->click), + topLeft_Rect(contentBounds_InputWidget_(d))); + if (dist_I2(curPos, relClick) < gap_UI * 8) { + printf("tap on cursor!\n"); + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + } + else { + printf("tap elsewhere\n"); + }*/ + return true_EventResult; + } + case drag_ClickResult: + printf("[%p] drag %d,%d\n", w, pos_Click(&d->click).x, pos_Click(&d->click).y); + if (d->inFlags & dragCursor_InputWidgetFlag) { + iZap(d->mark); + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + refresh_Widget(w); + } + else if (d->inFlags & dragMarkerStart_InputWidgetFlag) { + d->mark.start = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); + refresh_Widget(w); + } + else if (d->inFlags & dragMarkerEnd_InputWidgetFlag) { + d->mark.end = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); + refresh_Widget(w); + } + return true_EventResult; + // printf("[%p] aborted\n", w); +// d->inFlags &= ~touchBehavior_InputWidgetFlag; +// setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); +// return true_EventResult; + case finished_ClickResult: + case aborted_ClickResult: + printf("[%p] ended\n", w); + uint32_t tapElapsed = SDL_GetTicks() - d->tapStartTime; + printf("tapElapsed: %u\n", tapElapsed); + if (!isFocused_Widget(w)) { + setFocus_Widget(w); + d->lastTapPos = latestPosition_Touch(); + d->tapStartTime = SDL_GetTicks(); + d->tapCount = 0; + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + } + else if (!isEmpty_Range(&d->mark) && !isMoved_Click(&d->click)) { + if (isInsideMark_InputWidget_(d, cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, latestPosition_Touch())))) { + showClipMenu_(latestPosition_Touch()); + } + else { + iZap(d->mark); + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + } + } + else if (SDL_GetTicks() - d->lastTapTime > 1000 && + d->tapCount == 0 && isEmpty_Range(&d->mark) && !isMoved_Click(&d->click) && + distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor) < gap_UI * 5) { + showClipMenu_(latestPosition_Touch()); + } + else { + if (~d->inFlags & isMarking_InputWidgetFlag) { + iZap(d->mark); + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + } + } + if (d->inFlags & (dragCursor_InputWidgetFlag | dragMarkerStart_InputWidgetFlag | + dragMarkerEnd_InputWidgetFlag)) { + printf("[%p] finished cursor/marker drag\n", w); + d->inFlags &= ~(dragCursor_InputWidgetFlag | + dragMarkerStart_InputWidgetFlag | + dragMarkerEnd_InputWidgetFlag); + setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); + } + d->inFlags &= ~isMarking_InputWidgetFlag; + showCursor_InputWidget_(d); + refresh_Widget(w); +#if 0 + d->inFlags &= ~touchBehavior_InputWidgetFlag; + if (flags_Widget(w) & touchDrag_WidgetFlag) { + setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); + return true_EventResult; + } + if (!isMoved_Click(&d->click)) { + if (!isFocused_Widget(w)) { + setFocus_Widget(w); + if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { + d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + } + } + else { + iZap(d->mark); + d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + } + } +#endif + return true_EventResult; + } +#endif +// if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && +// contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { +// /* Eat all mouse clicks on the widget. */ +// return true_EventResult; +// } + return ignored_EventResult; +} + +static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); + /* Resize according to width immediately. */ + if (d->lastUpdateWidth != w->rect.size.x) { + d->inFlags |= needUpdateBuffer_InputWidgetFlag; + if (d->inFlags & isUrl_InputWidgetFlag) { + /* Restore/omit the default scheme if necessary. */ + setText_InputWidget(d, text_InputWidget(d)); } + updateAllLinesAndResizeHeight_InputWidget_(d); + d->lastUpdateWidth = w->rect.size.x; + } + if (isCommand_Widget(w, ev, "focus.gained")) { + begin_InputWidget(d); + return iFalse; + } + else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") || + isCommand_UserEvent(ev, "window.focus.gained"))) { + startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained")); + d->cursorVis = 1; + refresh_Widget(d); + return iFalse; + } + else if (isCommand_UserEvent(ev, "keyroot.changed")) { + d->inFlags |= needUpdateBuffer_InputWidgetFlag; + } + else if (isCommand_UserEvent(ev, "lang.changed")) { + set_String(&d->hint, &d->srcHint); + translate_Lang(&d->hint); + return iFalse; + } + else if (isCommand_Widget(w, ev, "focus.lost")) { + end_InputWidget(d, iTrue); + return iFalse; + } + else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) && + isEditing_InputWidget_(d)) { + copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut")); + return iTrue; + } + else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) { + paste_InputWidget_(d); return iTrue; } + else if (isCommand_UserEvent(ev, "theme.changed")) { + if (d->buffered) { + d->inFlags |= needUpdateBuffer_InputWidgetFlag; + } + return iFalse; + } +// else if (isCommand_UserEvent(ev, "keyboard.changed")) { +// if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { +// iRect rect = bounds_Widget(w); +// rect.pos.y -= value_Anim(&get_Window()->rootOffset); +// const iInt2 visRoot = visibleSize_Root(w->root); +// if (bottom_Rect(rect) > visRoot.y) { +// setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); +// } +// } +// return iFalse; +// } + else if (isCommand_UserEvent(ev, "text.insert")) { + pushUndo_InputWidget_(d); + deleteMarked_InputWidget_(d); + insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); + contentsWereChanged_InputWidget_(d); + return iTrue; + } + else if (isCommand_Widget(w, ev, "input.backup")) { + if (d->inFlags & needBackup_InputWidgetFlag) { + saveBackup_InputWidget_(d); + } + return iTrue; + } + else if (isMetricsChange_UserEvent(ev)) { + updateMetrics_InputWidget_(d); + // updateLinesAndResize_InputWidget_(d); + } + else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { + copy_InputWidget_(d, iFalse); + return iTrue; + } + /* Click behavior depends on device type. */ { + const int mbResult = (deviceType_App() == desktop_AppDeviceType + ? processPointerEvents_InputWidget_(d, ev) + : processTouchEvents_InputWidget_(d, ev)); + if (mbResult) { + return mbResult >> 1; + } + } if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { return iTrue; } @@ -1884,6 +2188,8 @@ struct Impl_MarkPainter { const iInputLine * line; iInt2 pos; iRanges mark; + iRect firstMarkRect; + iRect lastMarkRect; }; 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 } rect.size.x = iMax(gap_UI / 3, rect.size.x); mp->pos.y += lineHeight_Text(mp->d->font); - fillRect_Paint(mp->paint, rect, uiMarked_ColorId); + fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId); + if (deviceType_App() != desktop_AppDeviceType) { + if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect; + mp->lastMarkRect = rect; + } return iTrue; } @@ -1962,6 +2272,7 @@ static void draw_InputWidget_(const iInputWidget *d) { }; const iRangei visLines = visibleLineRange_InputWidget_(d); const int visLineOffsetY = visLineOffsetY_InputWidget_(d); + iRect markerRects[2]; /* If buffered, just draw the buffered copy. */ if (d->buffered && !isFocused) { /* 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) { .paint = &p, .d = d, .contentBounds = contentBounds, - .mark = mark_InputWidget_(d) + .mark = mark_InputWidget_(d), }; wrapText.context = ▮ 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) { marker.pos = drawPos; addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */ } + markerRects[0] = marker.firstMarkRect; + markerRects[1] = marker.lastMarkRect; wrapText.wrapFunc = NULL; wrapText.context = NULL; } /* Draw the insertion point. */ - if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y)) { + if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) && + isEmpty_Range(&d->mark)) { iInt2 curSize; iRangecc cursorChar = iNullRange; int visWrapsAbove = 0; @@ -2040,6 +2354,11 @@ static void draw_InputWidget_(const iInputWidget *d) { } } unsetClip_Paint(&p); + if (!isEmpty_Rect(markerRects[0])) { + for (int i = 0; i < 2; ++i) { + drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId); + } + } drawChildren_Widget(w); } 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) { static void setColor_Paint_(const iPaint *d, int color) { const iColor clr = get_Color(color & mask_ColorId); - SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b, clr.a * d->alpha / 255); + SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b, + (color & opaque_ColorId ? 255 : clr.a) * d->alpha / 255); } void init_Paint(iPaint *d) { @@ -186,6 +187,22 @@ void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t n, int color) free(offsetPoints); } +void drawPin_Paint(iPaint *d, iRect rangeRect, int dir, int pinColor) { + const int height = height_Rect(rangeRect); + iRect pin; + if (dir == 0) { + pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)), + init_I2(gap_UI / 2, height + gap_UI) }; + } + else { + pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4), + init_I2(gap_UI / 2, height + gap_UI) }; + } + fillRect_Paint(d, pin, pinColor); + fillRect_Paint(d, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin), + init1_I2(gap_UI * 2)), pinColor); +} + iInt2 size_SDLTexture(SDL_Texture *d) { iInt2 size; 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) { drawLine_Paint(d, pos, addY_I2(pos, len), color); } +void drawPin_Paint (iPaint *, iRect rangeRect, int dir, int pinColor); + iInt2 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) { { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" }, { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" }, { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" }, - }, + }, 6); iWidget *barMenu = makeMenu_Widget(root, (iMenuItem[]){ { leftHalf_Icon " ${menu.sidebar.left}", 0, 0, "sidebar.toggle" }, { rightHalf_Icon " ${menu.sidebar.right}", 0, 0, "sidebar2.toggle" }, - }, + }, deviceType_App() == phone_AppDeviceType ? 1 : 2); iWidget *clipMenu = makeMenu_Widget(root, - (iMenuItem[]){ - { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, - { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, - { "---" }, - { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, - }, - 4); +#if defined (iPlatformMobile) + (iMenuItem[]){ + { ">>>" scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, + { ">>>" clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, + { ">>>" clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, + { "---" }, + { ">>>" delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" }, + { ">>>" select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, + { ">>>" undo_Icon " ${menu.undo}", 0, 0, "input.undo" }, + }, 7); +#else + (iMenuItem[]){ + { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, + { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, + { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, + { "---" }, + { delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" }, + { undo_Icon " ${menu.undo}", 0, 0, "input.undo" }, + { "---" }, + { select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, + }, 8); +#endif iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){ { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, { "${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) { } if (elapsed > 50 && !touch->isTapBegun) { /* Looks like a possible tap. */ + touchState_()->currentTouchPos = initF3_I2(touch->pos[0]); dispatchNotification_Touch_(touch, widgetTapBegins_UserEventCode); dispatchMotion_Touch_(touch->pos[0], 0); refresh_Widget(touch->affinity); @@ -471,13 +472,13 @@ iBool processEvent_Touch(const SDL_Event *ev) { } iTouchState *d = touchState_(); iWindow *window = get_Window(); - if (!isFinished_Anim(&window->rootOffset)) { - return iFalse; - } +// if (!isFinished_Anim(&window->rootOffset)) { +// return iFalse; +// } const iInt2 rootSize = size_Window(window); const SDL_TouchFingerEvent *fing = &ev->tfinger; const iFloat3 pos = add_F3(init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0), /* pixels */ - init_F3(0, -value_Anim(&window->rootOffset), 0)); + init_F3(0, 0 /*-value_Anim(&window->rootOffset)*/, 0)); const uint32_t nowTime = SDL_GetTicks(); if (ev->type == SDL_FINGERDOWN) { /* 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) { setFrameColor_Widget(menu, uiSeparator_ColorId); } iBool haveIcons = iFalse; + iWidget *horizGroup = NULL; for (size_t i = 0; i < n; ++i) { const iMenuItem *item = &items[i]; if (!item->label) { break; } - if (equal_CStr(item->label, "---")) { + const char *labelText = item->label; + if (!startsWith_CStr(labelText, ">>>")) { + horizGroup = NULL; + } + if (equal_CStr(labelText, "---")) { addChild_Widget(menu, iClob(makeMenuSeparator_())); } else { iBool isInfo = iFalse; - const char *labelText = item->label; + if (startsWith_CStr(labelText, ">>>")) { + labelText += 3; + if (!horizGroup) { + horizGroup = makeHDiv_Widget(); + setFlags_Widget(horizGroup, resizeHeightOfChildren_WidgetFlag, iFalse); + setFlags_Widget(horizGroup, arrangeHeight_WidgetFlag, iTrue); + addChild_Widget(menu, iClob(horizGroup)); + } + } if (startsWith_CStr(labelText, "```")) { labelText += 3; isInfo = iTrue; } iLabelWidget *label = addChildFlags_Widget( - menu, + horizGroup ? horizGroup : menu, iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | drawKey_WidgetFlag | itemFlags); @@ -766,6 +779,34 @@ void openMenu_Widget(iWidget *d, iInt2 windowCoord) { openMenuFlags_Widget(d, windowCoord, iTrue); } +static void updateMenuItemFonts_Widget_(iWidget *d) { + const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); + const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; + iForEach(ObjectList, i, children_Widget(d)) { + if (isInstance_Object(i.object, &Class_LabelWidget)) { + iLabelWidget *label = i.object; + const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); + if (isWrapped_LabelWidget(label)) { + continue; + } + if (deviceType_App() == desktop_AppDeviceType) { + setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); + } + else if (isPortraitPhone) { + if (!isSlidePanel) { + setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); + } + } + else { + setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); + } + } + else if (childCount_Widget(i.object)) { + updateMenuItemFonts_Widget_(i.object); + } + } +} + void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { const iRect rootRect = rect_Root(d->root); const iInt2 rootSize = rootRect.size; @@ -788,28 +829,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { } d->rect.size.x = rootSize.x; } - /* Update item fonts. */ { - iForEach(ObjectList, i, children_Widget(d)) { - if (isInstance_Object(i.object, &Class_LabelWidget)) { - iLabelWidget *label = i.object; - const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); - if (isWrapped_LabelWidget(label)) { - continue; - } - if (deviceType_App() == desktop_AppDeviceType) { - setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); - } - else if (isPortraitPhone) { - if (!isSlidePanel) { - setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); - } - } - else { - setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); - } - } - } - } + updateMenuItemFonts_Widget_(d); arrange_Widget(d); if (isPortraitPhone) { 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) { applyVisualOffset_Widget_(w, &pos); addv_I2(&window, pos); } -#if defined (iPlatformMobile) - window.y += value_Anim(&get_Window()->rootOffset); -#endif +//#if defined (iPlatformMobile) +// window.y += value_Anim(&get_Window()->rootOffset); +//#endif return window; } @@ -1072,23 +1072,33 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { } iBool scrollOverflow_Widget(iWidget *d, int delta) { - iRect bounds = boundsWithoutVisualOffset_Widget(d); - const iInt2 rootSize = size_Root(d->root); - const iRect winRect = safeRect_Root(d->root); - const int yTop = top_Rect(winRect); - const int yBottom = bottom_Rect(winRect); + iRect bounds = boundsWithoutVisualOffset_Widget(d); +// const iInt2 rootSize = size_Root(d->root); + const iRect winRect = adjusted_Rect(safeRect_Root(d->root), + zero_I2(), + init_I2(0, -get_Window()->keyboardHeight)); + const int yTop = top_Rect(winRect); + const int yBottom = bottom_Rect(winRect); if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { return iFalse; /* fits inside just fine */ } //const int safeBottom = rootSize.y - yBottom; - bounds.pos.y += delta; - const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; + iRangei validPosRange = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; + if (validPosRange.start > validPosRange.end) { + validPosRange.start = validPosRange.end; /* no room to scroll */ + } + if (delta) { + if (delta < 0 && bounds.pos.y < validPosRange.start) { + delta = 0; + } + if (delta > 0 && bounds.pos.y > validPosRange.end) { + delta = 0; + } + bounds.pos.y += delta; // printf("range: %d ... %d\n", range.start, range.end); - if (range.start >= range.end) { - bounds.pos.y = range.end; } else { - bounds.pos.y = iClamp(bounds.pos.y, range.start, range.end); + bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end); } // if (delta >= 0) { // bounds.pos.y = iMin(bounds.pos.y, yTop); @@ -1454,7 +1464,7 @@ void setDrawBufferEnabled_Widget(iWidget *d, iBool enable) { static void beginBufferDraw_Widget_(const iWidget *d) { if (d->drawBuf) { - printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); +// printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); if (d->drawBuf->isValid) { iAssert(!isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size)); // printf(" drawBuf:%dx%d boundsForDraw:%dx%d\n", @@ -1503,7 +1513,7 @@ void draw_Widget(const iWidget *d) { endBufferDraw_Widget_(d); } if (d->drawBuf) { - iAssert(d->drawBuf->isValid); + //iAssert(d->drawBuf->isValid); const iRect bounds = bounds_Widget(d); SDL_RenderCopy(renderer_Window(get_Window()), d->drawBuf->texture, NULL, &(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) { d->ignoreClick = iFalse; d->focusGainedAt = 0; d->keyboardHeight = 0; - init_Anim(&d->rootOffset, 0.0f); +// init_Anim(&d->rootOffset, 0.0f); uint32_t flags = 0; #if defined (iPlatformAppleDesktop) SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); @@ -1215,10 +1215,10 @@ iBool isOpenGLRenderer_Window(void) { void setKeyboardHeight_Window(iWindow *d, int height) { if (d->keyboardHeight != height) { d->keyboardHeight = height; - if (height == 0) { - setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue); - setValue_Anim(&d->rootOffset, 0, 250); - } +// if (height == 0) { +// setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue); +// setValue_Anim(&d->rootOffset, 0, 250); +// } postCommandf_App("keyboard.changed arg:%d", height); postRefresh_App(); } 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 { SDL_Cursor * cursors[SDL_NUM_SYSTEM_CURSORS]; SDL_Cursor * pendingCursor; int loadAnimTimer; - iAnim rootOffset; +// iAnim rootOffset; int keyboardHeight; /* mobile software keyboards */ }; -- cgit v1.2.3