diff --git a/desktop_version/CMakeLists.txt b/desktop_version/CMakeLists.txt index 2bbc77ef31..549e8481d7 100644 --- a/desktop_version/CMakeLists.txt +++ b/desktop_version/CMakeLists.txt @@ -102,6 +102,7 @@ set(VVV_SRC src/Spacestation2.cpp src/TerminalScripts.cpp src/Textbox.cpp + src/TextInput.cpp src/Tower.cpp src/UtilityClass.cpp src/WarpClass.cpp diff --git a/desktop_version/src/Editor.cpp b/desktop_version/src/Editor.cpp index 2bb89a4ada..79fefb51cf 100644 --- a/desktop_version/src/Editor.cpp +++ b/desktop_version/src/Editor.cpp @@ -21,13 +21,12 @@ #include "Music.h" #include "Screen.h" #include "Script.h" +#include "TextInput.h" #include "UTF8.h" #include "UtilityClass.h" #include "VFormat.h" #include "Vlogging.h" -#define SCRIPT_LINE_PADDING 6 - editorclass::editorclass(void) { reset(); @@ -104,10 +103,6 @@ void editorclass::reset(void) clear_script_buffer(); - script_cursor_x = 0; - script_cursor_y = 0; - script_offset = 0; - lines_visible = 25; current_script = "null"; script_list_offset = 0; @@ -247,33 +242,36 @@ static void editormenurender(int tr, int tg, int tb) break; case Menu::ed_desc: { - const std::string input_text = key.keybuffer + ((ed.entframe < 2) ? "_" : " "); + const char* cursor = (TextInput::flash_timer < 15) ? "_" : " "; + + bool title_is_gettext; + std::string title = translate_title(cl.title, &title_is_gettext); if (ed.current_text_mode == TEXT_TITLE) { - font::print(PR_2X | PR_CEN | PR_FONT_LEVEL, -1, 35, input_text, tr, tg, tb); - } - else - { - bool title_is_gettext; - std::string title = translate_title(cl.title, &title_is_gettext); - font::print(PR_2X | PR_CEN | (title_is_gettext ? PR_FONT_INTERFACE : PR_FONT_LEVEL), -1, 35, title, tr, tg, tb); + title += cursor; } + font::print(PR_2X | PR_CEN | (title_is_gettext ? PR_FONT_INTERFACE : PR_FONT_LEVEL), -1, 35, title, tr, tg, tb); + bool creator_is_gettext = false; - std::string creator = (ed.current_text_mode == TEXT_CREATOR) ? input_text : translate_creator(cl.creator, &creator_is_gettext); + std::string creator = translate_creator(cl.creator, &creator_is_gettext); + if (ed.current_text_mode == TEXT_CREATOR) + { + creator += cursor; + } int sp = SDL_max(10, font::height(PR_FONT_LEVEL)); graphics.print_level_creator((creator_is_gettext ? PR_FONT_INTERFACE : PR_FONT_LEVEL), 60, creator, tr, tg, tb); - font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp, (ed.current_text_mode == TEXT_WEBSITE) ? input_text : cl.website, tr, tg, tb); - font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 3, (ed.current_text_mode == TEXT_DESC1) ? input_text : cl.Desc1, tr, tg, tb); - font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 4, (ed.current_text_mode == TEXT_DESC2) ? input_text : cl.Desc2, tr, tg, tb); + font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp, cl.website + ((ed.current_text_mode == TEXT_WEBSITE) ? cursor : ""), tr, tg, tb); + font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 3, cl.Desc1 + ((ed.current_text_mode == TEXT_DESC1) ? cursor : ""), tr, tg, tb); + font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 4, cl.Desc2 + ((ed.current_text_mode == TEXT_DESC2) ? cursor : ""), tr, tg, tb); if (ed.current_text_mode == TEXT_DESC3) { - font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 5, input_text, tr, tg, tb); + font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 5, cl.Desc3 + cursor, tr, tg, tb); } else if (sp <= 10) { @@ -1493,16 +1491,10 @@ void editorrender(void) graphics.fill_rect(0, 238 - textheight, 320, 240, graphics.getRGB(32, 32, 32)); graphics.fill_rect(0, 239 - textheight, 320, 240, graphics.getRGB(0, 0, 0)); font::print_wrap(0, 4, 240 - textheight, wrapped.c_str(), 255, 255, 255, 8, 312); - std::string input = key.keybuffer; - if (ed.entframe < 2) - { - input += "_"; - } - else - { - input += " "; - } - font::print(PR_CEN | PR_FONT_LEVEL | PR_CJK_HIGH, -1, 232, input, 196, 196, 255 - help.glow); + + const char* cursor = (TextInput::flash_timer < 15) ? "_" : " "; + + font::print(PR_CEN | PR_FONT_LEVEL | PR_CJK_HIGH, -1, 232, *ed.current_text_ptr + cursor, 196, 196, 255 - help.glow); break; } case EditorSubState_DRAW_WARPTOKEN: @@ -1573,21 +1565,13 @@ void editorrender(void) ); font::print(PR_CEN, -1, 228, namebuffer, 123, 111, 218); - // Draw text - int font_height = font::height(PR_FONT_LEVEL); - for (int i = 0; i < ed.lines_visible; i++) - { - if (i + ed.script_offset < (int) ed.script_buffer.size()) - { - font::print(PR_FONT_LEVEL | PR_CJK_LOW, 16, 20 + (i * font_height), ed.script_buffer[i + ed.script_offset], 123, 111, 218); - } - } + TextInputInfo info; + info.text_color = graphics.getRGB(123, 111, 218); + info.selected_color = graphics.getRGB(61, 48, 162); + info.visible_lines = 200 / font::height(PR_FONT_LEVEL); + info.visible_padding = 48 / font::height(PR_FONT_LEVEL); - // Draw cursor - if (ed.entframe < 2) - { - font::print(PR_FONT_LEVEL | PR_CJK_LOW, 16 + font::len(PR_FONT_LEVEL, ed.script_buffer[ed.script_cursor_y].c_str()), 20 + ((ed.script_cursor_y - ed.script_offset) * font_height), "_", 123, 111, 218); - } + TextInput::draw_text(PR_FONT_LEVEL | PR_CJK_LOW, 16, 20, &ed.script_buffer, info); break; } default: @@ -1734,14 +1718,12 @@ static void input_submitted(void) { extern editorclass ed; - *ed.current_text_ptr = key.keybuffer; - ed.help_open = false; ed.shiftkey = false; bool reset_text_mode = true; - key.disabletextentry(); + TextInput::detach_input(); ed.substate = EditorSubState_MAIN; @@ -1828,42 +1810,33 @@ static void input_submitted(void) } break; case TEXT_TITLE: - cl.title = key.keybuffer; if (cl.title == "") { cl.title = "Untitled Level"; } break; case TEXT_CREATOR: - cl.creator = key.keybuffer; if (cl.creator == "") { cl.creator = "Unknown"; } break; case TEXT_WEBSITE: - cl.website = key.keybuffer; break; case TEXT_DESC1: - cl.Desc1 = key.keybuffer; ed.current_text_mode = TEXT_DESC2; ed.substate = EditorSubState_MENU_INPUT; reset_text_mode = false; - key.enabletextentry(); - ed.current_text_ptr = &(key.keybuffer); - key.keybuffer = cl.Desc2; + + TextInput::attach_input(&cl.Desc2); break; case TEXT_DESC2: - cl.Desc2 = key.keybuffer; - if (font::height(PR_FONT_LEVEL) <= 10) { ed.current_text_mode = TEXT_DESC3; - key.enabletextentry(); + TextInput::attach_input(&cl.Desc3); ed.substate = EditorSubState_MENU_INPUT; reset_text_mode = false; - ed.current_text_ptr = &(key.keybuffer); - key.keybuffer = cl.Desc3; } else { @@ -1872,7 +1845,6 @@ static void input_submitted(void) break; case TEXT_DESC3: - cl.Desc3 = key.keybuffer; break; default: break; @@ -1898,6 +1870,8 @@ void editorlogic(void) ed.entframedelay = 8; } + TextInput::flash_timer = (TextInput::flash_timer + 1) % 30; + ed.old_note_timer = ed.note_timer; ed.note_timer = SDL_max(ed.note_timer - 1, 0); @@ -2347,17 +2321,14 @@ static void editormenuactionpress(void) ed.current_text_mode = TEXT_TITLE; ed.substate = EditorSubState_MENU_INPUT; - key.enabletextentry(); - ed.current_text_ptr = &(key.keybuffer); if (title_is_gettext) { - key.keybuffer = ""; - } - else - { - key.keybuffer = cl.title; + cl.title = ""; } + + TextInput::attach_input(&cl.title); + break; } case 1: @@ -2367,31 +2338,25 @@ static void editormenuactionpress(void) ed.current_text_mode = TEXT_CREATOR; ed.substate = EditorSubState_MENU_INPUT; - key.enabletextentry(); - ed.current_text_ptr = &(key.keybuffer); + if (creator_is_gettext) { - key.keybuffer = ""; - } - else - { - key.keybuffer = cl.creator; + cl.creator = ""; } + + TextInput::attach_input(&cl.creator); break; } case 2: ed.current_text_mode = TEXT_DESC1; ed.substate = EditorSubState_MENU_INPUT; - key.enabletextentry(); - ed.current_text_ptr = &(key.keybuffer); - key.keybuffer = cl.Desc1; + + TextInput::attach_input(&cl.Desc1); break; case 3: ed.current_text_mode = TEXT_WEBSITE; ed.substate = EditorSubState_MENU_INPUT; - key.enabletextentry(); - ed.current_text_ptr = &(key.keybuffer); - key.keybuffer=cl.website; + TextInput::attach_input(&cl.website); break; case 4: game.createmenu(Menu::ed_font); @@ -2424,11 +2389,6 @@ static void editormenuactionpress(void) key.keybuffer = ""; ed.script_list_offset = 0; ed.selected_script = 0; - - ed.script_cursor_y = 0; - ed.script_cursor_x = 0; - ed.script_offset = 0; - ed.lines_visible = 200 / font::height(PR_FONT_LEVEL); break; case 2: music.playef(11); @@ -2823,20 +2783,22 @@ void editorclass::get_input_line(const enum TextMode mode, const std::string& pr { state = EditorState_DRAW; substate = EditorSubState_DRAW_INPUT; - current_text_mode = mode; - current_text_ptr = ptr; - current_text_desc = prompt; - key.enabletextentry(); + if (ptr) { - key.keybuffer = *ptr; + TextInput::attach_input(ptr); + current_text_ptr = ptr; } else { key.keybuffer = ""; - current_text_ptr = &(key.keybuffer); + TextInput::attach_input(&key.keybuffer); + current_text_ptr = &key.keybuffer; } + current_text_mode = mode; + current_text_desc = prompt; + old_entity_text = key.keybuffer; } @@ -3169,7 +3131,8 @@ void editorinput(void) if (escape_pressed) { // Cancel it, and remove the enemy it's tied to if necessary - key.disabletextentry(); + TextInput::detach_input(); + if (ed.current_text_mode >= FIRST_ENTTEXT && ed.current_text_mode <= LAST_ENTTEXT) { *ed.current_text_ptr = ed.old_entity_text; @@ -3282,11 +3245,11 @@ void editorinput(void) break; case EditorSubState_MENU_INPUT: - if (escape_pressed && key.textentry()) + if (escape_pressed && TextInput::taking_input) { ed.substate = EditorSubState_MAIN; - key.disabletextentry(); ed.current_text_mode = TEXT_NONE; + TextInput::detach_input(); music.playef(11); } @@ -3368,17 +3331,11 @@ void editorinput(void) { game.mapheld = true; ed.substate = EditorSubState_SCRIPTS_EDIT; - key.enabletextentry(); - key.keybuffer = ""; - ed.current_text_ptr = &(key.keybuffer); + ed.current_script = script.customscripts[(script.customscripts.size() - 1) - ed.selected_script].name; ed.load_script_in_editor(ed.current_script); - ed.script_cursor_y = ed.script_buffer.size() - 1; - ed.script_offset = SDL_max(ed.script_cursor_y - (ed.lines_visible - SCRIPT_LINE_PADDING), 0); - - key.keybuffer = ed.script_buffer[ed.script_cursor_y]; - ed.script_cursor_x = UTF8_total_codepoints(ed.script_buffer[ed.script_cursor_y].c_str()); + TextInput::attach_input(&ed.script_buffer); music.playef(11); } @@ -3395,41 +3352,11 @@ void editorinput(void) // Alright, now re-add the script. ed.create_script(ed.current_script, ed.script_buffer); + TextInput::detach_input(); } if (ed.keydelay > 0) ed.keydelay--; - if (up_pressed && ed.keydelay <= 0) - { - ed.keydelay = 3; - ed.script_cursor_y = SDL_max(0, ed.script_cursor_y - 1); - - key.keybuffer = ed.script_buffer[ed.script_cursor_y]; - } - - if (down_pressed && ed.keydelay <= 0) - { - ed.keydelay = 3; - ed.script_cursor_y = SDL_min((int) ed.script_buffer.size() - 1, ed.script_cursor_y + 1); - - key.keybuffer = ed.script_buffer[ed.script_cursor_y]; - } - - if (key.linealreadyemptykludge) - { - ed.keydelay = 6; - key.linealreadyemptykludge = false; - } - - if (key.pressedbackspace && ed.script_buffer[ed.script_cursor_y] == "" && ed.keydelay <= 0) - { - //Remove this line completely - ed.remove_line(ed.script_cursor_y); - ed.script_cursor_y = SDL_max(0, ed.script_cursor_y - 1); - key.keybuffer = ed.script_buffer[ed.script_cursor_y]; - ed.keydelay = 6; - } - /* Remove all pipes, they are the line separator in the XML * When this loop reaches the end, it wraps to SIZE_MAX; SIZE_MAX + 1 is 0 */ {size_t i; for (i = key.keybuffer.length() - 1; i + 1 > 0; --i) @@ -3440,40 +3367,6 @@ void editorinput(void) } }} - ed.script_buffer[ed.script_cursor_y] = key.keybuffer; - ed.script_cursor_x = UTF8_total_codepoints(ed.script_buffer[ed.script_cursor_y].c_str()); - - if (enter_pressed) - { - //Continue to next line - if (ed.script_cursor_y >= (int)ed.script_buffer.size()) //we're on the last line - { - ed.script_cursor_y++; - - key.keybuffer = ed.script_buffer[ed.script_cursor_y]; - ed.script_cursor_x = UTF8_total_codepoints(ed.script_buffer[ed.script_cursor_y].c_str()); - } - else - { - //We're not, insert a line instead - ed.script_cursor_y++; - - ed.insert_line(ed.script_cursor_y); - key.keybuffer = ""; - ed.script_cursor_x = 0; - } - } - - if (ed.script_cursor_y < ed.script_offset + SCRIPT_LINE_PADDING) - { - ed.script_offset = SDL_max(0, ed.script_cursor_y - SCRIPT_LINE_PADDING); - } - - if (ed.script_cursor_y > ed.script_offset + ed.lines_visible - SCRIPT_LINE_PADDING) - { - ed.script_offset = SDL_min((int) ed.script_buffer.size() - ed.lines_visible + SCRIPT_LINE_PADDING, ed.script_cursor_y - ed.lines_visible + SCRIPT_LINE_PADDING); - } - break; } default: diff --git a/desktop_version/src/Editor.h b/desktop_version/src/Editor.h index a7fc6f4e75..fb71c3ee95 100644 --- a/desktop_version/src/Editor.h +++ b/desktop_version/src/Editor.h @@ -194,7 +194,6 @@ class editorclass int note_timer; int old_note_timer; std::string note; - std::string keybuffer; std::string filename; std::string loaded_filepath; @@ -232,9 +231,6 @@ class editorclass std::vector script_buffer; std::string current_script; - int script_cursor_x, script_cursor_y; - int script_offset; - int lines_visible; //Functions for interfacing with the script: void create_script(const std::string& name, const std::vector& contents); diff --git a/desktop_version/src/KeyPoll.cpp b/desktop_version/src/KeyPoll.cpp index 84d0a90118..f388a3c2ad 100644 --- a/desktop_version/src/KeyPoll.cpp +++ b/desktop_version/src/KeyPoll.cpp @@ -13,6 +13,7 @@ #include "LocalizationStorage.h" #include "Music.h" #include "Screen.h" +#include "TextInput.h" #include "UTF8.h" #include "Vlogging.h" @@ -47,29 +48,12 @@ KeyPoll::KeyPoll(void) leftbutton=0; rightbutton=0; middlebutton=0; mx=0; my=0; resetWindow = 0; - pressedbackspace=false; linealreadyemptykludge = false; isActive = true; } -void KeyPoll::enabletextentry(void) -{ - keybuffer=""; - SDL_StartTextInput(); -} - -void KeyPoll::disabletextentry(void) -{ - SDL_StopTextInput(); -} - -bool KeyPoll::textentry(void) -{ - return SDL_IsTextInputActive() == SDL_TRUE; -} - void KeyPoll::toggleFullscreen(void) { gameScreen.toggleFullScreen(); @@ -143,6 +127,7 @@ void KeyPoll::Poll(void) SDL_Event evt; while (SDL_PollEvent(&evt)) { + TextInput::handle_events(evt); switch (evt.type) { /* Keyboard Input */ @@ -150,11 +135,6 @@ void KeyPoll::Poll(void) { keymap[evt.key.keysym.sym] = true; - if (evt.key.keysym.sym == SDLK_BACKSPACE) - { - pressedbackspace = true; - } - #ifdef __APPLE__ /* OSX prefers the command keys over the alt keys. -flibit */ altpressed = keymap[SDLK_LGUI] || keymap[SDLK_RGUI]; #else @@ -176,50 +156,12 @@ void KeyPoll::Poll(void) } BUTTONGLYPHS_keyboard_set_active(true); - - if (textentry()) - { - if (evt.key.keysym.sym == SDLK_BACKSPACE && !keybuffer.empty()) - { - keybuffer.erase(UTF8_backspace(keybuffer.c_str(), keybuffer.length())); - if (keybuffer.empty()) - { - linealreadyemptykludge = true; - } - } - else if ( evt.key.keysym.sym == SDLK_v && - keymap[SDLK_LCTRL] ) - { - char* text = SDL_GetClipboardText(); - if (text != NULL) - { - keybuffer += text; - VVV_free(text); - } - } - else if ( evt.key.keysym.sym == SDLK_x && - keymap[SDLK_LCTRL] ) - { - if (SDL_SetClipboardText(keybuffer.c_str()) == 0) - { - keybuffer = ""; - } - } - } break; } case SDL_KEYUP: keymap[evt.key.keysym.sym] = false; - if (evt.key.keysym.sym == SDLK_BACKSPACE) - { - pressedbackspace = false; - } break; case SDL_TEXTINPUT: - if (!altpressed) - { - keybuffer += evt.text.text; - } break; /* Mouse Input */ diff --git a/desktop_version/src/KeyPoll.h b/desktop_version/src/KeyPoll.h index d6c40b998a..5a22616e3d 100644 --- a/desktop_version/src/KeyPoll.h +++ b/desktop_version/src/KeyPoll.h @@ -46,10 +46,6 @@ class KeyPoll KeyPoll(void); - void enabletextentry(void); - - void disabletextentry(void); - void Poll(void); bool isDown(SDL_Keycode key); @@ -65,8 +61,6 @@ class KeyPoll int leftbutton, rightbutton, middlebutton; int mx, my; - bool textentry(void); - bool pressedbackspace; std::string keybuffer; bool linealreadyemptykludge; diff --git a/desktop_version/src/RoomnameTranslator.cpp b/desktop_version/src/RoomnameTranslator.cpp index 02d4001948..b4cd03e980 100644 --- a/desktop_version/src/RoomnameTranslator.cpp +++ b/desktop_version/src/RoomnameTranslator.cpp @@ -10,6 +10,7 @@ #include "LocalizationMaint.h" #include "Map.h" #include "Script.h" +#include "TextInput.h" #include "UtilityClass.h" #include "VFormat.h" @@ -183,7 +184,7 @@ namespace roomname_translator print_explanation(loc::get_roomname_explanation(map.custommode, game.roomx, game.roomy)); - if (key.textentry()) + if (TextInput::taking_input) { *force_roomname_hidden = true; graphics.render_roomname(PR_FONT_LEVEL, key.keybuffer.c_str(), 255,255,255); @@ -216,7 +217,7 @@ namespace roomname_translator if (edit_mode) { - if (key.textentry()) + if (TextInput::taking_input) { print_explanation((key.keybuffer + "_").c_str()); @@ -326,17 +327,17 @@ namespace roomname_translator return true; } - if (key.textentry()) + if (TextInput::taking_input) { if (key_pressed_once(SDLK_ESCAPE, &held_escape)) { // Without saving - key.disabletextentry(); + TextInput::detach_input(); } if (key_pressed_once(SDLK_RETURN, &held_return)) { - key.disabletextentry(); + TextInput::detach_input(); if (!expl_mode) { @@ -413,7 +414,8 @@ namespace roomname_translator return true; } - key.enabletextentry(); + TextInput::attach_input(&key.keybuffer); + if (!expl_mode) { key.keybuffer = loc::get_roomname_translation(map.custommode, game.roomx, game.roomy); diff --git a/desktop_version/src/TextInput.cpp b/desktop_version/src/TextInput.cpp new file mode 100644 index 0000000000..4379b7e0fb --- /dev/null +++ b/desktop_version/src/TextInput.cpp @@ -0,0 +1,906 @@ +#include "TextInput.h" + +#include +#include +#include + +#include "Constants.h" +#include "Font.h" +#include "Graphics.h" +#include "KeyPoll.h" +#include "Screen.h" +#include "UTF8.h" +#include "UtilityClass.h" +#include "Vlogging.h" + +static inline int adjust_center(std::vector* text, uint32_t flags, int line) +{ + if (flags & PR_CEN) + { + return font::len(flags, text->at(line).c_str()) / 2; + } + return 0; +} + +namespace TextInput +{ + bool taking_input; + bool selecting; + int flash_timer; + bool using_vector; + bool can_select; + bool can_move_cursor; + std::string* current_text_line; + std::vector* current_text; + SDL_Point cursor_pos; + SDL_Point cursor_select_pos; + int cursor_x_tallest; + int display_offset; + + void init(void) + { + taking_input = false; + } + + void attach_input(std::vector* text) + { + taking_input = true; + current_text = text; + selecting = false; + flash_timer = 0; + using_vector = true; + can_select = true; + can_move_cursor = true; + display_offset = 0; + SDL_StartTextInput(); + + send_cursor_to_end(); + } + + void attach_input(std::string* text) + { + taking_input = true; + current_text_line = text; + selecting = false; + flash_timer = 0; + using_vector = false; + display_offset = 0; + SDL_StartTextInput(); + + // Temporary + can_select = false; + can_move_cursor = false; + + cursor_pos.x = UTF8_total_codepoints(current_text_line->c_str()); + cursor_pos.y = 0; + cursor_x_tallest = cursor_pos.x; + } + + void detach_input(void) + { + taking_input = false; + SDL_StopTextInput(); + } + + void send_cursor_to_end(void) + { + cursor_pos.y = get_lines() - 1; + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + cursor_x_tallest = cursor_pos.x; + } + + void insert_text(std::string text) + { + // Insert text at cursor position, respecting newlines + // Don't bother deleting selection, already done + + std::string::iterator it; + for (it = text.begin(); it != text.end(); it++) + { + if (*it == '\n') + { + insert_newline(); + } + else if (*it != '\r' && *it != '\0') + { + // Add character + std::string current = get_line(cursor_pos.y); + current.insert(current.begin() + cursor_pos.x, *it); + set_line(cursor_pos.y, current.c_str()); + + cursor_pos.x++; + } + } + cursor_x_tallest = cursor_pos.x; + } + + void insert_newline(void) + { + if (!using_vector) + { + insert_text(" "); + return; + } + + char* first_part = UTF8_substr(get_line(cursor_pos.y), 0, cursor_pos.x); + char* second_part = UTF8_substr(get_line(cursor_pos.y), cursor_pos.x, UTF8_total_codepoints(get_line(cursor_pos.y))); + + set_line(cursor_pos.y, first_part); + current_text->insert(current_text->begin() + cursor_pos.y + 1, second_part); + + SDL_free(first_part); + SDL_free(second_part); + + cursor_pos.y++; + cursor_pos.x = 0; + cursor_x_tallest = 0; + } + + void select_all(void) + { + cursor_pos.x = 0; + cursor_pos.y = 0; + cursor_select_pos.x = UTF8_total_codepoints(get_line(get_lines() - 1)); + cursor_select_pos.y = get_lines() - 1; + selecting = true; + } + + int get_lines(void) + { + if (using_vector) + { + return current_text->size(); + } + return 1; + } + + const char* get_line(int line) + { + if (using_vector) + { + return current_text->at(line).c_str(); + } + return current_text_line->c_str(); + } + + void set_line(int line, const char* text) + { + if (using_vector) + { + current_text->at(line) = text; + } + else + { + current_text_line->assign(text); + } + } + + void add_to_line(int line, const char* text) + { + if (using_vector) + { + current_text->at(line) += text; + } + else + { + std::string temp = *current_text_line + text; + current_text_line->assign(temp); + } + } + + bool process_selection(void) + { + if (SDL_GetModState() & KMOD_SHIFT) + { + if (!selecting && can_select) + { + cursor_select_pos = cursor_pos; + selecting = true; + } + } + else + { + if (selecting) + { + selecting = false; + return true; + } + } + return false; + } + + char* get_selected_text(void) + { + /* Caller must free */ + + SelectionRect rect = reorder_selection_positions(); + + if (rect.y == rect.y2) + { + return UTF8_substr(get_line(rect.y), rect.x, rect.x2); + } + + char* select_part_first_line = UTF8_substr(get_line(rect.y), rect.x, UTF8_total_codepoints(get_line(rect.y))); + char* select_part_last_line = UTF8_substr(get_line(rect.y2), 0, rect.x2); + + // Loop through the lines in between + int total_length = SDL_strlen(select_part_first_line) + SDL_strlen(select_part_last_line) + 1; + for (int i = rect.y + 1; i < rect.y2; i++) + { + total_length += SDL_strlen(get_line(i)) + 1; + } + + char* select_part = (char*)SDL_malloc(total_length + 1); + strcpy(select_part, select_part_first_line); + strcat(select_part, "\n"); + for (int i = rect.y + 1; i < rect.y2; i++) + { + strcat(select_part, get_line(i)); + strcat(select_part, "\n"); + } + strcat(select_part, select_part_last_line); + strcat(select_part, "\0"); + + SDL_free(select_part_first_line); + SDL_free(select_part_last_line); + + return select_part; + } + + SelectionRect reorder_selection_positions(void) + { + SelectionRect positions; + bool in_front = false; + + if (cursor_pos.y > cursor_select_pos.y) + { + in_front = true; + } + else if (cursor_pos.y == cursor_select_pos.y) + { + if (cursor_pos.x >= cursor_select_pos.x) + { + in_front = true; + } + } + + if (in_front) + { + positions.x = cursor_select_pos.x; + positions.x2 = cursor_pos.x; + positions.y = cursor_select_pos.y; + positions.y2 = cursor_pos.y; + } + else + { + positions.x = cursor_pos.x; + positions.x2 = cursor_select_pos.x; + positions.y = cursor_pos.y; + positions.y2 = cursor_select_pos.y; + } + + return positions; + } + + void remove_characters(int x, int y, int x2, int y2) + { + if (x == x2 && y == y2) + { + return; + } + // Get the rest of the last line + char* rest_of_string = UTF8_substr(get_line(y2), x2, UTF8_total_codepoints(get_line(y2))); + + for (int i = y2; i > y; i--) + { + if (cursor_pos.y >= i) + { + cursor_pos.y--; + } + if (cursor_select_pos.y >= i) + { + cursor_select_pos.y--; + } + + // Erase the current line + current_text->erase(current_text->begin() + i); + } + + // Erase from the start of the selection to the end + char* erased = UTF8_erase(get_line(y), x, UTF8_total_codepoints(get_line(y))); + set_line(y, erased); + // Add the rest of the last line to the end of the first line + add_to_line(cursor_pos.y, rest_of_string); + cursor_pos.x = x; + SDL_free(erased); + SDL_free(rest_of_string); + } + + void remove_selection(void) + { + SelectionRect positions = reorder_selection_positions(); + remove_characters(positions.x, positions.y, positions.x2, positions.y2); + selecting = false; + } + + void move_cursor_up(void) + { + bool reset = process_selection(); // Only returns true if you don't hold shift + if (reset && cursor_pos.y > cursor_select_pos.y) + { + cursor_pos.y = cursor_select_pos.y; + } + + if (cursor_pos.y > 0) + { + cursor_pos.y--; + cursor_pos.x = cursor_x_tallest; + if (cursor_pos.x > (int) UTF8_total_codepoints(get_line(cursor_pos.y))) + { + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + } + } + } + + void move_cursor_down(void) + { + bool reset = process_selection(); // Only returns true if you don't hold shift + if (reset && cursor_pos.y < cursor_select_pos.y) + { + cursor_pos.y = cursor_select_pos.y; + } + + if (cursor_pos.y < get_lines() - 1) + { + cursor_pos.y++; + cursor_pos.x = cursor_x_tallest; + if (cursor_pos.x > (int) UTF8_total_codepoints(get_line(cursor_pos.y))) + { + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + } + } + else + { + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + } + } + + bool is_word_part(unsigned char character) + { + return character > 0x7F || SDL_isdigit(character) || SDL_isalpha(character) || character == '_' || character == '-'; + } + + void move_cursor_left(void) + { + bool reset = process_selection(); // Only returns true if you don't hold shift + if (reset) + { + cursor_pos.x = cursor_select_pos.x; + } + + if (cursor_pos.x == 0 && cursor_pos.y == 0) + { + cursor_x_tallest = 0; + return; + } + + if (SDL_GetModState() & KMOD_CTRL) + { + // If the current character is a word part, move to the start of the word. Else, move to the start of the next non-word. + char* character = UTF8_substr(get_line(cursor_pos.y), cursor_pos.x - 1, cursor_pos.x); + bool is_word = is_word_part(character[0]); + SDL_free(character); + + while (true) + { + character = UTF8_substr(get_line(cursor_pos.y), cursor_pos.x - 1, cursor_pos.x); + if (is_word_part(character[0]) != is_word) + { + SDL_free(character); + break; + } + SDL_free(character); + cursor_pos.x--; + if (cursor_pos.x <= 0 && cursor_pos.y > 0) + { + cursor_pos.y--; + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + break; + } + } + } + else + { + if (cursor_pos.x > 0) + { + cursor_pos.x--; + } + else if (cursor_pos.y > 0) + { + cursor_pos.y--; + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + } + } + + cursor_x_tallest = cursor_pos.x; + } + + void move_cursor_right(void) + { + bool reset = process_selection(); // Only returns true if you don't hold shift + if (reset) + { + cursor_pos.x = cursor_select_pos.x; + } + + if (cursor_pos.x >= (int) UTF8_total_codepoints(get_line(cursor_pos.y)) && cursor_pos.y == get_lines() - 1) + { + cursor_x_tallest = cursor_pos.x; + return; + } + + if (SDL_GetModState() & KMOD_CTRL) + { + // If the current character is a word part, move to the end of the word. Else, move to the end of the next non-word. + char* character = UTF8_substr(get_line(cursor_pos.y), cursor_pos.x, cursor_pos.x + 1); + bool is_word = is_word_part(character[0]); + SDL_free(character); + + while (true) + { + character = UTF8_substr(get_line(cursor_pos.y), cursor_pos.x, cursor_pos.x + 1); + if (is_word_part(character[0]) != is_word) + { + SDL_free(character); + break; + } + SDL_free(character); + cursor_pos.x++; + if (cursor_pos.x >= (int) UTF8_total_codepoints(get_line(cursor_pos.y)) && cursor_pos.y < get_lines() - 1) + { + cursor_pos.y++; + cursor_pos.x = 0; + break; + } + } + } + else + { + + if (cursor_pos.x < (int) UTF8_total_codepoints(get_line(cursor_pos.y))) + { + cursor_pos.x++; + } + else if (cursor_pos.y < get_lines() - 1) + { + cursor_pos.y++; + cursor_pos.x = 0; + } + } + + cursor_x_tallest = cursor_pos.x; + } + + void backspace(void) + { + // The user pressed backspace. + if (selecting) + { + remove_selection(); + return; + } + + if (cursor_pos.x == 0) + { + if (cursor_pos.y > 0) + { + // Get the rest of the last line + char* rest_of_string = UTF8_substr(get_line(cursor_pos.y), 0, UTF8_total_codepoints(get_line(cursor_pos.y))); + + // Erase the current line + current_text->erase(current_text->begin() + cursor_pos.y); + + // Move the cursor up + cursor_pos.y--; + + // Move the cursor to the end of the line + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + cursor_x_tallest = cursor_pos.x; + + // Add the rest of the last line to the end of the first line + add_to_line(cursor_pos.y, rest_of_string); + SDL_free(rest_of_string); + } + } + else + { + if (SDL_GetModState() & KMOD_CTRL) + { + char* character = UTF8_substr(get_line(cursor_pos.y), cursor_pos.x - 1, cursor_pos.x); + bool is_word = is_word_part(character[0]); + SDL_free(character); + + const int remove_y = cursor_pos.y; + int remove_x = cursor_pos.x; + int start_x = cursor_pos.x; + while (true) + { + character = UTF8_substr(get_line(cursor_pos.y), cursor_pos.x - 1, cursor_pos.x); + if (is_word_part(character[0]) != is_word) + { + SDL_free(character); + break; + } + SDL_free(character); + cursor_pos.x--; + remove_x = cursor_pos.x; + + // Slightly different logic for wrapping + if (cursor_pos.x <= 0 && cursor_pos.y > 0) + { + cursor_pos.y--; + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + remove_x = 0; + break; + } + } + + // Erase the characters before the cursor + char* erased = UTF8_erase(get_line(remove_y), remove_x, start_x); + set_line(remove_y, erased); + SDL_free(erased); + + if (cursor_pos.y != remove_y) + { + // Remove the line + + char* rest_of_string = UTF8_substr(get_line(remove_y), 0, UTF8_total_codepoints(get_line(remove_y))); + + // Erase the current line + current_text->erase(current_text->begin() + remove_y); + + // Add the rest of the last line to the end of the first line + add_to_line(cursor_pos.y, rest_of_string); + SDL_free(rest_of_string); + } + } + else + { + // Erase the character before the cursor + char* erased = UTF8_erase(get_line(cursor_pos.y), cursor_pos.x - 1, cursor_pos.x); + set_line(cursor_pos.y, erased); + SDL_free(erased); + + // Move the cursor back + cursor_pos.x--; + cursor_x_tallest = cursor_pos.x; + } + } + } + + void delete_key(void) + { + // The user pressed delete. + if (selecting) + { + remove_selection(); + return; + } + + if (cursor_pos.x == (int) UTF8_total_codepoints(get_line(cursor_pos.y))) + { + if (cursor_pos.y < get_lines() - 1) + { + // Get the rest of the last line + char* rest_of_string = UTF8_substr(get_line(cursor_pos.y + 1), 0, UTF8_total_codepoints(get_line(cursor_pos.y + 1))); + + // Erase the current line + current_text->erase(current_text->begin() + cursor_pos.y + 1); + + // Add the rest of the last line to the end of the first line + add_to_line(cursor_pos.y, rest_of_string); + SDL_free(rest_of_string); + } + } + else + { + if (SDL_GetModState() & KMOD_CTRL) + { + char* character = UTF8_substr(get_line(cursor_pos.y), cursor_pos.x, cursor_pos.x + 1); + bool is_word = is_word_part(character[0]); + SDL_free(character); + + const int remove_y = cursor_pos.y; + int remove_x = cursor_pos.x; + int start_x = cursor_pos.x; + while (true) + { + character = UTF8_substr(get_line(cursor_pos.y), cursor_pos.x, cursor_pos.x + 1); + if (is_word_part(character[0]) != is_word) + { + SDL_free(character); + break; + } + SDL_free(character); + cursor_pos.x++; + remove_x = cursor_pos.x; + } + + // Erase the characters before the cursor + char* erased = UTF8_erase(get_line(remove_y), start_x, remove_x); + set_line(remove_y, erased); + SDL_free(erased); + + cursor_pos.x = start_x; + } + else + { + // Erase the character before the cursor + char* erased = UTF8_erase(get_line(cursor_pos.y), cursor_pos.x, cursor_pos.x + 1); + set_line(cursor_pos.y, erased); + SDL_free(erased); + } + } + } + + void handle_events(SDL_Event e) + { + if (!taking_input) return; + + if (e.type == SDL_KEYDOWN) + { + // Show cursor!! + flash_timer = 0; + + // Handle backspace + if (e.key.keysym.sym == SDLK_BACKSPACE) + { + // Remove the character + backspace(); + } + else if (e.key.keysym.sym == SDLK_v && SDL_GetModState() & KMOD_CTRL) + { + if (selecting) + { + remove_selection(); + } + char* clipboard_text = SDL_GetClipboardText(); + insert_text(clipboard_text); + SDL_free(clipboard_text); + } + else if (e.key.keysym.sym == SDLK_c && SDL_GetModState() & KMOD_CTRL) + { + if (!can_select) + { + SDL_SetClipboardText(get_line(cursor_pos.y)); + return; + } + char* selected = get_selected_text(); + SDL_SetClipboardText(selected); + SDL_free(selected); + } + else if (e.key.keysym.sym == SDLK_x && SDL_GetModState() & KMOD_CTRL) + { + if (!can_select) + { + SDL_SetClipboardText(get_line(cursor_pos.y)); + set_line(cursor_pos.y, ""); + cursor_pos.x = 0; + cursor_x_tallest = 0; + return; + } + char* selected = get_selected_text(); + SDL_SetClipboardText(selected); + SDL_free(selected); + remove_selection(); + } + else if (e.key.keysym.sym == SDLK_a && SDL_GetModState() & KMOD_CTRL && can_select) + { + select_all(); + } + else if (e.key.keysym.sym == SDLK_RETURN && using_vector) + { + insert_newline(); + } + else if (e.key.keysym.sym == SDLK_UP && can_move_cursor) + { + move_cursor_up(); + } + else if (e.key.keysym.sym == SDLK_DOWN && can_move_cursor) + { + move_cursor_down(); + } + else if (e.key.keysym.sym == SDLK_LEFT && can_move_cursor) + { + move_cursor_left(); + } + else if (e.key.keysym.sym == SDLK_RIGHT && can_move_cursor) + { + move_cursor_right(); + } + else if (e.key.keysym.sym == SDLK_HOME && can_move_cursor) + { + cursor_pos.x = 0; + cursor_x_tallest = 0; + if (SDL_GetModState() & KMOD_CTRL) + { + cursor_pos.y = 0; + } + } + else if (e.key.keysym.sym == SDLK_END && can_move_cursor) + { + if (SDL_GetModState() & KMOD_CTRL) + { + cursor_pos.y = get_lines() - 1; + } + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + cursor_x_tallest = cursor_pos.x; + } + else if (e.key.keysym.sym == SDLK_DELETE) + { + delete_key(); + } + else if (e.key.keysym.sym == SDLK_PAGEUP && can_move_cursor) + { + cursor_pos.y -= 10; + if (cursor_pos.y < 0) + { + cursor_pos.y = 0; + } + if (cursor_pos.x > (int) UTF8_total_codepoints(get_line(cursor_pos.y))) + { + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + } + } + else if (e.key.keysym.sym == SDLK_PAGEDOWN && can_move_cursor) + { + cursor_pos.y += 10; + if (cursor_pos.y >= get_lines()) + { + cursor_pos.y = get_lines() - 1; + } + if (cursor_pos.x > (int) UTF8_total_codepoints(get_line(cursor_pos.y))) + { + cursor_pos.x = UTF8_total_codepoints(get_line(cursor_pos.y)); + } + } + } + //Special text input event + else if (e.type == SDL_TEXTINPUT) + { + // Show cursor!! + flash_timer = 0; + + //Append character(s) + if (selecting) + { + remove_selection(); + } + insert_text(e.text.text); + } + } + + void draw_text(uint32_t flags, int text_x, int text_y, std::vector* text, TextInputInfo info) + { + const SDL_Color text_color = info.text_color; + const SDL_Color selected_color = info.selected_color; + const int visible_lines = info.visible_lines; + const int visible_padding = info.visible_padding; + + if ((flags & PR_CEN) && text_x == -1) + { + text_x = SCREEN_WIDTH_PIXELS / 2; + } + + if (cursor_pos.y < display_offset + visible_padding) + { + display_offset = SDL_max(0, TextInput::cursor_pos.y - visible_padding); + } + + if (cursor_pos.y > display_offset + visible_lines - visible_padding) + { + display_offset = SDL_min((int) text->size() - visible_lines + visible_padding, cursor_pos.y - visible_lines + visible_padding); + } + + // Draw text + int font_height = font::height(flags); + for (int i = 0; i < visible_lines; i++) + { + if (i + display_offset < (int) text->size()) + { + font::print(flags & ~PR_CEN, text_x - adjust_center(text, flags, i + display_offset), text_y + (i * font_height), text->at(i + display_offset), text_color.r, text_color.g, text_color.b); + } + } + + /* Draw selection boxes OVER the text, + * so we can redraw the text over top + * with a different color. */ + + if (selecting) + { + const int y = cursor_select_pos.y; + const int h = cursor_pos.y - y; + + const SelectionRect rect = reorder_selection_positions(); + + if (h == 0) + { + // If the selection is only a single line + + char* offset_x = UTF8_substr(text->at(rect.y).c_str(), 0, rect.x); + char* cut_string = UTF8_substr(text->at(rect.y).c_str(), rect.x, rect.x2); + int align_offset = adjust_center(text, flags, rect.y); + + graphics.fill_rect(text_x + font::len(flags, offset_x) - align_offset, text_y + (y - display_offset) * font_height, font::len(flags, cut_string), font_height, text_color); + font::print(flags & ~PR_CEN, text_x + font::len(flags, offset_x) - align_offset, text_y + (rect.y2 - display_offset) * font_height, cut_string, selected_color.r, selected_color.g, selected_color.b); + + SDL_free(offset_x); + SDL_free(cut_string); + } + else + { + // It's multiple lines, so draw multiple selection rectangles + + if (rect.y - display_offset >= 0) + { + const char* line = text->at(rect.y).c_str(); + char* offset_x = UTF8_substr(line, 0, rect.x); + char* selection_w = UTF8_substr(line, rect.x, UTF8_total_codepoints(line)); + int align_offset = adjust_center(text, flags, rect.y); + + graphics.fill_rect(text_x + font::len(flags, offset_x) - align_offset, text_y + (rect.y - display_offset) * font_height, SDL_max(font::len(PR_FONT_LEVEL, selection_w), 1), font_height, text_color); + font::print(flags & ~PR_CEN, text_x + font::len(flags, offset_x) - align_offset, text_y + (rect.y - display_offset) * font_height, selection_w, selected_color.r, selected_color.g, selected_color.b); + + SDL_free(offset_x); + SDL_free(selection_w); + } + + for (int i = 1; i < rect.y2 - rect.y; i++) + { + const int local_y = rect.y + i - display_offset; + if (local_y >= 0 && local_y < visible_lines) + { + const int line_width = SDL_max(font::len(flags, text->at(rect.y + i).c_str()), 1); + int align_offset = adjust_center(text, flags, rect.y + i); + + graphics.fill_rect(text_x - align_offset, text_y + local_y * font_height, line_width, font_height, text_color); + font::print(flags & ~PR_CEN, text_x - align_offset, text_y + local_y * font_height, text->at(rect.y + i).c_str(), selected_color.r, selected_color.g, selected_color.b); + } + } + + if (rect.y2 - display_offset < visible_lines) + { + const char* line_2 = text->at(rect.y2).c_str(); + char* selection_w = UTF8_substr(line_2, 0, rect.x2); + const int line_width = SDL_max(font::len(flags, selection_w), 1); + int align_offset = adjust_center(text, flags, rect.y2); + + graphics.fill_rect(text_x - align_offset, text_y + (rect.y2 - display_offset) * font_height, line_width, font_height, text_color); + font::print(flags & ~PR_CEN, text_x - align_offset, text_y + (rect.y2 - display_offset) * font_height, selection_w, selected_color.r, selected_color.g, selected_color.b); + + SDL_free(selection_w); + } + } + } + + // Draw cursor + if (TextInput::flash_timer < 15) + { + char* substr = UTF8_substr(text->at(TextInput::cursor_pos.y).c_str(), 0, TextInput::cursor_pos.x); + int align_offset = adjust_center(text, flags, TextInput::cursor_pos.y); + + if (TextInput::cursor_pos.x < (int) text->at(TextInput::cursor_pos.y).size() || TextInput::selecting) + { + graphics.set_color(text_color); + int x = text_x + font::len(flags, substr) - align_offset; + int y = text_y + ((TextInput::cursor_pos.y - display_offset) * font_height); + SDL_RenderDrawLine(gameScreen.m_renderer, x, y, x, y + font_height - 1); + } + else + { + font::print(flags & ~PR_CEN, text_x + font::len(flags, substr) - align_offset, text_y + ((TextInput::cursor_pos.y - display_offset) * font_height), "_", text_color.r, text_color.g, text_color.b); + } + SDL_free(substr); + } + } +} diff --git a/desktop_version/src/TextInput.h b/desktop_version/src/TextInput.h new file mode 100644 index 0000000000..7998f51774 --- /dev/null +++ b/desktop_version/src/TextInput.h @@ -0,0 +1,46 @@ +#ifndef TEXTINPUT_H +#define TEXTINPUT_H + +#include +#include +#include + +struct SelectionRect +{ + int x; + int y; + int x2; + int y2; +}; + +struct TextInputInfo +{ + SDL_Color text_color; + SDL_Color selected_color; + int visible_lines; + int visible_padding; +}; + +namespace TextInput +{ + extern bool taking_input; + extern bool selecting; + extern int flash_timer; + extern SDL_Point cursor_pos; + extern SDL_Point cursor_select_pos; + + void send_cursor_to_end(void); + void insert_newline(void); + int get_lines(void); + const char* get_line(int line); + void set_line(int line, const char* text); + void add_to_line(int line, const char* text); + SelectionRect reorder_selection_positions(void); + void handle_events(SDL_Event e); + void attach_input(std::vector* text); + void attach_input(std::string* text); + void detach_input(void); + void draw_text(uint32_t flags, int x, int y, std::vector* text, TextInputInfo info); +} + +#endif /* TEXTINPUT_H */ diff --git a/desktop_version/src/UTF8.c b/desktop_version/src/UTF8.c index 528b996412..d4d2b7ac56 100644 --- a/desktop_version/src/UTF8.c +++ b/desktop_version/src/UTF8.c @@ -1,5 +1,7 @@ #include "UTF8.h" +#include "SDL.h" + #define STARTS_0(byte) ((byte & 0x80) == 0x00) #define STARTS_10(byte) ((byte & 0xC0) == 0x80) #define STARTS_110(byte) ((byte & 0xE0) == 0xC0) @@ -200,3 +202,87 @@ size_t UTF8_backspace(const char* str, size_t len) return len; } + +char* UTF8_substr(const char* str, size_t start, size_t end) +{ + /* Given a string, return a substring of it. + * The start and end are codepoint indices, not byte indices. + * Caller must VVV_free */ + + const char* start_ptr = str; + const char* end_ptr = str; + + if (end <= start) + { + char* substr = SDL_malloc(1); + substr[0] = '\0'; + return substr; + } + + for (size_t i = 0; i < start; i++) + { + if (UTF8_next(&start_ptr) == 0) + { + char* substr = SDL_malloc(1); + substr[0] = '\0'; + return substr; + } + } + + for (size_t i = 0; i < end; i++) + { + if (UTF8_next(&end_ptr) == 0) + { + break; + } + } + + size_t len = end_ptr - start_ptr; + char* substr = SDL_malloc(len + 1); + SDL_memcpy(substr, start_ptr, len); + substr[len] = '\0'; + return substr; +} + +char* UTF8_erase(const char* str, size_t start, size_t end) +{ + /* Given a string, return a new string with the given range erased. + * The start and end are codepoint indices, not byte indices. + * Caller must VVV_free */ + + const char* start_ptr = str; + const char* end_ptr = str; + + if (end <= start || str[0] == '\0') + { + char* substr = SDL_malloc(1); + substr[0] = '\0'; + return substr; + } + + for (size_t i = 0; i < start; i++) + { + if (UTF8_next(&start_ptr) == 0) + { + char* substr = SDL_malloc(1); + substr[0] = '\0'; + return substr; + } + } + + for (size_t i = 0; i < end; i++) + { + if (UTF8_next(&end_ptr) == 0) + { + break; + } + } + + size_t len = SDL_strlen(str); + size_t new_len = len - (end_ptr - start_ptr); + char* substr = SDL_malloc(new_len + 1); + SDL_memcpy(substr, str, start_ptr - str); + SDL_memcpy(substr + (start_ptr - str), end_ptr, len - (end_ptr - str)); + substr[new_len] = '\0'; + return substr; +} diff --git a/desktop_version/src/UTF8.h b/desktop_version/src/UTF8.h index 0f9b1cf16f..f793507f57 100644 --- a/desktop_version/src/UTF8.h +++ b/desktop_version/src/UTF8.h @@ -27,6 +27,8 @@ UTF8_encoding UTF8_encode(uint32_t codepoint); size_t UTF8_total_codepoints(const char* str); size_t UTF8_backspace(const char* str, size_t len); +char* UTF8_substr(const char* str, size_t start, size_t end); +char* UTF8_erase(const char* str, size_t start, size_t end); #ifdef __cplusplus } /* extern "C" */ diff --git a/desktop_version/src/main.cpp b/desktop_version/src/main.cpp index 80323d4bf0..d9ba97c72d 100644 --- a/desktop_version/src/main.cpp +++ b/desktop_version/src/main.cpp @@ -31,6 +31,7 @@ #include "RenderFixed.h" #include "Screen.h" #include "Script.h" +#include "TextInput.h" #include "Unused.h" #include "UtilityClass.h" #include "Vlogging.h" @@ -954,7 +955,7 @@ static enum LoopCode loop_end(void) key.linealreadyemptykludge = false; //Mute button - if (key.isDown(KEYBOARD_m) && game.mutebutton<=0 && !key.textentry()) + if (key.isDown(KEYBOARD_m) && game.mutebutton<=0 && !TextInput::taking_input) { game.mutebutton = 8; if (game.muted) @@ -971,7 +972,7 @@ static enum LoopCode loop_end(void) game.mutebutton--; } - if (key.isDown(KEYBOARD_n) && game.musicmutebutton <= 0 && !key.textentry()) + if (key.isDown(KEYBOARD_n) && game.musicmutebutton <= 0 && !TextInput::taking_input) { game.musicmutebutton = 8; game.musicmuted = !game.musicmuted;