Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added text line wrapping and substring range information #395

Merged
merged 2 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading