FreeTypeGL-0.0.1: src/demo/demo-console.c
/* ============================================================================
* Freetype GL - A C OpenGL Freetype engine
* Platform: Any
* WWW: http://code.google.com/p/freetype-gl/
* ----------------------------------------------------------------------------
* Copyright 2011,2012 Nicolas P. Rougier. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY NICOLAS P. ROUGIER ''AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL NICOLAS P. ROUGIER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of Nicolas P. Rougier.
* ========================================================================= */
#include "freetype-gl.h"
#include "vertex-buffer.h"
#include "markup.h"
#if defined(_WIN32) || defined(_WIN64)
# define wcpncpy wcsncpy
# define wcpcpy wcscpy
#endif
// -------------------------------------------------------------- constants ---
const int __SIGNAL_ACTIVATE__ = 0;
const int __SIGNAL_COMPLETE__ = 1;
const int __SIGNAL_HISTORY_NEXT__ = 2;
const int __SIGNAL_HISTORY_PREV__ = 3;
#define MAX_LINE_LENGTH 511
const int MARKUP_NORMAL = 0;
const int MARKUP_DEFAULT = 0;
const int MARKUP_ERROR = 1;
const int MARKUP_WARNING = 2;
const int MARKUP_OUTPUT = 3;
const int MARKUP_BOLD = 4;
const int MARKUP_ITALIC = 5;
const int MARKUP_BOLD_ITALIC = 6;
const int MARKUP_FAINT = 7;
#define MARKUP_COUNT 8
// ------------------------------------------------------- typedef & struct ---
typedef struct {
float x,y;
vec4 color;
} point_t;
typedef struct {
float x, y, z;
float s, t;
float r, g, b, a;
} vertex_t;
struct _console_t {
vector_t * lines;
wchar_t * prompt;
wchar_t killring[MAX_LINE_LENGTH+1];
wchar_t input[MAX_LINE_LENGTH+1];
size_t cursor;
markup_t markup[MARKUP_COUNT];
vertex_buffer_t * buffer;
vec2 pen;
void (*handlers[4])( struct _console_t *, wchar_t * );
};
typedef struct _console_t console_t;
// ------------------------------------------------------- global variables ---
static console_t * console;
static vertex_buffer_t *lines_buffer;
// ------------------------------------------------------------ console_new ---
console_t *
console_new( void )
{
console_t *self = (console_t *) malloc( sizeof(console_t) );
if( !self )
{
return self;
}
self->lines = vector_new( sizeof(wchar_t *) );
self->prompt = (wchar_t *) wcsdup( L">>> " );
self->cursor = 0;
self->buffer = vertex_buffer_new( "v3f:t2f:c4f" );
self->input[0] = L'\0';
self->killring[0] = L'\0';
self->handlers[__SIGNAL_ACTIVATE__] = 0;
self->handlers[__SIGNAL_COMPLETE__] = 0;
self->handlers[__SIGNAL_HISTORY_NEXT__] = 0;
self->handlers[__SIGNAL_HISTORY_PREV__] = 0;
self->pen.x = self->pen.y = 0;
texture_atlas_t * atlas = texture_atlas_new( 512, 512, 1 );
vec4 white = {{1,1,1,1}};
vec4 black = {{0,0,0,1}};
vec4 none = {{0,0,1,0}};
markup_t normal;
normal.family = "fonts/VeraMono.ttf";
normal.size = 13.0;
normal.bold = 0;
normal.italic = 0;
normal.rise = 0.0;
normal.spacing = 0.0;
normal.gamma = 1.0;
normal.foreground_color = black;
normal.background_color = none;
normal.underline = 0;
normal.underline_color = white;
normal.overline = 0;
normal.overline_color = white;
normal.strikethrough = 0;
normal.strikethrough_color = white;
/*
markup_t normal = {
.family = "fonts/VeraMono.ttf",
.size = 13.0,
.bold = 0,
.italic = 0,
.rise = 0.0,
.spacing = 0.0,
.gamma = 1.0,
.foreground_color = black,
.background_color = none,
.underline = 0,
.underline_color = white,
.overline = 0,
.overline_color = white,
.strikethrough = 0,
.strikethrough_color = white,
.font = 0,
};
*/
normal.font = texture_font_new( atlas, "fonts/VeraMono.ttf", 13 );
markup_t bold = normal;
bold.bold = 1;
bold.font = texture_font_new( atlas, "fonts/VeraMoBd.ttf", 13 );
markup_t italic = normal;
italic.italic = 1;
bold.font = texture_font_new( atlas, "fonts/VeraMoIt.ttf", 13 );
markup_t bold_italic = normal;
bold.bold = 1;
italic.italic = 1;
italic.font = texture_font_new( atlas, "fonts/VeraMoBI.ttf", 13 );
markup_t faint = normal;
faint.foreground_color.r = 0.35;
faint.foreground_color.g = 0.35;
faint.foreground_color.b = 0.35;
markup_t error = normal;
error.foreground_color.r = 1.00;
error.foreground_color.g = 0.00;
error.foreground_color.b = 0.00;
markup_t warning = normal;
warning.foreground_color.r = 1.00;
warning.foreground_color.g = 0.50;
warning.foreground_color.b = 0.50;
markup_t output = normal;
output.foreground_color.r = 0.00;
output.foreground_color.g = 0.00;
output.foreground_color.b = 1.00;
self->markup[MARKUP_NORMAL] = normal;
self->markup[MARKUP_ERROR] = error;
self->markup[MARKUP_WARNING] = warning;
self->markup[MARKUP_OUTPUT] = output;
self->markup[MARKUP_FAINT] = faint;
self->markup[MARKUP_BOLD] = bold;
self->markup[MARKUP_ITALIC] = italic;
self->markup[MARKUP_BOLD_ITALIC] = bold_italic;
return self;
}
// -------------------------------------------------------- console_delete ---
void
console_delete( console_t *self )
{ }
// ----------------------------------------------------- console_add_glyph ---
void
console_add_glyph( console_t *self,
wchar_t current,
wchar_t previous,
markup_t *markup )
{
texture_glyph_t *glyph = texture_font_get_glyph( markup->font, current );
if( previous != L'\0' )
{
self->pen.x += texture_font_glyph_get_kerning( font, glyph, previous );
}
float r = markup->foreground_color.r;
float g = markup->foreground_color.g;
float b = markup->foreground_color.b;
float a = markup->foreground_color.a;
int x0 = self->pen.x + glyph->offset_x;
int y0 = self->pen.y + glyph->offset_y;
int x1 = x0 + glyph->width;
int y1 = y0 - glyph->height;
float s0 = glyph->s0;
float t0 = glyph->t0;
float s1 = glyph->s1;
float t1 = glyph->t1;
GLuint indices[] = {0,1,2, 0,2,3};
vertex_t vertices[] = { { x0,y0,0, s0,t0, r,g,b,a },
{ x0,y1,0, s0,t1, r,g,b,a },
{ x1,y1,0, s1,t1, r,g,b,a },
{ x1,y0,0, s1,t0, r,g,b,a } };
vertex_buffer_push_back( self->buffer, vertices, 4, indices, 6 );
self->pen.x += glyph->advance_x;
self->pen.y += glyph->advance_y;
}
// -------------------------------------------------------- console_render ---
void
console_render( console_t *self )
{
int viewport[4];
glGetIntegerv( GL_VIEWPORT, viewport );
size_t i, index;
self->pen.x = 0;
self->pen.y = viewport[3];
vertex_buffer_clear( console->buffer );
int cursor_x = self->pen.x;
int cursor_y = self->pen.y;
markup_t markup;
// console_t buffer
markup = self->markup[MARKUP_FAINT];
self->pen.y -= markup.font->height;
for( i=0; i<self->lines->size; ++i )
{
wchar_t *text = * (wchar_t **) vector_get( self->lines, i ) ;
if( wcslen(text) > 0 )
{
console_add_glyph( console, text[0], L'\0', &markup );
for( index=1; index < wcslen(text)-1; ++index )
{
console_add_glyph( console, text[index], text[index-1], &markup );
}
}
self->pen.y -= markup.font->height - markup.font->linegap;
self->pen.x = 0;
cursor_x = self->pen.x;
cursor_y = self->pen.y;
}
// Prompt
markup = self->markup[MARKUP_BOLD];
if( wcslen( self->prompt ) > 0 )
{
console_add_glyph( console, self->prompt[0], L'\0', &markup );
for( index=1; index < wcslen(self->prompt); ++index )
{
console_add_glyph( console, self->prompt[index], self->prompt[index-1], &markup );
}
}
cursor_x = (int) self->pen.x;
// Input
markup = self->markup[MARKUP_NORMAL];
if( wcslen(self->input) > 0 )
{
console_add_glyph( console, self->input[0], L'\0', &markup );
if( self->cursor > 0)
{
cursor_x = (int) self->pen.x;
}
for( index=1; index < wcslen(self->input); ++index )
{
console_add_glyph( console, self->input[index], self->input[index-1], &markup );
if( index < self->cursor )
{
cursor_x = (int) self->pen.x;
}
}
}
// Cursor
vertex_buffer_clear( lines_buffer );
float x = cursor_x+1;
float y1 = cursor_y+markup.font->descender;
float y2 = y1 + markup.font->height - markup.font->linegap;
point_t vertices[2] ={ {x, y1, markup.foreground_color},
{x, y2, markup.foreground_color} };
GLuint indices[] = {0,1};
vertex_buffer_push_back( lines_buffer, vertices, 2, indices, 2 );
glColor4f(1,1,1,1);
glEnable( GL_TEXTURE_2D );
vertex_buffer_render( console->buffer, GL_TRIANGLES, "vtc" );
glDisable( GL_TEXTURE_2D );
vertex_buffer_render( lines_buffer, GL_LINES, "vc" );
}
// ------------------------------------------------------- console_connect ---
void
console_connect( console_t *self,
const char *signal,
void (*handler)(console_t *, wchar_t *))
{
if( strcmp( signal,"activate" ) == 0 )
{
self->handlers[__SIGNAL_ACTIVATE__] = handler;
}
else if( strcmp( signal,"complete" ) == 0 )
{
self->handlers[__SIGNAL_COMPLETE__] = handler;
}
else if( strcmp( signal,"history-next" ) == 0 )
{
self->handlers[__SIGNAL_HISTORY_NEXT__] = handler;
}
else if( strcmp( signal,"history-prev" ) == 0 )
{
self->handlers[__SIGNAL_HISTORY_PREV__] = handler;
}
}
// --------------------------------------------------------- console_print ---
void
console_print( console_t *self, wchar_t *text )
{
// Make sure there is at least one line
if( self->lines->size == 0 )
{
wchar_t *line = wcsdup( L"" );
vector_push_back( self->lines, &line );
}
// Make sure last line does not end with '\n'
wchar_t *last_line = *(wchar_t **) vector_get( self->lines, self->lines->size-1 ) ;
if( wcslen( last_line ) != 0 )
{
if( last_line[wcslen( last_line ) - 1] == L'\n' )
{
wchar_t *line = wcsdup( L"" );
vector_push_back( self->lines, &line );
}
}
last_line = *(wchar_t **) vector_get( self->lines, self->lines->size-1 ) ;
wchar_t *start = text;
wchar_t *end = wcschr(start, L'\n');
size_t len = wcslen( last_line );
if( end != NULL)
{
wchar_t *line = (wchar_t *) malloc( (len + end - start + 2)*sizeof( wchar_t ) );
wcpncpy( line, last_line, len );
wcpncpy( line + len, text, end-start+1 );
line[len+end-start+1] = L'\0';
vector_set( self->lines, self->lines->size-1, &line );
free( last_line );
if( (end-start) < (wcslen( text )-1) )
{
console_print(self, end+1 );
}
return;
}
else
{
wchar_t *line = (wchar_t *) malloc( (len + wcslen(text) + 1) * sizeof( wchar_t ) );
wcpncpy( line, last_line, len );
wcpcpy( line + len, text );
vector_set( self->lines, self->lines->size-1, &line );
free( last_line );
return;
}
}
// ------------------------------------------------------- console_process ---
void
console_process( console_t *self,
const char *action,
const unsigned char key )
{
size_t len = wcslen(self->input);
if( strcmp(action, "type") == 0 )
{
if( len < MAX_LINE_LENGTH )
{
memmove( self->input + self->cursor+1,
self->input + self->cursor,
(len - self->cursor+1)*sizeof(wchar_t) );
self->input[self->cursor] = (wchar_t) key;
self->cursor++;
}
else
{
fprintf( stderr, "Input buffer is full\n" );
}
}
else
{
if( strcmp( action, "enter" ) == 0 )
{
if( console->handlers[__SIGNAL_ACTIVATE__] )
{
(*console->handlers[__SIGNAL_ACTIVATE__])(console, console->input);
}
console_print( self, self->prompt );
console_print( self, self->input );
console_print( self, L"\n" );
self->input[0] = L'\0';
self->cursor = 0;
}
else if( strcmp( action, "right" ) == 0 )
{
if( self->cursor < wcslen(self->input) )
{
self->cursor += 1;
}
}
else if( strcmp( action, "left" ) == 0 )
{
if( self->cursor > 0 )
{
self->cursor -= 1;
}
}
else if( strcmp( action, "delete" ) == 0 )
{
memmove( self->input + self->cursor,
self->input + self->cursor+1,
(len - self->cursor)*sizeof(wchar_t) );
}
else if( strcmp( action, "backspace" ) == 0 )
{
if( self->cursor > 0 )
{
memmove( self->input + self->cursor-1,
self->input + self->cursor,
(len - self->cursor+1)*sizeof(wchar_t) );
self->cursor--;
}
}
else if( strcmp( action, "kill" ) == 0 )
{
if( self->cursor < len )
{
wcpcpy(self->killring, self->input + self->cursor );
self->input[self->cursor] = L'\0';
fwprintf(stderr, L"Kill ring: %ls\n", self->killring);
}
}
else if( strcmp( action, "yank" ) == 0 )
{
size_t l = wcslen(self->killring);
if( (len + l) < MAX_LINE_LENGTH )
{
memmove( self->input + self->cursor + l,
self->input + self->cursor,
(len - self->cursor)*sizeof(wchar_t) );
memcpy( self->input + self->cursor,
self->killring, l*sizeof(wchar_t));
self->cursor += l;
}
}
else if( strcmp( action, "home" ) == 0 )
{
self->cursor = 0;
}
else if( strcmp( action, "end" ) == 0 )
{
self->cursor = wcslen( self->input );
}
else if( strcmp( action, "clear" ) == 0 )
{
}
else if( strcmp( action, "history-prev" ) == 0 )
{
if( console->handlers[__SIGNAL_HISTORY_PREV__] )
{
(*console->handlers[__SIGNAL_HISTORY_PREV__])(console, console->input);
}
}
else if( strcmp( action, "history-next" ) == 0 )
{
if( console->handlers[__SIGNAL_HISTORY_NEXT__] )
{
(*console->handlers[__SIGNAL_HISTORY_NEXT__])(console, console->input);
}
}
else if( strcmp( action, "complete" ) == 0 )
{
if( console->handlers[__SIGNAL_COMPLETE__] )
{
(*console->handlers[__SIGNAL_COMPLETE__])(console, console->input);
}
}
}
}
// ----------------------------------------------------------- on_key_press ---
void
on_key_press ( unsigned char key, int x, int y )
{
// fprintf( stderr, "key: %d\n", key);
if (key == 1)
{
console_process( console, "home", 0 );
}
else if (key == 4)
{
console_process( console, "delete", 0 );
}
else if (key == 5)
{
console_process( console, "end", 0 );
}
else if (key == 8)
{
console_process( console, "backspace", 0 );
}
else if (key == 9)
{
console_process( console, "complete", 0 );
}
else if (key == 11)
{
console_process( console, "kill", 0 );
}
else if (key == 12)
{
console_process( console, "clear", 0 );
}
else if (key == 13)
{
console_process( console, "enter", 0 );
}
else if (key == 25)
{
console_process( console, "yank", 0 );
}
else if (key == 27)
{
console_process( console, "escape", 0 );
}
else if (key == 127)
{
console_process( console, "backspace", 0 );
}
else if( key > 31)
{
console_process( console, "type", key );
}
glutPostRedisplay();
}
// --------------------------------------------------- on_special_key_press ---
void
on_special_key_press( int key, int x, int y )
{
switch (key)
{
case GLUT_KEY_UP:
console_process( console, "history-prev", 0 );
break;
case GLUT_KEY_DOWN:
console_process( console, "history-next", 0 );
break;
case GLUT_KEY_LEFT:
console_process( console, "left", 0 );
break;
case GLUT_KEY_RIGHT:
console_process( console, "right", 0 );
break;
case GLUT_KEY_HOME:
console_process( console, "home", 0 );
break;
case GLUT_KEY_END:
console_process( console, "end", 0 );
break;
default:
break;
}
glutPostRedisplay();
}
// ------------------------------------------------------------- on_display ---
void on_display (void) {
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
console_render( console );
glutSwapBuffers();
}
// ------------------------------------------------------------- on_reshape ---
void on_reshape (int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, -1, 1);
glMatrixMode(GL_MODELVIEW);
glutPostRedisplay();
}
// ---------------------------------------------------------------- on_init ---
void on_init( void )
{
}
// ----------------------------------------------------------------------------
void console_activate (console_t *self, wchar_t * input)
{
//console_print( self, L"Activate callback\n" );
fwprintf( stderr, L"Activate callback : %ls\n", input );
}
void console_complete (console_t *self, wchar_t *input)
{
// console_print( self, L"Complete callback\n" );
fwprintf( stderr, L"Complete callback : %ls\n", input );
}
void console_history_prev (console_t *self, wchar_t *input)
{
// console_print( self, L"History prev callback\n" );
fwprintf( stderr, L"History prev callback : %ls\n", input );
}
void console_history_next (console_t *self, wchar_t *input)
{
// console_print( self, L"History next callback\n" );
fwprintf( stderr, L"History next callback : %ls\n", input );
}
int
main( int argc, char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutCreateWindow( argv[0] );
glutReshapeFunc( on_reshape );
glutDisplayFunc( on_display );
glutKeyboardFunc( on_key_press );
glutSpecialFunc( on_special_key_press );
glutReshapeWindow( 600,400 );
lines_buffer = vertex_buffer_new( "v2f:c4f" );
console = console_new();
console_print( console,
L"OpenGL Freetype console\n"
L"Copyright 2011 Nicolas P. Rougier. All rights reserved.\n \n" );
console_connect( console, "activate", console_activate );
console_connect( console, "complete", console_complete );
console_connect( console, "history-prev", console_history_prev );
console_connect( console, "history-next", console_history_next );
glClearColor( 1.00, 1.00, 1.00, 1.00 );
glDisable( GL_DEPTH_TEST );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_TEXTURE_2D );
glEnable( GL_BLEND );
glutMainLoop();
return 0;
}