diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-27 13:59:45 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-27 13:59:45 +0300 |
commit | 1496a2a027770f0b50bbb7bbcb0c7f11f022b906 (patch) | |
tree | dfd3b988add4cf287d14c81a14e0c0a76e9b228e /src | |
parent | c15e1e3126862306244a42d8b058231acb407309 (diff) |
DocumentWidget: Marking a selection with the mouse
Diffstat (limited to 'src')
-rw-r--r-- | src/gmdocument.c | 26 | ||||
-rw-r--r-- | src/gmdocument.h | 8 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 65 | ||||
-rw-r--r-- | src/ui/text.c | 27 | ||||
-rw-r--r-- | 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) { | |||
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 | ||
438 | const iGmRun *findRunCStr_GmDocument(const iGmDocument *d, const char *textCStr) { | 443 | const 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 | |||
451 | const 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 | ||
473 | const 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 | |||
460 | iDefineClass(GmDocument) | 480 | 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; | |||
11 | 11 | ||
12 | struct Impl_GmRun { | 12 | struct 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 | ||
21 | const char * findLoc_GmRun (const iGmRun *, iInt2 pos); | ||
22 | |||
20 | iDeclareClass(GmDocument) | 23 | iDeclareClass(GmDocument) |
21 | iDeclareObjectConstruction(GmDocument) | 24 | iDeclareObjectConstruction(GmDocument) |
22 | 25 | ||
@@ -32,6 +35,7 @@ iRangecc findText_GmDocument (const iGmDocument *, const iString | |||
32 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); | 35 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); |
33 | 36 | ||
34 | const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos); | 37 | const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos); |
35 | const iGmRun * findRunCStr_GmDocument (const iGmDocument *, const char *textCStr); | 38 | const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos); |
39 | const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc); | ||
36 | const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); | 40 | const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); |
37 | const iString * title_GmDocument (const iGmDocument *); | 41 | 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) { | |||
112 | return rect; | 112 | return rect; |
113 | } | 113 | } |
114 | 114 | ||
115 | static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | ||
116 | return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), d->scrollY); | ||
117 | } | ||
118 | |||
115 | static void requestUpdated_DocumentWidget_(iAnyObject *obj) { | 119 | static 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 | ||
375 | static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | ||
376 | return findLoc_GmDocument(d->doc, documentPos_DocumentWidget_(d, pos)); | ||
377 | } | ||
378 | |||
371 | static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { | 379 | static 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 | ||
569 | static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, | 595 | static 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 | ||
625 | static void draw_DocumentWidget_(const iDocumentWidget *d) { | 660 | 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) { | |||
274 | return glyph; | 274 | return glyph; |
275 | } | 275 | } |
276 | 276 | ||
277 | enum iRunMode { measure_RunMode, draw_RunMode, drawPermanentColor_RunMode }; | 277 | enum iRunMode { measure_RunMode, measureNoWrap_RunMode, draw_RunMode, drawPermanentColor_RunMode }; |
278 | 278 | ||
279 | static iChar nextChar_(const char **chPos, const char *end) { | 279 | static 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 | ||
421 | iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos) { | 420 | iInt2 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 | ||
434 | iInt2 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 | |||
435 | iInt2 advance_Text(int fontId, const char *text) { | 448 | iInt2 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); | |||
53 | iInt2 advance_Text (int fontId, const char *text); | 53 | iInt2 advance_Text (int fontId, const char *text); |
54 | iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ | 54 | iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ |
55 | iInt2 advanceRange_Text (int fontId, iRangecc text); | 55 | iInt2 advanceRange_Text (int fontId, iRangecc text); |
56 | iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos); | 56 | |
57 | iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos); | ||
58 | iInt2 tryAdvanceNoWrap_Text (int fontId, iRangecc text, int width, const char **endPos); | ||
57 | 59 | ||
58 | enum iAlignment { | 60 | enum iAlignment { |
59 | left_Alignment, | 61 | left_Alignment, |