Skip to content

Commit

Permalink
Added text line wrapping and substring range information
Browse files Browse the repository at this point in the history
  • Loading branch information
slouken committed Oct 3, 2024
1 parent b626c1f commit 86240ed
Show file tree
Hide file tree
Showing 4 changed files with 405 additions and 40 deletions.
150 changes: 136 additions & 14 deletions examples/showfont.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,22 @@ typedef struct {
int cursor;
bool cursorVisible;
Uint64 lastCursorChange;
bool highlighting;
int highlight1, highlight2;
} Scene;

static bool GetHighlightExtents(Scene *scene, int *marker1, int *marker2)
{
if (scene->highlight1 >= 0 && scene->highlight2 >= 0) {
*marker1 = SDL_min(scene->highlight1, scene->highlight2);
*marker2 = SDL_max(scene->highlight1, scene->highlight2) - 1;
if (*marker2 > *marker1) {
return true;
}
}
return false;
}

static void DrawScene(Scene *scene)
{
SDL_Renderer *renderer = scene->renderer;
Expand All @@ -96,10 +110,26 @@ static void DrawScene(Scene *scene)
focusRect.y -= 1;
focusRect.w += 2;
focusRect.h += 2;
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF);
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
SDL_RenderRect(renderer, &focusRect);
}

int marker1, marker2;
if (GetHighlightExtents(scene, &marker1, &marker2)) {
TTF_SubString **highlights = TTF_GetTextSubStringsForRange(scene->text, marker1, marker2, NULL);
if (highlights) {
SDL_SetRenderDrawColor(renderer, 0xCC, 0xCC, 0x00, 0xFF);
for (int i = 0; highlights[i]; ++i) {
SDL_FRect rect;
SDL_RectToFRect(&highlights[i]->rect, &rect);
rect.x += x;
rect.y += y;
SDL_RenderFillRect(renderer, &rect);
}
SDL_free(highlights);
}
}

switch (scene->textEngine) {
case TextEngineSurface:
/* Flush the renderer so we can draw directly to the window surface */
Expand Down Expand Up @@ -212,7 +242,7 @@ static void MoveCursorUp(Scene *scene)
x = substring.rect.x;
}
y = substring.rect.y - fontHeight;
if (TTF_GetTextSubStringAtPoint(scene->text, x, y, &substring)) {
if (TTF_GetTextSubStringForPoint(scene->text, x, y, &substring)) {
scene->cursor = GetCursorTextIndex(scene->font, x, &substring);
}
}
Expand All @@ -231,7 +261,7 @@ static void MoveCursorDown(Scene *scene)
x = substring.rect.x;
}
y = substring.rect.y + substring.rect.h + fontHeight;
if (TTF_GetTextSubStringAtPoint(scene->text, x, y, &substring)) {
if (TTF_GetTextSubStringForPoint(scene->text, x, y, &substring)) {
scene->cursor = GetCursorTextIndex(scene->font, x, &substring);
}
}
Expand All @@ -250,26 +280,61 @@ static void SetTextFocus(Scene *scene, bool focused)
} else {
SDL_StopTextInput(scene->window);
}

/* Reset the highlight */
scene->highlighting = false;
scene->highlight1 = -1;
scene->highlight2 = -1;
}

static void HandleTextClick(Scene *scene, float x, float y)
static void HandleTextMouseDown(Scene *scene, float x, float y)
{
TTF_SubString substring;

if (!scene->textFocus) {
SetTextFocus(scene, true);
return;
}

/* Set the cursor position */
TTF_SubString substring;
int textX = (int)SDL_roundf(x - (scene->textRect.x + 4.0f));
int textY = (int)SDL_roundf(y - (scene->textRect.y + 4.0f));
if (!TTF_GetTextSubStringForPoint(scene->text, textX, textY, &substring)) {
SDL_Log("Couldn't get cursor location: %s\n", SDL_GetError());
return;
}

scene->cursor = GetCursorTextIndex(scene->font, textX, &substring);
scene->highlighting = true;
scene->highlight1 = scene->cursor;
scene->highlight2 = -1;
}

static void HandleTextMouseMotion(Scene *scene, float x, float y)
{
if (!scene->highlighting) {
return;
}

/* Set the highlight position */
TTF_SubString substring;
int textX = (int)SDL_roundf(x - (scene->textRect.x + 4.0f));
int textY = (int)SDL_roundf(y - (scene->textRect.y + 4.0f));
if (!TTF_GetTextSubStringAtPoint(scene->text, textX, textY, &substring)) {
if (!TTF_GetTextSubStringForPoint(scene->text, textX, textY, &substring)) {
SDL_Log("Couldn't get cursor location: %s\n", SDL_GetError());
return;
}

scene->cursor = GetCursorTextIndex(scene->font, textX, &substring);
scene->highlight2 = scene->cursor;
}

static void HandleTextMouseUp(Scene *scene, float x, float y)
{
(void)x; (void)y;

if (scene->highlighting) {
scene->highlighting = false;
}
}

static void Cleanup(int exitcode)
Expand Down Expand Up @@ -491,6 +556,8 @@ int main(int argc, char *argv[])
Cleanup(2);
}
scene.font = font;
scene.highlight1 = -1;
scene.highlight2 = -1;

/* Show which font file we're looking at */
SDL_snprintf(string, sizeof(string), "Font file: %s", argv[0]); /* possible overflow */
Expand Down Expand Up @@ -587,7 +654,7 @@ int main(int argc, char *argv[])
{
SDL_FPoint pt = { event.button.x, event.button.y };
if (SDL_PointInRectFloat(&pt, &scene.textRect)) {
HandleTextClick(&scene, pt.x, pt.y);
HandleTextMouseDown(&scene, pt.x, pt.y);
} else if (scene.textFocus) {
SetTextFocus(&scene, false);
} else {
Expand All @@ -599,6 +666,24 @@ int main(int argc, char *argv[])
}
break;

case SDL_EVENT_MOUSE_MOTION:
{
SDL_FPoint pt = { event.button.x, event.button.y };
if (SDL_PointInRectFloat(&pt, &scene.textRect)) {
HandleTextMouseMotion(&scene, pt.x, pt.y);
}
}
break;

case SDL_EVENT_MOUSE_BUTTON_UP:
{
SDL_FPoint pt = { event.button.x, event.button.y };
if (SDL_PointInRectFloat(&pt, &scene.textRect)) {
HandleTextMouseUp(&scene, pt.x, pt.y);
}
}
break;

case SDL_EVENT_KEY_DOWN:
switch (event.key.key) {
case SDLK_A:
Expand Down Expand Up @@ -636,7 +721,19 @@ int main(int argc, char *argv[])
if (scene.textFocus) {
/* Copy to clipboard */
if (event.key.mod & SDL_KMOD_CTRL) {
SDL_SetClipboardText(scene.text->text);
int marker1, marker2;
if (GetHighlightExtents(&scene, &marker1, &marker2)) {
size_t length = marker2 - marker1 + 1;
char *temp = (char *)SDL_malloc(length + 1);
if (temp) {
SDL_memcpy(temp, &scene.text->text[marker1], length);
temp[length] = '\0';
SDL_SetClipboardText(temp);
SDL_free(temp);
}
} else {
SDL_SetClipboardText(scene.text->text);
}
}
}
break;
Expand Down Expand Up @@ -718,17 +815,37 @@ int main(int argc, char *argv[])
if (event.key.mod & SDL_KMOD_CTRL) {
/* Copy to clipboard and delete text */
if (scene.text->text) {
SDL_SetClipboardText(scene.text->text);
TTF_DeleteTextString(scene.text, 0, -1);
int marker1, marker2;
if (GetHighlightExtents(&scene, &marker1, &marker2)) {
size_t length = marker2 - marker1 + 1;
char *temp = (char *)SDL_malloc(length + 1);
if (temp) {
SDL_memcpy(temp, &scene.text->text[marker1], length);
temp[length] = '\0';
SDL_SetClipboardText(scene.text->text);
SDL_free(temp);
}
TTF_DeleteTextString(scene.text, marker1, (int)length);
scene.cursor = marker1;
scene.highlight1 = -1;
scene.highlight2 = -1;
} else {
SDL_SetClipboardText(scene.text->text);
TTF_DeleteTextString(scene.text, 0, -1);
}
}
}
}
break;
case SDLK_LEFT:
if (scene.textFocus) {
if (event.key.mod & SDL_KMOD_CTRL) {
/* Move to the beginning of the line (FIXME) */
scene.cursor = 0;
/* Move to the beginning of the line */
TTF_SubString substring;
if (TTF_GetTextSubString(scene.text, scene.cursor, &substring) &&
TTF_GetTextSubStringForLine(scene.text, substring.line_index, &substring)) {
scene.cursor = substring.offset;
}
} else {
MoveCursorLeft(&scene);
}
Expand All @@ -737,7 +854,12 @@ int main(int argc, char *argv[])
case SDLK_RIGHT:
if (scene.textFocus) {
if (event.key.mod & SDL_KMOD_CTRL) {
/* Move to the end of the line (FIXME) */
/* Move to the end of the line */
TTF_SubString substring;
if (TTF_GetTextSubString(scene.text, scene.cursor, &substring) &&
TTF_GetTextSubStringForLine(scene.text, substring.line_index, &substring)) {
scene.cursor = substring.offset + substring.length;
}
} else {
MoveCursorRight(&scene);
}
Expand Down
52 changes: 43 additions & 9 deletions include/SDL3_ttf/SDL_ttf.h
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,7 @@ typedef struct TTF_Text
{
char *text; /**< A copy of the text used to create this text object, useful for layout and debugging. This will be freed automatically when the object is destroyed. */
SDL_FColor color; /**< The color of the text, read-write. You can change this anytime. */
int num_lines; /**< The number of lines in the text, 0 if it's empty */

int refcount; /**< Application reference count, used when freeing surface */

Expand Down Expand Up @@ -1696,16 +1697,18 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSize(TTF_Text *text, int *w, int *h)
*/
typedef struct TTF_SubString
{
int offset; /**< The byte offset from the beginning of the text */
int length; /**< The byte length starting at the offset */
SDL_Rect rect; /**< The rectangle, relative to the top left of the text, containing the substring */
int offset; /**< The byte offset from the beginning of the text */
int length; /**< The byte length starting at the offset */
int line_index; /**< The index of the line that contains this substring */
int cluster_index; /**< The internal cluster index, used for quickly iterating */
SDL_Rect rect; /**< The rectangle, relative to the top left of the text, containing the substring */
} TTF_SubString;

/**
* Get the portion of a text string that surrounds a text offset.
* Get the substring of a text object that surrounds a text offset.
*
* If the offset is less than 0, this will return a zero width substring at
* the beginning of the text. If the offset is greater than or equal to the
* If `offset` is less than 0, this will return a zero width substring at
* the beginning of the text. If `offset` is greater than or equal to the
* length of the text string, this will return a zero width substring at the
* end of the text.
*
Expand All @@ -1718,6 +1721,38 @@ typedef struct TTF_SubString
*/
extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring);

/**
* Get the substring of a text object that contains the given line.
*
* If `line` is less than 0, this will return a zero width substring at the beginning of the text. If `line` is greater than or equal to `text->num_lines` this will return a zero width substring at the end of the text.
*
* \param text the TTF_Text to query.
* \param line a zero-based line index, in the range [0 .. text->num_lines-1].
* \param substring a pointer filled in with the substring containing the offset.
* \returns true on success or false on failure; call SDL_GetError() for more
* information.
*/
extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubStringForLine(TTF_Text *text, int line, TTF_SubString *substring);

/**
* Get the substrings of a text object that contain a range of text.
*
* The smaller offset will be clamped to 0 and the larger offset will be clamped to the length of text minus 1. The substrings that are returned will include the first offset and the second offset inclusive, e.g. {0, 2} of "abcd" will return "abc". If the text is empty, this will return a single zero width substring.
*
* If an offset is negative, it will be considered as an offset from the end of the text, so {0, -1} would return substrings for the entire text.
*
* \param text the TTF_Text to query.
* \param offset1 the first byte offset into the text string.
* \param offset2 the second byte offset into the text string.
* \param count a pointer filled in with the number of substrings returned, may
* be NULL.
* \returns a NULL terminated array of substring pointers or NULL on
* failure; call SDL_GetError() for more information. This is a
* single allocation that should be freed with SDL_free() when it is
* no longer needed.
*/
extern SDL_DECLSPEC TTF_SubString ** SDLCALL TTF_GetTextSubStringsForRange(TTF_Text *text, int offset1, int offset2, int *count);

/**
* Get the portion of a text string that is closest to a point.
*
Expand All @@ -1728,12 +1763,11 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubString(TTF_Text *text, int offset
* outside the bounds of the text area.
* \param y the y coordinate relative to the top side of the text, may be
* outside the bounds of the text area.
* \param substring a pointer filled in with the closest substring of text to
* the given point, may be NULL.
* \param substring a pointer filled in with the closest substring of text to the given point.
* \returns true on success or false on failure; call SDL_GetError() for more
* information.
*/
extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubStringAtPoint(TTF_Text *text, int x, int y, TTF_SubString *substring);
extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubStringForPoint(TTF_Text *text, int x, int y, TTF_SubString *substring);

/**
* Update the layout of a text object.
Expand Down
Loading

0 comments on commit 86240ed

Please sign in to comment.