From 1496a2a027770f0b50bbb7bbcb0c7f11f022b906 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 27 Jul 2020 13:59:45 +0300 Subject: DocumentWidget: Marking a selection with the mouse --- src/gmdocument.c | 26 +++++++++++++++++--- src/gmdocument.h | 8 ++++-- src/ui/documentwidget.c | 65 +++++++++++++++++++++++++++++++++++++------------ src/ui/text.c | 27 ++++++++++++++------ src/ui/text.h | 4 ++- 5 files changed, 102 insertions(+), 28 deletions(-) diff --git a/src/gmdocument.c b/src/gmdocument.c index 05ec5141..0a0966be 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -272,8 +272,13 @@ static void doLayout_GmDocument_(iGmDocument *d) { while (!isEmpty_Range(&runLine)) { run.bounds.pos = addX_I2(pos, indent * gap_UI); const char *contPos; - run.bounds.size = tryAdvanceRange_Text( - run.font, runLine, isPreformat ? 0 : (d->size.x - run.bounds.pos.x), &contPos); + const int avail = d->size.x - run.bounds.pos.x; + const iInt2 dims = + tryAdvance_Text(run.font, runLine, isPreformat ? 0 : avail, &contPos); + run.bounds.size.x = iMax(avail, dims.x); /* Extends to the right edge for selection. */ + run.bounds.size.y = dims.y; + run.visBounds = run.bounds; + run.visBounds.size.x = dims.x; if (contPos > runLine.start) { run.text = (iRangecc){ runLine.start, contPos }; } @@ -435,7 +440,15 @@ const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) { return NULL; } -const iGmRun *findRunCStr_GmDocument(const iGmDocument *d, const char *textCStr) { +const char *findLoc_GmDocument(const iGmDocument *d, iInt2 pos) { + const iGmRun *run = findRun_GmDocument(d, pos); + if (run) { + return findLoc_GmRun(run, pos); + } + return NULL; +} + +const iGmRun *findRunAtLoc_GmDocument(const iGmDocument *d, const char *textCStr) { iConstForEach(Array, i, &d->layout) { const iGmRun *run = i.value; if (contains_Range(&run->text, textCStr) || run->text.start > textCStr /* went past */) { @@ -457,4 +470,11 @@ const iString *title_GmDocument(const iGmDocument *d) { return &d->title; } +const char *findLoc_GmRun(const iGmRun *d, iInt2 pos) { + const int x = pos.x - left_Rect(d->bounds); + const char *loc; + tryAdvanceNoWrap_Text(d->font, d->text, x, &loc); + return loc; +} + iDefineClass(GmDocument) diff --git a/src/gmdocument.h b/src/gmdocument.h index 6972d328..f392a9f2 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -11,12 +11,15 @@ typedef uint16_t iGmLinkId; struct Impl_GmRun { iRangecc text; - iRect bounds; /* advance metrics */ + iRect bounds; /* used for hit testing, extends to edge */ + iRect visBounds; /* actual text bounds */ uint8_t font; uint8_t color; iGmLinkId linkId; /* zero for non-links */ }; +const char * findLoc_GmRun (const iGmRun *, iInt2 pos); + iDeclareClass(GmDocument) iDeclareObjectConstruction(GmDocument) @@ -32,6 +35,7 @@ iRangecc findText_GmDocument (const iGmDocument *, const iString iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos); -const iGmRun * findRunCStr_GmDocument (const iGmDocument *, const char *textCStr); +const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos); +const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc); const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); const iString * title_GmDocument (const iGmDocument *); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 9c6fe718..bc1b8e66 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -112,6 +112,10 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { return rect; } +static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { + return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), d->scrollY); +} + static void requestUpdated_DocumentWidget_(iAnyObject *obj) { iDocumentWidget *d = obj; const int wasPending = exchange_Atomic(&d->isSourcePending, iTrue); @@ -368,6 +372,10 @@ const iString *valueString_Command(const char *cmd, const char *label) { return collect_String(newCStr_String(suffixPtr_Command(cmd, label))); } +static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { + return findLoc_GmDocument(d->doc, documentPos_DocumentWidget_(d, pos)); +} + static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { iWidget *w = as_Widget(d); if (isResize_UserEvent(ev)) { @@ -441,15 +449,15 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e } else { const iBool wrap = d->foundMark.start != NULL; - d->foundMark = finder( - d->doc, text_InputWidget(find), dir > 0 ? d->foundMark.end : d->foundMark.start); + d->foundMark = finder(d->doc, text_InputWidget(find), dir > 0 ? d->foundMark.end + : d->foundMark.start); if (!d->foundMark.start && wrap) { /* Wrap around. */ d->foundMark = finder(d->doc, text_InputWidget(find), NULL); } if (d->foundMark.start) { const iGmRun *found; - if ((found = findRunCStr_GmDocument(d->doc, d->foundMark.start)) != NULL) { + if ((found = findRunAtLoc_GmDocument(d->doc, d->foundMark.start)) != NULL) { scrollTo_DocumentWidget_(d, mid_Rect(found->bounds).y); } } @@ -530,24 +538,42 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e } processContextMenuEvent_Widget(d->menu, ev); switch (processEvent_Click(&d->click, ev)) { - case finished_ClickResult: - if (d->hoverLink) { - iAssert(d->hoverLink->linkId); - postCommandf_App("open url:%s", - cstr_String(absoluteUrl_DocumentWidget_( - d, linkUrl_GmDocument(d->doc, d->hoverLink->linkId)))); - } - return iTrue; case started_ClickResult: d->selecting = iFalse; return iTrue; - case double_ClickResult: - case drag_ClickResult: + case drag_ClickResult: { /* Begin selecting a range of text. */ - d->selectMark = iNullRange; - d->selecting = iTrue; + if (!d->selecting) { + d->selecting = iTrue; + d->selectMark.start = d->selectMark.end = + sourceLoc_DocumentWidget_(d, d->click.startPos); + refresh_Widget(w); + } + const char *loc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); + if (!d->selectMark.start) { + d->selectMark.start = d->selectMark.end = loc; + } + else if (loc) { + d->selectMark.end = loc; + } refresh_Widget(w); return iTrue; + } + case finished_ClickResult: + if (!isMoved_Click(&d->click)) { + if (d->hoverLink) { + iAssert(d->hoverLink->linkId); + postCommandf_App("open url:%s", + cstr_String(absoluteUrl_DocumentWidget_( + d, linkUrl_GmDocument(d->doc, d->hoverLink->linkId)))); + } + if (d->selectMark.start) { + d->selectMark = iNullRange; + refresh_Widget(w); + } + } + return iTrue; + case double_ClickResult: case aborted_ClickResult: return iTrue; default: @@ -568,6 +594,10 @@ struct Impl_DrawContext { static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, iRangecc mark, iBool *isInside) { + if (mark.start > mark.end) { + /* Selection may be done in either direction. */ + iSwap(const char *, mark.start, mark.end); + } if ((!*isInside && contains_Range(&run->text, mark.start)) || *isInside) { int x = 0; if (!*isInside) { @@ -582,6 +612,9 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol else { *isInside = iTrue; /* at least until the next run */ } + if (w > width_Rect(run->visBounds) - x) { + w = width_Rect(run->visBounds) - x; + } const iInt2 visPos = add_I2(run->bounds.pos, addY_I2(d->bounds.pos, -d->widget->scrollY)); fillRect_Paint(&d->paint, (iRect){ addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) }, color); @@ -620,6 +653,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark); drawString_Text(run->font, visPos, run->color, &text); deinit_String(&text); + +// drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, red_ColorId); } static void draw_DocumentWidget_(const iDocumentWidget *d) { diff --git a/src/ui/text.c b/src/ui/text.c index c8e8c787..800ebc14 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -274,7 +274,7 @@ static const iGlyph *glyph_Font_(iFont *d, iChar ch) { return glyph; } -enum iRunMode { measure_RunMode, draw_RunMode, drawPermanentColor_RunMode }; +enum iRunMode { measure_RunMode, measureNoWrap_RunMode, draw_RunMode, drawPermanentColor_RunMode }; static iChar nextChar_(const char **chPos, const char *end) { if (*chPos == end) { @@ -298,7 +298,7 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe const iInt2 orig = pos; float xpos = pos.x; float xposMax = xpos; - iAssert(xposLimit == 0 || mode == measure_RunMode); + iAssert(xposLimit == 0 || mode == measure_RunMode || mode == measureNoWrap_RunMode); const char *lastWordEnd = text.start; if (continueFrom_out) { *continueFrom_out = text.end; @@ -338,19 +338,18 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe continue; } } - /* TODO: Remember the glyph's font, no need to look it up constantly. */ const iGlyph *glyph = glyph_Font_(d, ch); int x1 = xpos; const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; int x2 = x1 + glyph->rect[hoff].size.x; + /* Out of the allotted space? */ if (xposLimit > 0 && x2 > xposLimit) { - /* Out of space. */ *continueFrom_out = lastWordEnd; break; } size.x = iMax(size.x, x2 - orig.x); size.y = iMax(size.y, pos.y + glyph->font->height - orig.y); - if (mode != measure_RunMode) { + if (mode != measure_RunMode && mode != measureNoWrap_RunMode) { SDL_Rect dst = { x1 + glyph->d[hoff].x, pos.y + glyph->font->baseline + glyph->d[hoff].y, glyph->rect[hoff].size.x, @@ -359,7 +358,7 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe } xpos += glyph->advance; xposMax = iMax(xposMax, xpos); - if (!isSpace_Char(prevCh) && isSpace_Char(ch)) { + if (mode == measureNoWrap_RunMode || (!isSpace_Char(prevCh) && isSpace_Char(ch))) { lastWordEnd = chPos; } /* Check the next character. */ @@ -418,7 +417,7 @@ iInt2 advanceRange_Text(int fontId, iRangecc text) { return init_I2(advance, height); } -iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos) { +iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { int advance; const int height = run_Font_(&text_.fonts[fontId], measure_RunMode, @@ -432,6 +431,20 @@ iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **en return init_I2(advance, height); } +iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) { + int advance; + const int height = run_Font_(&text_.fonts[fontId], + measureNoWrap_RunMode, + text, + iInvalidSize, + zero_I2(), + width, + endPos, + &advance) + .y; + return init_I2(advance, height); +} + iInt2 advance_Text(int fontId, const char *text) { return advanceRange_Text(fontId, range_CStr(text)); } diff --git a/src/ui/text.h b/src/ui/text.h index 0e432043..95d49fda 100644 --- a/src/ui/text.h +++ b/src/ui/text.h @@ -53,7 +53,9 @@ iInt2 measureRange_Text (int fontId, iRangecc text); iInt2 advance_Text (int fontId, const char *text); iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ iInt2 advanceRange_Text (int fontId, iRangecc text); -iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos); + +iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos); +iInt2 tryAdvanceNoWrap_Text (int fontId, iRangecc text, int width, const char **endPos); enum iAlignment { left_Alignment, -- cgit v1.2.3