summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-27 13:59:45 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-27 13:59:45 +0300
commit1496a2a027770f0b50bbb7bbcb0c7f11f022b906 (patch)
treedfd3b988add4cf287d14c81a14e0c0a76e9b228e
parentc15e1e3126862306244a42d8b058231acb407309 (diff)
DocumentWidget: Marking a selection with the mouse
-rw-r--r--src/gmdocument.c26
-rw-r--r--src/gmdocument.h8
-rw-r--r--src/ui/documentwidget.c65
-rw-r--r--src/ui/text.c27
-rw-r--r--src/ui/text.h4
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) {
272 while (!isEmpty_Range(&runLine)) { 272 while (!isEmpty_Range(&runLine)) {
273 run.bounds.pos = addX_I2(pos, indent * gap_UI); 273 run.bounds.pos = addX_I2(pos, indent * gap_UI);
274 const char *contPos; 274 const char *contPos;
275 run.bounds.size = tryAdvanceRange_Text( 275 const int avail = d->size.x - run.bounds.pos.x;
276 run.font, runLine, isPreformat ? 0 : (d->size.x - run.bounds.pos.x), &contPos); 276 const iInt2 dims =
277 tryAdvance_Text(run.font, runLine, isPreformat ? 0 : avail, &contPos);
278 run.bounds.size.x = iMax(avail, dims.x); /* Extends to the right edge for selection. */
279 run.bounds.size.y = dims.y;
280 run.visBounds = run.bounds;
281 run.visBounds.size.x = dims.x;
277 if (contPos > runLine.start) { 282 if (contPos > runLine.start) {
278 run.text = (iRangecc){ runLine.start, contPos }; 283 run.text = (iRangecc){ runLine.start, contPos };
279 } 284 }
@@ -435,7 +440,15 @@ const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) {
435 return NULL; 440 return NULL;
436} 441}
437 442
438const iGmRun *findRunCStr_GmDocument(const iGmDocument *d, const char *textCStr) { 443const char *findLoc_GmDocument(const iGmDocument *d, iInt2 pos) {
444 const iGmRun *run = findRun_GmDocument(d, pos);
445 if (run) {
446 return findLoc_GmRun(run, pos);
447 }
448 return NULL;
449}
450
451const iGmRun *findRunAtLoc_GmDocument(const iGmDocument *d, const char *textCStr) {
439 iConstForEach(Array, i, &d->layout) { 452 iConstForEach(Array, i, &d->layout) {
440 const iGmRun *run = i.value; 453 const iGmRun *run = i.value;
441 if (contains_Range(&run->text, textCStr) || run->text.start > textCStr /* went past */) { 454 if (contains_Range(&run->text, textCStr) || run->text.start > textCStr /* went past */) {
@@ -457,4 +470,11 @@ const iString *title_GmDocument(const iGmDocument *d) {
457 return &d->title; 470 return &d->title;
458} 471}
459 472
473const char *findLoc_GmRun(const iGmRun *d, iInt2 pos) {
474 const int x = pos.x - left_Rect(d->bounds);
475 const char *loc;
476 tryAdvanceNoWrap_Text(d->font, d->text, x, &loc);
477 return loc;
478}
479
460iDefineClass(GmDocument) 480iDefineClass(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;
11 11
12struct Impl_GmRun { 12struct Impl_GmRun {
13 iRangecc text; 13 iRangecc text;
14 iRect bounds; /* advance metrics */ 14 iRect bounds; /* used for hit testing, extends to edge */
15 iRect visBounds; /* actual text bounds */
15 uint8_t font; 16 uint8_t font;
16 uint8_t color; 17 uint8_t color;
17 iGmLinkId linkId; /* zero for non-links */ 18 iGmLinkId linkId; /* zero for non-links */
18}; 19};
19 20
21const char * findLoc_GmRun (const iGmRun *, iInt2 pos);
22
20iDeclareClass(GmDocument) 23iDeclareClass(GmDocument)
21iDeclareObjectConstruction(GmDocument) 24iDeclareObjectConstruction(GmDocument)
22 25
@@ -32,6 +35,7 @@ iRangecc findText_GmDocument (const iGmDocument *, const iString
32iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); 35iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before);
33 36
34const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos); 37const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos);
35const iGmRun * findRunCStr_GmDocument (const iGmDocument *, const char *textCStr); 38const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos);
39const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc);
36const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); 40const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId);
37const iString * title_GmDocument (const iGmDocument *); 41const 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) {
112 return rect; 112 return rect;
113} 113}
114 114
115static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) {
116 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), d->scrollY);
117}
118
115static void requestUpdated_DocumentWidget_(iAnyObject *obj) { 119static void requestUpdated_DocumentWidget_(iAnyObject *obj) {
116 iDocumentWidget *d = obj; 120 iDocumentWidget *d = obj;
117 const int wasPending = exchange_Atomic(&d->isSourcePending, iTrue); 121 const int wasPending = exchange_Atomic(&d->isSourcePending, iTrue);
@@ -368,6 +372,10 @@ const iString *valueString_Command(const char *cmd, const char *label) {
368 return collect_String(newCStr_String(suffixPtr_Command(cmd, label))); 372 return collect_String(newCStr_String(suffixPtr_Command(cmd, label)));
369} 373}
370 374
375static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) {
376 return findLoc_GmDocument(d->doc, documentPos_DocumentWidget_(d, pos));
377}
378
371static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { 379static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) {
372 iWidget *w = as_Widget(d); 380 iWidget *w = as_Widget(d);
373 if (isResize_UserEvent(ev)) { 381 if (isResize_UserEvent(ev)) {
@@ -441,15 +449,15 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
441 } 449 }
442 else { 450 else {
443 const iBool wrap = d->foundMark.start != NULL; 451 const iBool wrap = d->foundMark.start != NULL;
444 d->foundMark = finder( 452 d->foundMark = finder(d->doc, text_InputWidget(find), dir > 0 ? d->foundMark.end
445 d->doc, text_InputWidget(find), dir > 0 ? d->foundMark.end : d->foundMark.start); 453 : d->foundMark.start);
446 if (!d->foundMark.start && wrap) { 454 if (!d->foundMark.start && wrap) {
447 /* Wrap around. */ 455 /* Wrap around. */
448 d->foundMark = finder(d->doc, text_InputWidget(find), NULL); 456 d->foundMark = finder(d->doc, text_InputWidget(find), NULL);
449 } 457 }
450 if (d->foundMark.start) { 458 if (d->foundMark.start) {
451 const iGmRun *found; 459 const iGmRun *found;
452 if ((found = findRunCStr_GmDocument(d->doc, d->foundMark.start)) != NULL) { 460 if ((found = findRunAtLoc_GmDocument(d->doc, d->foundMark.start)) != NULL) {
453 scrollTo_DocumentWidget_(d, mid_Rect(found->bounds).y); 461 scrollTo_DocumentWidget_(d, mid_Rect(found->bounds).y);
454 } 462 }
455 } 463 }
@@ -530,24 +538,42 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
530 } 538 }
531 processContextMenuEvent_Widget(d->menu, ev); 539 processContextMenuEvent_Widget(d->menu, ev);
532 switch (processEvent_Click(&d->click, ev)) { 540 switch (processEvent_Click(&d->click, ev)) {
533 case finished_ClickResult:
534 if (d->hoverLink) {
535 iAssert(d->hoverLink->linkId);
536 postCommandf_App("open url:%s",
537 cstr_String(absoluteUrl_DocumentWidget_(
538 d, linkUrl_GmDocument(d->doc, d->hoverLink->linkId))));
539 }
540 return iTrue;
541 case started_ClickResult: 541 case started_ClickResult:
542 d->selecting = iFalse; 542 d->selecting = iFalse;
543 return iTrue; 543 return iTrue;
544 case double_ClickResult: 544 case drag_ClickResult: {
545 case drag_ClickResult:
546 /* Begin selecting a range of text. */ 545 /* Begin selecting a range of text. */
547 d->selectMark = iNullRange; 546 if (!d->selecting) {
548 d->selecting = iTrue; 547 d->selecting = iTrue;
548 d->selectMark.start = d->selectMark.end =
549 sourceLoc_DocumentWidget_(d, d->click.startPos);
550 refresh_Widget(w);
551 }
552 const char *loc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click));
553 if (!d->selectMark.start) {
554 d->selectMark.start = d->selectMark.end = loc;
555 }
556 else if (loc) {
557 d->selectMark.end = loc;
558 }
549 refresh_Widget(w); 559 refresh_Widget(w);
550 return iTrue; 560 return iTrue;
561 }
562 case finished_ClickResult:
563 if (!isMoved_Click(&d->click)) {
564 if (d->hoverLink) {
565 iAssert(d->hoverLink->linkId);
566 postCommandf_App("open url:%s",
567 cstr_String(absoluteUrl_DocumentWidget_(
568 d, linkUrl_GmDocument(d->doc, d->hoverLink->linkId))));
569 }
570 if (d->selectMark.start) {
571 d->selectMark = iNullRange;
572 refresh_Widget(w);
573 }
574 }
575 return iTrue;
576 case double_ClickResult:
551 case aborted_ClickResult: 577 case aborted_ClickResult:
552 return iTrue; 578 return iTrue;
553 default: 579 default:
@@ -568,6 +594,10 @@ struct Impl_DrawContext {
568 594
569static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, 595static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color,
570 iRangecc mark, iBool *isInside) { 596 iRangecc mark, iBool *isInside) {
597 if (mark.start > mark.end) {
598 /* Selection may be done in either direction. */
599 iSwap(const char *, mark.start, mark.end);
600 }
571 if ((!*isInside && contains_Range(&run->text, mark.start)) || *isInside) { 601 if ((!*isInside && contains_Range(&run->text, mark.start)) || *isInside) {
572 int x = 0; 602 int x = 0;
573 if (!*isInside) { 603 if (!*isInside) {
@@ -582,6 +612,9 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
582 else { 612 else {
583 *isInside = iTrue; /* at least until the next run */ 613 *isInside = iTrue; /* at least until the next run */
584 } 614 }
615 if (w > width_Rect(run->visBounds) - x) {
616 w = width_Rect(run->visBounds) - x;
617 }
585 const iInt2 visPos = add_I2(run->bounds.pos, addY_I2(d->bounds.pos, -d->widget->scrollY)); 618 const iInt2 visPos = add_I2(run->bounds.pos, addY_I2(d->bounds.pos, -d->widget->scrollY));
586 fillRect_Paint(&d->paint, (iRect){ addX_I2(visPos, x), 619 fillRect_Paint(&d->paint, (iRect){ addX_I2(visPos, x),
587 init_I2(w, height_Rect(run->bounds)) }, color); 620 init_I2(w, height_Rect(run->bounds)) }, color);
@@ -620,6 +653,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
620 fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark); 653 fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark);
621 drawString_Text(run->font, visPos, run->color, &text); 654 drawString_Text(run->font, visPos, run->color, &text);
622 deinit_String(&text); 655 deinit_String(&text);
656
657// drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, red_ColorId);
623} 658}
624 659
625static void draw_DocumentWidget_(const iDocumentWidget *d) { 660static 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) {
274 return glyph; 274 return glyph;
275} 275}
276 276
277enum iRunMode { measure_RunMode, draw_RunMode, drawPermanentColor_RunMode }; 277enum iRunMode { measure_RunMode, measureNoWrap_RunMode, draw_RunMode, drawPermanentColor_RunMode };
278 278
279static iChar nextChar_(const char **chPos, const char *end) { 279static iChar nextChar_(const char **chPos, const char *end) {
280 if (*chPos == end) { 280 if (*chPos == end) {
@@ -298,7 +298,7 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
298 const iInt2 orig = pos; 298 const iInt2 orig = pos;
299 float xpos = pos.x; 299 float xpos = pos.x;
300 float xposMax = xpos; 300 float xposMax = xpos;
301 iAssert(xposLimit == 0 || mode == measure_RunMode); 301 iAssert(xposLimit == 0 || mode == measure_RunMode || mode == measureNoWrap_RunMode);
302 const char *lastWordEnd = text.start; 302 const char *lastWordEnd = text.start;
303 if (continueFrom_out) { 303 if (continueFrom_out) {
304 *continueFrom_out = text.end; 304 *continueFrom_out = text.end;
@@ -338,19 +338,18 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
338 continue; 338 continue;
339 } 339 }
340 } 340 }
341 /* TODO: Remember the glyph's font, no need to look it up constantly. */
342 const iGlyph *glyph = glyph_Font_(d, ch); 341 const iGlyph *glyph = glyph_Font_(d, ch);
343 int x1 = xpos; 342 int x1 = xpos;
344 const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; 343 const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0;
345 int x2 = x1 + glyph->rect[hoff].size.x; 344 int x2 = x1 + glyph->rect[hoff].size.x;
345 /* Out of the allotted space? */
346 if (xposLimit > 0 && x2 > xposLimit) { 346 if (xposLimit > 0 && x2 > xposLimit) {
347 /* Out of space. */
348 *continueFrom_out = lastWordEnd; 347 *continueFrom_out = lastWordEnd;
349 break; 348 break;
350 } 349 }
351 size.x = iMax(size.x, x2 - orig.x); 350 size.x = iMax(size.x, x2 - orig.x);
352 size.y = iMax(size.y, pos.y + glyph->font->height - orig.y); 351 size.y = iMax(size.y, pos.y + glyph->font->height - orig.y);
353 if (mode != measure_RunMode) { 352 if (mode != measure_RunMode && mode != measureNoWrap_RunMode) {
354 SDL_Rect dst = { x1 + glyph->d[hoff].x, 353 SDL_Rect dst = { x1 + glyph->d[hoff].x,
355 pos.y + glyph->font->baseline + glyph->d[hoff].y, 354 pos.y + glyph->font->baseline + glyph->d[hoff].y,
356 glyph->rect[hoff].size.x, 355 glyph->rect[hoff].size.x,
@@ -359,7 +358,7 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
359 } 358 }
360 xpos += glyph->advance; 359 xpos += glyph->advance;
361 xposMax = iMax(xposMax, xpos); 360 xposMax = iMax(xposMax, xpos);
362 if (!isSpace_Char(prevCh) && isSpace_Char(ch)) { 361 if (mode == measureNoWrap_RunMode || (!isSpace_Char(prevCh) && isSpace_Char(ch))) {
363 lastWordEnd = chPos; 362 lastWordEnd = chPos;
364 } 363 }
365 /* Check the next character. */ 364 /* Check the next character. */
@@ -418,7 +417,7 @@ iInt2 advanceRange_Text(int fontId, iRangecc text) {
418 return init_I2(advance, height); 417 return init_I2(advance, height);
419} 418}
420 419
421iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos) { 420iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) {
422 int advance; 421 int advance;
423 const int height = run_Font_(&text_.fonts[fontId], 422 const int height = run_Font_(&text_.fonts[fontId],
424 measure_RunMode, 423 measure_RunMode,
@@ -432,6 +431,20 @@ iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **en
432 return init_I2(advance, height); 431 return init_I2(advance, height);
433} 432}
434 433
434iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **endPos) {
435 int advance;
436 const int height = run_Font_(&text_.fonts[fontId],
437 measureNoWrap_RunMode,
438 text,
439 iInvalidSize,
440 zero_I2(),
441 width,
442 endPos,
443 &advance)
444 .y;
445 return init_I2(advance, height);
446}
447
435iInt2 advance_Text(int fontId, const char *text) { 448iInt2 advance_Text(int fontId, const char *text) {
436 return advanceRange_Text(fontId, range_CStr(text)); 449 return advanceRange_Text(fontId, range_CStr(text));
437} 450}
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);
53iInt2 advance_Text (int fontId, const char *text); 53iInt2 advance_Text (int fontId, const char *text);
54iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ 54iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */
55iInt2 advanceRange_Text (int fontId, iRangecc text); 55iInt2 advanceRange_Text (int fontId, iRangecc text);
56iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos); 56
57iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos);
58iInt2 tryAdvanceNoWrap_Text (int fontId, iRangecc text, int width, const char **endPos);
57 59
58enum iAlignment { 60enum iAlignment {
59 left_Alignment, 61 left_Alignment,