/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code. If not, see .
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "ConsoleHistory.h"
#include "../renderer/ResolutionScale.h"
#include "Common_local.h"
#define CON_TEXTSIZE 0x30000
#define NUM_CON_TIMES 4
#define CONSOLE_FIRSTREPEAT 200
#define CONSOLE_REPEAT 100
#define COMMAND_HISTORY 64
struct overlayText_t {
idStr text;
justify_t justify;
int time;
};
// the console will query the cvar and command systems for
// command completion information
class idConsoleLocal : public idConsole {
public:
virtual void Init();
virtual void Shutdown();
virtual bool ProcessEvent( const sysEvent_t * event, bool forceAccept );
virtual bool Active();
virtual void ClearNotifyLines();
virtual void Close();
virtual void Print( const char *text );
virtual void Draw( bool forceFullScreen );
virtual void PrintOverlay( idOverlayHandle &handle, justify_t justify, const char *text, ... );
virtual idDebugGraph * CreateGraph( int numItems );
virtual void DestroyGraph( idDebugGraph * graph );
void Dump( const char *toFile );
void Clear();
private:
void KeyDownEvent( int key );
void Linefeed();
void PageUp();
void PageDown();
void Top();
void Bottom();
void DrawInput();
void DrawNotify();
void DrawSolidConsole( float frac );
void Scroll();
void SetDisplayFraction( float frac );
void UpdateDisplayFraction();
void DrawTextLeftAlign( float x, float &y, const char *text, ... );
void DrawTextRightAlign( float x, float &y, const char *text, ... );
float DrawFPS( float y );
float DrawMemoryUsage( float y );
void DrawOverlayText( float & leftY, float & rightY, float & centerY );
void DrawDebugGraphs();
//============================
// allow these constants to be adjusted for HMD
int LOCALSAFE_LEFT;
int LOCALSAFE_RIGHT;
int LOCALSAFE_TOP;
int LOCALSAFE_BOTTOM;
int LOCALSAFE_WIDTH;
int LOCALSAFE_HEIGHT;
int LINE_WIDTH;
int TOTAL_LINES;
bool keyCatching;
short text[CON_TEXTSIZE];
int current; // line where next message will be printed
int x; // offset in current line for next print
int display; // bottom of console displays this line
int lastKeyEvent; // time of last key event for scroll delay
int nextKeyEvent; // keyboard repeat rate
float displayFrac; // approaches finalFrac at con_speed
float finalFrac; // 0.0 to 1.0 lines of console to display
int fracTime; // time of last displayFrac update
int vislines; // in scanlines
int times[NUM_CON_TIMES]; // cls.realtime time the line was generated
// for transparent notify lines
idVec4 color;
idEditField historyEditLines[COMMAND_HISTORY];
int nextHistoryLine;// the last line in the history buffer, not masked
int historyLine; // the line being displayed from history buffer
// will be <= nextHistoryLine
idEditField consoleField;
idList< overlayText_t > overlayText;
idList< idDebugGraph *> debugGraphs;
static idCVar con_speed;
static idCVar con_notifyTime;
static idCVar con_noPrint;
};
static idConsoleLocal localConsole;
idConsole * console = &localConsole;
idCVar idConsoleLocal::con_speed( "con_speed", "3", CVAR_SYSTEM, "speed at which the console moves up and down" );
idCVar idConsoleLocal::con_notifyTime( "con_notifyTime", "3", CVAR_SYSTEM, "time messages are displayed onscreen when console is pulled up" );
#ifdef DEBUG
idCVar idConsoleLocal::con_noPrint( "con_noPrint", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "print on the console but not onscreen when console is pulled up" );
#else
idCVar idConsoleLocal::con_noPrint( "con_noPrint", "1", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "print on the console but not onscreen when console is pulled up" );
#endif
/*
=============================================================================
Misc stats
=============================================================================
*/
/*
==================
idConsoleLocal::DrawTextLeftAlign
==================
*/
void idConsoleLocal::DrawTextLeftAlign( float x, float &y, const char *text, ... ) {
char string[MAX_STRING_CHARS];
va_list argptr;
va_start( argptr, text );
idStr::vsnPrintf( string, sizeof( string ), text, argptr );
va_end( argptr );
renderSystem->DrawSmallStringExt( x, y + 2, string, colorWhite, true );
y += SMALLCHAR_HEIGHT + 4;
}
/*
==================
idConsoleLocal::DrawTextRightAlign
==================
*/
void idConsoleLocal::DrawTextRightAlign( float x, float &y, const char *text, ... ) {
char string[MAX_STRING_CHARS];
va_list argptr;
va_start( argptr, text );
int i = idStr::vsnPrintf( string, sizeof( string ), text, argptr );
va_end( argptr );
renderSystem->DrawSmallStringExt( x - i * SMALLCHAR_WIDTH, y + 2, string, colorWhite, true );
y += SMALLCHAR_HEIGHT + 4;
}
/*
==================
idConsoleLocal::DrawFPS
==================
*/
#define FPS_FRAMES 6
float idConsoleLocal::DrawFPS( float y ) {
static int previousTimes[FPS_FRAMES];
static int index;
static int previous;
// don't use serverTime, because that will be drifting to
// correct for internet lag changes, timescales, timedemos, etc
int t = Sys_Milliseconds();
int frameTime = t - previous;
previous = t;
previousTimes[index % FPS_FRAMES] = frameTime;
index++;
if ( index > FPS_FRAMES ) {
// average multiple frames together to smooth changes out a bit
int total = 0;
for ( int i = 0 ; i < FPS_FRAMES ; i++ ) {
total += previousTimes[i];
}
if ( !total ) {
total = 1;
}
int fps = 1000000 * FPS_FRAMES / total;
fps = ( fps + 500 ) / 1000;
const char * s = va( "%ifps", fps );
int w = strlen( s ) * BIGCHAR_WIDTH;
renderSystem->DrawBigStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, s, colorWhite, true );
}
y += BIGCHAR_HEIGHT + 4;
// print the resolution scale so we can tell when we are at reduced resolution
idStr resolutionText;
resolutionScale.GetConsoleText( resolutionText );
int w = resolutionText.Length() * BIGCHAR_WIDTH;
renderSystem->DrawBigStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, resolutionText.c_str(), colorWhite, true );
const int gameThreadTotalTime = commonLocal.GetGameThreadTotalTime();
const int gameThreadGameTime = commonLocal.GetGameThreadGameTime();
const int gameThreadRenderTime = commonLocal.GetGameThreadRenderTime();
const int rendererBackEndTime = commonLocal.GetRendererBackEndMicroseconds();
const int rendererShadowsTime = commonLocal.GetRendererShadowsMicroseconds();
const int rendererGPUIdleTime = commonLocal.GetRendererIdleMicroseconds();
const int rendererGPUTime = commonLocal.GetRendererGPUMicroseconds();
const int maxTime = 16;
y += SMALLCHAR_HEIGHT + 4;
idStr timeStr;
timeStr.Format( "%sG+RF: %4d", gameThreadTotalTime > maxTime ? S_COLOR_RED : "", gameThreadTotalTime );
w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH;
renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false );
y += SMALLCHAR_HEIGHT + 4;
timeStr.Format( "%sG: %4d", gameThreadGameTime > maxTime ? S_COLOR_RED : "", gameThreadGameTime );
w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH;
renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false );
y += SMALLCHAR_HEIGHT + 4;
timeStr.Format( "%sRF: %4d", gameThreadRenderTime > maxTime ? S_COLOR_RED : "", gameThreadRenderTime );
w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH;
renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false );
y += SMALLCHAR_HEIGHT + 4;
timeStr.Format( "%sRB: %4.1f", rendererBackEndTime > maxTime * 1000 ? S_COLOR_RED : "", rendererBackEndTime / 1000.0f );
w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH;
renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false );
y += SMALLCHAR_HEIGHT + 4;
timeStr.Format( "%sSV: %4.1f", rendererShadowsTime > maxTime * 1000 ? S_COLOR_RED : "", rendererShadowsTime / 1000.0f );
w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH;
renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false );
y += SMALLCHAR_HEIGHT + 4;
timeStr.Format( "%sIDLE: %4.1f", rendererGPUIdleTime > maxTime * 1000 ? S_COLOR_RED : "", rendererGPUIdleTime / 1000.0f );
w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH;
renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false );
y += SMALLCHAR_HEIGHT + 4;
timeStr.Format( "%sGPU: %4.1f", rendererGPUTime > maxTime * 1000 ? S_COLOR_RED : "", rendererGPUTime / 1000.0f );
w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH;
renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false );
return y + BIGCHAR_HEIGHT + 4;
}
/*
==================
idConsoleLocal::DrawMemoryUsage
==================
*/
float idConsoleLocal::DrawMemoryUsage( float y ) {
return y;
}
//=========================================================================
/*
==============
Con_Clear_f
==============
*/
static void Con_Clear_f( const idCmdArgs &args ) {
localConsole.Clear();
}
/*
==============
Con_Dump_f
==============
*/
static void Con_Dump_f( const idCmdArgs &args ) {
if ( args.Argc() != 2 ) {
common->Printf( "usage: conDump \n" );
return;
}
idStr fileName = args.Argv(1);
fileName.DefaultFileExtension(".txt");
common->Printf( "Dumped console text to %s.\n", fileName.c_str() );
localConsole.Dump( fileName.c_str() );
}
/*
==============
idConsoleLocal::Init
==============
*/
void idConsoleLocal::Init() {
int i;
keyCatching = false;
LOCALSAFE_LEFT = 32;
LOCALSAFE_RIGHT = 608;
LOCALSAFE_TOP = 24;
LOCALSAFE_BOTTOM = 456;
LOCALSAFE_WIDTH = LOCALSAFE_RIGHT - LOCALSAFE_LEFT;
LOCALSAFE_HEIGHT = LOCALSAFE_BOTTOM - LOCALSAFE_TOP;
LINE_WIDTH = ( ( LOCALSAFE_WIDTH / SMALLCHAR_WIDTH ) - 2 );
TOTAL_LINES = (CON_TEXTSIZE / LINE_WIDTH);
lastKeyEvent = -1;
nextKeyEvent = CONSOLE_FIRSTREPEAT;
consoleField.Clear();
consoleField.SetWidthInChars( LINE_WIDTH );
for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) {
historyEditLines[i].Clear();
historyEditLines[i].SetWidthInChars( LINE_WIDTH );
}
cmdSystem->AddCommand( "clear", Con_Clear_f, CMD_FL_SYSTEM, "clears the console" );
cmdSystem->AddCommand( "conDump", Con_Dump_f, CMD_FL_SYSTEM, "dumps the console text to a file" );
}
/*
==============
idConsoleLocal::Shutdown
==============
*/
void idConsoleLocal::Shutdown() {
cmdSystem->RemoveCommand( "clear" );
cmdSystem->RemoveCommand( "conDump" );
debugGraphs.DeleteContents( true );
}
/*
================
idConsoleLocal::Active
================
*/
bool idConsoleLocal::Active() {
return keyCatching;
}
/*
================
idConsoleLocal::ClearNotifyLines
================
*/
void idConsoleLocal::ClearNotifyLines() {
int i;
for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) {
times[i] = 0;
}
}
/*
================
idConsoleLocal::Close
================
*/
void idConsoleLocal::Close() {
keyCatching = false;
SetDisplayFraction( 0 );
displayFrac = 0; // don't scroll to that point, go immediately
ClearNotifyLines();
}
/*
================
idConsoleLocal::Clear
================
*/
void idConsoleLocal::Clear() {
int i;
for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) {
text[i] = (idStr::ColorIndex(C_COLOR_CYAN)<<8) | ' ';
}
Bottom(); // go to end
}
/*
================
idConsoleLocal::Dump
Save the console contents out to a file
================
*/
void idConsoleLocal::Dump( const char *fileName ) {
int l, x, i;
short * line;
idFile *f;
char * buffer = (char *)alloca( LINE_WIDTH + 3 );
f = fileSystem->OpenFileWrite( fileName );
if ( !f ) {
common->Warning( "couldn't open %s", fileName );
return;
}
// skip empty lines
l = current - TOTAL_LINES + 1;
if ( l < 0 ) {
l = 0;
}
for ( ; l <= current ; l++ )
{
line = text + ( l % TOTAL_LINES ) * LINE_WIDTH;
for ( x = 0; x < LINE_WIDTH; x++ )
if ( ( line[x] & 0xff ) > ' ' )
break;
if ( x != LINE_WIDTH )
break;
}
// write the remaining lines
for ( ; l <= current; l++ ) {
line = text + ( l % TOTAL_LINES ) * LINE_WIDTH;
for( i = 0; i < LINE_WIDTH; i++ ) {
buffer[i] = line[i] & 0xff;
}
for ( x = LINE_WIDTH-1; x >= 0; x-- ) {
if ( buffer[x] <= ' ' ) {
buffer[x] = 0;
} else {
break;
}
}
buffer[x+1] = '\r';
buffer[x+2] = '\n';
buffer[x+3] = 0;
f->Write( buffer, strlen( buffer ) );
}
fileSystem->CloseFile( f );
}
/*
================
idConsoleLocal::PageUp
================
*/
void idConsoleLocal::PageUp() {
display -= 2;
if ( current - display >= TOTAL_LINES ) {
display = current - TOTAL_LINES + 1;
}
}
/*
================
idConsoleLocal::PageDown
================
*/
void idConsoleLocal::PageDown() {
display += 2;
if ( display > current ) {
display = current;
}
}
/*
================
idConsoleLocal::Top
================
*/
void idConsoleLocal::Top() {
display = 0;
}
/*
================
idConsoleLocal::Bottom
================
*/
void idConsoleLocal::Bottom() {
display = current;
}
/*
=============================================================================
CONSOLE LINE EDITING
==============================================================================
*/
/*
====================
KeyDownEvent
Handles history and console scrollback
====================
*/
void idConsoleLocal::KeyDownEvent( int key ) {
// Execute F key bindings
if ( key >= K_F1 && key <= K_F12 ) {
idKeyInput::ExecKeyBinding( key );
return;
}
// ctrl-L clears screen
if ( key == K_L && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) {
Clear();
return;
}
// enter finishes the line
if ( key == K_ENTER || key == K_KP_ENTER ) {
common->Printf ( "]%s\n", consoleField.GetBuffer() );
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, consoleField.GetBuffer() ); // valid command
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "\n" );
// copy line to history buffer
if ( consoleField.GetBuffer()[ 0 ] != '\n' && consoleField.GetBuffer()[ 0 ] != '\0' ) {
consoleHistory.AddToHistory( consoleField.GetBuffer() );
}
consoleField.Clear();
consoleField.SetWidthInChars( LINE_WIDTH );
const bool captureToImage = false;
common->UpdateScreen( captureToImage );// force an update, because the command
// may take some time
return;
}
// command completion
if ( key == K_TAB ) {
consoleField.AutoComplete();
return;
}
// command history (ctrl-p ctrl-n for unix style)
if ( ( key == K_UPARROW ) ||
( key == K_P && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) ) {
idStr hist = consoleHistory.RetrieveFromHistory( true );
if ( !hist.IsEmpty() ) {
consoleField.SetBuffer( hist );
}
return;
}
if ( ( key == K_DOWNARROW ) ||
( key == K_N && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) ) {
idStr hist = consoleHistory.RetrieveFromHistory( false );
if ( !hist.IsEmpty() ) {
consoleField.SetBuffer( hist );
}
return;
}
// console scrolling
if ( key == K_PGUP ) {
PageUp();
lastKeyEvent = eventLoop->Milliseconds();
nextKeyEvent = CONSOLE_FIRSTREPEAT;
return;
}
if ( key == K_PGDN ) {
PageDown();
lastKeyEvent = eventLoop->Milliseconds();
nextKeyEvent = CONSOLE_FIRSTREPEAT;
return;
}
if ( key == K_MWHEELUP ) {
PageUp();
return;
}
if ( key == K_MWHEELDOWN ) {
PageDown();
return;
}
// ctrl-home = top of console
if ( key == K_HOME && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) {
Top();
return;
}
// ctrl-end = bottom of console
if ( key == K_END && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) {
Bottom();
return;
}
// pass to the normal editline routine
consoleField.KeyDownEvent( key );
}
/*
==============
Scroll
deals with scrolling text because we don't have key repeat
==============
*/
void idConsoleLocal::Scroll( ) {
if (lastKeyEvent == -1 || (lastKeyEvent+200) > eventLoop->Milliseconds()) {
return;
}
// console scrolling
if ( idKeyInput::IsDown( K_PGUP ) ) {
PageUp();
nextKeyEvent = CONSOLE_REPEAT;
return;
}
if ( idKeyInput::IsDown( K_PGDN ) ) {
PageDown();
nextKeyEvent = CONSOLE_REPEAT;
return;
}
}
/*
==============
SetDisplayFraction
Causes the console to start opening the desired amount.
==============
*/
void idConsoleLocal::SetDisplayFraction( float frac ) {
finalFrac = frac;
fracTime = Sys_Milliseconds();
}
/*
==============
UpdateDisplayFraction
Scrolls the console up or down based on conspeed
==============
*/
void idConsoleLocal::UpdateDisplayFraction() {
if ( con_speed.GetFloat() <= 0.1f ) {
fracTime = Sys_Milliseconds();
displayFrac = finalFrac;
return;
}
// scroll towards the destination height
if ( finalFrac < displayFrac ) {
displayFrac -= con_speed.GetFloat() * ( Sys_Milliseconds() - fracTime ) * 0.001f;
if ( finalFrac > displayFrac ) {
displayFrac = finalFrac;
}
fracTime = Sys_Milliseconds();
} else if ( finalFrac > displayFrac ) {
displayFrac += con_speed.GetFloat() * ( Sys_Milliseconds() - fracTime ) * 0.001f;
if ( finalFrac < displayFrac ) {
displayFrac = finalFrac;
}
fracTime = Sys_Milliseconds();
}
}
/*
==============
ProcessEvent
==============
*/
bool idConsoleLocal::ProcessEvent( const sysEvent_t *event, bool forceAccept ) {
const bool consoleKey = event->evType == SE_KEY && event->evValue == K_GRAVE && com_allowConsole.GetBool();
// we always catch the console key event
if ( !forceAccept && consoleKey ) {
// ignore up events
if ( event->evValue2 == 0 ) {
return true;
}
consoleField.ClearAutoComplete();
// a down event will toggle the destination lines
if ( keyCatching ) {
Close();
Sys_GrabMouseCursor( true );
} else {
consoleField.Clear();
keyCatching = true;
if ( idKeyInput::IsDown( K_LSHIFT ) || idKeyInput::IsDown( K_RSHIFT ) ) {
// if the shift key is down, don't open the console as much
SetDisplayFraction( 0.2f );
} else {
SetDisplayFraction( 0.5f );
}
}
return true;
}
// if we aren't key catching, dump all the other events
if ( !forceAccept && !keyCatching ) {
return false;
}
// handle key and character events
if ( event->evType == SE_CHAR ) {
// never send the console key as a character
if ( event->evValue != '`' && event->evValue != '~' ) {
consoleField.CharEvent( event->evValue );
}
return true;
}
if ( event->evType == SE_KEY ) {
// ignore up key events
if ( event->evValue2 == 0 ) {
return true;
}
KeyDownEvent( event->evValue );
return true;
}
// we don't handle things like mouse, joystick, and network packets
return false;
}
/*
==============================================================================
PRINTING
==============================================================================
*/
/*
===============
Linefeed
===============
*/
void idConsoleLocal::Linefeed() {
int i;
// mark time for transparent overlay
if ( current >= 0 ) {
times[current % NUM_CON_TIMES] = Sys_Milliseconds();
}
x = 0;
if ( display == current ) {
display++;
}
current++;
for ( i = 0; i < LINE_WIDTH; i++ ) {
int offset = ( (unsigned int)current % TOTAL_LINES ) * LINE_WIDTH + i;
text[offset] = (idStr::ColorIndex(C_COLOR_CYAN)<<8) | ' ';
}
}
/*
================
Print
Handles cursor positioning, line wrapping, etc
================
*/
void idConsoleLocal::Print( const char *txt ) {
int y;
int c, l;
int color;
if ( TOTAL_LINES == 0 ) {
// not yet initialized
return;
}
color = idStr::ColorIndex( C_COLOR_CYAN );
while ( (c = *(const unsigned char*)txt) != 0 ) {
if ( idStr::IsColor( txt ) ) {
if ( *(txt+1) == C_COLOR_DEFAULT ) {
color = idStr::ColorIndex( C_COLOR_CYAN );
} else {
color = idStr::ColorIndex( *(txt+1) );
}
txt += 2;
continue;
}
y = current % TOTAL_LINES;
// if we are about to print a new word, check to see
// if we should wrap to the new line
if ( c > ' ' && ( x == 0 || text[y*LINE_WIDTH+x-1] <= ' ' ) ) {
// count word length
for (l=0 ; l< LINE_WIDTH ; l++) {
if ( txt[l] <= ' ') {
break;
}
}
// word wrap
if (l != LINE_WIDTH && (x + l >= LINE_WIDTH) ) {
Linefeed();
}
}
txt++;
switch( c ) {
case '\n':
Linefeed ();
break;
case '\t':
do {
text[y*LINE_WIDTH+x] = (color << 8) | ' ';
x++;
if ( x >= LINE_WIDTH ) {
Linefeed();
x = 0;
}
} while ( x & 3 );
break;
case '\r':
x = 0;
break;
default: // display character and advance
text[y*LINE_WIDTH+x] = (color << 8) | c;
x++;
if ( x >= LINE_WIDTH ) {
Linefeed();
x = 0;
}
break;
}
}
// mark time for transparent overlay
if ( current >= 0 ) {
times[current % NUM_CON_TIMES] = Sys_Milliseconds();
}
}
/*
==============================================================================
DRAWING
==============================================================================
*/
/*
================
DrawInput
Draw the editline after a ] prompt
================
*/
void idConsoleLocal::DrawInput() {
int y, autoCompleteLength;
y = vislines - ( SMALLCHAR_HEIGHT * 2 );
if ( consoleField.GetAutoCompleteLength() != 0 ) {
autoCompleteLength = strlen( consoleField.GetBuffer() ) - consoleField.GetAutoCompleteLength();
if ( autoCompleteLength > 0 ) {
renderSystem->DrawFilled( idVec4( 0.8f, 0.2f, 0.2f, 0.45f ),
LOCALSAFE_LEFT + 2 * SMALLCHAR_WIDTH + consoleField.GetAutoCompleteLength() * SMALLCHAR_WIDTH,
y + 2, autoCompleteLength * SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT - 2 );
}
}
renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) );
renderSystem->DrawSmallChar( LOCALSAFE_LEFT + 1 * SMALLCHAR_WIDTH, y, ']' );
consoleField.Draw( LOCALSAFE_LEFT + 2 * SMALLCHAR_WIDTH, y, SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, true );
}
/*
================
DrawNotify
Draws the last few lines of output transparently over the game top
================
*/
void idConsoleLocal::DrawNotify() {
int x, v;
short *text_p;
int i;
int time;
int currentColor;
if ( con_noPrint.GetBool() ) {
return;
}
currentColor = idStr::ColorIndex( C_COLOR_WHITE );
renderSystem->SetColor( idStr::ColorForIndex( currentColor ) );
v = 0;
for ( i = current-NUM_CON_TIMES+1; i <= current; i++ ) {
if ( i < 0 ) {
continue;
}
time = times[i % NUM_CON_TIMES];
if ( time == 0 ) {
continue;
}
time = Sys_Milliseconds() - time;
if ( time > con_notifyTime.GetFloat() * 1000 ) {
continue;
}
text_p = text + (i % TOTAL_LINES)*LINE_WIDTH;
for ( x = 0; x < LINE_WIDTH; x++ ) {
if ( ( text_p[x] & 0xff ) == ' ' ) {
continue;
}
if ( idStr::ColorIndex(text_p[x]>>8) != currentColor ) {
currentColor = idStr::ColorIndex(text_p[x]>>8);
renderSystem->SetColor( idStr::ColorForIndex( currentColor ) );
}
renderSystem->DrawSmallChar( LOCALSAFE_LEFT + (x+1)*SMALLCHAR_WIDTH, v, text_p[x] & 0xff );
}
v += SMALLCHAR_HEIGHT;
}
renderSystem->SetColor( colorCyan );
}
/*
================
DrawSolidConsole
Draws the console with the solid background
================
*/
void idConsoleLocal::DrawSolidConsole( float frac ) {
int i, x;
float y;
int rows;
short *text_p;
int row;
int lines;
int currentColor;
lines = idMath::Ftoi( SCREEN_HEIGHT * frac );
if ( lines <= 0 ) {
return;
}
if ( lines > SCREEN_HEIGHT ) {
lines = SCREEN_HEIGHT;
}
// draw the background
y = frac * SCREEN_HEIGHT - 2;
if ( y < 1.0f ) {
y = 0.0f;
} else {
renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.75f ), 0, 0, SCREEN_WIDTH, y );
}
renderSystem->DrawFilled( colorCyan, 0, y, SCREEN_WIDTH, 2 );
// draw the version number
renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) );
idStr version = va( "%s.%i.%i", ENGINE_VERSION, BUILD_NUMBER, BUILD_NUMBER_MINOR );
i = version.Length();
for ( x = 0; x < i; x++ ) {
renderSystem->DrawSmallChar( LOCALSAFE_WIDTH - ( i - x ) * SMALLCHAR_WIDTH,
(lines-(SMALLCHAR_HEIGHT+SMALLCHAR_HEIGHT/4)), version[x] );
}
// draw the text
vislines = lines;
rows = (lines-SMALLCHAR_WIDTH)/SMALLCHAR_WIDTH; // rows of text to draw
y = lines - (SMALLCHAR_HEIGHT*3);
// draw from the bottom up
if ( display != current ) {
// draw arrows to show the buffer is backscrolled
renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) );
for ( x = 0; x < LINE_WIDTH; x += 4 ) {
renderSystem->DrawSmallChar( LOCALSAFE_LEFT + (x+1)*SMALLCHAR_WIDTH, idMath::Ftoi( y ), '^' );
}
y -= SMALLCHAR_HEIGHT;
rows--;
}
row = display;
if ( x == 0 ) {
row--;
}
currentColor = idStr::ColorIndex( C_COLOR_WHITE );
renderSystem->SetColor( idStr::ColorForIndex( currentColor ) );
for ( i = 0; i < rows; i++, y -= SMALLCHAR_HEIGHT, row-- ) {
if ( row < 0 ) {
break;
}
if ( current - row >= TOTAL_LINES ) {
// past scrollback wrap point
continue;
}
text_p = text + (row % TOTAL_LINES)*LINE_WIDTH;
for ( x = 0; x < LINE_WIDTH; x++ ) {
if ( ( text_p[x] & 0xff ) == ' ' ) {
continue;
}
if ( idStr::ColorIndex(text_p[x]>>8) != currentColor ) {
currentColor = idStr::ColorIndex(text_p[x]>>8);
renderSystem->SetColor( idStr::ColorForIndex( currentColor ) );
}
renderSystem->DrawSmallChar( LOCALSAFE_LEFT + (x+1)*SMALLCHAR_WIDTH, idMath::Ftoi( y ), text_p[x] & 0xff );
}
}
// draw the input prompt, user text, and cursor if desired
DrawInput();
renderSystem->SetColor( colorCyan );
}
/*
==============
Draw
ForceFullScreen is used by the editor
==============
*/
void idConsoleLocal::Draw( bool forceFullScreen ) {
if ( forceFullScreen ) {
// if we are forced full screen because of a disconnect,
// we want the console closed when we go back to a session state
Close();
// we are however catching keyboard input
keyCatching = true;
}
Scroll();
UpdateDisplayFraction();
if ( forceFullScreen ) {
DrawSolidConsole( 1.0f );
} else if ( displayFrac ) {
DrawSolidConsole( displayFrac );
} else {
// only draw the notify lines if the developer cvar is set,
// or we are a debug build
if ( !con_noPrint.GetBool() ) {
DrawNotify();
}
}
float lefty = LOCALSAFE_TOP;
float righty = LOCALSAFE_TOP;
float centery = LOCALSAFE_TOP;
if ( com_showFPS.GetBool() ) {
righty = DrawFPS( righty );
}
if ( com_showMemoryUsage.GetBool() ) {
righty = DrawMemoryUsage( righty );
}
DrawOverlayText( lefty, righty, centery );
DrawDebugGraphs();
}
/*
========================
idConsoleLocal::PrintOverlay
========================
*/
void idConsoleLocal::PrintOverlay( idOverlayHandle &handle, justify_t justify, const char *text, ... ) {
if ( handle.index >= 0 && handle.index < overlayText.Num() ) {
if ( overlayText[handle.index].time == handle.time ) {
return;
}
}
char string[MAX_PRINT_MSG];
va_list argptr;
va_start( argptr, text );
idStr::vsnPrintf( string, sizeof( string ), text, argptr );
va_end( argptr );
overlayText_t &overlay = overlayText.Alloc();
overlay.text = string;
overlay.justify = justify;
overlay.time = Sys_Milliseconds();
handle.index = overlayText.Num() - 1;
handle.time = overlay.time;
}
/*
========================
idConsoleLocal::DrawOverlayText
========================
*/
void idConsoleLocal::DrawOverlayText( float & leftY, float & rightY, float & centerY ) {
for ( int i = 0; i < overlayText.Num(); i++ ) {
const idStr & text = overlayText[i].text;
int maxWidth = 0;
int numLines = 0;
for ( int j = 0; j < text.Length(); j++ ) {
int width = 1;
for (; j < text.Length() && text[j] != '\n'; j++ ) {
width++;
}
numLines++;
if ( width > maxWidth ) {
maxWidth = width;
}
}
idVec4 bgColor( 0.0f, 0.0f, 0.0f, 0.75f );
const float width = maxWidth * SMALLCHAR_WIDTH;
const float height = numLines * ( SMALLCHAR_HEIGHT + 4 );
const float bgAdjust = - 0.5f * SMALLCHAR_WIDTH;
if ( overlayText[i].justify == JUSTIFY_LEFT ) {
renderSystem->DrawFilled( bgColor, LOCALSAFE_LEFT + bgAdjust, leftY, width, height );
} else if ( overlayText[i].justify == JUSTIFY_RIGHT ) {
renderSystem->DrawFilled( bgColor, LOCALSAFE_RIGHT - width + bgAdjust, rightY, width, height );
} else if ( overlayText[i].justify == JUSTIFY_CENTER_LEFT || overlayText[i].justify == JUSTIFY_CENTER_RIGHT ) {
renderSystem->DrawFilled( bgColor, LOCALSAFE_LEFT + ( LOCALSAFE_WIDTH - width + bgAdjust ) * 0.5f, centerY, width, height );
} else {
assert( false );
}
idStr singleLine;
for ( int j = 0; j < text.Length(); j += singleLine.Length() + 1 ) {
singleLine = "";
for ( int k = j; k < text.Length() && text[k] != '\n'; k++ ) {
singleLine.Append( text[k] );
}
if ( overlayText[i].justify == JUSTIFY_LEFT ) {
DrawTextLeftAlign( LOCALSAFE_LEFT, leftY, "%s", singleLine.c_str() );
} else if ( overlayText[i].justify == JUSTIFY_RIGHT ) {
DrawTextRightAlign( LOCALSAFE_RIGHT, rightY, "%s", singleLine.c_str() );
} else if ( overlayText[i].justify == JUSTIFY_CENTER_LEFT ) {
DrawTextLeftAlign( LOCALSAFE_LEFT + ( LOCALSAFE_WIDTH - width ) * 0.5f, centerY, "%s", singleLine.c_str() );
} else if ( overlayText[i].justify == JUSTIFY_CENTER_RIGHT ) {
DrawTextRightAlign( LOCALSAFE_LEFT + ( LOCALSAFE_WIDTH + width ) * 0.5f, centerY, "%s", singleLine.c_str() );
} else {
assert( false );
}
}
}
overlayText.SetNum( 0 );
}
/*
========================
idConsoleLocal::CreateGraph
========================
*/
idDebugGraph * idConsoleLocal::CreateGraph( int numItems ) {
idDebugGraph * graph = new (TAG_SYSTEM) idDebugGraph( numItems );
debugGraphs.Append( graph );
return graph;
}
/*
========================
idConsoleLocal::DestroyGraph
========================
*/
void idConsoleLocal::DestroyGraph( idDebugGraph * graph ) {
debugGraphs.Remove( graph );
delete graph;
}
/*
========================
idConsoleLocal::DrawDebugGraphs
========================
*/
void idConsoleLocal::DrawDebugGraphs() {
for ( int i = 0; i < debugGraphs.Num(); i++ ) {
debugGraphs[i]->Render( renderSystem );
}
}