diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-22 15:34:08 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-22 15:34:08 +0300 |
commit | e0e53e4a51afcbd22345d12416645d3fc5483a18 (patch) | |
tree | 37c7ca552d60b6ccb179a807d9dbcfee8b097cfd /src/ui | |
parent | e16f37f75399ef4dd2f267efbc720768489f96a1 (diff) |
Text wrapping; basic scrolling in DocumentWidget
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/documentwidget.c | 21 | ||||
-rw-r--r-- | src/ui/text.c | 95 | ||||
-rw-r--r-- | src/ui/text.h | 1 |
3 files changed, 81 insertions, 36 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index c0dbcded..7fe30f1e 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -25,6 +25,7 @@ struct Impl_DocumentWidget { | |||
25 | iString *newSource; | 25 | iString *newSource; |
26 | iGmDocument *doc; | 26 | iGmDocument *doc; |
27 | int pageMargin; | 27 | int pageMargin; |
28 | int scrollY; | ||
28 | }; | 29 | }; |
29 | 30 | ||
30 | iDeclareType(Url) | 31 | iDeclareType(Url) |
@@ -71,6 +72,7 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
71 | d->newSource = new_String(); | 72 | d->newSource = new_String(); |
72 | d->doc = new_GmDocument(); | 73 | d->doc = new_GmDocument(); |
73 | d->pageMargin = 5; | 74 | d->pageMargin = 5; |
75 | d->scrollY = 0; | ||
74 | setUrl_DocumentWidget(d, collectNewCStr_String("file:///home/jaakko/test.gmi")); | 76 | setUrl_DocumentWidget(d, collectNewCStr_String("file:///home/jaakko/test.gmi")); |
75 | } | 77 | } |
76 | 78 | ||
@@ -173,13 +175,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
173 | return iTrue; | 175 | return iTrue; |
174 | } | 176 | } |
175 | } | 177 | } |
178 | else if (ev->type == SDL_MOUSEWHEEL) { | ||
179 | d->scrollY -= 3 * ev->wheel.y * lineHeight_Text(default_FontId); | ||
180 | postRefresh_App(); | ||
181 | return iTrue; | ||
182 | } | ||
176 | return processEvent_Widget(w, ev); | 183 | return processEvent_Widget(w, ev); |
177 | } | 184 | } |
178 | 185 | ||
179 | iDeclareType(DrawContext) | 186 | iDeclareType(DrawContext) |
180 | 187 | ||
181 | struct Impl_DrawContext { | 188 | struct Impl_DrawContext { |
182 | const iDocumentWidget *d; | 189 | const iDocumentWidget *widget; |
183 | iRect bounds; | 190 | iRect bounds; |
184 | iPaint paint; | 191 | iPaint paint; |
185 | }; | 192 | }; |
@@ -189,8 +196,9 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
189 | iString text; | 196 | iString text; |
190 | /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ | 197 | /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ |
191 | initRange_String(&text, run->text); | 198 | initRange_String(&text, run->text); |
192 | drawString_Text(run->font, add_I2(d->bounds.pos, run->bounds.pos), run->color, &text); | 199 | iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); |
193 | drawRect_Paint(&d->paint, moved_Rect(run->bounds, d->bounds.pos), red_ColorId); | 200 | drawString_Text(run->font, add_I2(run->bounds.pos, origin), run->color, &text); |
201 | // drawRect_Paint(&d->paint, moved_Rect(run->bounds, origin), red_ColorId); | ||
194 | deinit_String(&text); | 202 | deinit_String(&text); |
195 | } | 203 | } |
196 | 204 | ||
@@ -206,11 +214,14 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
206 | iConstCast(iDocumentWidget *, d)->state = ready_DocumentState; | 214 | iConstCast(iDocumentWidget *, d)->state = ready_DocumentState; |
207 | } | 215 | } |
208 | if (d->state != ready_DocumentState) return; | 216 | if (d->state != ready_DocumentState) return; |
209 | iDrawContext ctx = {.d = d, .bounds = bounds_Widget(w) }; | 217 | iDrawContext ctx = {.widget = d, .bounds = bounds_Widget(w) }; |
210 | shrink_Rect(&ctx.bounds, init1_I2(gap_UI * d->pageMargin)); | 218 | shrink_Rect(&ctx.bounds, init1_I2(gap_UI * d->pageMargin)); |
211 | init_Paint(&ctx.paint); | 219 | init_Paint(&ctx.paint); |
212 | drawRect_Paint(&ctx.paint, ctx.bounds, teal_ColorId); | 220 | drawRect_Paint(&ctx.paint, ctx.bounds, teal_ColorId); |
213 | render_GmDocument(d->doc, (iRangei){ 0, height_Rect(ctx.bounds) }, drawRun_DrawContext_, &ctx); | 221 | render_GmDocument(d->doc, |
222 | (iRangei){ d->scrollY, d->scrollY + height_Rect(ctx.bounds) }, | ||
223 | drawRun_DrawContext_, | ||
224 | &ctx); | ||
214 | } | 225 | } |
215 | 226 | ||
216 | iBeginDefineSubclass(DocumentWidget, Widget) | 227 | iBeginDefineSubclass(DocumentWidget, Widget) |
diff --git a/src/ui/text.c b/src/ui/text.c index 23e28e4d..afad778c 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -15,6 +15,7 @@ | |||
15 | #include <the_Foundation/vec2.h> | 15 | #include <the_Foundation/vec2.h> |
16 | 16 | ||
17 | #include <SDL_surface.h> | 17 | #include <SDL_surface.h> |
18 | #include <SDL_hints.h> | ||
18 | #include <stdarg.h> | 19 | #include <stdarg.h> |
19 | 20 | ||
20 | iDeclareType(Glyph) | 21 | iDeclareType(Glyph) |
@@ -115,12 +116,12 @@ void init_Text(SDL_Renderer *render) { | |||
115 | /* Load the fonts. */ { | 116 | /* Load the fonts. */ { |
116 | const struct { const iBlock *ttf; int size; } fontData[max_FontId] = { | 117 | const struct { const iBlock *ttf; int size; } fontData[max_FontId] = { |
117 | { &fontFiraSansRegular_Embedded, fontSize_UI }, | 118 | { &fontFiraSansRegular_Embedded, fontSize_UI }, |
118 | { &fontFiraMonoRegular_Embedded, fontSize_UI }, | 119 | { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.85f }, |
119 | { &fontFiraSansRegular_Embedded, fontSize_UI * 1.5f }, | 120 | { &fontFiraSansRegular_Embedded, fontSize_UI * 1.5f }, |
120 | { &fontFiraSansLightItalic_Embedded, fontSize_UI }, | 121 | { &fontFiraSansLightItalic_Embedded, fontSize_UI }, |
121 | { &fontFiraSansBold_Embedded, fontSize_UI }, | 122 | { &fontFiraSansBold_Embedded, fontSize_UI }, |
122 | { &fontFiraSansBold_Embedded, fontSize_UI * 1.5f }, | 123 | { &fontFiraSansBold_Embedded, fontSize_UI * 1.35f }, |
123 | { &fontFiraSansBold_Embedded, fontSize_UI * 1.75f }, | 124 | { &fontFiraSansBold_Embedded, fontSize_UI * 1.7f }, |
124 | { &fontFiraSansBold_Embedded, fontSize_UI * 2.0f }, | 125 | { &fontFiraSansBold_Embedded, fontSize_UI * 2.0f }, |
125 | }; | 126 | }; |
126 | iForIndices(i, fontData) { | 127 | iForIndices(i, fontData) { |
@@ -377,18 +378,26 @@ static iChar nextChar_(const char **chPos, const char *end) { | |||
377 | } | 378 | } |
378 | 379 | ||
379 | static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLen, iInt2 pos, | 380 | static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLen, iInt2 pos, |
380 | int *runAdvance_out) { | 381 | int xposLimit, const char **continueFrom_out, int *runAdvance_out) { |
381 | iInt2 size = zero_I2(); | 382 | iInt2 size = zero_I2(); |
382 | const iInt2 orig = pos; | 383 | const iInt2 orig = pos; |
383 | const stbtt_fontinfo *info = &d->font; | 384 | const stbtt_fontinfo *info = &d->font; |
384 | float xpos = pos.x; | 385 | float xpos = pos.x; |
385 | float xposMax = xpos; | 386 | float xposMax = xpos; |
387 | iAssert(xposLimit == 0 || mode == measure_RunMode); | ||
388 | const char *lastWordEnd = text.start; | ||
389 | if (continueFrom_out) { | ||
390 | *continueFrom_out = text.end; | ||
391 | } | ||
392 | iChar prevCh = 0; | ||
386 | for (const char *chPos = text.start; chPos != text.end; ) { | 393 | for (const char *chPos = text.start; chPos != text.end; ) { |
394 | iAssert(chPos < text.end); | ||
387 | iChar ch = nextChar_(&chPos, text.end); | 395 | iChar ch = nextChar_(&chPos, text.end); |
388 | /* Special instructions. */ { | 396 | /* Special instructions. */ { |
389 | if (ch == '\n') { | 397 | if (ch == '\n') { |
390 | xpos = pos.x; | 398 | xpos = pos.x; |
391 | pos.y += d->height; | 399 | pos.y += d->height; |
400 | prevCh = ch; | ||
392 | continue; | 401 | continue; |
393 | } | 402 | } |
394 | if (ch == '\r') { | 403 | if (ch == '\r') { |
@@ -397,20 +406,26 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
397 | if (mode == draw_RunMode) { | 406 | if (mode == draw_RunMode) { |
398 | SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); | 407 | SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); |
399 | } | 408 | } |
409 | prevCh = 0; | ||
400 | continue; | 410 | continue; |
401 | } | 411 | } |
402 | } | 412 | } |
403 | const iGlyph *glyph = glyph_Font_(d, ch); | 413 | const iGlyph *glyph = glyph_Font_(d, ch); |
404 | int x1 = iRound(xpos); | 414 | float x1 = xpos; |
405 | int x2 = x1 + glyph->rect.size.x; | 415 | float x2 = x1 + glyph->rect.size.x; |
416 | if (xposLimit > 0 && x2 > xposLimit) { | ||
417 | /* Out of space. */ | ||
418 | *continueFrom_out = lastWordEnd; | ||
419 | break; | ||
420 | } | ||
406 | size.x = iMax(size.x, x2 - orig.x); | 421 | size.x = iMax(size.x, x2 - orig.x); |
407 | size.y = iMax(size.y, pos.y + d->height - orig.y); | 422 | size.y = iMax(size.y, pos.y + d->height - orig.y); |
408 | if (mode != measure_RunMode) { | 423 | if (mode != measure_RunMode) { |
409 | SDL_Rect dst = { x1 + glyph->dx, | 424 | SDL_FRect dst = { x1 + glyph->dx, |
410 | pos.y + d->baseline + glyph->dy, | 425 | pos.y + d->baseline + glyph->dy, |
411 | glyph->rect.size.x, | 426 | glyph->rect.size.x, |
412 | glyph->rect.size.y }; | 427 | glyph->rect.size.y }; |
413 | SDL_RenderCopy(text_.render, text_.cache, (const SDL_Rect *) &glyph->rect, &dst); | 428 | SDL_RenderCopyF(text_.render, text_.cache, (const SDL_Rect *) &glyph->rect, &dst); |
414 | } | 429 | } |
415 | int advance, lsb; | 430 | int advance, lsb; |
416 | const iBool spec = isSpecialChar_(ch); | 431 | const iBool spec = isSpecialChar_(ch); |
@@ -420,6 +435,9 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
420 | } | 435 | } |
421 | xpos += d->scale * advance; | 436 | xpos += d->scale * advance; |
422 | xposMax = iMax(xposMax, xpos); | 437 | xposMax = iMax(xposMax, xpos); |
438 | if (!isSpace_Char(prevCh) && isSpace_Char(ch)) { | ||
439 | lastWordEnd = chPos; | ||
440 | } | ||
423 | /* Check the next character. */ { | 441 | /* Check the next character. */ { |
424 | /* TODO: No need to decode the next char twice; check this on the next iteration. */ | 442 | /* TODO: No need to decode the next char twice; check this on the next iteration. */ |
425 | const char *peek = chPos; | 443 | const char *peek = chPos; |
@@ -428,6 +446,7 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
428 | xpos += d->scale * stbtt_GetCodepointKernAdvance(info, ch, next); | 446 | xpos += d->scale * stbtt_GetCodepointKernAdvance(info, ch, next); |
429 | } | 447 | } |
430 | } | 448 | } |
449 | prevCh = ch; | ||
431 | if (--maxLen == 0) { | 450 | if (--maxLen == 0) { |
432 | break; | 451 | break; |
433 | } | 452 | } |
@@ -446,44 +465,56 @@ iInt2 measure_Text(int fontId, const char *text) { | |||
446 | if (!*text) { | 465 | if (!*text) { |
447 | return init_I2(0, lineHeight_Text(fontId)); | 466 | return init_I2(0, lineHeight_Text(fontId)); |
448 | } | 467 | } |
449 | return run_Font_( | 468 | return run_Font_(&text_.fonts[fontId], |
450 | &text_.fonts[fontId], measure_RunMode, range_CStr(text), iInvalidSize, zero_I2(), NULL); | 469 | measure_RunMode, |
470 | range_CStr(text), | ||
471 | iInvalidSize, | ||
472 | zero_I2(), | ||
473 | 0, | ||
474 | NULL, | ||
475 | NULL); | ||
451 | } | 476 | } |
452 | 477 | ||
453 | iInt2 advance_Text(int fontId, const char *text) { | 478 | iInt2 advanceRange_Text(int fontId, iRangecc text) { |
454 | int advance; | 479 | int advance; |
455 | const int height = run_Font_(&text_.fonts[fontId], | 480 | const int height = run_Font_(&text_.fonts[fontId], |
456 | measure_RunMode, | 481 | measure_RunMode, |
457 | range_CStr(text), | 482 | text, |
458 | iInvalidSize, | 483 | iInvalidSize, |
459 | zero_I2(), | 484 | zero_I2(), |
485 | 0, | ||
486 | NULL, | ||
460 | &advance) | 487 | &advance) |
461 | .y; | 488 | .y; |
462 | return init_I2(advance, height); | 489 | return init_I2(advance, height); |
463 | } | 490 | } |
464 | 491 | ||
465 | iInt2 advanceN_Text(int fontId, const char *text, size_t n) { | 492 | iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos) { |
466 | if (n == 0) { | ||
467 | return init_I2(0, lineHeight_Text(fontId)); | ||
468 | } | ||
469 | int advance; | 493 | int advance; |
470 | run_Font_(&text_.fonts[fontId], measure_RunMode, range_CStr(text), n, zero_I2(), &advance); | 494 | const int height = run_Font_(&text_.fonts[fontId], |
471 | return init_I2(advance, lineHeight_Text(fontId)); | 495 | measure_RunMode, |
496 | text, | ||
497 | iInvalidSize, | ||
498 | zero_I2(), | ||
499 | width, | ||
500 | endPos, | ||
501 | &advance) | ||
502 | .y; | ||
503 | return init_I2(advance, height); | ||
472 | } | 504 | } |
473 | 505 | ||
474 | iInt2 advanceRange_Text(int fontId, iRangecc text) { | 506 | iInt2 advance_Text(int fontId, const char *text) { |
475 | /* TODO: Rangecc should be the default for runs; no need to copy here */ | 507 | return advanceRange_Text(fontId, range_CStr(text)); |
476 | iString str; | ||
477 | initRange_String(&str, text); | ||
478 | const iInt2 metrics = advance_Text(fontId, cstr_String(&str)); | ||
479 | deinit_String(&str); | ||
480 | return metrics; | ||
481 | } | 508 | } |
482 | 509 | ||
483 | iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos) { | 510 | iInt2 advanceN_Text(int fontId, const char *text, size_t n) { |
511 | if (n == 0) { | ||
512 | return init_I2(0, lineHeight_Text(fontId)); | ||
513 | } | ||
484 | int advance; | 514 | int advance; |
485 | const int height = run_Font_(&text_.fonts[fontId], measure_RunMode, text, iInvalidSize, zero_I2(), &advance).y; | 515 | run_Font_( |
486 | 516 | &text_.fonts[fontId], measure_RunMode, range_CStr(text), n, zero_I2(), 0, NULL, &advance); | |
517 | return init_I2(advance, lineHeight_Text(fontId)); | ||
487 | } | 518 | } |
488 | 519 | ||
489 | static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) { | 520 | static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) { |
@@ -495,6 +526,8 @@ static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) { | |||
495 | text, | 526 | text, |
496 | iInvalidSize, | 527 | iInvalidSize, |
497 | pos, | 528 | pos, |
529 | 0, | ||
530 | NULL, | ||
498 | NULL); | 531 | NULL); |
499 | } | 532 | } |
500 | 533 | ||
diff --git a/src/ui/text.h b/src/ui/text.h index 9bf69c80..ddf9da86 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -42,6 +42,7 @@ iInt2 measure_Text (int fontId, const char *text); | |||
42 | iInt2 advance_Text (int fontId, const char *text); | 42 | iInt2 advance_Text (int fontId, const char *text); |
43 | iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ | 43 | iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ |
44 | iInt2 advanceRange_Text (int fontId, iRangecc text); | 44 | iInt2 advanceRange_Text (int fontId, iRangecc text); |
45 | iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos); | ||
45 | 46 | ||
46 | void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); /* negative pos to switch alignment */ | 47 | void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); /* negative pos to switch alignment */ |
47 | void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); | 48 | void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); |