From 73920030fff7ab28509b9c4b179113e72e8dace6 Mon Sep 17 00:00:00 2001 From: nakst <> Date: Sun, 5 Dec 2021 17:30:26 +0000 Subject: [PATCH] update actions for textbox ensure caret visible --- desktop/gui.cpp | 31 ++++++++++++++++++++++--------- desktop/os.header | 8 +++++++- desktop/text.cpp | 46 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/desktop/gui.cpp b/desktop/gui.cpp index f2428ad..356dfec 100644 --- a/desktop/gui.cpp +++ b/desktop/gui.cpp @@ -106,7 +106,7 @@ EsElement *UIFindHoverElementRecursively(EsElement *element, int offsetX, int of const EsStyle *UIGetDefaultStyleVariant(const EsStyle *style, EsElement *parent); void AccessKeysCenterHint(EsElement *element, EsMessage *message); void UIRemoveFocusFromElement(EsElement *oldFocus); -void UIQueueEnsureVisibleMessage(EsElement *element); +void UIQueueEnsureVisibleMessage(EsElement *element, bool center); void ColorPickerCreate(EsElement *parent, struct ColorPickerHost host, uint32_t initialColor, bool showTextbox); void InspectorSetup(EsWindow *window); @@ -134,6 +134,7 @@ void InspectorNotifyElementContentChanged(EsElement *element); #define UI_STATE_ANIMATING (1 << 9) #define UI_STATE_CHECK_VISIBLE (1 << 10) #define UI_STATE_QUEUED_ENSURE_VISIBLE (1 << 11) +#define UI_STATE_ENSURE_VISIBLE_CENTER (1 << 12) // Behaviour modifiers: #define UI_STATE_STRONG_PRESSED (1 << 12) @@ -2588,6 +2589,8 @@ struct Scrollbar : EsElement { }; void ScrollbarLayout(Scrollbar *scrollbar) { + // TODO Do this as an UpdateAction? + if (scrollbar->viewportSize >= scrollbar->contentSize || scrollbar->viewportSize <= 0 || scrollbar->contentSize <= 0) { EsElementSetDisabled(scrollbar, true); } else { @@ -3194,12 +3197,16 @@ int ProcessPanelMessage(EsElement *element, EsMessage *message) { } } else if (message->type == ES_MSG_ENSURE_VISIBLE) { if (panel->scroll.enabled[0] || panel->scroll.enabled[1]) { - EsElement *child = message->child, *e = child; - int offsetX = panel->scroll.position[0], offsetY = panel->scroll.position[1]; + EsElement *child = message->ensureVisible.descendent, *e = child; + int offsetX = 0, offsetY = 0; while (e != element) offsetX += e->offsetX, offsetY += e->offsetY, e = e->parent; EsRectangle bounds = panel->GetBounds(); - panel->scroll.SetX(offsetX + child->width / 2 - bounds.r / 2); - panel->scroll.SetY(offsetY + child->height / 2 - bounds.b / 2); + + if (message->ensureVisible.center || !EsRectangleContainsAll(bounds, ES_RECT_4PD(offsetX, offsetY, child->width, child->height))) { + panel->scroll.SetX(offsetX + panel->scroll.position[0] + child->width / 2 - bounds.r / 2); + panel->scroll.SetY(offsetY + panel->scroll.position[1] + child->height / 2 - bounds.b / 2); + } + return ES_HANDLED; } else { // This is not a scroll container, so don't update the child element being made visible. @@ -6161,12 +6168,14 @@ bool EsElementIsFocused(EsElement *element) { void UISendEnsureVisibleMessage(EsElement *element, EsGeneric) { EsElement *child = element, *e = element; + bool center = element->state & UI_STATE_ENSURE_VISIBLE_CENTER; EsAssert(element->state & UI_STATE_QUEUED_ENSURE_VISIBLE); - element->state &= ~UI_STATE_QUEUED_ENSURE_VISIBLE; + element->state &= ~(UI_STATE_QUEUED_ENSURE_VISIBLE | UI_STATE_ENSURE_VISIBLE_CENTER); while (e->parent) { EsMessage m = { ES_MSG_ENSURE_VISIBLE }; - m.child = child; + m.ensureVisible.descendent = child; + m.ensureVisible.center = center; e = e->parent; if (ES_HANDLED == EsMessageSend(e, &m)) { @@ -6177,7 +6186,11 @@ void UISendEnsureVisibleMessage(EsElement *element, EsGeneric) { EsAssert(~element->state & UI_STATE_QUEUED_ENSURE_VISIBLE); } -void UIQueueEnsureVisibleMessage(EsElement *element) { +void UIQueueEnsureVisibleMessage(EsElement *element, bool center) { + if (center) { + element->state |= UI_STATE_ENSURE_VISIBLE_CENTER; + } + if (~element->state & UI_STATE_QUEUED_ENSURE_VISIBLE) { element->state |= UI_STATE_QUEUED_ENSURE_VISIBLE; UpdateAction action = {}; @@ -6258,7 +6271,7 @@ void EsElementFocus(EsElement *element, uint32_t flags) { // Ensure the element is visible. if ((flags & ES_ELEMENT_FOCUS_ENSURE_VISIBLE) && element) { - UIQueueEnsureVisibleMessage(element); + UIQueueEnsureVisibleMessage(element, true); } } diff --git a/desktop/os.header b/desktop/os.header index f2cacfa..118198a 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -1632,6 +1632,11 @@ struct EsMessageScroll { int scroll, previous; }; +struct EsMessageEnsureVisible { + bool center; // If false, only center the element if it is partially obscured. + EsElement *descendent; +}; + // List view messages. struct EsMessageIterateIndex { @@ -1862,11 +1867,12 @@ struct EsMessage { EsMessageScrollWheel scrollWheel; EsMessageWindowActivated windowActivated; EsMessageScroll scroll; + EsMessageEnsureVisible ensureVisible; const EsStyle *childStyleVariant; EsRectangle *accessKeyHintBounds; EsPainter *painter; - EsElement *child; EsCursorStyle cursorStyle; + EsElement *child; // List view messages: EsMessageIterateIndex iterateIndex; diff --git a/desktop/text.cpp b/desktop/text.cpp index 32ec698..72dc07b 100644 --- a/desktop/text.cpp +++ b/desktop/text.cpp @@ -2919,6 +2919,8 @@ struct EsTextbox : EsElement { char *editStartContent; int32_t editStartContentBytes; + bool ensureCaretVisibleQueued; + EsUICallback overlayCallback; EsGeneric overlayData; @@ -2926,7 +2928,7 @@ struct EsTextbox : EsElement { uintptr_t activeLineAllocated; int32_t activeLineIndex, activeLineStart, activeLineOldBytes, activeLineBytes; - int32_t longestLine, longestLineWidth; // To set the horizontal scrollbar's size. + int32_t longestLine, longestLineWidth; // To set the horizontal scroll bar's size. TextboxCaret carets[2]; // carets[1] is the actual caret; carets[0] is the selection anchor. TextboxCaret wordSelectionAnchor, wordSelectionAnchor2; @@ -3341,11 +3343,22 @@ TextboxVisibleLine *TextboxGetVisibleLine(EsTextbox *textbox, int32_t documentLi ? nullptr : &textbox->visibleLines[documentLineIndex - textbox->firstVisibleLine]; } -void EsTextboxEnsureCaretVisible(EsTextbox *textbox, bool verticallyCenter) { +void TextboxEnsureCaretVisibleActionCallback(EsElement *element, EsGeneric context) { + EsTextbox *textbox = (EsTextbox *) element; + bool verticallyCenter = context.u; TextboxCaret caret = textbox->carets[1]; - EsRectangle bounds = textbox->GetBounds(); - { + EsPrint("TextboxEnsureCaretVisibleActionCallback ------------\n"); + + for (uintptr_t i = 0; i < 3; i++) { + // ScrollPane::SetY causes ES_MSG_SCROLL_Y to get sent to the textbox. + // This causes a TextboxRefreshVisibleLines, which may cause new lines to added. + // If these lines had not been previously horizontally measured, this will then occur. + // This then causes a ScrollPane::Refresh for the new horizontal width. + // If this causes the horizontal scroll bar to appear, then the caret may no longer be fully visible. + // Therefore, we repeat up to 3 times to ensure that the caret is definitely fully visible. + + EsRectangle bounds = textbox->GetBounds(); DocumentLine *line = &textbox->lines[caret.line]; int caretY = line->yPosition + textbox->insets.t; @@ -3364,13 +3377,21 @@ void EsTextboxEnsureCaretVisible(EsTextbox *textbox, bool verticallyCenter) { } } - textbox->scroll.SetY(scrollY); + if (textbox->scroll.position[1] != scrollY) { + EsPrint(" new scroll %d\n", scrollY); + textbox->scroll.SetY(scrollY); + } else { + break; + } + } else { + break; } } TextboxVisibleLine *visibleLine = TextboxGetVisibleLine(textbox, caret.line); if (visibleLine) { + EsRectangle bounds = textbox->GetBounds(); DocumentLine *line = &textbox->lines[caret.line]; int scrollX = textbox->scroll.position[0]; int viewportWidth = bounds.r; @@ -3386,7 +3407,19 @@ void EsTextboxEnsureCaretVisible(EsTextbox *textbox, bool verticallyCenter) { textbox->scroll.SetX(scrollX); } - UIQueueEnsureVisibleMessage(textbox); + UIQueueEnsureVisibleMessage(textbox, false); + textbox->ensureCaretVisibleQueued = false; +} + +void EsTextboxEnsureCaretVisible(EsTextbox *textbox, bool verticallyCenter) { + if (!textbox->ensureCaretVisibleQueued) { + UpdateAction action = {}; + action.element = textbox; + action.callback = TextboxEnsureCaretVisibleActionCallback; + action.context.u = verticallyCenter; + textbox->window->updateActions.Add(action); + textbox->ensureCaretVisibleQueued = true; + } } bool TextboxMoveCaret(EsTextbox *textbox, TextboxCaret *caret, bool right, int moveType, bool strongWhitespace = false) { @@ -3667,6 +3700,7 @@ void TextboxUndoItemCallback(const void *item, EsUndoManager *manager, EsMessage textbox->carets[0] = header->caretsBefore[0]; textbox->carets[1] = header->caretsBefore[1]; EsTextboxInsert(textbox, (const char *) (header + 1), header->insertBytes, true); + EsTextboxEnsureCaretVisible(textbox); } else if (message->type == ES_MSG_UNDO_CANCEL) { // Nothing to do. }