Tsa05
Member
I think we've all dodged around this a bit for decades, so I'm just going to post a multiline textbox script now and have done with it.
GMS has long gotten away with having no built-in way to allow reasonable multi-line text input because
A) Why would your player want to write an essay in your game?
b) Wouldn't you prefer to code your own to match the aesthetic of your game?
The answers have always been
A) Because <infinitely varied specific uses of text input>
B) No, and how would I pop up the system keyboard on mobile with that?
Ok, so here's half, at least. A multiline textbox. I don't know how to make an os keyboard pop up for mobile, but I suppose you can make one with the virtual keys thing?
Edit: I was linked to this, which would be an effective way to connect the textbox script with mobile keyboards: https://marketplace.yoyogames.com/assets/245/keyboard
Objective: A single script that handles textbox input.
There's lots more things we can do by ballooning everything into UI objects, and that's great, but I want to cut right to the heart of the thing. What should such a box have, how should it be implemented, what are the quirks and how to solve them?
And it's really hard to have this discussion on the Reviews section of the Marketplace (most multiline textbox script requests are met with "find an extension"). So, I'm going to put on a helmet and toss this out there, so we at least have something to discuss.
EDIT: New, more optimized solution, and additionally optimized solution using surfaces combined into this post. The remainder of the OP remains as a more backwards compatible, simpler version; the new set of scripts in post #19 runs 25x faster on my computer (literally 25x!)
Yes, it yanks info from keyboard_string. Yes, it checks some specific vk presses. Yes, I bet there's os hooks and dlls and egos abound. But, here's something made in GML--where should we go from here?
EDIT: Re-wrote with entirely new method. Have a look. Now, Enter key fully and properly works and all the rest. Original tucked in a spoiler in a later post.
Useage: Set an instance variable as a string--maybe like... text= "";
Call the script in the draw event of an object:
draw_input(x,y,width,height,caption,text,limit)
A thing to possibly do~~~
Something that I do in "persistent" scripts like this--kinda odd, I guess, but try it....
You'll notice that this script pollutes your global scope. It sets like...11? local variables and uses them to track things from one call to the next. Also means that you can't do multiple boxes onscreen at once. Easy solution: put the return value into index zero of an array, and pop the remaining locals into the return array as well instead of being local. Pass the whole array in (with optional parameters and defaults in the script), and when you want to get the result text it's just the zero index of the return. This'll let you run multiple boxes and not have any locals to clean up. Yay.
GMS has long gotten away with having no built-in way to allow reasonable multi-line text input because
A) Why would your player want to write an essay in your game?
b) Wouldn't you prefer to code your own to match the aesthetic of your game?
The answers have always been
A) Because <infinitely varied specific uses of text input>
B) No, and how would I pop up the system keyboard on mobile with that?
Ok, so here's half, at least. A multiline textbox. I don't know how to make an os keyboard pop up for mobile, but I suppose you can make one with the virtual keys thing?
Edit: I was linked to this, which would be an effective way to connect the textbox script with mobile keyboards: https://marketplace.yoyogames.com/assets/245/keyboard
Objective: A single script that handles textbox input.
There's lots more things we can do by ballooning everything into UI objects, and that's great, but I want to cut right to the heart of the thing. What should such a box have, how should it be implemented, what are the quirks and how to solve them?
And it's really hard to have this discussion on the Reviews section of the Marketplace (most multiline textbox script requests are met with "find an extension"). So, I'm going to put on a helmet and toss this out there, so we at least have something to discuss.
EDIT: New, more optimized solution, and additionally optimized solution using surfaces combined into this post. The remainder of the OP remains as a more backwards compatible, simpler version; the new set of scripts in post #19 runs 25x faster on my computer (literally 25x!)
Yes, it yanks info from keyboard_string. Yes, it checks some specific vk presses. Yes, I bet there's os hooks and dlls and egos abound. But, here's something made in GML--where should we go from here?
EDIT: Re-wrote with entirely new method. Have a look. Now, Enter key fully and properly works and all the rest. Original tucked in a spoiler in a later post.
Code:
/// @description draw_input(x,y,width,height,caption,text, limit)
/// @arg x The x-position of the box
/// @arg y The y-position of the box
/// @arg width The width of the box
/// @arg height The height of the box
/// @arg caption Caption to display in the box
/// @arg text Text to work on
/// @arg limit Max # of characters allowed in the box
/**************************************************************/
/* Information
* Developed by Tsa05 on the GameMaker forum for the SLUDGE engine
* Use of the script is subject to the following conditions:
* 1) Leave the information section intact
* 2) Give this script freely
*
* This script should be called from a Draw event.
* The return value should be continuously passed back in.
* This script erases keyboard_string.
* Some instance variables are used to track persistent info;
* instance variables all start with underscore (_)
* Text *will* overflow the box if your limit permits it.
*
* Undo (Ctrl+Z)/Redo (Ctrl+Shift+Z) is available, but REQUIRES additional steps:
* 1) Set useUndo to 1 in the settings area below. Now you have a memory leak.
* 2) Somewhere in your game, when you decide to stop showing
* this box, you've got to clear the histroy to solve the leak:
* if(ds_exists(_textUndoList,ds_type_list)) ds_map_destroy(_textUndoList);
*
* This script was designed for GMS2, and uses variable_instance_exists.
* To use with GMS 1.4, you'd need to remove that section and define
* those variables in a calling instance.
*/
var bx = argument[0];
var by = argument[1];
var bw = argument[2];
var bh = argument[3];
var bc = argument[4];
var bt = argument[5]; // Whole input string
var bl = argument[6];
/******* Settings ********/
var c_border = make_color_rgb(44,44,44);
var c_fill = make_color_rgb(70,70,70);
var c_text = c_white;
var c_select = c_white;
var c_cursor = c_white;
var repeatDelay = room_speed/20;
var firstDelayFactor = 10;
var doubleClick = room_speed/3;
var blinkSpeed = 5; // Higher number == slower cursor blink
var hintOpacity = .15; //Opacuty of the hint cursor. Zero for no hint
var useUndo = 0; // Should undo/redo exist?
var nl = "\n"; // Linebreak character
var space = " "; // Space character
/*************************/
/* Keys **/
var isLeft = keyboard_check(vk_left);
var isRight = keyboard_check(vk_right);
var isUp = keyboard_check(vk_up);
var isDown = keyboard_check(vk_down);
var isShift = keyboard_check(vk_shift);
var isDel = keyboard_check(vk_delete);
var isBack = keyboard_check(vk_backspace);
var isCtrl = keyboard_check(vk_control);
var isHome = keyboard_check(vk_home);
var isEnd = keyboard_check(vk_end);
var isAny = keyboard_check(vk_anykey);
var isEnter = keyboard_check(vk_enter);
var isC = keyboard_check(ord("C"));
var isV = keyboard_check(ord("V"));
var isX = keyboard_check(ord("X"));
var isA = keyboard_check(ord("A"));
var isZ = keyboard_check(ord("Z"));
/*************************/
/* Mouse **/
var isPressed = mouse_check_button_pressed(mb_left);
var isHeld = mouse_check_button(mb_left);
var isReleased = mouse_check_button_released(mb_left);
/*************************/
// Set up things this script needs
var pad = 3;
var indent = 2;
var okbs = keyboard_string;
var wrapped = bt;
keyboard_string = "";
var tbw = bw-2*indent*pad;
var op = 0; // Current opacity for mouse hint cursor
var obt = bt;
var undoLock = 0;
var offset = 0; // Offset used to track how many visual linebreaks added before cursor
/******* Define Locals ********/
if(!variable_instance_exists(id,"_txtBoxDelay")){
_txtBoxDelay = -4;
}
if(!variable_instance_exists(id,"_cursorAfterNewline")){
_cursorAfterNewline = 0;
}
if(!variable_instance_exists(id,"_selectAfterNewline")){
_selectAfterNewline = 0;
}
if(!variable_instance_exists(id,"_cursorPos")){
_cursorPos = string_length(bt);
}
if(!variable_instance_exists(id,"_selectCursorPos")){
_selectCursorPos = -1;
}
if(!variable_instance_exists(id,"_dragCursor")){
_dragCursor = -1;
}
if(!variable_instance_exists(id,"_doubleClickTimer")){
_doubleClickTimer = -1;
}
if(!variable_instance_exists(id,"_doubleClickLock")){
_doubleClickLock = -1;
}
if(!variable_instance_exists(id,"_textBlinkCounter")){
// I hate to track something this mundane, but
// there's a subtle need for blink to reset on keypress
_textBlinkCounter = 0;
}
if(useUndo){
if(!variable_instance_exists(id,"_textUndoList")){
_textUndoList = ds_list_create();
ds_list_add(_textUndoList,bt);
}
if(!variable_instance_exists(id,"_textUndoPos")){
_textUndoPos = 0;
}
}
/***************************/
_textBlinkCounter+=1;
if(isAny||isHeld){
_textBlinkCounter=0;
}
/////////////////////////
// Draw the box
draw_set_color(c_fill);
draw_roundrect_ext(bx,by,bx+bw,by+bh, 2*pad, 2*pad, 0);
draw_set_color(c_border);
for(var z=0;z<pad;z+=1){
draw_roundrect_ext(bx+z,by+z,bx+bw-z,by+bh-z, 2*pad, 2*pad, 1);
}
draw_set_color(c_text);
if(bc!=""){ // Draw a caption
draw_text(bx+indent*pad, by+3*pad, bc);
by+=3*pad+string_height(bc)+pad;
}
/////////////////////////
// Insert text
if(_selectCursorPos==_cursorPos) _selectCursorPos = -1; // Do not permit length zero (GMS bug with string_copy)
if(_selectCursorPos>=0 && okbs!=""){
if(_cursorPos<_selectCursorPos){
var c1 = _cursorPos+1;
var c2 = _selectCursorPos+1;
var af = 1;
}else{
var c2 = _cursorPos+1;
var c1 = _selectCursorPos+1;
var af = 0;
}
var len = c2-c1;
bt = string_delete(bt,c1,len);
if(!af){
_cursorPos-=len;
}
_selectCursorPos = -1;
_selectAfterNewline = 0;
_cursorAfterNewline = 0;
}
/////////////////////////
// Handle text add/remove
if(okbs!=""){
if(string_length(bt+okbs)<bl){
bt = string_insert(okbs,bt,_cursorPos+1);
_cursorPos+=string_length(okbs);
_cursorAfterNewline = 0;
}else{
// Add character by character
var tkbs = okbs;
while(string_length(bt)<bl){
bt = string_insert(string_char_at(tkbs,1),bt,_cursorPos+1);
tkbs=string_delete(tkbs,1,1);
_cursorPos+=1;
_cursorAfterNewline = 0;
}
}
}
if(string_length(bt)>bl){
bt = string_copy(bt,1,bl);
if(_cursorPos>string_length(bt)){
_cursorPos = string_length(bt);
}
}
if(_txtBoxDelay<=0){ // See if text is inserted via paste
if(_selectCursorPos!=-1){
// put cursor in order
if(_selectCursorPos<_cursorPos){
var low = _selectCursorPos+1;
var high = _cursorPos+1;
}else{
var low = _cursorPos+1;
var high = _selectCursorPos+1;
}
}else{
low = _cursorPos; high = _cursorPos;
}
/////////////////////////
// Remove text
if(isDel){
if(_selectCursorPos!=-1){
bt = string_delete(bt,low,high-low);
_selectCursorPos = -1;
_cursorPos = low-1;
}else{
bt = string_delete(bt,_cursorPos+1,1);
}
}else if(isBack){
if(_selectCursorPos!=-1){
// put things in order
bt = string_delete(bt,low,high-low);
_selectCursorPos = -1;
_cursorPos = low-1;
}else{
if(_cursorPos>0){
bt = string_delete(bt,_cursorPos,1);
_cursorPos-=1;
}
}
_cursorAfterNewline = 0;
}else
/////////////////////////
// Add newline
if(isEnter){
if(_selectCursorPos!=-1){
bt = string_delete(bt,low,high-low);
_selectCursorPos = -1;
_cursorPos = low-1;
}
bt = string_insert(nl,bt,_cursorPos+1);
_cursorPos+=string_length(nl);
_cursorAfterNewline = 1;
}
else
/////////////////////////
// CutCopyPaste
if(isCtrl){
// Crtl + X is a combo of cut and copy
if(isC || isX){
// Copy
if(_selectCursorPos>=0){
if(_cursorPos<_selectCursorPos){
var c1 = _cursorPos+1;
var c2 = _selectCursorPos+1;
var af = 1;
}else{
var c2 = _cursorPos+1;
var c1 = _selectCursorPos+1;
var af = 0;
}
var len = c2-c1;
var newStr = string_copy(bt,c1,len);
clipboard_set_text(newStr);
}
}
if(isV || isX){
// Paste and Cut
if(clipboard_has_text()){
if(_selectCursorPos>=0){ // Trim out selected text, if any
if(_cursorPos<_selectCursorPos){
var c1 = _cursorPos+1;
var c2 = _selectCursorPos+1;
var af = 1;
}else{
var c2 = _cursorPos+1;
var c1 = _selectCursorPos+1;
var af = 0;
}
var len = c2-c1;
bt = string_delete(bt,c1,len);
if(!af){
_cursorPos-=len;
}
_selectCursorPos = -1;
}
if(isV){
// Add new text
var newStr = clipboard_get_text();
if(string_length(bt+newStr)<bl){
bt = string_insert(newStr,bt,_cursorPos+1);
_cursorPos+=string_length(newStr);
}else{
// Add character by character
var ts = newStr;
while(string_length(bt)<bl){
bt = string_insert(string_char_at(ts,1),bt,_cursorPos+1);
ts=string_delete(ts,1,1);
_cursorPos+=1;
}
}
}
}
}
}
}
/////////////////////////
// Convert to text matrix
var p = 1;
var row = 0;
var col = 0;
var textArray = 0;
var ts = bt;
while(p<=string_length(ts)){
if(string_pos(nl, ts)==p){
// There's a newline!
p+=string_length(nl)-1; // Minus one because we're already +1
textArray[row, col] = nl;
ts = string_delete(ts,1,p);
col = 0; row+=1; p = 1;
}else{
var currentW = string_width(string_copy(ts,1,p));
if(currentW>tbw){
row+=1;
col = 0;
ts = string_delete(ts,1,p-1);
p = 1;
}
var c = string_char_at(ts,p);
textArray[row, col] = c;
col+=1;
p+=1;
}
}
/////////////////////////
// Check where the mouse
// is among the text
var tbx = bx+indent*pad;
var tx = tbx;
var ty = by;
var mPos = -1;
var mCoords = -1;
var cCoords = -1;
var sCoords = -1;
draw_set_color(c_white);
var ah = array_height_2d(textArray);
for(var row = 0; row<ah; row+=1){
var rowLength = array_length_2d(textArray, row);
for(var col = 0; col<rowLength; col+=1){
var c = textArray[row, col];
var cw = string_width(c); var ch = string_height(c);
var leftEdge = tx;
if(col==0) leftEdge-=cw/2; // Swag~ Gives extra room to click in the margin.
var isTrail = point_in_rectangle(mouse_x, mouse_y, leftEdge, ty, tx+cw/2, ty+ch);
var isLead = point_in_rectangle(mouse_x, mouse_y, tx+cw/2,ty, tbx+tbw, ty+ch);
if(isTrail){ // Trailing Edge
mPos = [row, col-1];
mCoords = [tx, ty, tx, ty+ch];
}else if(isLead){ // Leading edge
mPos = [row, col];
mCoords = [tx+cw, ty, tx+cw, ty+ch];
}
tx+=string_width(c);
}
ty+=string_height(c);
tx = tbx;
}
/////////////////////////
// Handle the mouse
var dragging = 0;
if(is_array(mCoords)){
if(isHeld){
if(!_dragCursor){
// Set the cursor
if(isShift){
// Fresh click in a new place, but moving selection
// Find the lower cursor position, move it.
var scp = 1;
for(var z=0;z<mPos[0];z+=1){
// Increment scp by a whole row
scp+=array_length_2d(textArray, z);
}
scp+=mPos[1];
if(mPos[1]<0){
_selectAfterNewline = 1;
}else{
_selectAfterNewline = 0;
}
_selectCursorPos = scp;
}else{
// If it's just an innocent little click, clear selections
// and start a new drag
var cp = 1;
for(var z=0;z<mPos[0];z+=1){
// Increment scp by a whole row
cp+=array_length_2d(textArray, z);
}
cp+=mPos[1];
if(mPos[1]<0){
_cursorAfterNewline = 1;
}else{
_cursorAfterNewline = 0;
}
_cursorPos = cp;
_dragCursor = 1;
_selectCursorPos = -1; _selectAfterNewline = 0;
}
}else{
// Move the selection cursor
if(_doubleClickTimer>0){
// This click was within doubleClick time and place.
_doubleClickTimer = -1;
// Find space before
var pc = string_copy(bt,1,_cursorPos);
var p1 = 0;
while(string_pos(" ",pc)){
var p1 = string_pos(" ",pc);
pc = string_replace(pc," ","_");
}
// Find space after
var pc = string_delete(bt,1,_cursorPos);
var p2 = string_pos(" ",pc)-1;
if(p2<0){
_selectCursorPos = string_length(bt);
}else{
_selectCursorPos = _cursorPos + p2;
}
_cursorPos = p1;
_doubleClickLock = 1;
}else if(!_doubleClickLock){
var scp = 1;
for(var z=0;z<mPos[0];z+=1){
// Increment scp by a whole row
scp+=array_length_2d(textArray, z);
}
scp+=mPos[1];
_selectCursorPos = scp;
}
}
}
}
/////////////////////////
// Find the cursor grid
var cr = 0; var cc = 0;
var tracker = 1;
var arrayHeight = array_height_2d(textArray);
while(tracker<_cursorPos){
tracker+=1;
var rowLength = array_length_2d(textArray, cr);
if(cc>=rowLength-1){
cc = 0;
cr+=1;
// If the last character is a newline,
// it might be multiple characters
var a = string_length(textArray[cr, cc])-1;
tracker+=a;
}else{
cc+=1;
}
}
if(_cursorPos==0){
cc=-1;
}else if(_cursorAfterNewline && cr<arrayHeight-1){
cc=-1; cr +=1;
}
/////////////////////////
// Find the selection
var scr = 0; var scc = 0;
var tracker = 1;
var arrayHeight = array_height_2d(textArray);
while(tracker<_selectCursorPos){
tracker+=1;
var rowLength = array_length_2d(textArray, scr);
if(scc>=rowLength-1){
scc = 0;
scr+=1;
// If the last character is a newline,
// it might be multiple characters
var a = string_length(textArray[scr, scc])-1;
tracker+=a;
}else{
scc+=1;
}
}
if(_selectCursorPos==0){
scc=-1;
}else if(_selectAfterNewline && scr<arrayHeight-1){
scc=-1; scr +=1;
}
/////////////////////////
// Draw the text
/////////////////////////
// Find the cursor
/////////////////////////
// Find the selection
var tbx = bx+indent*pad;
var tx = tbx;
var ty = by;
draw_set_color(c_cursor);
cCoords = [tx, ty, tx, ty+string_height("|")];
var ah = array_height_2d(textArray);
for(var row = 0; row<ah; row+=1){
var rowLength = array_length_2d(textArray, row);
for(var col = 0; col<rowLength; col+=1){
var c = textArray[row, col];
cw = string_width(c);
draw_text(tx, ty, c);
////////////////////////////////////
// Check cursor //
var modX = 0;
if( (row == cr) && ((col == cc) or (col==0&&cc=-1)) ){
cCoords = [tx+cw+modX, ty, tx+cw+modX, ty+ch];
}
////////////////////////////////////
// Check selection //
if(_selectCursorPos!=-1){
if( (row == scr) && ((col == scc) or (col==0&&scc=-1)) ){
sCoords = [tx+cw+modX, ty, tx+cw+modX, ty+ch];
}
}
////////////////////////////////////
tx+=string_width(c);
}
ty+=string_height(c);
tx = tbx;
}
if(!isHeld){
if(_selectCursorPos!=_cursorPos&&_selectCursorPos!=-1){
// If there's a selection, don't enable double-clicking on cancel
dragging = 1;
}
_dragCursor=-1;
_doubleClickLock = -1;
}
if(_doubleClickTimer>0){
_doubleClickTimer-=1;
}
if(isReleased && !dragging){
_doubleClickTimer = doubleClick;
}
//////////////////////////////////////////////////
// Now that the current state of decorations
// is computed, draw them
//////////////////////////////////////////////////
/////////////////////////
// Draw the mouse
if(is_array(mCoords)){
//show_debug_message("mrow:"+string(mPos[0])+" mcol:"+string(mPos[1]));
draw_set_alpha(hintOpacity);
draw_line(mCoords[0], mCoords[1], mCoords[2], mCoords[3]);
draw_set_alpha(1);
}
/////////////////////////
// Draw the cursor
if(is_array(cCoords)){
var a = round(.5*cos(_textBlinkCounter*(room_speed/blinkSpeed))+.5);
draw_set_alpha(a);
draw_set_color(c_select);
if(_cursorPos==0||_cursorAfterNewline){
if(is_array(textArray)){
var cw = string_width(textArray[cr,0]);
}
cCoords[0]-=cw; cCoords[2]-=cw;
}
draw_line(cCoords[0], cCoords[1], cCoords[2], cCoords[3]);
draw_set_alpha(1);
}
/////////////////////////
// Draw the selection
draw_set_color(c_select);
if(is_array(sCoords) && is_array(cCoords)){
if(_selectCursorPos==0||_selectAfterNewline){
var cw = string_width(textArray[0,0]);
sCoords[0]-=cw; sCoords[2]-=cw;
}
var lower = 0; var upper = 0;
if(_selectCursorPos<_cursorPos){
lower = sCoords; upper = cCoords;
}else{
lower = cCoords; upper = sCoords;
}
// Reverse cursor coords if needed
// to always draw low to high
var x1 = lower[0]; var tx1 = upper[0];
var y1 = lower[1]; var ty1 = upper[1];
var x2 = lower[2]; var tx2 = upper[2];
var y2 = lower[3]; var ty2 = upper[3];
var h = y2-y1;
var outline = 1;
while(y1<ty1){
// Draw to the end of the lines
// until we have the right line
draw_rectangle(x1, y1,tbx+tbw, y2, outline);
x1 = tbx;
y1+=h;y2+=h;
}
if(y1!=ty2) // Eliminate 1 px selection at start of line
draw_rectangle(x1, y1, tx2, ty2, outline);
}
//////////////////////////////////////////////////
// Now that the current state is computed,
// Compute input movement for next cycle
//////////////////////////////////////////////////
if(isAny && _txtBoxDelay<=0){
/////////////////////////
// Prepare cursor info
/////////////////////////
if(isShift && _selectCursorPos==-1){
_selectCursorPos = _cursorPos;
scc = cc; scr = cr;
}else if(!isShift && !isCtrl && _selectCursorPos!=-1){
// Just useability
// Arrow keys canceling a selection
// Jumps to last selection point
cc = scc; cr = scr;
}
// Left Pressed
if(isLeft){
var amt = 0;
if(isShift){
if(isCtrl){
// Find the nearest previous space or line
amt =1;
while(scc>=amt && textArray[scr, scc-amt]!=space){
amt+=1;
}
}else{amt = 1; }
if(scc>=0){
scc-=amt;
}
if(scc<0){
if(scr>0){
scr-=1;
scc = array_length_2d(textArray, scr)-1;
if(!_selectAfterNewline){
_selectAfterNewline = 1;
}else{
_selectAfterNewline = 0;
scc-=1;
}
}else{
scc = -1;
}
}else{
_selectAfterNewline = 0;
}
}else{
if(isCtrl){
// Find the nearest previous space or line
amt =1;
while(cc>=amt && textArray[cr, cc-amt]!=space){
amt+=1;
}
}else{amt = 1; }
if(cc>=0){
cc-=amt;
}
if(cc<0){
if(cr>0){
cr-=1;
cc = array_length_2d(textArray, cr)-1;
if(!_cursorAfterNewline){
_cursorAfterNewline = 1;
}else{
_cursorAfterNewline = 0;
}
}else{
cc = -1;
}
}else{
_cursorAfterNewline = 0;
}
_selectCursorPos = -1;
}
} else
/////////////////////////
// Right Pressed
if(isRight){
var amt = 0;
if(isShift){
var rowLength = array_length_2d(textArray, scr);
var arrayHeight = array_height_2d(textArray);
if(isCtrl){
// Find the nearest previous space or line
amt =1;
while(scc+amt<rowLength && textArray[scr, scc+amt]!=space){
amt+=1;
}
}else{amt = 1; }
scc+=amt;
if(scc>=rowLength){
if(scr<arrayHeight-1){
scr+=1;
scc = -1;
if(!_selectAfterNewline){
_selectAfterNewline = 1;
}else{
_selectAfterNewline = 0;
scc+=string_length(nl);
}
}else{
scc = rowLength-1;
_selectAfterNewline = 0;
}
}else{ _selectAfterNewline = 0; }
}else{
var rowLength = array_length_2d(textArray, cr);
var arrayHeight = array_height_2d(textArray);
if(isCtrl){
// Find the nearest previous space or line
amt =1;
while(cc+amt<rowLength && textArray[cr, cc+amt]!=space){
amt+=1;
}
}else{amt = 1; }
cc+=amt;
if(cc>=rowLength){
if(cr<arrayHeight-1){
cr+=1;
cc = -1;
if(!_cursorAfterNewline){
_cursorAfterNewline = 1;
}else{
_cursorAfterNewline = 0;
}
}else{
cc = rowLength-1;
_cursorAfterNewline = 0;
}
}else{ _cursorAfterNewline = 0; }
_selectCursorPos = -1;
}
}
else
/////////////////////////
// Up Pressed
if(isUp){
if(isShift){
var amt = 1;
if(scr>0){
scr-=1;
var rowLength = array_length_2d(textArray, scr);
if(scc>rowLength-1){
scc = rowLength-1;
}
}else{
scc = -1;
}
if(scc<0){
if(scr>0){
scr-=1;
scc = array_length_2d(textArray, scr)-1;
}else{
}
}
}else{
var amt = 1;
if(cr>0){
cr-=1;
var rowLength = array_length_2d(textArray, cr);
if(cc>rowLength-1){
cc = rowLength-1;
}
}else{
cc = -1;
}
if(cc<0){
if(cr>0){
cr-=1;
cc = array_length_2d(textArray, cr)-1;
}else{
}
}
_selectCursorPos = -1;
}
}
else
/////////////////////////
// Down Pressed
if(isDown){
var arrayHeight = array_height_2d(textArray);
if(isShift){
if(scr<=arrayHeight-2){
scr+=1;
var rowLength = array_length_2d(textArray, scr);
if(scc>rowLength-1){
scc = rowLength-1;
}
}else{
scc = array_length_2d(textArray, scr)-1;
_selectAfterNewline = 0;
}
}else{
if(cr<=arrayHeight-2){
cr+=1;
var rowLength = array_length_2d(textArray, cr);
if(cc>rowLength-1){
cc = rowLength-1;
}
}else{
cc = array_length_2d(textArray, cr)-1;
_cursorAfterNewline = 0;
}
_selectCursorPos = -1;
}
} else
/////////////////////////
// Home Pressed
if(isHome){
if(isShift){
_selectAfterNewline = 1;
if(isCtrl){
scc = -1;
scr = 0;
}else{
scc = -1;
}
}else{
_cursorAfterNewline = 1;
if(isCtrl){
cc = -1;
cr = 0;
}else{
cc = -1;
}
_selectCursorPos = -1;
}
}else
/////////////////////////
// End Pressed
if(isEnd){
var arrayHeight = array_height_2d(textArray);
if(isShift){
_selectAfterNewline = 0;
var rowLength = array_length_2d(textArray, scr);
if(isCtrl){
scr = arrayHeight-1;
scc = array_length_2d(textArray, scr)-1;
}else{
scc = rowLength-1;
}
}else{
_cursorAfterNewline = 0;
var rowLength = array_length_2d(textArray, cr);
if(isCtrl){
cr = arrayHeight-1;
cc = array_length_2d(textArray, cr)-1;
}else{
cc = rowLength-1;
}
_selectCursorPos = -1;
}
}else
/////////////////////////
// Ctrl+A Pressed
if(isCtrl and isA){
cc = -1; cr = 0;
_cursorPos = 0; _selectCursorPos = string_length(bt);
scr = array_height_2d(textArray)-1;
scc = array_length_2d(textArray, scr)-1;
}
/////////////////////////
// Undo/Redo
if(isCtrl && isZ && useUndo){
undoLock = 1;
if(isShift){
// Step foward
if(_textUndoPos<ds_list_size(_textUndoList)-1){
_textUndoPos+=1;
if(_textUndoPos>=ds_list_size(_textUndoList)){
_textUndoPos = ds_list_size(_textUndoList)-1;
}
}
bt=ds_list_find_value(_textUndoList,_textUndoPos);
}else{
_textUndoPos-=1;
var s = ds_list_find_value(_textUndoList,_textUndoPos);
if(!is_undefined(s)){
bt=s;
}
if(_textUndoPos<0) _textUndoPos = 0;
}
if(_cursorPos>string_length(bt)){
_cursorPos = string_length(bt);
}
if(_selectCursorPos>string_length(bt)){
_selectCursorPos = -1;
}
}
//////////////////////////////////////////////////
//////////////////////////////////////////////////
/////////////////////////
// Selection Position
if(scc<0) _selectAfterNewline = 1;
var tracker = 0;
var trR = 0;
while(trR<scr){
var rowLength = array_length_2d(textArray, trR);
trR+=1;
tracker+=rowLength;
}
tracker+=scc+1;
if(isShift)
_selectCursorPos = tracker;
/////////////////////////
// Cursor Position
if(cc<0){ _cursorAfterNewline = 1; }
var tracker = 0;
var trR = 0;
while(trR<cr){
var rowLength = array_length_2d(textArray, trR);
trR+=1;
tracker+=rowLength;
}
tracker+=cc+1;
_cursorPos = tracker;
/////////////////////////
// Add Delay
if(_txtBoxDelay<-1){
_txtBoxDelay = firstDelayFactor*repeatDelay;
if( (isCtrl || isShift) &&
!(isLeft || isRight
|| isUp || isDown
|| isHome || isEnd
|| isX || isC || isV || isA || isZ)
){
_txtBoxDelay = -4;
}
}else{
_txtBoxDelay = repeatDelay;
}
}else{
/////////////////////////
// Remove delay
if(keyboard_check_released(vk_anykey)){
_txtBoxDelay=-4;
}else{
if(_txtBoxDelay>0 && isAny){
_txtBoxDelay -= 1;
}else{
if(!isAny){
_txtBoxDelay=-4;
}else{
_txtBoxDelay=0;
}
}
}
}
// Add an undo state
if(obt!=bt && !undoLock && useUndo){
while(_textUndoPos<ds_list_size(_textUndoList)-1){
ds_list_delete(_textUndoList,_textUndoPos+1);
}
ds_list_add(_textUndoList,bt)
_textUndoPos = ds_list_size(_textUndoList)-1;
}
return bt; // Is it safe to come out now?
//////////////////////////////////////////////////
Call the script in the draw event of an object:
draw_input(x,y,width,height,caption,text,limit)
A thing to possibly do~~~
Something that I do in "persistent" scripts like this--kinda odd, I guess, but try it....
You'll notice that this script pollutes your global scope. It sets like...11? local variables and uses them to track things from one call to the next. Also means that you can't do multiple boxes onscreen at once. Easy solution: put the return value into index zero of an array, and pop the remaining locals into the return array as well instead of being local. Pass the whole array in (with optional parameters and defaults in the script), and when you want to get the result text it's just the zero index of the return. This'll let you run multiple boxes and not have any locals to clean up. Yay.
Last edited: