From 64e23a3455ef1e71786c4b3a835a91954e08ab79 Mon Sep 17 00:00:00 2001 From: Physick <96335032+DegustatorPonos@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:57:38 +0500 Subject: Editor position work --- src/scenes/puzzleScene.c | 21 +++-- src/ui_elements/TextEditor.c | 189 +++++++++++++++++++++++++++++++++---------- src/ui_elements/UI.h | 28 +++++-- 3 files changed, 183 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/src/scenes/puzzleScene.c b/src/scenes/puzzleScene.c index d49b530..9f7b4d7 100644 --- a/src/scenes/puzzleScene.c +++ b/src/scenes/puzzleScene.c @@ -8,7 +8,6 @@ typedef struct { Puzzle *PuzzlePtr; TextArea *EditorInfo; int scroll; - int editor_scroll; } puzzleSceneData; const int HEADER_FONT_SIZE = 50; @@ -67,9 +66,9 @@ bool drawPuzzleScene(void *self) { DrawPuzzleName(data->PuzzlePtr->Name); Rectangle body = GetBodyRect(); - DrawRecBordered(body, WHITE, COLORSCHEME_ORANGE, BORDER); - DrawTextArea(data->EditorInfo, body, GetMonoFont(), 20, 2, &data->editor_scroll); - return true; + // DrawRecBordered(body, WHITE, COLORSCHEME_ORANGE, BORDER); + // DrawTextArea(data->EditorInfo, body, GetMonoFont(), 20, 2, &data->editor_scroll); + // return true; Rectangle pane1, pane2; GetPanesSplit(&pane1, &pane2, body, 0.5, 0.4); @@ -78,7 +77,7 @@ bool drawPuzzleScene(void *self) { DrawTextInRec(pane1, data->PuzzlePtr->Description, GetMainFont(), 20, COLORSCHEME_BROWN, 3, &data->scroll); - DrawTextArea(data->EditorInfo, pane2, GetMonoFont(), 20, 2, &data->editor_scroll); + DrawTextArea(data->EditorInfo, pane2); return true; } @@ -106,7 +105,17 @@ Scene *CreatePuzzleScene(Puzzle *puzzle) { puzzleSceneData *data = malloc(sizeof(puzzleSceneData)); data->PuzzlePtr = puzzle; - data->EditorInfo = CreateTextArea(); + + TextAreaDisplayParams *EditorDisplayParams = malloc(sizeof(TextAreaDisplayParams)); + if (EditorDisplayParams != NULL) { + EditorDisplayParams->font = GetMonoFont(); + EditorDisplayParams->spacing = 1; + EditorDisplayParams->font_size = 20; // this will change + EditorDisplayParams->scroll_h = 0; + EditorDisplayParams->scroll_v = 0; + } + data->EditorInfo = CreateTextArea(EditorDisplayParams); + outp->data = data; return outp; diff --git a/src/ui_elements/TextEditor.c b/src/ui_elements/TextEditor.c index fa85443..620abea 100644 --- a/src/ui_elements/TextEditor.c +++ b/src/ui_elements/TextEditor.c @@ -7,14 +7,23 @@ #include #include -void HighlightCharacter(Vector2 lineStartPos, char *line, int index, Font *font, int height, int spacing) { +float GetCursorXPos(TextArea *ta) { + int index = ta->CursorPosition.x; + char *line = ta->CurrentNode->text; float x_position = 0; for (int i = 0; i < index; i++) { - x_position += MeasureChar(font, height, line[i]); - x_position += spacing; + x_position += MeasureChar(ta->DisplayParams->font, + ta->DisplayParams->font_size, + line[i]); + x_position += ta->DisplayParams->spacing; } + return x_position; +} + +/// We pass x_position not to call GetCursorXPos twice (it's expencive) +void DrawCursor(TextArea *ta, Vector2 lineStartPos, float x_position) { DrawLine(lineStartPos.x + x_position, lineStartPos.y, - lineStartPos.x + x_position, lineStartPos.y + height, COLORSCHEME_BROWN); + lineStartPos.x + x_position, lineStartPos.y + ta->DisplayParams->font_size, COLORSCHEME_BROWN); } TextNode *CreateNode(TextNode *prev, TextNode *next) { @@ -77,7 +86,7 @@ void AppendTextToNode(TextNode *base, char *new_text) { base->len += new_len; } -TextArea *CreateTextArea() { +TextArea *CreateTextArea(TextAreaDisplayParams *DisplayParams) { TextArea *outp = malloc(sizeof(TextArea)); if (outp == NULL) return NULL; @@ -90,9 +99,31 @@ TextArea *CreateTextArea() { outp->StartNode = start_node; outp->CurrentNode = start_node; outp->NodeLenth = 1; + outp->DisplayParams = DisplayParams; return outp; } +void EnsureCursorIsInFrame(TextArea *ta) { + TextAreaDisplayParams *params = ta->DisplayParams; + Vector2 cursorPos = { + .x = GetCursorXPos(ta), + .y = ta->CursorPosition.y * ta->DisplayParams->font_size, + }; + + if (cursorPos.y < params->scroll_v) { + params->scroll_v = cursorPos.y; + } else if (params->scroll_v + params->_area_height - params->font_size < cursorPos.y) { + params->scroll_v = cursorPos.y + params->_area_height; + } + + if (cursorPos.x < params->scroll_h) { + params->scroll_h = cursorPos.x - (params->_area_width / 2.0f); + if (params->scroll_h < 0) params->scroll_h = 0; + } else if (params->scroll_h + params->_area_width - params->font_size < cursorPos.x) { + params->scroll_h = cursorPos.x - (params->_area_width / 2.0f); + } +} + bool PushNode(TextArea *ta) { if (ta == NULL || ta->CurrentNode == NULL) return false; TextNode *new_node = CreateNode(ta->CurrentNode, ta->CurrentNode->NextNode); @@ -134,6 +165,7 @@ void PopCurrent(TextArea *ta) { void FreeTextArea(TextArea *ta) { FreeNodeRec(ta->StartNode); + if (ta->DisplayParams != NULL) free(ta->DisplayParams); free(ta); } @@ -143,6 +175,26 @@ char GetCurrentChar(TextArea *ta) { return ta->CurrentNode->text[(int)ta->CursorPosition.x]; } +void MoveCursorDown(TextArea *ta) { + if (ta == NULL || ta->CurrentNode == NULL) return; + if (ta->CurrentNode->NextNode == NULL) return; + ta->CurrentNode = ta->CurrentNode->NextNode; + + ta->CursorPosition.y += 1; + size_t line_len = ta->CurrentNode->len; + if (ta->CursorPosition.x > line_len) ta->CursorPosition.x = line_len; +} + +void MoveCursorUp(TextArea *ta) { + if (ta == NULL || ta->CurrentNode == NULL) return; + if (ta->CursorPosition.y == 0 || ta->CurrentNode->PrevNode == NULL) return; + ta->CurrentNode = ta->CurrentNode->PrevNode; + ta->CursorPosition.y -= 1; + + size_t line_len = ta->CurrentNode->len; + if (ta->CursorPosition.x > line_len) ta->CursorPosition.x = line_len; +} + /// This function is used in the skips through words () bool IsSkippable(char c) { return isalpha(c) || isdigit(c); @@ -153,7 +205,14 @@ void MoveCursorRight(TextArea *ta) { ta->CursorPosition.x += 1; size_t line_len = ta->CurrentNode->len; - if (ta->CursorPosition.x > line_len) ta->CursorPosition.x = line_len; + if (ta->CursorPosition.x > line_len) { + if (ta->CurrentNode->NextNode == NULL) { + ta->CursorPosition.x = line_len; + return; + } + MoveCursorDown(ta); + ta->CursorPosition.x = 0; + } } void MoveCursorRightWord(TextArea *ta) { @@ -163,7 +222,14 @@ void MoveCursorRightWord(TextArea *ta) { } while (IsSkippable(GetCurrentChar(ta))); size_t line_len = ta->CurrentNode->len; - if (ta->CursorPosition.x > line_len) ta->CursorPosition.x = line_len; + if (ta->CursorPosition.x > line_len) { + if (ta->CurrentNode->NextNode == NULL) { + ta->CursorPosition.x = line_len; + return; + } + MoveCursorDown(ta); + ta->CursorPosition.x = 0; + } } void MoveCursorEnd(TextArea *ta) { @@ -174,7 +240,15 @@ void MoveCursorEnd(TextArea *ta) { void MoveCursorLeft(TextArea *ta) { if (ta == NULL || ta->CurrentNode == NULL) return; ta->CursorPosition.x -= 1; - if (ta->CursorPosition.x < 0) ta->CursorPosition.x = 0; + + if (ta->CursorPosition.x < 0) { + if (ta->CursorPosition.y == 0 || ta->CurrentNode->PrevNode == NULL) { + ta->CursorPosition.x = 0; + return; + } + MoveCursorUp(ta); + MoveCursorEnd(ta); + } } void MoveCursorLeftWord(TextArea *ta) { @@ -183,8 +257,14 @@ void MoveCursorLeftWord(TextArea *ta) { ta->CursorPosition.x -= 1; } while (IsSkippable(GetCurrentChar(ta)) && ta->CursorPosition.x > 0); - size_t line_len = ta->CurrentNode->len; - if (ta->CursorPosition.x > line_len) ta->CursorPosition.x = line_len; + if (ta->CursorPosition.x < 0) { + if (ta->CursorPosition.y == 0 || ta->CurrentNode->PrevNode == NULL) { + ta->CursorPosition.x = 0; + return; + } + MoveCursorUp(ta); + MoveCursorEnd(ta); + } } void MoveCursorHome(TextArea *ta) { @@ -192,26 +272,6 @@ void MoveCursorHome(TextArea *ta) { ta->CursorPosition.x = 0; } -void MoveCursorDown(TextArea *ta) { - if (ta == NULL || ta->CurrentNode == NULL) return; - if (ta->CurrentNode->NextNode == NULL) return; - ta->CurrentNode = ta->CurrentNode->NextNode; - - ta->CursorPosition.y += 1; - size_t line_len = ta->CurrentNode->len; - if (ta->CursorPosition.x > line_len) ta->CursorPosition.x = line_len; -} - -void MoveCursorUp(TextArea *ta) { - if (ta == NULL || ta->CurrentNode == NULL) return; - if (ta->CursorPosition.y == 0 || ta->CurrentNode->PrevNode == NULL) return; - ta->CurrentNode = ta->CurrentNode->PrevNode; - ta->CursorPosition.y -= 1; - - size_t line_len = ta->CurrentNode->len; - if (ta->CursorPosition.x > line_len) ta->CursorPosition.x = line_len; -} - void BreakLine(TextArea *ta) { if (ta == NULL || ta->CurrentNode == NULL) return; if (ta->CursorPosition.x == 0) { @@ -245,10 +305,12 @@ void BreakLine(TextArea *ta) { ta->CursorPosition.y += 1; ta->CursorPosition.x = 0; ta->CurrentNode = ta->CurrentNode->NextNode; + EnsureCursorIsInFrame(ta); } void InsertChar(TextArea *ta, char new_char) { if (ta == NULL || ta->CurrentNode == NULL) return; + EnsureCursorIsInFrame(ta); size_t line_len = ta->CurrentNode->len; if (line_len + 2 > ta->CurrentNode->cap) { @@ -300,6 +362,7 @@ void DeleteChar(TextArea *ta) { ta->CursorPosition.x -= 1; ta->CurrentNode->len -= 1; if (ta->CurrentNode->len < 0) ta->CurrentNode->len = 0; + EnsureCursorIsInFrame(ta); } bool IsKeyPressedFix(int key) { @@ -322,6 +385,16 @@ void HandleControlComands(TextArea *ta) { if (IsKeyPressedFix(KEY_RIGHT)) MoveCursorRightWord(ta); if (IsKeyPressedFix(KEY_LEFT)) MoveCursorLeftWord(ta); + if (IsKeyPressedFix(KEY_EQUAL)) { + if (ta->DisplayParams->font_size < MAX_FONT_SIZE) + ta->DisplayParams->font_size += 1; + } + + if (IsKeyPressedFix(KEY_MINUS)) { + if (ta->DisplayParams->font_size > MIN_FONT_SIZE) + ta->DisplayParams->font_size -= 1; + } + // emacs-like stuff if (IsKeyPressedFix(KEY_F)) MoveCursorRight(ta); if (IsKeyPressedFix(KEY_B)) MoveCursorLeft(ta); @@ -380,27 +453,57 @@ void HandleKeyboard(TextArea *ta) { if (IsKeyPressedFix(KEY_F3)) PrintAllLines(ta); } +/// Assumes that the cursor is in the scroll area +void HandleScroll(TextArea *ta, Rectangle rec) { + int scroll = GetMouseWheelMove() * SCROLL_SENC; + if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) { + ta->DisplayParams->scroll_h -= scroll; + if (ta->DisplayParams->scroll_h < 0) + ta->DisplayParams->scroll_h = 0; + // TODO: figure out if we can easily get max width. Probably need some + // lazy eval caching + } else { + int text_height = ta->NodeLenth * ta->DisplayParams->font_size; + int max_scroll = text_height - rec.height; + ta->DisplayParams->scroll_v -= scroll; + if (ta->DisplayParams->scroll_v < 0) + ta->DisplayParams->scroll_v = 0; + if (text_height > rec.height && ta->DisplayParams->scroll_v > max_scroll) + ta->DisplayParams->scroll_v = max_scroll; + } +} + /// draws a text area on the screen. Main draw entry point -void DrawTextArea(TextArea *ta, Rectangle rec, Font *font, int height, int spacing, int *scroll) { - int vertical_acc = rec.y - *scroll; - int text_height = ta->NodeLenth * height; - int max_scroll = text_height - rec.height; - - if (text_height >= rec.height && IsMouseInRec(rec)) { - *scroll -= GetMouseWheelMove() * SCROLL_SENC; - if (*scroll < 0) *scroll = 0; - if (*scroll > max_scroll) *scroll = max_scroll; +void DrawTextArea(TextArea *ta, Rectangle rec) { + // those are used to move a cursor in frame when typing + ta->DisplayParams->_area_height = rec.height; + ta->DisplayParams->_area_width = rec.width; + + TextAreaDisplayParams *params = ta->DisplayParams; + // int vertical_acc = rec.y - *scroll; + + if (IsMouseInRec(rec)) { + HandleScroll(ta, rec); } - Vector2 pos = { .x = rec.x + spacing, .y = rec.y - *scroll }; + Vector2 pos = { + .x = rec.x + params->spacing - params->scroll_h, + .y = rec.y + params->spacing - params->scroll_v + }; + + Vector2 cursorPos = { + .x = GetCursorXPos(ta), + .y = ta->CursorPosition.y * ta->DisplayParams->font_size, + }; + TextNode *node = ta->StartNode; BeginScissorMode(rec.x, rec.y, rec.width, rec.height); while (node != NULL) { - DrawTextEx(*font, node->text, pos, height, spacing, COLORSCHEME_BROWN); + DrawTextEx(*params->font, node->text, pos, params->font_size, params->spacing, COLORSCHEME_BROWN); if (node == ta->CurrentNode) - HighlightCharacter(pos, node->text, ta->CursorPosition.x, font, height, spacing); + DrawCursor(ta, pos, cursorPos.x); node = node->NextNode; - pos.y += height; + pos.y += params->font_size; } EndScissorMode(); } diff --git a/src/ui_elements/UI.h b/src/ui_elements/UI.h index eaf17e8..9f126c3 100644 --- a/src/ui_elements/UI.h +++ b/src/ui_elements/UI.h @@ -7,6 +7,9 @@ static const float SCROLL_SENC = 10; static const float TAB_WIDTH = 4; +static const int MIN_FONT_SIZE = 10; +static const int MAX_FONT_SIZE = 100; + void DrawRecBordered(Rectangle rec, Color bg, Color border_color, int border_width); void DrawTextInRec(Rectangle rec, char *text, Font *font, int height, Color color, float padding, int *scroll); float MeasureChar(Font *font, int height, char symbol); @@ -24,6 +27,21 @@ typedef struct { size_t cap; } TextNode; + +typedef struct { + Font *font; + int font_size; + int spacing; + + int scroll_h; + int scroll_v; + + /// DO NOT MODIFY THAT! It's for internal use only + int _area_width; + /// DO NOT MODIFY THAT! It's for internal use only + int _area_height; +} TextAreaDisplayParams; + typedef struct { TextNode *StartNode; /// For the quick access @@ -31,17 +49,15 @@ typedef struct { size_t NodeLenth; Vector2 CursorPosition; + TextAreaDisplayParams *DisplayParams; } TextArea; -TextArea *CreateTextArea(); -void FreeTextArea(TextArea *ta); -void DrawTextArea(TextArea *ta, Rectangle rec, Font *font, int height, int spacing, int *scroll); +TextArea *CreateTextArea(TextAreaDisplayParams *DisplayParams); +void DrawTextArea(TextArea *ta, Rectangle rec); void InsertChar(TextArea *ta, char new_char); void DeleteChar(TextArea *ta); void HandleKeyboard(TextArea *ta); -void MoveCursorRight(TextArea *ta); -void MoveCursorLeft(TextArea *ta); -void MoveCursorDown(TextArea *ta); +void FreeTextArea(TextArea *ta); #endif // UI_ELEMENTS_H -- cgit v1.3