Hi all.

Heres a simple, but I think useful, little system Ive spent the last few days on.
Paracharlie asked about a TEXT object with scrolling (for an RPG project from memory).

That inspired me to this project. Its become more of a 'multi-player chat' system,
but would easily do what he was asking for, just by ignoring the text-entry stage.


On to the project!

This system lets you attach a 'history' object onto an existing TEXT, so when you
add text to it (using "iChat_add_line()"), it will also be added to the history,
and you can scroll forward or back both by calling "iChat_scroll()", and from the
text-entry mode using the arrow keys, pgup and pgdn.

It comes complete with a interactive demo, so just save the script as "iChat_demo.c"
or something to that effect, and run it...
It should contain enough documentation to keep everyone happpy...

As usual, Im always looking for bug reports, and suggested improvements,
usage questions, or any other help.

Code:
#include <acknex.h>
#include <default.c>
////
//
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//																										//
//		iChat with Demo	-	FunctionLibrary for attaching a scrollable history	//
//									and text-entry system to an existing TEXT object.	//
//																										//
//============================================================================//
//																										//
//		Author	:	EvilSOB																		//
//		Written	: 	18-08-2010																	//
//		Version	:	1.0																			//
//																										//
//		Inspired by a request by Paracharlie for an RPG chat window.				//
//																										//
//																										//
//		Requirements:	Acknex.h																	//
//																										//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//																										//
//		TEXT* iChat_Create(TEXT* iChat_text, int max_hist);							//
//																										//
//----------------------------------------------------------------------------//
//		This function creates a new, empty iChat handler system, and attaches	//
//		it to the supplied "iChat_text" TEXT object.										//
//		"max_hist" specifies how many lines in total can be stored by the iChat	//
//		history before being auto-truncated when it reaches capacity.				//
//		This number also includes the lines on displayed at any time.				//
//		"iChat_text".skill_x now contains a void* to the history TEXT object.	//
//		"iChat_text".skill_y contains a read-only count of the number of used	//
//		contained in the iChat history.														//
//		Returns a pointer to the history TEXT on success, or FALSE on failure.	//
//		Fails when "iChat_text" is invalid, or "max_history" is less than 1.		//
//----------------------------------------------------------------------------//
//		Usage:																						//
//			iChat_Create(dialog_txt, 200);													//
//			//this code creates a new, empty iChat attached to TEXT* dialog_txt	//
//																										//
//																										//
//																										//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//																										//
//		var iChat_Destroy(TEXT* iChat_text);												//
//																										//
//----------------------------------------------------------------------------//
//		Frees all strings in use by the iChat-table attached to "iChat_text".	//
//		It then frees the iChat-table itself, and clears the skill_x & skill_y	//
//		values stored in the "iChat_text" TEXT object.	The "iChat_text" is 		//
//		left otherwise unchanged.																//
//		Returns TRUE on sucess, or FALSE on failure. Fails if "iChat_text"		//
//		is invalid, or does not have a iChat object attached.							//
//----------------------------------------------------------------------------//
//		Usage:																						//
//			iChat_Destroy(dialog_txt);															//
//			//sys_free's all resources used by the iChat list attached to the		//
//			//TEXT object 'dialog_text'.														//
//																										//
//																										//
//																										//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//																										//
//		var iChat_scroll(TEXT* iChat_text, int lines);									//
//																										//
//----------------------------------------------------------------------------//
//		Scrolls the displayed area of iChat-table by "lines" number of lines.	//
//		Negative is how many lines to scroll UP by, positive numbers are DOWN.	//
//		If "lines" is set higher or lower than the actual number of used lines,	//
//		it will merely scroll to the Top or End of the table with no errors.		//
//		If "lines" is supplied as ZERO, it merely forces a redraw of the table.	//
//		Returns TRUE on sucess, or FALSE on failure. Fails if "iChat_text"		//
//		is invalid, or does not have a iChat object attached.							//
//----------------------------------------------------------------------------//
//		Usage:																						//
//			if(key_cuu)	iChat_scroll(dialog_txt, -1);	//scroll up by one line		//
//			if(key_cud)	iChat_scroll(dialog_txt,  1);	//scroll down by one line	//
//																										//
//																										//
//																										//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//																										//
//		var iChat_add_line(TEXT* iChat_text, STRING* text_line);						//
//																										//
//----------------------------------------------------------------------------//
//		Appends the supplied line of text to the END of "iChat_text"'s history.	//
//		If the end of the table is displayed, the table will automatically		//
//		 'scroll up' by one line. Does not interfere with	text-entry-mode.		//
//		Returns the new total line-count on sucess, or FALSE on failure.			//
//		Fails if "iChat_text" is invalid, or does not have a iChat object 		//
//		attached, or "text_line is invalid.													//
//----------------------------------------------------------------------------//
//		Usage:																						//
//			iChat_add_line(dialog_text, "New line added!");								//
//																										//
//																										//
// 	NOTE:	When the history 'hits' capacity, then the oldest 10% of the 		//
//				history lines will be truncated in one step.								//
//				This percentage can be overridden by redefining "iChat_TRUNC"		//
//				from its existing value of '10'% to a different percentage.			//
//				If any truncated lines are on display, the displayed-area will		//
//				auto-scroll to remain at the top of the list...							//
//																										//
//																										//
//																										//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//																										//
//		void iChat_start_input(TEXT* iChat_text, int width, int pos);				//
//																										//
//----------------------------------------------------------------------------//
//		Starts text-entry-mode in "iChat_text" at the line-number("pos").			//
//		If "pos" is -1, then cursor is positioned after the last USED line.		//
//		If "pos" is larger than the "iChat_text".strings, its set at the end.	//
//		"width" is the maximum length of the input string.(see 'inkey' in manual)
//		Input-Keys :: 'ESC' or 'TAB' keys abort text-entry mode.						//
//		Input-Keys :: 'Enter' or 'Return' to accept, blank lines ARE accepted.	//
//		Input-Keys :: 'Up' and 'Down' arrow keys scroll up & down by ONE line.	//
//		Input-Keys :: 'PgUp' and 'PgDn' keys scroll up & down by FIVE lines.		//
//		NOTE: PgUp & PgDn scroll sizes can be adjusted at any time by varying	//
//		the value of the variable "iChat_Page"(type long).								//
//----------------------------------------------------------------------------//
//		Usage:																						//
//			iChat_start_input(dialog_text, 40, -1);										//
//			//this starts an 'inkey()' text-entry at the next available line.		//
//																										//
//																										//
//																										//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//		Global Parameters and Controls														//
//																										//
#define	iChat_TRUNC		10		//can be over-ridden safely							//
long		iChat_Page	=	5;		//can be over-ridden safely							//
int		iChat_Input =	-1;	//text-entry-mode line-number !DO NOT DISTURB!	//
//																										//
////////////////////////////////////////////////////////////////////////////////
//
//
//
////////////////////////////////////////////////////////////////////////////////
//		Start of iChat Functions																//
////////////////////////////////////////////////////////////////////////////////
//
TEXT* iChat_Create(TEXT* iChat_text, int max_hist)
{	// Creates a new iChat handler system in the "iChat_text" TEXT object.
	// skill_x now contains a void* to the history TEXT object.
	// skill_y contains the number of lines contained in the history.(READ-ONLY)
	// Returns a pointer to the history TEXT on success, or FALSE on failure.
	// [ NOTE: history.skill_x=entries count, history.skill_y=scroll position ]
	//
	if((!iChat_text)||(max_hist<=0))		return(0);	//FAILED: Invalid parameters
	iChat_text.skill_x = (void*)txt_create(max_hist, iChat_text.layer);
	iChat_text.skill_y = 0;
	return((TEXT*)iChat_text.skill_x);	//SUCCESS:
}
//
////////////////////////////////////////////////////////////////////////////////
//
var iChat_Destroy(TEXT* iChat_text)
{	// Frees all strings in use by the history-table, and then frees the 
	//	history-table itself. And clears the skill_x & skill_y workspace values
	//	stored in the "iChat_text" TEXT. The "iChat_text" is otherwise unchanged.
	// Returns TRUE on sucess, or FALSE on failure.
	//
	if(!iChat_text)						return(0);	//FAILED: Invalid parameters
	if(!iChat_text.skill_x)				return(0);	//FAILED: Not an iChat object
	if(((long)(handle((void*)iChat_text.skill_x)>>14)&31)!=20)
												return(0);	//FAILED: Not an iChat object
	int i;	TEXT*	iChat_hist = (TEXT*)iChat_text.skill_x;
	//
	for(i=0;i<iChat_hist.strings;i++)	//free history
		if((iChat_hist.pstring)[i])	str_remove((iChat_hist.pstring)[i]);
	txt_remove(iChat_hist);		//remove cleared history object
	for(i=0;i<iChat_text.strings;i++)	//clear chat-text
		if((iChat_text.pstring)[i])	str_cpy((iChat_text.pstring)[i],"");
	iChat_text.skill_x = iChat_text.skill_y = 0;
	return(1);	//SUCCESS:
}
//
////////////////////////////////////////////////////////////////////////////////
//
var iChat_scroll(TEXT* iChat_text, int lines)
{	// Scrolls the displayed section of table by the selected number of lines.
	// Negative is how many lines to scroll UP by, positive numbers are DOWN.
	// If "lines" is set higher or lower than the actual number of used lines,
	//	it will only scroll to the Top or End of the table, with no errors.
	//	If "lines" is supplied as ZERO, it merely forces a re-draw of the table.
	//
	if(!iChat_text)						return(0);	//FAILED: Invalid parameters
	if(!iChat_text.skill_x)				return(0);	//FAILED: Not an iChat object
	if(((long)(handle((void*)iChat_text.skill_x)>>14)&31)!=20)
												return(0);	//FAILED: Not an iChat object
	int i;	TEXT*	iChat_hist = (TEXT*)iChat_text.skill_x;
	//
	var limit = maxv(0, iChat_hist.skill_x - iChat_text.strings + 1);
	iChat_hist.skill_y = clamp(iChat_hist.skill_y + lines, 0, limit);
	if((iChat_Input>-1)&&(iChat_Input<iChat_text.strings-1))	//shuffle input?
	{	STRING* tmp_ptr = (iChat_text.pstring)[iChat_Input];
		(iChat_text.pstring)[iChat_Input]   = (iChat_text.pstring)[iChat_Input+1];
		(iChat_text.pstring)[iChat_Input+1] = tmp_ptr;	iChat_Input++;				 }
	for(i=0; i<(iChat_text.strings-1); i++)
	{	int idx = i + iChat_hist.skill_y;	//calculate offset
		if((idx<0)||(idx>=iChat_hist.skill_x))	break;	//outside history limit
		if((iChat_hist.pstring)[idx].length)
			str_cpy((iChat_text.pstring)[i], (iChat_hist.pstring)[idx]);	}
	return(1);	//SUCCESS:
}
//
////////////////////////////////////////////////////////////////////////////////
//
var iChat_add_line(TEXT* iChat_text, STRING* text_line)
{	// Appends the supplied line of text to the END of "iChat_text".
	// If the last line is visible, the table will automatically 'scroll up'.
	// Returns TRUE on sucess, or FALSE on failure.
	//
	if(!iChat_text)						return(0);	//FAILED: Invalid parameters
	if(!iChat_text.skill_x)				return(0);	//FAILED: Not an iChat object
	if(((long)(handle((void*)iChat_text.skill_x)>>14)&31)!=20)
												return(0);	//FAILED: Not an iChat object
	int i;	TEXT*	iChat_hist = (TEXT*)iChat_text.skill_x;
	//
	if(iChat_hist.skill_x==iChat_hist.strings)	//if table full
	{	int i, del=iChat_hist.strings/maxv(1,iChat_TRUNC);	//phase out oldest ??%
		for(i=0; i<iChat_hist.strings; i++)
		{	if(i<(iChat_hist.strings-del))
				str_cpy((iChat_hist.pstring)[i], (iChat_hist.pstring)[i+del]);
			else	str_cpy((iChat_hist.pstring)[i], "");						  }
		iChat_hist.skill_x -= del;	iChat_hist.skill_y -= del;				  }
	str_cpy((iChat_hist.pstring)[iChat_hist.skill_x], _chr(text_line));
	iChat_text.skill_y = iChat_hist.skill_x++;			
	if(!(iChat_hist.skill_y+(iChat_text.strings-iChat_hist.skill_x)))
			iChat_scroll(iChat_text, 1);	//auto-scroll if at end
	else	iChat_scroll(iChat_text, 0);	//otherwise just re-draw
	return(iChat_text.skill_y);
}
//
////////////////////////////////////////////////////////////////////////////////
//
//
void	iChat_start_input(TEXT* iChat_text, int width, int pos)
{	// Starts a string-entry in "iChat_text" at the chosen line-number("pos").
	// If "pos" is -1, then cursor is positioned after the last USED line.
	// If "pos" is larger than the iChat_text length, it is set at the last line.
	// Input-Keys :: 'ESC' or 'TAB'to abort, and scroll up & down are active.
	// Input-Keys :: 'Enter' or 'Return' to accept, blank lines WILL be accepted.
	//	"width" is the maximum length of the input string.(see 'inkey' in manual)
	//
	if(!iChat_text)						return(0);	//FAILED: Invalid parameters
	if(!iChat_text.skill_x)				return(0);	//FAILED: Not an iChat object
	if(((long)(handle((void*)iChat_text.skill_x)>>14)&31)!=20)
												return(0);	//FAILED: Not an iChat object
	int i;	TEXT*	iChat_hist = (TEXT*)iChat_text.skill_x;
	if(proc_status(iChat_start_input))	return;	//already in use
	//
	STRING* str_width=str_create("");	str_cat_num(str_width,"#%.0f",width);
	if(pos == -1)	pos = minv(iChat_text.strings-1, iChat_text.skill_y);
	while(1)	{	iChat_Input = minv(pos, iChat_text.strings-1);	//track input pos
		STRING* input = (iChat_text.pstring)[iChat_Input];	//shortcut to input
		str_cpy(input, _chr(str_width));		//insert blank line for input
		var end = inkey(input);				//type into supplied text-line
		if((end==9)||(end==27))	break;	//aborted by user. (TAB or ESC pressed)
		else if(end==1)	break;	//input aborted elsewhere(inkey_active==0)
		else if(end==72)	iChat_scroll(iChat_text, -1);	//small up   (key_up)
		else if(end==73)	iChat_scroll(iChat_text, -iChat_Page);	//  (key_pgup)
		else if(end==80)	iChat_scroll(iChat_text,  1);	//small down (key_up)
		else if(end==81)	iChat_scroll(iChat_text,  iChat_Page);	//  (key_pgup)
		else {	pos = iChat_Input+1;		iChat_Input=-1;	//store entered line
					iChat_add_line(iChat_text, input);	  }	//auto-increment line
	}
	str_remove(str_width);	//clear up workspace;
}
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
//
//	Purely DEMO code from here on in.....
//
//
//
COLOR* white = { blue=255; green=255; red=255;	}
COLOR* lgrey = { blue=127; green=127; red=127;	}
FONT*		iChat_font = "Arial#16";
//
BMAP* 	chat_back = "#230x200x24";
PANEL*	iChat_pan  = {	layer=100;	pos_x=300; pos_y=150;	bmap=chat_back;
								event=iChat_txt_input;										}
TEXT*		iChat_txt = {	layer=101;	strings=11;		font=iChat_font;			}
//
//
//
//
void toggle_iChat_pan()			//enable open chat panel
{
	if(is(iChat_pan,SHOW))
	{	reset(iChat_pan,SHOW);	reset(iChat_txt,SHOW);	}	//toggle OFF
	else
	{	set(iChat_pan,SHOW);		set(iChat_txt,SHOW);			//toggle ON
		iChat_txt.pos_x=iChat_pan.pos_x+10;						//and position TEXT
		iChat_txt.pos_y=iChat_pan.pos_y+10;				}		//to PANEL position
}
//
//
//
//
void iChat_txt_input()	{	iChat_start_input(iChat_txt, 30, -1);	}	//pan-event
//
//
//
//
void toggle_chatter()	//enable "synthesised? other people conversing
{	if(proc_status(toggle_chatter))	{	proc_kill(4);	return;	}
	while(1)	
	{	wait(-random(5));
		switch(integer(random(6)))
		{	case 0:
				iChat_add_line(iChat_txt, "[george] Hi guys, wots happening?");
			case 1:
				iChat_add_line(iChat_txt, "[frank] Wheres everybody gone?");
			case 2:
				iChat_add_line(iChat_txt, "[noobie] Ahh! He got meeeee!");
			case 3:
				iChat_add_line(iChat_txt, "[leet] Mwahahahaaa!  Got Ya!!!");
			case 4:
				iChat_add_line(iChat_txt, "[jacob] No-one will find me here");
			case 5:
				iChat_add_line(iChat_txt, "[EvilSOB] I am THE MASTER!");
}	}	}
//
//
//
//
//
void main()
{
	mouse_mode=4;	level_load(NULL);		wait(5);		diag("\n\n\n");
	//
	on_t	= toggle_iChat_pan;		//enable open chat panel
	on_c	= toggle_chatter;			//enable "synthesised? other people conversing
	//
	//
	iChat_Create(iChat_txt, 20);	//make ordinary TEXT object into an iChat object
	iChat_add_line(iChat_txt, "iChat_Panel initialised!");	//optional first line
	//
	//////////////////////////////////////////////////////////////////////////////
	TEXT* iChat_hist = (TEXT*)iChat_txt.skill_x;		 //show history for debug/demo
	iChat_hist.pos_y=190;	set(iChat_hist, SHOW);	 //show history for debug/demo
	//////////////////////////////////////////////////////////////////////////////
	//
	while(1)
	{
		draw_text("Press 'T' to toggle Chat-Panel visibility.", 5, 5, white);
		draw_text("Left-Click on panel to trigger Text-Entry.", 5, 25, white);
		draw_text("ENTIRE history display."	, 5, 150, lgrey);
		draw_text("[ normally hidden ]"		, 5, 170, lgrey);
		//
		//
		wait(1);
	}
	//
}




"There is no fate but what WE make." - CEO Cyberdyne Systems Corp.
A8.30.5 Commercial