summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-22 15:34:08 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-22 15:34:08 +0300
commite0e53e4a51afcbd22345d12416645d3fc5483a18 (patch)
tree37c7ca552d60b6ccb179a807d9dbcfee8b097cfd
parente16f37f75399ef4dd2f267efbc720768489f96a1 (diff)
Text wrapping; basic scrolling in DocumentWidget
-rw-r--r--src/gmdocument.c46
-rw-r--r--src/gmdocument.h1
-rw-r--r--src/ui/documentwidget.c21
-rw-r--r--src/ui/text.c95
-rw-r--r--src/ui/text.h1
5 files changed, 118 insertions, 46 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c
index feff44e6..3f3f8358 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -150,11 +150,18 @@ static void doLayout_GmDocument_(iGmDocument *d) {
150 run.text = (iRangecc){ bullet, bullet + strlen(bullet) }; 150 run.text = (iRangecc){ bullet, bullet + strlen(bullet) };
151 pushBack_Array(&d->layout, &run); 151 pushBack_Array(&d->layout, &run);
152 } 152 }
153 run.text = line; 153 iRangecc runLine = line;
154 run.bounds.pos = addX_I2(pos, indent * gap_UI); 154 while (!isEmpty_Range(&runLine)) {
155 run.bounds.size = advanceRange_Text(run.font, line); 155 run.bounds.pos = addX_I2(pos, indent * gap_UI);
156 pushBack_Array(&d->layout, &run); 156 const char *contPos;
157 pos.y += run.bounds.size.y; 157 run.bounds.size = tryAdvanceRange_Text(
158 run.font, runLine, isPreformat ? 0 : (d->size.x - run.bounds.pos.x), &contPos);
159 run.text = (iRangecc){ runLine.start, contPos };
160 pushBack_Array(&d->layout, &run);
161 runLine.start = contPos;
162 trimStart_Rangecc(&runLine);
163 pos.y += lineHeight_Text(run.font);
164 }
158 prevType = type; 165 prevType = type;
159 } 166 }
160 d->size.y = pos.y; 167 d->size.y = pos.y;
@@ -185,9 +192,22 @@ static void normalize_GmDocument(iGmDocument *d) {
185 iRangecc src = range_String(&d->source); 192 iRangecc src = range_String(&d->source);
186 iRangecc line = iNullRange; 193 iRangecc line = iNullRange;
187 iBool isPreformat = iFalse; 194 iBool isPreformat = iFalse;
195 const int preTabWidth = 4; /* TODO: user-configurable parameter */
188 while (nextSplit_Rangecc(&src, "\n", &line)) { 196 while (nextSplit_Rangecc(&src, "\n", &line)) {
189 if (isPreformat) { 197 if (isPreformat) {
190 appendRange_String(normalized, line); 198 /* Replace any tab characters with spaces for visualization. */
199 for (const char *ch = line.start; ch != line.end; ch++) {
200 if (*ch == '\t') {
201 int column = ch - line.start;
202 int numSpaces = (column / preTabWidth + 1) * preTabWidth - column;
203 while (numSpaces-- > 0) {
204 appendCStrN_String(normalized, " ", 1);
205 }
206 }
207 else {
208 appendCStrN_String(normalized, ch, 1);
209 }
210 }
191 appendCStr_String(normalized, "\n"); 211 appendCStr_String(normalized, "\n");
192 if (lineType_Rangecc_(&line) == preformatted_GmLineType) { 212 if (lineType_Rangecc_(&line) == preformatted_GmLineType) {
193 isPreformat = iFalse; 213 isPreformat = iFalse;
@@ -202,21 +222,23 @@ static void normalize_GmDocument(iGmDocument *d) {
202 } 222 }
203 iBool isPrevSpace = iFalse; 223 iBool isPrevSpace = iFalse;
204 for (const char *ch = line.start; ch != line.end; ch++) { 224 for (const char *ch = line.start; ch != line.end; ch++) {
205 if (isNormalizableSpace_(*ch)) { 225 char c = *ch;
226 if (isNormalizableSpace_(c)) {
206 if (isPrevSpace) { 227 if (isPrevSpace) {
207 continue; 228 continue; /* skip repeated spaces */
208 } 229 }
230 c = ' ';
209 isPrevSpace = iTrue; 231 isPrevSpace = iTrue;
210 } 232 }
211 else { 233 else {
212 isPrevSpace = iFalse; 234 isPrevSpace = iFalse;
213 } 235 }
214 appendCStrN_String(normalized, ch, 1); 236 appendCStrN_String(normalized, &c, 1);
215 } 237 }
216 appendCStr_String(normalized, "\n"); 238 appendCStr_String(normalized, "\n");
217 } 239 }
218 set_String(&d->source, collect_String(normalized)); 240 set_String(&d->source, collect_String(normalized));
219 printf("normalized:\n%s\n", cstr_String(&d->source)); 241// printf("normalized:\n%s\n", cstr_String(&d->source));
220} 242}
221 243
222void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { 244void setSource_GmDocument(iGmDocument *d, const iString *source, int width) {
@@ -244,4 +266,8 @@ void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRende
244 } 266 }
245} 267}
246 268
269iInt2 size_GmDocument(const iGmDocument *d) {
270 return d->size;
271}
272
247iDefineClass(GmDocument) 273iDefineClass(GmDocument)
diff --git a/src/gmdocument.h b/src/gmdocument.h
index dfad1d46..70e912df 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -26,3 +26,4 @@ void setSource_GmDocument (iGmDocument *, const iString *source, int width
26typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); 26typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
27 27
28void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *); 28void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *);
29iInt2 size_GmDocument (const iGmDocument *);
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
30iDeclareType(Url) 31iDeclareType(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
179iDeclareType(DrawContext) 186iDeclareType(DrawContext)
180 187
181struct Impl_DrawContext { 188struct 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
216iBeginDefineSubclass(DocumentWidget, Widget) 227iBeginDefineSubclass(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
20iDeclareType(Glyph) 21iDeclareType(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
379static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLen, iInt2 pos, 380static 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
453iInt2 advance_Text(int fontId, const char *text) { 478iInt2 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
465iInt2 advanceN_Text(int fontId, const char *text, size_t n) { 492iInt2 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
474iInt2 advanceRange_Text(int fontId, iRangecc text) { 506iInt2 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
483iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos) { 510iInt2 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
489static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) { 520static 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);
42iInt2 advance_Text (int fontId, const char *text); 42iInt2 advance_Text (int fontId, const char *text);
43iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ 43iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */
44iInt2 advanceRange_Text (int fontId, iRangecc text); 44iInt2 advanceRange_Text (int fontId, iRangecc text);
45iInt2 tryAdvanceRange_Text(int fontId, iRangecc text, int width, const char **endPos);
45 46
46void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); /* negative pos to switch alignment */ 47void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); /* negative pos to switch alignment */
47void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); 48void drawString_Text (int fontId, iInt2 pos, int color, const iString *text);