Tutorial:Textinput enhanced

From Bennu Wiki
Jump to: navigation, search


This is an extended / enhanched version of the original textinput example. It's a bit more complex and should be seen as a follow-up for those who understand the basics and want to move on to a little more adavanced version.

It uses multiple processes to handle cursor movement and string editing logic. It also shows some use of structs. The code is taken from a work in progress gui library. It also supports a clipboard (wich is simply a buffer).

The mechanism behind the use of the buffers and how they are affected by the cursor is explained in full detail in the sourcecode. The "widget_object_id[]" struct is something you can mostly ignore, it was carried over from the libary this routine was written for. Originally, it contained data for other widgets as well, but I removed that from this version.


Example code

/* 

  Enhanced textinput example by Handsource-DYKO. This example is based on the original textinput example,
  I extended it for use in gui library project. This enhanced version supports:
  
  - A moving blinking cursor that is keyboard controlled (LEFT, RIGHT, HOME and END keys).
  
  - Inserting or removing text at any position in the string.
  
  - Selecting text to a buffer, and with A copy, insert and delete function.
  
    To select text, move the cursor to the start (or end) of the selection and hit "ALT". Then move the
    cursor to end / start of the selection and hit "ALT" again. To copy the text, hit "CTRL".
	
    If you want to delete text, hit the "DEL" key, copying is not necessary. To insert a copied selection
    at any point in the text, move the cursor to the desired position and hit the "INS" key.
  
  To make these features possible, the routines uses 3 string buffers.
*/


#IFdef __VERSION__
IMPORT "mod_video";
IMPORT "mod_text";
IMPORT "mod_say";
IMPORT "mod_key";
IMPORT "mod_string";
IMPORT "mod_proc";
IMPORT "mod_map";
IMPORT "mod_draw";
IMPORT "mod_debug";
IMPORT "mod_sys";
IMPORT "mod_wm";
#ENDIF



GLOBAL

/* textinput */
STRUCT textclipboard;

   string text_selection_buffer;       // buffer for copy, paste and delete in a textinput widget.
   
   int text_selection_start_position;
   int text_selection_end_position;
   
   int selection_temp;                 // helper variable used from swapping the selection start and end if they are reverse.
   
   int selection_length;               // (length of the buffer string).
   
END



/* this is copied from a wip-project "dyko_guilib". */
STRUCT widget_object_id[9];

   
   int id; // id code of the widget.
   
   
   STRUCT inputbox;
	  
      int cursor_position;                      // (within the string, not on screen).
      char current_cursor_character_at_cursor;
	  
      /*
	the whole string is split in a left portion and a right portion. this is done for the cursor, both portions
	are updated when the cursor moves, or when characters are inserted or removed from the string. both parts are
	created as child processes, and use the write_in_map function to display. of each portion the number of char-
	acters is kept track of, as well as the width in pixels of the map. This way we can handle multi-width fonts.
      */
      int left_string_length;      // width in pixels of the write_in_map image of the left portion of the string.
      int right_string_length;
      int string_total_length;     // idem, but for whole string.
	  
      int num_chars_left_string;   
      int num_chars_right_string;
      int num_chars_total_string;
	  
	  
      string leftstring;          // left part of the string for the cursor.
      string rightstring;         // right part of the string for the cursor.
      string textstring;          // entire stringbuffer.
   END

END
 

 
 
 

PROCESS main();

PRIVATE

   
   int text_id[20]; 
   
   string tempchar;

   
   
BEGIN

   set_fps(60,0);
   set_mode (800, 600, 32);
   
   
   text_id[0] = write (0, 10, 10, ALIGN_CENTER_LEFT, "textinput demonstration. Hit 'ESC' to quit.");
   
   text_id[1] = write (0, 10, 30, ALIGN_CENTER_LEFT, "string length: ");
   text_id[2] = write_int (0, 130, 30, ALIGN_CENTER_LEFT, &widget_object_id[0].inputbox.num_chars_total_string);
   text_id[3] = write (0, 10, 40, ALIGN_CENTER_LEFT, "cursor position: ");
   text_id[4] = write_int (0, 130, 40, ALIGN_CENTER_LEFT, &widget_object_id[0].inputbox.cursor_position);
   text_id[3] = write (0, 190, 40, ALIGN_CENTER_LEFT, "character: ");
   text_id[4] = write_string (0, 270, 40, ALIGN_CENTER_LEFT, &tempchar);
   
   text_id[5] = write (0, 10, 50, ALIGN_CENTER_LEFT, "left string length + left string: ");
   text_id[6] = write_int (0, 230, 50, ALIGN_CENTER_LEFT, &widget_object_id[0].inputbox.num_chars_left_string);
   text_id[7] = write_string (0, 270, 50, ALIGN_CENTER_LEFT, &widget_object_id[0].inputbox.leftstring);
   
   text_id[8] = write (0, 10, 60, ALIGN_CENTER_LEFT, "right string length + right string: ");
   text_id[9] = write_int (0, 230, 60, ALIGN_CENTER_LEFT, &widget_object_id[0].inputbox.num_chars_right_string);
   text_id[10] = write_string (0, 270, 60, ALIGN_CENTER_LEFT, &widget_object_id[0].inputbox.rightstring);
   
   text_id[11] = write (0, 10, 70, ALIGN_CENTER_LEFT, "selection buffer (start, end): ");
   text_id[12] = write_int (0, 230, 70, ALIGN_CENTER_LEFT, &textclipboard.text_selection_start_position);
   text_id[13] = write_int (0, 270, 70, ALIGN_CENTER_LEFT, &textclipboard.text_selection_end_position);
   text_id[14] = write_string (0, 310, 70, ALIGN_CENTER_LEFT, &textclipboard.text_selection_buffer);
   
   
   
   /*
     d_textinput (int object_array_index, int x, int y, int font);
   */   
   widget_object_id[0].inputbox.textstring = "initial text string.";
   widget_object_id[0].id = d_textinput (0, 10, 100, 0);
   

   LOOP
    
      tempchar = widget_object_id[0].inputbox.current_cursor_character_at_cursor;
	
      IF (key(_esc) OR exit_status == TRUE)
	 exit();
      END
	  
	  
      FRAME;
   END
   
END


/*
  
  Function name : clear_clipboard.
  
                 Function that clears the textselection clipboard. this function is used when the cursor
		 is moved and no action is performed.
				 
  Valid input is considered to be:
  
  - buffer_clear: TRUE or FALSE. By default, the buffer string remain intact to support pasting copied text.
    If it has to be cleared, set the argument to "TRUE".
  
       
*/ 
 
FUNCTION clear_clipboard (byte buffer_clear);

BEGIN

   // undo any selected text.
   IF (buffer_clear == TRUE)
      textclipboard.text_selection_buffer      = "";
   END
   
   textclipboard.text_selection_start_position = 0;
   textclipboard.text_selection_end_position   = 0;
   
   textclipboard.selection_temp                = 0;  
   textclipboard.selection_length              = 0;		 
   
END  


  
  
/* this is the main widget. */  
  
PROCESS d_textinput (int object_array_index, int x, int y, int font);


PRIVATE


BEGIN

   // initiate the actual input handler.
   textinput_handler (object_array_index, x ,y, font);

   LOOP
      FRAME;
   END


ONEXIT:

END  
  

  
/*
  
  Process name : text_input_draw_cursor.
  
                 Helper process for the "textinput_handler" process. This process puts a blinking
		 cursor in the text. The "textinput_handler" positions this cursor, because it's 
		 a child process.
				 
				 
  Valid input is considered to be:
  
        - the object_array_index, this an reference to a number in the main object array that stores
          several properties of a widget, such as it's process id and the tooltip text array.
		  
        - the x any coordinates of the text widget.		  
		
	- the font.

		  
*/ 

PROCESS text_input_draw_cursor (int object_array_index, int y, font);

PRIVATE

int cursor_graph;

int width; height;

BEGIN

   width = 1;
   height = text_height (font, widget_object_id[object_array_index].inputbox.textstring);
   
   cursor_graph = map_new  (width, height, 32);
   
   map_clear(0, cursor_graph, rgba(0,0,0,0));
   
   drawing_map (0, cursor_graph);
   drawing_color (rgba(255,0,0,255));
   
   //         x0, y0, x1, y1
   draw_line (0, 0, 0, height);
   
   ctype = c_screen;
   
   graph = cursor_graph;

   LOOP
      
      // make the cursor blink
      graph = cursor_graph;
      FRAME (1200);
	  
      graph = 0;
      FRAME (1200);
	  
      FRAME;
   END

ONEXIT:

   // free resources
   unload_map (0, cursor_graph);
END  
  
  


/*
  
  Process name : textinput_handler.
  
                 Helper function for the d_textinput widget. It handles the keyboard scanning and string
		 managment for the textinput. This process is basically the "brain" for the textinput 
		 related widgets. 
				 
		 It works with three text buffers (the string is devided in a LEFT and RIGHT bufffer 
		 portion, wich concatinated form the actual main input string. The LEFT buffer is the
		 string part LEFT of the cursor position, and the RIGHT part is the buffer RIGHT of the
		 cursor. When the cursor moves or when characters are inserted / deleted the LEFT buffer
                 is affected. Either buffer may be 0 in length. 
				 
		 If the cursor is at the beginning of the string, the LEFT buffer is 0, and the RIGHT buffer
		 may contain contents. When the cursor is at the end of the string, the RIGHT buffer is 0,
		 and the LEFT buffer contains contents. When the entire string is still empty, both bufffers
		 are 0 length.
				 
		 Visually it looks like this:
		 -------------------------------------------------------  
		 "Hello, world! this is a input string." (total string)
			|
			cursor position
                
                 "Hello,"                                (left buffer)
                 " world! this is a input string."       (right buffer)

                 -------------------------------------------------------                 
                 Example cursor at beginning:
				 
		 "Hello, world! this is a input string." (total string)
		  |
		  cursor position
                
                 "Hello, world! this is a input string." (left buffer)
                 ""                                      (right buffer)
				 
		 -------------------------------------------------------  
		 Example cursor at end:
				 
		 "Hello, world! this is a input string." (total string)
				                      |
				                      cursor position
                
                 ""                                      (left buffer)
                 "Hello, world! this is a input string." (right buffer)
				 
		 -------------------------------------------------------  
		 Example empty string:
				 
		 ""                                     (total string)
		 |
		 cursor position
                
                 ""                                     (left buffer)
                 ""                                     (right buffer)
		 -------------------------------------------------------  
				 
		 The routine also supports copy, paste and delete withing the textstring. It uses an extra
		 (global) buffer for this, wich is the global variable called: "textclipboard.text_selection_buffer".
		 Selecting text is done by positioning the cursor at the start of the selection, hitting 
		 the alt key, then moving the cursor to the end of the selection and hitting the control
		 key again.
				 
		 The start and end positions of the selectionbuffer are stored in these global variables:
		 "textclipboard.text_selection_start_position" and "textclipboard.text_selection_end_position".
				 
				 
  Valid input is considered to be:
  
        - the object_array_index, this an reference to a number in the main object array that stores
          several properties of a widget, such as it's process id and the tooltip text array.
		  
        - the x any coordinates of the text widget.		  
		
	- the font.

		  
*/   

PROCESS textinput_handler (int object_array_index, int x, int y, int font);

  
PRIVATE

/* the string portions. input_buffer_string = left_buffer_string + right_buffer_string. */
string left_buffer_string;
string right_buffer_string;
string input_buffer_string;

string temp_buffer;

	
int key_time;
int key_time2;
	
byte last_ascii;
int txtid;
	
int text_length;      // length of the buffer in pixels.
int last_char_width;
int delta_width;

int cursor_position;
char current_cursor_character_at_cursor;
int string_length;

int cursor_id;

byte text_selection = 0;        // mark begin / end of text selection.

byte text_deleted  = FALSE;

BEGIN

   // show what you type in top left corner
   txtid = write_string(font, x, y, ALIGN_CENTER_LEFT, &input_buffer_string);
	
   
	
   // clear the string
   //input_buffer_string = "";
   
   /* 
     set the initail values. the string may be empty, but may already contain contents supplied by the widget. 
     also, set the cursor position and get string's length.
   */
   input_buffer_string = widget_object_id[object_array_index].inputbox.textstring; 
   cursor_position     = len (input_buffer_string);   
   string_length       = len (input_buffer_string);
   
   // draw the cursor
   cursor_id = text_input_draw_cursor (object_array_index, y, font);   
   
   // put it above the text
   cursor_id.z = text_z - 10;


	
   LOOP
   
   
      /*
	create the left and right buffers around the position of the cursor. these buffers grow or shrink depending
	on the cursor position, and on the characters beeing added or removed form them. together the form the complete
	input string.
      */
      left_buffer_string  = substr (input_buffer_string, 0              , cursor_position);
      right_buffer_string = substr (input_buffer_string, cursor_position, string_length);
	  
      //say ("left_buffer_string: "+left_buffer_string);
      //say ("right_buffer_string: "+right_buffer_string);
	  
	  
	  
      // update the cursor graphics' position.
      cursor_id.x = x + text_width (font, left_buffer_string); 
	  
	  

      IF (key(_esc))
	 exit();
      END

	  
	  
	  
      IF (key(_left))  /* move the cursor one position to the left. */
	     
	 FRAME (500);
	 IF (cursor_position > 0)
	    cursor_position --;
	 END
		 
	 // undo any selected text, when the cursor is moved and the selection was made
	 // so that no bad things can happen if the user moved te cursor before hitting
	 // DEL / INSERT / COPY.
	 IF (text_selection == 0)
	    clear_clipboard (FALSE); // selected text remains intact, but other data is cleared.
	 END
      END
	  
      IF (key(_right)) /* move the cursor one position to the right. */
	  
	 FRAME (500);
	 IF (cursor_position < string_length)
	    cursor_position ++;
	 END
		 
	 // undo any selected text, when the cursor is moved and the selection was made
	 // so that no bad things can happen if the user moved te cursor before hitting
	 // DEL / INSERT / COPY.
	 IF (text_selection == 0)
	    clear_clipboard (FALSE); // selected text remains intact, but other data is cleared.
	 END		
      END
	  
      IF (key(_home))  /* jump to the beginning of the string. */
	  
	 FRAME (500);
	 cursor_position = 0;
		 
	 // undo any selected text, when the cursor is moved and the selection was made
	 // so that no bad things can happen if the user moved te cursor before hitting
	 // DEL / INSERT / COPY.
         IF (text_selection == 0)
	    clear_clipboard (FALSE); // selected text remains intact, but other data is cleared.
	 END	
      END
	  
      IF (key(_end))   /* jump to the end of the string. */
	  
	 FRAME (500);
	 cursor_position = string_length;
		 
	 // undo any selected text, when the cursor is moved and the selection was made
	 // so that no bad things can happen if the user moved te cursor before hitting
	 // DEL / INSERT / COPY.
	 IF (text_selection == 0)
	    clear_clipboard (FALSE); // selected text remains intact, but other data is cleared.
	 END
      END
	  
      IF (key (_alt) AND text_selection == 0) /* mark start of selection. */
	     
	 FRAME (500);
	 textclipboard.text_selection_start_position = cursor_position;
		 
	 say ("text selection started at cursor position: "+textclipboard.text_selection_start_position);
	 text_selection = 1;
      END
	  
      IF (key (_alt) AND text_selection == 1) /* mark end of selection. */
	     
	 FRAME (500);
	 textclipboard.text_selection_end_position = cursor_position;
		 
	 say ("text selection ended at cursor position: "+textclipboard.text_selection_end_position);
	 text_selection = 2;
      END
	  
      IF (text_selection == 2)                /* selection is made, check the values, reverse if necessary. */
	  
	  say ("text selection done. checking values.");
	  
	  // check is the selection is reverse, if so, swap the begin and end values so that the start value
	  // is smaller then the end value.
	  IF (textclipboard.text_selection_start_position > textclipboard.text_selection_end_position)
		 
	     textclipboard.selection_temp = textclipboard.text_selection_start_position;
			
	     textclipboard.text_selection_start_position = textclipboard.text_selection_end_position;
             textclipboard.text_selection_end_position   = textclipboard.selection_temp;
	  END
		 
          textclipboard.selection_length = textclipboard.text_selection_end_position - textclipboard.text_selection_start_position;
		 
	  FRAME (1000);
	  text_selection = 0;
	  say ("done!");
       END
	  
/*-----------------------------------------------------------------------------------------------------------------*/
	  
	  
       IF (key(_control))   /* copy selection to the clipboard. */
	  
	  FRAME (500);
	
	  textclipboard.text_selection_buffer = substr (input_buffer_string, textclipboard.text_selection_start_position, 
		                                        (textclipboard.text_selection_end_position - 
                                                        textclipboard.text_selection_start_position));
         
	 //say ("copied: '"+textclipboard.text_selection_buffer+"' to the clipboard.");													  
       END
	  
	  
/*-----------------------------------------------------------------------------------------------------------------*/
	  
	  
       IF (key(_ins))      /* paste selection. */
	  
	  FRAME (500);
		 
	  input_buffer_string = left_buffer_string + textclipboard.text_selection_buffer + right_buffer_string;
		 
       END
	  
/*-----------------------------------------------------------------------------------------------------------------*/
	  
	  
       IF (key(_del))     /* delete selection. */
	  
	  FRAME (500);
		 
	  /* 
	   first, check if the selection is left or right from the cursor.
	   this check is important for determaining wich buffer (LEFT or RIGHT) must be affected and if the cursor
	   must be moved or not.
	  */
		 
	  /* cursor is right of the selection. */
	  IF (cursor_position => textclipboard.text_selection_end_position AND text_deleted == FALSE) 
		    
	     //say ("cursor position is RIGHT of selection");
	     //say ("cursor position: "+ cursor_position+" selection end: "+textclipboard.text_selection_end_position);
			
	     // remove the selected string from the LEFT buffer string.
	     left_buffer_string = substr (left_buffer_string, 0,   
			                 (widget_object_id[object_array_index].inputbox.num_chars_left_string -  
                                          textclipboard.selection_length));
			
	     // store the right buffer, so that it can be restored.
	     temp_buffer = right_buffer_string;
			
	     //say ("temp_buffer: "+temp_buffer);
			
	     // re-create the total string.
	     input_buffer_string = left_buffer_string + temp_buffer;
			
	     FRAME (100);
	     // we have to move the cursor to the left.
	     cursor_position -= textclipboard.selection_length;
		    
	     // avoid the execution of the next code section.
	     text_deleted = TRUE;
	  END       
		 
		 
	  /* cursor is left of the selection. */
	  IF (cursor_position =< textclipboard.text_selection_start_position AND text_deleted == FALSE) 
		 
	  //say ("cursor position is LEFT of selection (backwards selection order)");
	  //say ("cursor position: "+ cursor_position+" selection start: "+textclipboard.text_selection_start_position);
			
	  // remove the selected string from the RIGHT buffer string.
	  right_buffer_string = substr (right_buffer_string, textclipboard.selection_length, 
			                widget_object_id[object_array_index].inputbox.num_chars_right_string);
			
	  // re-create the total string.
	  input_buffer_string = left_buffer_string + right_buffer_string;
			
	  // cursor remains put, we don't have to move it because it's already left of the selection.
			
	  // avoid re-execution of the code section.
	  text_deleted = TRUE;
       END
    END
	  
	 
    // wait before re-enabling the delete function so that the strings have enough time to be properly restored.
    IF (text_deleted == TRUE)
	     
       // wait....
       FRAME (100);
       //say ("text deletion ok!");
       text_deleted = FALSE;
    END
	  
/*-----------------------------------------------------------------------------------------------------------------*/
	  
	  	  
	
    IF (ascii <> 0 AND last_ascii==ascii) // check IF a key is pressed and IF the same key
                                            // was pressed last frame
										
       IF (key_time == 0 OR key_time > fps / 4) // check IF the key was just pressed or it has been pressed
                                                  // for 0.25 seconds
							  
           IF (key_time == 0 OR key_time2 > fps / 30) // check IF the key was just pressed or it has been pressed
                                                       // for the last 0.03 seconds
              key_time2 = 0;
				
	      /* main character input code. simply check for the ascci code of the key that was pressed. */	
              SWITCH (ascii) 
				
                  CASE 8: // backspace key
				  
		      /*
			remove a character. the modified string constists of:
					 
			- the left buffer minus the removed character
			- the rightbuffer
					 
			both buffers can be any size, but may also be empty.
		     */
		     input_buffer_string = substr (left_buffer_string, 0, len (left_buffer_string) -1) + right_buffer_string;
					 
		     IF (cursor_position > 0)
			cursor_position --;
		     END
                  END
				   
                  CASE 13: // enter key
                     BREAK;
                  END
				  
                  DEFAULT: // addkey
				  	
                     /*					
		       add character that the user typed. the modified string consists of:
					 
		       - the left buffer      (can be any size, but may also be empty)
		       - the added character
		       - the right buffer     (can be any size, but may also be empty)
		     */
		      input_buffer_string = left_buffer_string + chr(ascii) + right_buffer_string;
					 
		      cursor_position ++;
					 
                  END			  
				  
               END
				
            END
			 
            key_time2 ++;
         END
		  
         key_time ++;
		  
      ELSE
	   
         key_time = key_time2 = 0; // reset
      END
	
   
      last_ascii = ascii;
	
  
      /* do some housekeeping on the strings size and cursor position. */
      string_length                      = len (input_buffer_string); 	  	  
      current_cursor_character_at_cursor = substr (input_buffer_string, cursor_position, 1);	  	 
	
  
      /* visual (pixel size) properties update. */
      text_length     = text_width (font, input_buffer_string); 	  	  
      last_char_width = text_width (font, substr(input_buffer_string, 0, len (input_buffer_string)-1)); 	   
      delta_width     = text_length - last_char_width;    
	   
	   
      /* update the widget_object_id[object_array_index].inputbox.textstring array properties for the widget. */
      widget_object_id[object_array_index].inputbox.textstring                         = input_buffer_string;	
      widget_object_id[object_array_index].inputbox.leftstring                         = left_buffer_string;
      widget_object_id[object_array_index].inputbox.rightstring                        = right_buffer_string;
	
  
      widget_object_id[object_array_index].inputbox.num_chars_total_string             = string_length;	 
      widget_object_id[object_array_index].inputbox.num_chars_left_string              = len (left_buffer_string);
      widget_object_id[object_array_index].inputbox.num_chars_right_string             = len (right_buffer_string);
	
  
      widget_object_id[object_array_index].inputbox.cursor_position                    = cursor_position;
      widget_object_id[object_array_index].inputbox.current_cursor_character_at_cursor = chr(current_cursor_character_at_cursor);
	  
	  
      //say ("text_length: "+text_length+" ; last_character's width: "+last_char_width+" ; delta_width: "+delta_width);
      //say ("d_textinput "+object_array_index+": text: "+widget_object_id[object_array_index].inputbox.textstring);
      //say ("current_cursor_character_at_cursor: "+widget_object_id[object_array_index].inputbox.current_cursor_character_at_cursor);
      FRAME;
   END // end of loop.


ONEXIT:

END    
  

  

Used in example: set_fps(), set_mode(), say(), write_string(), text_width(), text_height(), substr(), chr(), function, process, switch, ascii, fps, struct

You'll find a nice ASCII table here.