From d297b5d7d97e882ba06e60ac9b5f28d5ea1bd6d6 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 10 Feb 2020 23:57:51 +0100 Subject: [PATCH] GuiTextEditor() control example -WIP- --- examples/text_editor/text_editor.c | 297 +++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 examples/text_editor/text_editor.c diff --git a/examples/text_editor/text_editor.c b/examples/text_editor/text_editor.c new file mode 100644 index 0000000..7663841 --- /dev/null +++ b/examples/text_editor/text_editor.c @@ -0,0 +1,297 @@ +/******************************************************************************************* +* +* raygui - Controls test +* +* TEST CONTROLS: +* - GuiTextEditor() +* +* DEPENDENCIES: +* raylib 3.0 - Windowing/input management and drawing. +* raygui 2.7 - Immediate-mode GUI controls. +* +* COMPILATION (Windows - MinGW): +* gcc -o $(NAME_PART).exe $(FILE_NAME) -I../../src -lraylib -lopengl32 -lgdi32 -std=c99 +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2020 Ramon Santamaria (@raysan5) +* +**********************************************************************************************/ + +#include "raylib.h" + +#define RAYGUI_IMPLEMENTATION +#define RAYGUI_SUPPORT_ICONS +#include "../../src/raygui.h" + + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static char text01[128] = "Lorem ipsum dolor sit amet, \xE7\x8C\xBF\xE3\x82\x82\xE6\x9C\xA8\xE3\x81\x8B\xE3\x82\x89\xE8\x90\xBD\xE3\x81\xA1\xE3\x82\x8B consectetur adipiscing elit...\0"; // including some hiragana/kanji +static char text02[128] = "Here's another, much bigger textbox extended.\xf4\xa1\xa1\xff TIP: try COPY/PASTE ;)\0"; // Including some invalid UTF8 + +bool GuiTextEditor(Rectangle bounds, char *text, int textLen, bool editMode); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(int argc, char **argv) +{ + // Initialization + //--------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raygui - gui text editor test"); + + Font font = { 0 }; + + bool textEditor01EditMode = false; + bool textEditor02EditMode = false; + + SetTargetFPS(60); + //--------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + Vector2 mouse = GetMousePosition(); + + // Fonts drag & drop logic + if (IsFileDropped()) + { + int count = 0; + char **files = GetDroppedFiles(&count); + + if (IsFileExtension(files[0], ".ttf") || + IsFileExtension(files[0], ".otf") || + IsFileExtension(files[0], ".fnt")) + { + Font fnt = LoadFont(files[0]); + + if (fnt.texture.id != 0) + { + // Font was loaded, only change font on success + GuiSetFont(fnt); + + // Remove old font + if (font.texture.id != 0) UnloadFont(font); + font = fnt; + } + } + + ClearDroppedFiles(); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + // Draw textboxes extended + //--------------------------------------------------------------------------------------- + if (GuiTextEditor((Rectangle){ 20, 20, 380, 410 }, text01, strlen(text01), textEditor01EditMode)) textEditor01EditMode = !textEditor01EditMode; + if (GuiTextEditor((Rectangle){ 420, 20, 360, 410 }, text02, strlen(text02), textEditor02EditMode)) textEditor02EditMode = !textEditor02EditMode; + //--------------------------------------------------------------------------------------- + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +// Text editor control (Advanced text box) +bool GuiTextEditor(Rectangle bounds, char *text, int textSize, bool editMode) +{ + static Rectangle cursor = { 0 }; // Cursor position and size + static int framesCounter = 0; // Blinking cursor frames counter + static int cursorCodepoint = -1; + static int selectStartCp = -1; + static int selectLengthCp = 0; + + GuiControlState state = guiState; + bool pressed = false; + + bool textWrap = true; // TODO: Word-Wrap vs Char-Wrap -> textWrapMode { NO_WRAP_LOCK, NO_WRAP_OVERFLOW, CHAR_WRAP, WORD_WRAP } + + // WARNING: First string full traversal + int codepointCount = GetCodepointsCount(text); + + int textLen = strlen(text); // Text length in bytes + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (editMode) + { + state = GUI_STATE_PRESSED; + framesCounter++; + + // TODO: Cursor position logic (mouse and keys) + + // Characters selection logic + if (selectStartCp != -1) + { + if (IsKeyDown(KEY_LEFT_SHIFT) && IsKeyPressed(KEY_RIGHT)) + { + selectLengthCp++; + if (selectLengthCp >= (codepointCount - selectStartCp)) selectLengthCp = codepointCount - selectStartCp; + } + + if (IsKeyDown(KEY_LEFT_SHIFT) && IsKeyPressed(KEY_LEFT)) + { + selectLengthCp--; + if (selectLengthCp < 0) selectLengthCp = 0; + } + } + + int key = GetKeyPressed(); + + // TODO: On key pressed, place new character in cursor position + + // Exit edit mode logic + if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(0))) pressed = true; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = GUI_STATE_FOCUSED; + if (IsMouseButtonPressed(0)) pressed = true; + } + } + + if (pressed) + { + // Exiting edit mode, reset temp variables + framesCounter = 0; + cursor = (Rectangle){ 0 }; + + cursorCodepoint = -1; + selectStartCp = -1; + selectLengthCp = 0; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + DrawRectangleLinesEx(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha)); + + if (state == GUI_STATE_PRESSED) DrawRectangle(bounds.x + GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.height - 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)), guiAlpha)); + else if (state == GUI_STATE_DISABLED) DrawRectangle(bounds.x + GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH), bounds.height - 2*GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)), guiAlpha)); + + Font font = GetFontDefault(); + + int textOffsetY = 0; // Offset between lines (on line break '\n') + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scaleFactor = GuiGetStyle(DEFAULT, TEXT_SIZE)*2/font.baseSize; // Character quad scaling factor + + for (int i = 0, cp = 0; i < textLen; i++) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + Rectangle rec = { bounds.x + textOffsetX + font.chars[index].offsetX*scaleFactor, + bounds.y + textOffsetY + font.chars[index].offsetY*scaleFactor, + font.recs[index].width*scaleFactor, font.recs[index].height*scaleFactor }; + + // Automatic line break to wrap text inside box + if (textWrap && ((rec.x + rec.width) >= (bounds.x + bounds.width))) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0.0f; + + // Recalculate drawing rectangle position + rec = (Rectangle){ bounds.x + textOffsetX + font.chars[index].offsetX*scaleFactor, + bounds.y + textOffsetY + font.chars[index].offsetY*scaleFactor, + font.recs[index].width*scaleFactor, font.recs[index].height*scaleFactor }; + } + + // Check selected codepoint + if (editMode) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(GetMousePosition(), rec)) + { + cursor = rec; + cursorCodepoint = cp; + selectStartCp = cursorCodepoint; + selectLengthCp = 0; + + // TODO: Place cursor at the end if pressed out of text + } + + // On mouse left button down allow text selection + if ((selectStartCp != -1) && IsMouseButtonDown(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(GetMousePosition(), rec)) + { + if (cp >= selectStartCp) selectLengthCp = cp - selectStartCp; + else if (cp < selectStartCp) + { + //int temp = selectStartCp; + //selectStartCp = cp; + //selectLengthCp = temp - selectStartCp; + } + } + } + + + + if (codepoint == '\n') // Line break character + { + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0.0f; + } + else + { + // Draw codepoint glyph + if ((codepoint != ' ') && (codepoint != '\t') && ((rec.x + rec.width) < (bounds.x + bounds.width))) + { + DrawTexturePro(font.texture, font.recs[index], rec, (Vector2){ 0, 0 }, 0.0f, GetColor(GuiGetStyle(DEFAULT, TEXT_COLOR_NORMAL))); + } + + // TODO: On text overflow do something... move text to the left? + } + + // Draw codepoints selection from selectStartCp to selectLengthCp + // TODO: Consider spacing when drawing selected characters background + if (editMode && (selectStartCp != -1) && ((cp >= selectStartCp) && (cp <= (selectStartCp + selectLengthCp)))) DrawRectangleRec(rec, MAROON); + + if (font.chars[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + GuiGetStyle(DEFAULT, TEXT_SPACING)); + else textOffsetX += ((float)font.chars[index].advanceX*scaleFactor + GuiGetStyle(DEFAULT, TEXT_SPACING)); + + i += (codepointByteCount - 1); // Move text bytes counter to next codepoint + cp++; + } + + // Draw blinking cursor + if (editMode && ((framesCounter/20)%2 == 0)) DrawRectangleRec(cursor, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); + + //GuiDrawText(text, GetTextBounds(TEXTBOX, bounds), GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha)); + //-------------------------------------------------------------------- + + return pressed; +} \ No newline at end of file