diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-15 18:18:45 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-15 18:18:45 +0300 |
commit | aed6837cc1e1bfb9ccb238900ad7387444b624eb (patch) | |
tree | 3b502166a9b9cd69b46ea6c11949be82bedaff53 /src | |
parent | 9f7bbeecba762213c7dec4555e0cbb1da2b2ea66 (diff) |
Fixing regressions text metrics, InputWidget
`run_Font_` was moving the Y cursor position twice for each line break.
Checking for the HarfBuzz UNSAFE_TO_BREAK flag leads to some unexpected behavior near edges of words.
The old `tryAdvanceNoWrap` method should return the maximum horizontal advance of the text, and not the cursor position's advance.
`draw_WrapText` used the wrong foreground color.
`TextBuf` now uses WrapText to do all the measuring and drawing, making things much simpler.
Diffstat (limited to 'src')
-rw-r--r-- | src/ui/inputwidget.c | 68 | ||||
-rw-r--r-- | src/ui/text.c | 23 | ||||
-rw-r--r-- | src/ui/text.h | 4 |
3 files changed, 41 insertions, 54 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index b51b3985..29003ad5 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -747,7 +747,13 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const | |||
747 | 747 | ||
748 | static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { | 748 | static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { |
749 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); | 749 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); |
750 | int xPos = measureN_Text(d->font, cstr_String(&line->text), d->cursor - line->offset).advance.x; | 750 | int xPos1 = maxWidth_TextMetrics(measureN_Text(d->font, |
751 | cstr_String(&line->text), | ||
752 | d->cursor - line->offset)); | ||
753 | int xPos2 = maxWidth_TextMetrics(measureN_Text(d->font, | ||
754 | cstr_String(&line->text), | ||
755 | iMin(d->cursor + 1 - line->offset, line->len))); | ||
756 | const int xPos = (xPos1 + xPos2) / 2; | ||
751 | size_t newCursor = iInvalidPos; | 757 | size_t newCursor = iInvalidPos; |
752 | const size_t numLines = size_Array(&d->lines); | 758 | const size_t numLines = size_Array(&d->lines); |
753 | if (dir < 0 && d->cursorLine > 0) { | 759 | if (dir < 0 && d->cursorLine > 0) { |
@@ -861,32 +867,6 @@ static size_t skipWord_InputWidget_(const iInputWidget *d, size_t pos, int dir) | |||
861 | return pos; | 867 | return pos; |
862 | } | 868 | } |
863 | 869 | ||
864 | #if 0 | ||
865 | static iInt2 textOrigin_InputWidget_(const iInputWidget *d) { //}, const char *visText) { | ||
866 | // const iWidget *w = constAs_Widget(d); | ||
867 | iRect bounds = contentBounds_InputWidget_(d);/* adjusted_Rect(bounds_Widget(w), | ||
868 | addX_I2(padding_(), d->leftPadding), | ||
869 | neg_I2(addX_I2(padding_(), d->rightPadding)));*/ | ||
870 | // const iInt2 emSize = advance_Text(d->font, "M"); | ||
871 | // const int textWidth = advance_Text(d->font, visText).x; | ||
872 | // const int cursorX = advanceN_Text(d->font, visText, d->cursor).x; | ||
873 | // int xOff = 0; | ||
874 | // shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); | ||
875 | /* if (d->maxLen == 0) { | ||
876 | if (textWidth > width_Rect(bounds) - emSize.x) { | ||
877 | xOff = width_Rect(bounds) - emSize.x - textWidth; | ||
878 | } | ||
879 | if (cursorX + xOff < width_Rect(bounds) / 2) { | ||
880 | xOff = width_Rect(bounds) / 2 - cursorX; | ||
881 | } | ||
882 | xOff = iMin(xOff, 0); | ||
883 | }*/ | ||
884 | // const int yOff = 0.3f * lineHeight_Text(d->font); // (height_Rect(bounds) - lineHeight_Text(d->font)) / 2; | ||
885 | // return addY_I2(topLeft_Rect(bounds), yOff); | ||
886 | |||
887 | } | ||
888 | #endif | ||
889 | |||
890 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { | 870 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { |
891 | const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); | 871 | const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); |
892 | const size_t lineNumber = iMin(iMax(0, pos.y) / lineHeight_Text(d->font), | 872 | const size_t lineNumber = iMin(iMax(0, pos.y) / lineHeight_Text(d->font), |
@@ -1342,17 +1322,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1342 | return processEvent_Widget(w, ev); | 1322 | return processEvent_Widget(w, ev); |
1343 | } | 1323 | } |
1344 | 1324 | ||
1345 | #if 0 | ||
1346 | static iBool isWhite_(const iString *str) { | ||
1347 | iConstForEach(String, i, str) { | ||
1348 | if (!isSpace_Char(i.value)) { | ||
1349 | return iFalse; | ||
1350 | } | ||
1351 | } | ||
1352 | return iTrue; | ||
1353 | } | ||
1354 | #endif | ||
1355 | |||
1356 | static void draw_InputWidget_(const iInputWidget *d) { | 1325 | static void draw_InputWidget_(const iInputWidget *d) { |
1357 | const iWidget *w = constAs_Widget(d); | 1326 | const iWidget *w = constAs_Widget(d); |
1358 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); | 1327 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); |
@@ -1400,14 +1369,12 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1400 | /* Draw the selected range. */ | 1369 | /* Draw the selected range. */ |
1401 | const iRanges mark = mark_InputWidget_(d); | 1370 | const iRanges mark = mark_InputWidget_(d); |
1402 | if (mark.start < lineRange.end && mark.end > lineRange.start) { | 1371 | if (mark.start < lineRange.end && mark.end > lineRange.start) { |
1403 | const int m1 = measureN_Text(d->font, | 1372 | const int m1 = maxWidth_TextMetrics(measureN_Text(d->font, |
1404 | cstr_String(&line->text), | 1373 | cstr_String(&line->text), |
1405 | iMax(lineRange.start, mark.start) - line->offset) | 1374 | iMax(lineRange.start, mark.start) - line->offset)); |
1406 | .advance.x; | 1375 | const int m2 = maxWidth_TextMetrics(measureN_Text(d->font, |
1407 | const int m2 = measureN_Text(d->font, | ||
1408 | cstr_String(&line->text), | 1376 | cstr_String(&line->text), |
1409 | iMin(lineRange.end, mark.end) - line->offset) | 1377 | iMin(lineRange.end, mark.end) - line->offset)); |
1410 | .advance.x; | ||
1411 | fillRect_Paint(&p, | 1378 | fillRect_Paint(&p, |
1412 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), | 1379 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), |
1413 | init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)), | 1380 | init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)), |
@@ -1446,15 +1413,20 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1446 | } | 1413 | } |
1447 | const iInputLine *curLine = line_InputWidget_(d, d->cursorLine); | 1414 | const iInputLine *curLine = line_InputWidget_(d, d->cursorLine); |
1448 | const iString * text = &curLine->text; | 1415 | const iString * text = &curLine->text; |
1449 | /* The `gap_UI` offsets below are a hack. They are used because for some reason the | 1416 | /* The bounds include visible characters, while advance includes whitespace as well. |
1450 | cursor rect and the glyph inside don't quite position like during `run_Text_()`. */ | 1417 | Normally only the advance is needed, but if the cursor is at a newline, the advance |
1451 | const int prefixSize = measureN_Text(d->font, cstr_String(text), d->cursor - curLine->offset).advance.x; | 1418 | will have reset back to zero. */ |
1419 | const int prefixSize = maxWidth_TextMetrics(measureN_Text(d->font, | ||
1420 | cstr_String(text), | ||
1421 | d->cursor - curLine->offset)); | ||
1452 | const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font) * d->cursorLine), | 1422 | const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font) * d->cursorLine), |
1453 | prefixSize + | 1423 | prefixSize + |
1454 | (d->mode == insert_InputMode ? -curSize.x / 2 : 0)); | 1424 | (d->mode == insert_InputMode ? -curSize.x / 2 : 0)); |
1455 | const iRect curRect = { curPos, curSize }; | 1425 | const iRect curRect = { curPos, curSize }; |
1456 | fillRect_Paint(&p, curRect, uiInputCursor_ColorId); | 1426 | fillRect_Paint(&p, curRect, uiInputCursor_ColorId); |
1457 | if (d->mode == overwrite_InputMode) { | 1427 | if (d->mode == overwrite_InputMode) { |
1428 | /* The `gap_UI` offsets below are a hack. They are used because for some reason the | ||
1429 | cursor rect and the glyph inside don't quite position like during `run_Text_()`. */ | ||
1458 | draw_Text(d->font, | 1430 | draw_Text(d->font, |
1459 | addX_I2(curPos, iMin(1, gap_UI / 8)), | 1431 | addX_I2(curPos, iMin(1, gap_UI / 8)), |
1460 | uiInputCursorText_ColorId, | 1432 | uiInputCursorText_ColorId, |
diff --git a/src/ui/text.c b/src/ui/text.c index 0a3b7b2e..b283d700 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -1450,7 +1450,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1450 | wrapResumePos = run->logical.end; | 1450 | wrapResumePos = run->logical.end; |
1451 | wrapRuns.end = runIndex; | 1451 | wrapRuns.end = runIndex; |
1452 | wrapResumeRunIndex = runIndex + 1; | 1452 | wrapResumeRunIndex = runIndex + 1; |
1453 | yCursor += d->height; | 1453 | //yCursor += d->height; |
1454 | break; | 1454 | break; |
1455 | } | 1455 | } |
1456 | wrapResumeRunIndex = runCount; | 1456 | wrapResumeRunIndex = runCount; |
@@ -1489,10 +1489,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1489 | prevCh = ch; | 1489 | prevCh = ch; |
1490 | } | 1490 | } |
1491 | else { | 1491 | else { |
1492 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { | 1492 | //if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { |
1493 | safeBreakPos = logPos; | 1493 | safeBreakPos = logPos; |
1494 | breakAdvance = wrapAdvance; | 1494 | breakAdvance = wrapAdvance; |
1495 | } | 1495 | //} |
1496 | } | 1496 | } |
1497 | /* Out of room? */ | 1497 | /* Out of room? */ |
1498 | if (wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > | 1498 | if (wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > |
@@ -1787,7 +1787,8 @@ iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **e | |||
1787 | .maxWidth = width, | 1787 | .maxWidth = width, |
1788 | .wrapFunc = cbAdvanceOneLine_, | 1788 | .wrapFunc = cbAdvanceOneLine_, |
1789 | .context = endPos }; | 1789 | .context = endPos }; |
1790 | return measure_WrapText(&wrap, fontId).bounds.size; | 1790 | iTextMetrics tm = measure_WrapText(&wrap, fontId); |
1791 | return init_I2(maxWidth_TextMetrics(tm), tm.bounds.size.y); | ||
1791 | } | 1792 | } |
1792 | 1793 | ||
1793 | iTextMetrics measureN_Text(int fontId, const char *text, size_t n) { | 1794 | iTextMetrics measureN_Text(int fontId, const char *text, size_t n) { |
@@ -1969,7 +1970,7 @@ void draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) { | |||
1969 | .text = d->text, | 1970 | .text = d->text, |
1970 | .pos = pos, | 1971 | .pos = pos, |
1971 | .wrap = d, | 1972 | .wrap = d, |
1972 | .color = color, | 1973 | .color = color & mask_ColorId, |
1973 | }); | 1974 | }); |
1974 | } | 1975 | } |
1975 | 1976 | ||
@@ -2088,6 +2089,13 @@ iDefineTypeConstructionArgs(TextBuf, (int font, int color, const char *text), fo | |||
2088 | 2089 | ||
2089 | static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iBool doWrap, const char *text) { | 2090 | static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iBool doWrap, const char *text) { |
2090 | SDL_Renderer *render = text_.render; | 2091 | SDL_Renderer *render = text_.render; |
2092 | iWrapText wrapText = { | ||
2093 | .text = range_CStr(text), | ||
2094 | .maxWidth = maxWidth, | ||
2095 | .mode = (doWrap ? word_WrapTextMode : anyCharacter_WrapTextMode), | ||
2096 | }; | ||
2097 | d->size = measure_WrapText(&wrapText, font).bounds.size; | ||
2098 | #if 0 | ||
2091 | if (maxWidth == 0) { | 2099 | if (maxWidth == 0) { |
2092 | d->size = measure_Text(font, text).bounds.size; | 2100 | d->size = measure_Text(font, text).bounds.size; |
2093 | } | 2101 | } |
@@ -2101,6 +2109,7 @@ static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iB | |||
2101 | d->size.y += iMax(size.y, lineHeight_Text(font)); | 2109 | d->size.y += iMax(size.y, lineHeight_Text(font)); |
2102 | } | 2110 | } |
2103 | } | 2111 | } |
2112 | #endif | ||
2104 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); | 2113 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); |
2105 | if (d->size.x * d->size.y) { | 2114 | if (d->size.x * d->size.y) { |
2106 | d->texture = SDL_CreateTexture(render, | 2115 | d->texture = SDL_CreateTexture(render, |
@@ -2119,7 +2128,7 @@ static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iB | |||
2119 | SDL_SetRenderDrawColor(render, 255, 255, 255, 0); | 2128 | SDL_SetRenderDrawColor(render, 255, 255, 255, 0); |
2120 | SDL_RenderClear(render); | 2129 | SDL_RenderClear(render); |
2121 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ | 2130 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ |
2122 | const int fg = color | fillBackground_ColorId; | 2131 | #if 0 |
2123 | iRangecc range = range_CStr(text); | 2132 | iRangecc range = range_CStr(text); |
2124 | if (maxWidth == 0) { | 2133 | if (maxWidth == 0) { |
2125 | draw_Text_(font, zero_I2(), fg, range); | 2134 | draw_Text_(font, zero_I2(), fg, range); |
@@ -2137,6 +2146,8 @@ static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iB | |||
2137 | pos.y += lineHeight_Text(font); | 2146 | pos.y += lineHeight_Text(font); |
2138 | } | 2147 | } |
2139 | } | 2148 | } |
2149 | #endif | ||
2150 | draw_WrapText(&wrapText, font, zero_I2(), color | fillBackground_ColorId); | ||
2140 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); | 2151 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); |
2141 | SDL_SetRenderTarget(render, oldTarget); | 2152 | SDL_SetRenderTarget(render, oldTarget); |
2142 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); | 2153 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); |
diff --git a/src/ui/text.h b/src/ui/text.h index 09d92ce0..d779dd96 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -159,6 +159,10 @@ struct Impl_TextMetrics { | |||
159 | iInt2 advance; /* cursor offset */ | 159 | iInt2 advance; /* cursor offset */ |
160 | }; | 160 | }; |
161 | 161 | ||
162 | iLocalDef int maxWidth_TextMetrics(const iTextMetrics d) { | ||
163 | return iMax(width_Rect(d.bounds), d.advance.x); | ||
164 | } | ||
165 | |||
162 | iTextMetrics measureRange_Text (int fontId, iRangecc text); | 166 | iTextMetrics measureRange_Text (int fontId, iRangecc text); |
163 | iTextMetrics measureWrapRange_Text (int fontId, int maxWidth, iRangecc text); | 167 | iTextMetrics measureWrapRange_Text (int fontId, int maxWidth, iRangecc text); |
164 | iTextMetrics measureN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ | 168 | iTextMetrics measureN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ |