Text input?

Started by
8 comments, last by DvDmanDT 18 years, 8 months ago
Hello everyone.. I'm writing a game, an RTS to be specific.. Now, I was wondering how to do text input.. Or rather, how you'd do it.. I need it for menus (your have to write a name, and perhaps an IP to connect to in network games), in-game chat and a game console (So that clients might get to controll the server for example).. Now, we're using DirectInput, with buffers, for everything, and we're writing the GUI ourselves.. So, I was thinking about marking the input field as active, and then just send all key presses/releases to it, and let it decide what to do.. But I was also thinking that there might already be some nice easy to use class to help me out.. Any suggestions?
Advertisement
Do it yourself, it'll be more effecient when you know what's going on.
We should do this the Microsoft way: "WAHOOOO!!! IT COMPILES! SHIP IT!"
So how do I do it myself then?

Last time I did it, I made my own linked list, which contained a char and next/prev pointers.. Then in my text_input class, I had three ll pointers: first, last and active.. Then when the user pressed arrow keys, and/or home/end keys I moved around that active one, and on keypress (for the chars that is), I inserted that char before that active node.. I have a slight feeling that it's not the best way to do it.. How would you do it?
Let me pull out a lil code snipet I made...

#include <conio.h>#include <iostream>using namespace std;typedef struct charlink{	unsigned char data;	charlink* next;}charlinkthing;unsigned char* input(){	int s;	int size=0;	unsigned char ch;	unsigned char *string=NULL;	unsigned char *chstr=NULL;	bool done=false;	bool notrue=false;	charlinkthing* start=NULL;	charlinkthing* current=NULL;	charlinkthing* nextcur=NULL;	do	{		ch=getch();		if(ch==13)		{			cout << endl;			s=size;			current->next = NULL;			current=start;			chstr = new unsigned char[size+1];			string = chstr;			do			{				*chstr++=current->data;				nextcur=current;				current=nextcur->next;				nextcur=NULL;				s--;			}while(s);			*chstr='\0';			current=start->next;			delete start;			start=NULL;			do			{				if(current->next=NULL)					notrue=true;				else				{					nextcur=current->next;					delete current;					current = NULL;				}			}while(notrue);			done=true;		}		else		{			cout << ch;			if(!start)			{				current = new charlink;				current->data = ch;				start = current;				start->next = new charlink;				current = start->next;				size+=1;			}			else			{				current->data = ch;				nextcur = new charlink;				current->next = nextcur;				current = nextcur;				nextcur = NULL;				size+=1;			}		}	}while(!done);	return string;}(C) Copyright 2005.


Ever heard of bitmap fonts? Just use those to display the string...
(I believe getch() will work when using Win32, otherwise you'll have to search through msdn for a function...)

(You can use it, just don't say it's yours. You don't even have to say I made it...)
We should do this the Microsoft way: "WAHOOOO!!! IT COMPILES! SHIP IT!"
I'm using DirectInput already though.. Maybe I'll just use the last class I made.. Or maybe with some slight modifications..

Maybe I'll even release it.. :p
Pardon the ugliness of this code, it's for a GUI that's currently under development, and this is something thrown together just to see if it works. It works, though I need to do some research about how to better work the 'activate' functor.

#ifndef RMS_TEXTINPUT#define RMS_TEXTINPUT#include "rms_basic_gui.h"#include "rms_font.h"#include "rmsd3dfont.h"#include "rms_timer_renderers.h"#include <string>#include "charin_conversion.h"#include "rmsrect.h"#include "voidvoid_guiaction.h"#include "rms_gui_exclusivity.h"//#include "console.h"//extern	console_interface	*console;using	std::string;exclusive_group		textinputs;template <typename T>class	text_cursor:	virtual	public basero {//// A class to invisibly render/calc the rect needed to push the cursor one way or another.//private:protected:	T			*cursored_text;	ro_text			*invisible_text;	ro_text			*txt_cursor;	string::size_type	*cursor;	blinky			*ro_cursor;public:	void			reset();	virtual	void		render(){		rendertree();	}	virtual	void		disable(){		// Disable cursor		//exclusive_member::disable();		ro_cursor->disable();		ro_cursor->visibility(0);		//console->newtext("textinput: disabling.");	}	virtual void		enable(){		//exclusive_member::enable();		ro_cursor->enable();		ro_cursor->visibility(1);		//console->newtext("textinput: enabling.");	}		virtual bool		is_enabled(){		return(ro_cursor->is_visible());	}	text_cursor(depended_rect_generator *rg, T *ct, string::size_type *c): cursored_text(ct), cursor(c), basero(rg){		calculated_font_rect_generator<percentage_rect>		*cfrg=new calculated_font_rect_generator<percentage_rect>(0,0,new percentage_rect(rg,1,1,5,5));		invisible_text=new ro_text(cfrg,"",ct->font,0x00000000); 		cfrg->set(&invisible_text->font, invisible_text->txtref());		invisible_text->parent=this;		children.push_back(invisible_text);		ro_cursor=new blinky(20);		ro_cursor->parent=invisible_text;		invisible_text->children.push_back(ro_cursor);		cfrg=new calculated_font_rect_generator<percentage_rect>(0,0,new percentage_rect(cfrg->dependable(),1,1,4,6));		ro_text	*rot=new ro_text(cfrg,"_",ct->font,ct->color());		txt_cursor=rot;		cfrg->set(&rot->font, rot->txtref());		rot->parent=ro_cursor;		ro_cursor->children.push_back(rot);	}	virtual	~text_cursor(){	}};template <typename T>void		text_cursor<T>::reset(){//// Reset the invisible text. Essentially reset the cursor's rendering.//string			s;s.assign(cursored_text->str(),0,*cursor);if (s.length() && s[s.length()-1]==' '){	s.append(".");}invisible_text->str(s);invisible_text->tblrreset();txt_cursor->tblrreset();			// overkill?}	template <typename T,typename F>class	textinput:	virtual public basero,	public	exclusive_member{private:protected:	F			inputfunction;	T			*stored_text;	text_cursor<T>		*tcursor;	string::size_type       cursor;	bool			insertmode;		// 0 == write over  1 == insertpublic:	virtual void		gohome();	virtual void		goend();	virtual void		goleft();	virtual void		goright();	virtual void		bkspc();	virtual void		kbdel();	virtual void		cleartxt();	virtual void		charin(char a);	virtual void		submit(){		//inputfunction(stored_text->str());	}	const	string::size_type	*cpos(){return(&cursor);}	//virtual void		render();	virtual	void		enable(){		// enable cursor, set truly visible to allow for kbinput.		visibility(1);		tcursor->enable();	}	virtual void		disable(){		visibility(2);		tcursor->disable();	}	virtual bool		is_enabled(){		if(visible==1){			return(1);		}else{			return(0);		}	}	virtual void            activate(){                group->enable(this);        }        virtual void            deactivate(){                group->disable(this);        }	textinput(depended_rect_generator *rg, F inf, T *it): basero(rg), stored_text(it), cursor(0), insertmode(1), exclusive_member(&textinputs){		tcursor=new text_cursor<T>(rg,stored_text,&cursor);		tcursor->parent=stored_text;		stored_text->children.push_back(tcursor);		stored_text->parent=this;		children.push_back(stored_text);		kb.text_mode(1);		kb.textbind(charin_functor(this));		kb.keybind(KB_DEL,0,voidvoidfunctor(this,&textinput<T,F>::kbdel));		kb.keybind(KB_HOME,0,voidvoidfunctor(this,&textinput<T,F>::gohome));		kb.keybind(KB_END,0,voidvoidfunctor(this,&textinput<T,F>::goend));		kb.keybind(KB_LEFT,0,voidvoidfunctor(this,&textinput<T,F>::goleft));		kb.keybind(KB_RIGHT,0,voidvoidfunctor(this,&textinput<T,F>::goright));		kb.keybind(KB_BKSPACE,0,voidvoidfunctor(this,&textinput<T,F>::bkspc));		kb.keybind(KB_ESC,0,voidvoidfunctor(this,&textinput<T,F>::deactivate));		kb.keybind(KB_ENTER,0,voidvoidfunctor(this,&textinput<T,F>::submit));		mt.set_onclick(voidvoidfunctor(this,&textinput<T,F>::activate));		textinputs.add(this);	}	virtual ~textinput(){ textinputs.remove(this);}};template <typename T,typename F>void			textinput<T,F>::gohome(){ cursor=0; tcursor->reset(); }template <typename T,typename F>void			textinput<T,F>::goend(){ cursor=stored_text->str().length(); tcursor->reset(); }template <typename T,typename F>void                    textinput<T,F>::goleft(){ if (cursor!=0){ cursor=cursor-1; tcursor->reset(); }}template <typename T,typename F>void                    textinput<T,F>::goright(){ if (cursor!=stored_text->str().length()){ cursor=cursor+1; tcursor->reset(); }}template <typename T,typename F>void                    textinput<T,F>::bkspc(){	if (cursor!=0){	string s=stored_text->str();	cursor--;	s.erase(cursor,1);	stored_text->str(s);	tcursor->reset();}}template <typename T,typename F>void                    textinput<T,F>::kbdel(){if (cursor!=stored_text->str().length()){	string s=stored_text->str();	s.erase(cursor,1);        stored_text->str(s);        tcursor->reset();}}template <typename T,typename F>void                    textinput<T,F>::cleartxt(){stored_text->str("");cursor=0;tcursor->reset();}template <typename T,typename F>void                    textinput<T,F>::charin(char a){string	s=stored_text->str();if (insertmode){	s.insert(cursor,&a,1);}else{	s.replace(cursor,1,&a,1);}stored_text->str(s);cursor++;tcursor->reset();}#endif


I personally use windows input, but this class simply takes a char for input, no matter how you get it. Each renderable has a flag for if it takes text input [the text_mode call]. If it's set to true, it changes how the input handler determines what class gets the input. If this class is the 'top' class to get the input, the flag also tells the input handler to not pass the input to whatever functor is bound to the key in question, but to the text functor instead.

The 'exclusive_member' base class is something to ensure that only one text input is active at any given time. Having two blinking cursors confuses users!
I did something simular in DX for a console (you know those things they have in fps games) and i found that getting the input from windows messages for text only worked the best, because windows would tell me whether it was uppercase, lower case, when the key was being repeated, and this was all pretty much free from the windows message. I believe it was WM_CHAR was what you need to catch in you MessageProc() function. Also would suggesting using a std::string

~guyaton
~guyaton
The rest of the code I use for text input is a bit bugged up right now, but this function ought to help:

char nxt::DirectInput::ScanToChar(DWORD scanCode) const{	//obtain keyboard information	static HKL layout = GetKeyboardLayout(0);	static UCHAR keyboardState[256];	if (GetKeyboardState(keyboardState) == false)		return 0;	//translate keyboard press scan code identifier to a char	UINT vk = MapVirtualKeyEx(scanCode, 1, layout);	USHORT asciiValue;	ToAscii(vk, scanCode, keyboardState, &asciiValue, 0);	return static_cast<char>(asciiValue);}


It converts the DIK_WHATEVER value to the corresponding char. Of course, some keys DIK_F1 for example don't have a corresponding char so you'll have to filter out the results somehow.

EDIT: The functions used there are found in <windows.h>
guyaton is correct! handling WM_CHAR is the best method for text input. I have tried to use directinput before,but it is very hard to get the resonable input speed,sometime the text input too fast, sometime too slow. with WM_CHAR, windows take care everything for you.
Blizzard use WM_CHAR as their input source in starcraft and warcraft 3, so i think there is no problem using that.
While it seems like windows messages are the best way to go, I'm not really sure I can use it.. I suspect I'd have to do something damn ugly in order to get that working.. So I'll try load_bitmap_file's function first, and if I can't get good results, I'll try do get the WM_CHAR messages or whatever..

Thanks everyone!

This topic is closed to new replies.

Advertisement