Difference between revisions of "Game chat box"

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Jump to: navigation, search
(Ability to change font size and limit the number of entries (history))
 
(15 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 +
{{VersionBadge|0.5}} {{VersionBadge|0.6}}
 +
 
=== Introduction ===
 
=== Introduction ===
This snippet implements a chat box that can display text in 3 font sizes as well as limiting the number of entries (history) to a certain number.
+
This snippet implements a chat box that can display text in various fonts and font sizes as well as limiting the number of entries (history) to a certain number.  Please discuss this article within the [http://www.cegui.org.uk/phpBB2/viewtopic.php?p=13333 GameChatBox] thread.
  
 
==== .layout Special Features ====
 
==== .layout Special Features ====
 
The layout definition is deceptively simple.  It defines a FrameWindow containing two widgets; a Listbox in the upper region for chat history and an Editbox at the lower region for text input.
 
The layout definition is deceptively simple.  It defines a FrameWindow containing two widgets; a Listbox in the upper region for chat history and an Editbox at the lower region for text input.
  
What is not apparent is how the bottom portion of the Listbox and the entire Editbox are defined; they specify a proportion of 100% of their parent but with a negative offset.  Essentially the bottom of the Listbox extends to the bottom of the FrameWindow (the parent) and is then moved "up" to make room for the Editbox.  And the Editbox follows the same pattern.  This allows the Editbox to retain its height as the parent FrameWindow is resized.
+
What is not apparent is how the bottom portion of the Listbox and the entire Editbox are defined; they specify a proportion of 100% of their parent but with a negative offset.  Essentially the bottom of the Listbox extends to the bottom of the FrameWindow (the parent) and is then moved "up" to make room for the Editbox.  And the Editbox follows the same pattern.  This allows the Editbox to retain its height as the parent FrameWindow is resized.  The height of the Editbox is controlled by the height of the font used.
  
==== Creating the necessary .font files ====
+
Another feature is that rather than initializing values in code I opted to initialize them via the .layoutThus the history has an initial value of 5 and the default font size is 10.  The font name Combobox defines the logical font name for the dynamic font used within the chat boxThis type of definition, rather than Commonwealth-10, Commonwealth-12, etc allows us to change the font of the chat box without affecting any other widget that may use a font of the same logical name.
A .font file is a simple text file that defines a font within Cegui. They are located within the Samples\datafiles\fonts directoryThis snippet uses the Commonwealth font in sizes 10, 36, and 72To create the missing font definitions for sizes 36 and 72 simply make two copies of the existing "Commonwealth-10.font" fileThe first copy is to be named "Commonwealth-36.font" and the second copy "Commonwealth-72.font".  Edit the "Commonwealth-36.font" file and replace the value "10" by the value "36".  Similarly edit the "Commonwealth-72.font" file and replace the value "10" by the value "72".
+
  
 +
For more details on dynamically creating fonts consult [[DynamicFont]]
  
 
=== Files ===
 
=== Files ===
 
==== ChatBox_demo.h ====
 
==== ChatBox_demo.h ====
<code><cpp/>
+
<source lang="cpp">
 
#ifndef _ChatBox_h_
 
#ifndef _ChatBox_h_
 
#define _ChatBox_h_
 
#define _ChatBox_h_
Line 19: Line 21:
 
#include "CEGuiSample.h"
 
#include "CEGuiSample.h"
 
#include "CEGUI.h"
 
#include "CEGUI.h"
 +
#include "CEGUIXMLAttributes.h"
  
 
class DemoSample : public CEGuiSample
 
class DemoSample : public CEGuiSample
Line 34: Line 37:
 
SchemeManager::getSingleton().loadScheme("TaharezLook.scheme");
 
SchemeManager::getSingleton().loadScheme("TaharezLook.scheme");
 
System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow");
 
System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow");
//FontManager::getSingleton().createFont("Commonwealth-10.font");
+
if(!FontManager::getSingleton().isFontPresent("Commonwealth-10"))
 
+
FontManager::getSingleton().createFont("Commonwealth-10.font");
// Initialize the list of available fonts
+
mCurrentFont = 0;
+
addFont("Commonwealth-10");
+
addFont("Commonwealth-36");
+
addFont("Commonwealth-72");
+
  
 
// Set the GUI Sheet
 
// Set the GUI Sheet
Line 50: Line 48:
 
sheet->addChildWindow(guiLayout);
 
sheet->addChildWindow(guiLayout);
  
// Configure the events for the chat box
+
// Obtain the handles of some widgets
CEGUI::Window* editBox = winMgr.getWindow("/ChatBox/Text");
+
Window* historySize = winMgr.getWindow("/ChatBox/History");
editBox->subscribeEvent(CEGUI::Editbox::EventTextAccepted, CEGUI::Event::Subscriber(&DemoSample::EditTextAccepted, this));  
+
Window* fontName = winMgr.getWindow("/ChatBox/FontName");
editBox->subscribeEvent(CEGUI::Editbox::EventMouseClick, CEGUI::Event::Subscriber(&DemoSample::FontChange, this));  
+
Spinner* fontSize = static_cast<Spinner*>(winMgr.getWindow("/ChatBox/FontSize"));
 +
Window* chatText = winMgr.getWindow("/ChatBox/Text");
 +
 
 +
// Disable widgets until a valid font is registered
 +
fontName->setEnabled(false);
 +
fontSize->setEnabled(false);
 +
chatText->setEnabled(false);
 +
 
 +
// Retrieve the design-specified values
 +
mHistorySize = static_cast<size_t>(PropertyHelper::stringToUint(historySize->getText()));
 +
mDefaultFontSize = fontSize->getText();
 +
mChatFontName = fontName->getText();
 +
setHistorySize(mHistorySize);
 +
fontName->setText("");
  
 
// Configure the history size
 
// Configure the history size
mHistorySize = 100;
+
// Pressing <ENTER> changes the maximal number of entries within the history Listbox
CEGUI::Window* historyBox = winMgr.getWindow("/ChatBox/History");
+
historySize->subscribeEvent(Editbox::EventTextAccepted, Event::Subscriber(&DemoSample::Event_HistorySizeChange, this));
historyBox->subscribeEvent(CEGUI::Editbox::EventTextAccepted, CEGUI::Event::Subscriber(&DemoSample::HistoryTextAccepted, this));  
+
 
historyBox->setText( PropertyHelper::intToString(static_cast<int>(mHistorySize)) );
+
// Configure the text Editbox
 +
// Pressing <ENTER> puts the text into the history Listbox
 +
chatText->subscribeEvent(Editbox::EventTextAccepted, Event::Subscriber(&DemoSample::Event_ChatTextAdded, this));  
 +
 
 +
// Configure the font name Combobox
 +
// Selecting a name changes the font used in the history Listbox and the text Editbox
 +
fontName->subscribeEvent(Combobox::EventTextChanged, Event::Subscriber(&DemoSample::Event_FontChange, this));
 +
 
 +
// Configure the font size Spinner
 +
// Selecting a size changes the font size used in the history Listbox and the text Editbox
 +
fontSize->subscribeEvent(Spinner::EventValueChanged, Event::Subscriber(&DemoSample::Event_FontChange, this));  
 +
fontSize->setTextInputMode(Spinner::Integer);
 +
fontSize->setMinimumValue(4.0f);
 +
fontSize->setMaximumValue(72.0f);
 +
fontSize->setStepSize(1.0f);
 +
fontSize->setCurrentValue(PropertyHelper::stringToFloat(mDefaultFontSize));
 +
 
 +
// Initialize the list of fonts
 +
// The first registered font becomes the active font
 +
registerFont("Commonwealth", "Commonv2c.ttf");
 +
registerFont("DejaVuSans", "DejaVuSans.ttf");
 +
registerFont("Iconified", "Iconiv2.ttf");
 +
registerFont("MissingFile", "MissingFile.ttf");  // What happens if a font is missing?
 +
registerFont("Pixmap Font", "FairChar-30.font"); // And what about a non-Freetype font?
 
}
 
}
 
catch(Exception &e)
 
catch(Exception &e)
Line 75: Line 109:
 
void cleanupSample(void)
 
void cleanupSample(void)
 
{
 
{
 +
}
 +
 +
bool Event_HistorySizeChange(const CEGUI::EventArgs& args)
 +
{
 +
using namespace CEGUI;
 +
 +
WindowManager& winMgr = WindowManager::getSingleton();
 +
CEGUI::Window* historySize = winMgr.getWindow("/ChatBox/History");
 +
int size = PropertyHelper::stringToInt( historySize->getText() );
 +
setHistorySize(size);
 +
return true;
 +
}
 +
 +
bool Event_ChatTextAdded(const CEGUI::EventArgs& args)
 +
{
 +
using namespace CEGUI;
 +
 +
WindowManager& winMgr = WindowManager::getSingleton();
 +
Editbox* chatText = static_cast<Editbox*> (winMgr.getWindow("/ChatBox/Text"));
 +
addChatText(chatText->getText());
 +
 +
// Clear the text in the Editbox
 +
chatText->setText("");
 +
return true;
 +
}
 +
 +
bool Event_FontChange(const CEGUI::EventArgs& args)
 +
{
 +
using namespace CEGUI;
 +
WindowManager& winMgr = WindowManager::getSingleton();
 +
Window* fontName = winMgr.getWindow("/ChatBox/FontName");
 +
String name = fontName->getText();
 +
 +
Spinner* fontSize = static_cast<Spinner*>(winMgr.getWindow("/ChatBox/FontSize"));
 +
String size = PropertyHelper::floatToString(fontSize->getCurrentValue());
 +
 +
Window* chatText = winMgr.getWindow("/ChatBox/Text");
 +
chatText->setText(name + " - " + size);
 +
 +
changeFont(name, size);
 +
return true;
 
}
 
}
  
Line 87: Line 162:
  
 
WindowManager& winMgr = WindowManager::getSingleton();
 
WindowManager& winMgr = WindowManager::getSingleton();
Listbox* listBox = static_cast<Listbox*> (winMgr.getWindow("/ChatBox/List"));
+
Listbox* chatHistory = static_cast<Listbox*> (winMgr.getWindow("/ChatBox/List"));
ListboxItem* item;
+
ListboxItem* chatItem;
while(listBox->getItemCount() > mHistorySize)
+
while(chatHistory->getItemCount() > mHistorySize)
 
{
 
{
item = listBox->getListboxItemFromIndex(0);
+
// There are too many items within the history Listbox, purging them one at a time
listBox->removeItem(item);
+
chatItem = chatHistory->getListboxItemFromIndex(0);
 +
chatHistory->removeItem(chatItem);
 
}
 
}
 
}
 
}
 
}
 
}
  
bool HistoryTextAccepted(const CEGUI::EventArgs& args)
+
void addChatText(const CEGUI::String& pText)
 
{
 
{
 
using namespace CEGUI;
 
using namespace CEGUI;
  
 
WindowManager& winMgr = WindowManager::getSingleton();
 
WindowManager& winMgr = WindowManager::getSingleton();
CEGUI::Window* historyBox = winMgr.getWindow("/ChatBox/History");
+
Listbox* chatHistory = static_cast<Listbox*> (winMgr.getWindow("/ChatBox/List"));
int historySize = PropertyHelper::stringToInt( historyBox->getText() );
+
setHistorySize(historySize);
+
return true;
+
}
+
  
bool EditTextAccepted(const CEGUI::EventArgs& args)
+
// If there's text then add it
{
+
if(pText.size())
using namespace CEGUI;
+
 
+
// Variables for the Listbox and the Editbox
+
WindowManager& winMgr = WindowManager::getSingleton();
+
Editbox* editBox = static_cast<Editbox*> (winMgr.getWindow("/ChatBox/Text"));
+
Listbox* listBox = static_cast<Listbox*> (winMgr.getWindow("/ChatBox/List"));
+
 
+
// Add the Editbox text to the Listbox
+
ListboxTextItem* textItem;
+
if(listBox->getItemCount() == mHistorySize)
+
 
{
 
{
/* We have reached the capacity of the Listbox so re-use the first Listbox item.
+
// Add the Editbox text to the history Listbox
  This code is a little crafty.  By default the ListboxTextItem is created with
+
ListboxTextItem* chatItem;
  the auto-delete flag set to true, which results in its automatic deletion when
+
if(chatHistory->getItemCount() == mHistorySize)
  removed from the Listbox.  So we change that flag to false, extract the item
+
{
  from the Listbox, change its text, put the auto-delete flag back to true, and
+
/* We have reached the capacity of the Listbox so re-use the first Listbox item.
  finally put the item back into the Listbox. */
+
  This code is a little crafty.  By default the ListboxTextItem is created with
textItem = static_cast<ListboxTextItem*>(listBox->getListboxItemFromIndex(0));
+
  the auto-delete flag set to true, which results in its automatic deletion when
textItem->setAutoDeleted(false);
+
  removed from the Listbox.  So we change that flag to false, extract the item
listBox->removeItem(textItem);
+
  from the Listbox, change its text, put the auto-delete flag back to true, and
textItem->setAutoDeleted(true);
+
  finally put the item back into the Listbox. */
textItem->setText(editBox->getText());
+
chatItem = static_cast<ListboxTextItem*>(chatHistory->getListboxItemFromIndex(0));
}
+
chatItem->setAutoDeleted(false);
else
+
chatHistory->removeItem(chatItem);
{
+
chatItem->setAutoDeleted(true);
// Create a new listbox item
+
chatItem->setText(pText);
textItem = new ListboxTextItem(editBox->getText());
+
}
 +
else
 +
{
 +
// Create a new listbox item
 +
chatItem = new ListboxTextItem(pText);
 +
}
 +
chatHistory->addItem(chatItem);
 +
chatHistory->ensureItemIsVisible(chatHistory->getItemCount());
 
}
 
}
listBox->addItem(textItem);
 
listBox->setItemSelectState(textItem, true);
 
 
// Scroll the Listbox entries such that the new text is visible
 
listBox->ensureItemIsVisible(listBox->getItemCount());
 
 
// Clear the text in the Editbox
 
editBox->setText("");
 
 
return true;
 
 
}
 
}
  
void addFont(const CEGUI::String& pFont)
+
void registerFont(const CEGUI::String& pLogicalName, const CEGUI::String& pFileName)
 
{
 
{
 
using namespace CEGUI;
 
using namespace CEGUI;
  
if(!FontManager::getSingleton().isFontPresent(pFont))
+
// Ensure that font names are registered only once
 +
if(mFontList.find(pLogicalName) == mFontList.end())
 
{
 
{
mFontList.push_back(pFont);
+
// Test the font so that only valid fonts are available
FontManager::getSingleton().createFont(pFont + ".font");
+
String testFont = mChatFontName;
 +
if(mFontList.size() != 0)
 +
{
 +
// If the list is empty then attempt to create the font using the "real" font name
 +
// Otherwise use a "test" font name so as not to corrupt the "real" one
 +
testFont += "__test_font__";
 +
}
 +
Font* font = makeFont(testFont, pFileName, mDefaultFontSize);
 +
if(mFontList.size() != 0
 +
&& FontManager::getSingleton().isFontPresent(testFont))
 +
{
 +
// Since this was only a test font we destroy it
 +
FontManager::getSingleton().destroyFont(testFont);
 +
}
 +
if(!font)
 +
{
 +
// This font is invalid
 +
if(FontManager::getSingleton().isFontPresent(testFont))
 +
return;
 +
else
 +
return;
 +
}
 +
 
 +
WindowManager& winMgr = WindowManager::getSingleton();
 +
Combobox* fontName = static_cast<Combobox*>(winMgr.getWindow("/ChatBox/FontName"));
 +
mFontList[pLogicalName] = pFileName;
 +
ListboxTextItem* fontNameItem = new ListboxTextItem(pLogicalName);
 +
fontNameItem->setSelectionBrushImage("TaharezLook", "MultiListSelectionBrush");
 +
fontName->addItem(fontNameItem);
 +
if(fontName->getItemCount() == 1)
 +
{
 +
// Enable widgets now that at least one valid font has been found
 +
Spinner* fontSize = static_cast<Spinner*>(winMgr.getWindow("/ChatBox/FontSize"));
 +
Window* chatText = winMgr.getWindow("/ChatBox/Text");
 +
fontName->setEnabled(true);
 +
fontSize->setEnabled(true);
 +
chatText->setEnabled(true);
 +
 
 +
// The first registered font becomes the active font
 +
fontName->setText(pLogicalName); // This triggers a call to changeFont
 +
fontName->setItemSelectState(fontNameItem, true);
 +
}
 
}
 
}
 
}
 
}
  
bool FontChange(const CEGUI::EventArgs& args)
+
protected:
 +
CEGUI::Font* makeFont(const CEGUI::String& pFontName, const CEGUI::String& pFileName, const CEGUI::String& pSize)
 
{
 
{
 
using namespace CEGUI;
 
using namespace CEGUI;
const MouseEventArgs& mouseEventArgs = static_cast<const MouseEventArgs&>(args);
+
 
if(mouseEventArgs.button == RightButton)
+
Font* font;
 +
try
 
{
 
{
// Cycle through the installed fonts upon a right-click
+
if(FontManager::getSingleton().isFontPresent(pFontName))
mCurrentFont++;
+
{
if(mCurrentFont >= mFontList.size())
+
// The chat font is reused rather than deleted and recreated
mCurrentFont = 0;
+
// every time an attribute changes.  For this reason it is
 +
// important to use a unique logical name for the font.
 +
font = FontManager::getSingleton().getFont(pFontName);
 +
font->setProperty("FileName", pFileName);
 +
font->setProperty("PointSize", pSize);
 +
}
 +
else
 +
{
 +
// This is the first time we make the chat font so we need to create it
 +
XMLAttributes xmlAttributes;
  
changeFont(mFontList[mCurrentFont]);
+
// CEGUIFont.cpp
 +
xmlAttributes.add("Name", pFontName);
 +
xmlAttributes.add("Filename", pFileName);
 +
xmlAttributes.add("ResourceGroup", "");
 +
xmlAttributes.add("AutoScaled", "true");
 +
xmlAttributes.add("NativeHorzRes", "800");
 +
xmlAttributes.add("NativeVertRes", "600");
 +
 
 +
// CEGUIXMLAttributes.cpp
 +
xmlAttributes.add("Size", pSize);
 +
xmlAttributes.add("AntiAlias", "true");
 +
 
 +
font = FontManager::getSingleton().createFont("FreeType", xmlAttributes);
 +
}
 +
font->load();
 +
}
 +
catch(Exception& e)
 +
{
 +
// Display the error message in the chat window
 +
addChatText(e.getMessage());
 +
font = 0;
 
}
 
}
  
return true;
+
return font;
 
}
 
}
  
void changeFont(const CEGUI::String& pFont)
+
void changeFont(const CEGUI::String& pFontLogicalName, const CEGUI::String& pFontSize)
 
{
 
{
 
using namespace CEGUI;
 
using namespace CEGUI;
 
WindowManager& winMgr = WindowManager::getSingleton();
 
WindowManager& winMgr = WindowManager::getSingleton();
 +
 +
if(!FontManager::getSingleton().isFontPresent(mChatFontName))
 +
{
 +
addChatText("You must call registerFont() at least once with a valid font");
 +
return;
 +
}
 +
 +
FontList::iterator itFontList = mFontList.find(pFontLogicalName);
 +
if(itFontList == mFontList.end())
 +
{
 +
addChatText(pFontLogicalName + " has not been registered");
 +
return;
 +
}
  
 
// Measure the height of the selected font
 
// Measure the height of the selected font
Font* currentFont = FontManager::getSingleton().getFont(pFont);
+
Font* currentFont = makeFont(mChatFontName, (*itFontList).second, pFontSize);
 
float fontHeight = currentFont->getFontHeight();
 
float fontHeight = currentFont->getFontHeight();
  
Line 220: Line 365:
  
 
private:
 
private:
// List of available fonts
+
// Type of list for registered fonts
std::vector<CEGUI::String> mFontList;
+
typedef std::map<CEGUI::String, CEGUI::String> FontList;
  
// Currently active font
+
// List of registered fonts
std::vector<CEGUI::String>::size_type mCurrentFont;
+
FontList mFontList;
  
 
// Maximal number of entries to retain within the Listbox
 
// Maximal number of entries to retain within the Listbox
 
size_t mHistorySize;
 
size_t mHistorySize;
 +
 +
// Logical font name dedicated to the chat box
 +
// This allows us to modify the properties of that font and not affect the fonts used elsewhere
 +
CEGUI::String mChatFontName;
 +
 +
// Default font size
 +
CEGUI::String mDefaultFontSize;
 
};
 
};
  
 
#endif // _ChatBox_h_
 
#endif // _ChatBox_h_
</code>
+
</source>
  
 
==== Main.cpp ====
 
==== Main.cpp ====
<code><cpp/>
+
<source lang="cpp">
 
#if defined( __WIN32__ ) || defined( _WIN32 )
 
#if defined( __WIN32__ ) || defined( _WIN32 )
 
#define WIN32_LEAN_AND_MEAN
 
#define WIN32_LEAN_AND_MEAN
Line 253: Line 405:
 
     return i;
 
     return i;
 
}
 
}
</code>
+
</source>
  
 
==== ChatBox.layout ====
 
==== ChatBox.layout ====
<pre>
+
<source lang="xml">
 
<?xml version="1.0" encoding="UTF-8"?>
 
<?xml version="1.0" encoding="UTF-8"?>
  
Line 270: Line 422:
 
             <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 
             <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 
             <Property Name="TitlebarEnabled" Value="True" />
 
             <Property Name="TitlebarEnabled" Value="True" />
             <Property Name="UnifiedAreaRect" Value="{{0.15,0},{0.03,0},{0.7,0},{0.7,0}}" />
+
             <Property Name="UnifiedAreaRect" Value="{{0.01,0},{0.03,0},{0.6,0},{0.69375,0}}" />
 
             <Window Type="TaharezLook/Listbox" Name="/ChatBox/List" >
 
             <Window Type="TaharezLook/Listbox" Name="/ChatBox/List" >
 
                 <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 
                 <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                 <Property Name="UnifiedAreaRect" Value="{{0.01,0},{0.078,0},{0.99,0},{1,-30}}" />
+
                 <Property Name="UnifiedAreaRect" Value="{{0.02,0},{0.078,0},{0.98,0},{1,-30}}" />
 
             </Window>
 
             </Window>
 
             <Window Type="TaharezLook/Editbox" Name="/ChatBox/Text" >
 
             <Window Type="TaharezLook/Editbox" Name="/ChatBox/Text" >
 +
                <Property Name="Text" Value="Error: you did not register any font or none were valid" />
 
                 <Property Name="MaxTextLength" Value="1073741823" />
 
                 <Property Name="MaxTextLength" Value="1073741823" />
 
                 <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 
                 <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                 <Property Name="UnifiedAreaRect" Value="{{0.01,0},{1,-30},{0.99,0},{1,-5}}" />
+
                 <Property Name="UnifiedAreaRect" Value="{{0.02,0},{1,-30},{0.98,0},{1,-5}}" />
 
             </Window>
 
             </Window>
 
         </Window>
 
         </Window>
 
         <Window Type="TaharezLook/Editbox" Name="/ChatBox/History" >
 
         <Window Type="TaharezLook/Editbox" Name="/ChatBox/History" >
 +
            <Property Name="Text" Value="5" />
 
             <Property Name="MaxTextLength" Value="1073741823" />
 
             <Property Name="MaxTextLength" Value="1073741823" />
 
             <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 
             <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
             <Property Name="UnifiedAreaRect" Value="{{0.9,0},{0.03,0},{0.95,0},{0.1,0}}" />
+
             <Property Name="UnifiedAreaRect" Value="{{0.77,0},{0.03,0},{0.81,0},{0.1,0}}" />
 
         </Window>
 
         </Window>
 
         <Window Type="TaharezLook/StaticText" Name="/ChatBox/HistoryLabel" >
 
         <Window Type="TaharezLook/StaticText" Name="/ChatBox/HistoryLabel" >
 
             <Property Name="Text" Value="History size:" />
 
             <Property Name="Text" Value="History size:" />
 +
            <Property Name="HorzFormatting" Value="RightAligned" />
 
             <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 
             <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
             <Property Name="UnifiedAreaRect" Value="{{0.78,0},{0.03,0},{0.9,0},{0.1,0}}" />
+
             <Property Name="UnifiedAreaRect" Value="{{0.65,0},{0.03,0},{0.77,0},{0.1,0}}" />
 +
        </Window>
 +
        <Window Type="TaharezLook/StaticText" Name="/ChatBox/FonSizeLabel" >
 +
            <Property Name="Font" Value="Commonwealth-10" />
 +
            <Property Name="Text" Value="Font size:" />
 +
            <Property Name="HorzFormatting" Value="RightAligned" />
 +
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 +
            <Property Name="UnifiedAreaRect" Value="{{0.65,0},{0.1,0},{0.77,0},{0.17,0}}" />
 +
        </Window>
 +
        <Window Type="TaharezLook/Spinner" Name="/ChatBox/FontSize" >
 +
            <Property Name="Text" Value="10" />
 +
            <Property Name="StepSize" Value="1" />
 +
            <Property Name="CurrentValue" Value="10" />
 +
            <Property Name="MaximumValue" Value="72" />
 +
            <Property Name="MinimumValue" Value="-32768" />
 +
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 +
            <Property Name="UnifiedAreaRect" Value="{{0.77,0},{0.1,0},{0.83,0},{0.17,0}}" />
 +
        </Window>
 +
        <Window Type="TaharezLook/StaticText" Name="/ChatBox/FontNameLabel" >
 +
            <Property Name="Font" Value="Commonwealth-10" />
 +
            <Property Name="Text" Value="Font name:" />
 +
            <Property Name="HorzFormatting" Value="RightAligned" />
 +
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 +
            <Property Name="UnifiedAreaRect" Value="{{0.65,0},{0.17,0},{0.77,0},{0.24,0}}" />
 +
        </Window>
 +
        <Window Type="TaharezLook/Combobox" Name="/ChatBox/FontName" >
 +
            <Property Name="Text" Value="ChatBoxFont" />
 +
            <Property Name="ReadOnly" Value="True" />
 +
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
 +
            <Property Name="UnifiedAreaRect" Value="{{0.77,0},{0.17,0},{0.99,0},{0.45,0}}" />
 +
            <Property Name="MaxEditTextLength" Value="1073741823" />
 
         </Window>
 
         </Window>
 
     </Window>
 
     </Window>
 
</GUILayout>
 
</GUILayout>
</pre>
+
</source>
 
+
 
+
 
+
  
TODO: Create a new version that handles different fonts, both font names (such as Arial, Times New Roman) and font sizes (such as 8, 10, 12).  This will be done by adding a new widget within a looknfeel (such as TaharezLook.looknfeel).  The main feature will be that the Editbox's height will rely on the LineSpacing value of the font.
+
[[Category:Tutorials]]
 +
[[Category:Update requested]]

Latest revision as of 12:11, 3 March 2011

Written for CEGUI 0.5


Works with versions 0.5.x (obsolete)

Written for CEGUI 0.6


Works with versions 0.6.x (obsolete)

Introduction

This snippet implements a chat box that can display text in various fonts and font sizes as well as limiting the number of entries (history) to a certain number. Please discuss this article within the GameChatBox thread.

.layout Special Features

The layout definition is deceptively simple. It defines a FrameWindow containing two widgets; a Listbox in the upper region for chat history and an Editbox at the lower region for text input.

What is not apparent is how the bottom portion of the Listbox and the entire Editbox are defined; they specify a proportion of 100% of their parent but with a negative offset. Essentially the bottom of the Listbox extends to the bottom of the FrameWindow (the parent) and is then moved "up" to make room for the Editbox. And the Editbox follows the same pattern. This allows the Editbox to retain its height as the parent FrameWindow is resized. The height of the Editbox is controlled by the height of the font used.

Another feature is that rather than initializing values in code I opted to initialize them via the .layout. Thus the history has an initial value of 5 and the default font size is 10. The font name Combobox defines the logical font name for the dynamic font used within the chat box. This type of definition, rather than Commonwealth-10, Commonwealth-12, etc allows us to change the font of the chat box without affecting any other widget that may use a font of the same logical name.

For more details on dynamically creating fonts consult DynamicFont

Files

ChatBox_demo.h

#ifndef _ChatBox_h_
#define _ChatBox_h_
 
#include "CEGuiSample.h"
#include "CEGUI.h"
#include "CEGUIXMLAttributes.h"
 
class DemoSample : public CEGuiSample
{
public:
    bool initialiseSample()
	{
		using namespace CEGUI;
		try
		{
			// Retrieve the window manager
			WindowManager& winMgr = WindowManager::getSingleton();
 
			// Load the TaharezLook scheme and set up the default mouse cursor and font
			SchemeManager::getSingleton().loadScheme("TaharezLook.scheme");
			System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow");
			if(!FontManager::getSingleton().isFontPresent("Commonwealth-10"))
				FontManager::getSingleton().createFont("Commonwealth-10.font");
 
			// Set the GUI Sheet
			Window* sheet = winMgr.createWindow("DefaultWindow", "root_wnd");
			System::getSingleton().setGUISheet(sheet);
 
			// Load a layout
			Window* guiLayout = winMgr.loadWindowLayout("ChatBox.layout");
			sheet->addChildWindow(guiLayout);
 
			// Obtain the handles of some widgets
			Window* historySize = winMgr.getWindow("/ChatBox/History");
			Window* fontName = winMgr.getWindow("/ChatBox/FontName");
			Spinner* fontSize = static_cast<Spinner*>(winMgr.getWindow("/ChatBox/FontSize"));
			Window* chatText = winMgr.getWindow("/ChatBox/Text");
 
			// Disable widgets until a valid font is registered
			fontName->setEnabled(false);
			fontSize->setEnabled(false);
			chatText->setEnabled(false);
 
			// Retrieve the design-specified values
			mHistorySize = static_cast<size_t>(PropertyHelper::stringToUint(historySize->getText()));
			mDefaultFontSize = fontSize->getText();
			mChatFontName = fontName->getText();
			setHistorySize(mHistorySize);
			fontName->setText("");
 
			// Configure the history size
			// Pressing <ENTER> changes the maximal number of entries within the history Listbox
			historySize->subscribeEvent(Editbox::EventTextAccepted,	Event::Subscriber(&DemoSample::Event_HistorySizeChange, this)); 
 
			// Configure the text Editbox
			// Pressing <ENTER> puts the text into the history Listbox
			chatText->subscribeEvent(Editbox::EventTextAccepted,	Event::Subscriber(&DemoSample::Event_ChatTextAdded,	this)); 
 
			// Configure the font name Combobox
			// Selecting a name changes the font used in the history Listbox and the text Editbox
			fontName->subscribeEvent(Combobox::EventTextChanged,	Event::Subscriber(&DemoSample::Event_FontChange,	this)); 
 
			// Configure the font size Spinner
			// Selecting a size changes the font size used in the history Listbox and the text Editbox
			fontSize->subscribeEvent(Spinner::EventValueChanged,	Event::Subscriber(&DemoSample::Event_FontChange,	this)); 
			fontSize->setTextInputMode(Spinner::Integer);
			fontSize->setMinimumValue(4.0f);
			fontSize->setMaximumValue(72.0f);
			fontSize->setStepSize(1.0f);
			fontSize->setCurrentValue(PropertyHelper::stringToFloat(mDefaultFontSize));
 
			// Initialize the list of fonts
			// The first registered font becomes the active font
			registerFont("Commonwealth",	"Commonv2c.ttf");
			registerFont("DejaVuSans",		"DejaVuSans.ttf");
			registerFont("Iconified",		"Iconiv2.ttf");
			registerFont("MissingFile",		"MissingFile.ttf");  // What happens if a font is missing?
			registerFont("Pixmap Font",		"FairChar-30.font"); // And what about a non-Freetype font?
		}
		catch(Exception &e)
		{
			#if defined( __WIN32__ ) || defined( _WIN32 )
				MessageBox(NULL, e.getMessage().c_str(), "Error initializing the demo", MB_OK | MB_ICONERROR | MB_TASKMODAL);
			#else
				std::cerr << "Error initializing the demo:" << e.getMessage().c_str() << "\n";
			#endif
		}
 
		return true;
	}
 
	void cleanupSample(void)
	{
	}
 
	bool Event_HistorySizeChange(const CEGUI::EventArgs& args)
	{
		using namespace CEGUI;
 
		WindowManager& winMgr = WindowManager::getSingleton();
		CEGUI::Window* historySize = winMgr.getWindow("/ChatBox/History");
		int size = PropertyHelper::stringToInt( historySize->getText() );
		setHistorySize(size);
		return true;
	}
 
	bool Event_ChatTextAdded(const CEGUI::EventArgs& args)
	{
		using namespace CEGUI;
 
		WindowManager& winMgr = WindowManager::getSingleton();
		Editbox* chatText = static_cast<Editbox*> (winMgr.getWindow("/ChatBox/Text"));
		addChatText(chatText->getText());
 
		// Clear the text in the Editbox
		chatText->setText("");
		return true;
	}
 
	bool Event_FontChange(const CEGUI::EventArgs& args)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		Window* fontName = winMgr.getWindow("/ChatBox/FontName");
		String name = fontName->getText();
 
		Spinner* fontSize = static_cast<Spinner*>(winMgr.getWindow("/ChatBox/FontSize"));
		String size = PropertyHelper::floatToString(fontSize->getCurrentValue());
 
		Window* chatText = winMgr.getWindow("/ChatBox/Text");
		chatText->setText(name + " - " + size);
 
		changeFont(name, size);
		return true;
	}
 
	void setHistorySize(const size_t& pSize)
	{
		using namespace CEGUI;
 
		if(pSize > 0)
		{
			// A better validation would be to enforce a minimal and a maximal size
			mHistorySize = pSize;
 
			WindowManager& winMgr = WindowManager::getSingleton();
			Listbox* chatHistory = static_cast<Listbox*> (winMgr.getWindow("/ChatBox/List"));
			ListboxItem* chatItem;
			while(chatHistory->getItemCount() > mHistorySize)
			{
				// There are too many items within the history Listbox, purging them one at a time
				chatItem = chatHistory->getListboxItemFromIndex(0);
				chatHistory->removeItem(chatItem);
			}
		}
	}
 
	void addChatText(const CEGUI::String& pText)
	{
		using namespace CEGUI;
 
		WindowManager& winMgr = WindowManager::getSingleton();
		Listbox* chatHistory = static_cast<Listbox*> (winMgr.getWindow("/ChatBox/List"));
 
		// If there's text then add it
		if(pText.size())
		{
			// Add the Editbox text to the history Listbox
			ListboxTextItem* chatItem;
			if(chatHistory->getItemCount() == mHistorySize)
			{
				/* We have reached the capacity of the Listbox so re-use the first Listbox item.
				   This code is a little crafty.  By default the ListboxTextItem is created with
				   the auto-delete flag set to true, which results in its automatic deletion when
				   removed from the Listbox.  So we change that flag to false, extract the item
				   from the Listbox, change its text, put the auto-delete flag back to true, and
				   finally put the item back into the Listbox. */
				chatItem = static_cast<ListboxTextItem*>(chatHistory->getListboxItemFromIndex(0));
				chatItem->setAutoDeleted(false);
				chatHistory->removeItem(chatItem);
				chatItem->setAutoDeleted(true);
				chatItem->setText(pText);
			}
			else
			{
				// Create a new listbox item
				chatItem = new ListboxTextItem(pText);
			}
			chatHistory->addItem(chatItem);
			chatHistory->ensureItemIsVisible(chatHistory->getItemCount());
		}
	}
 
	void registerFont(const CEGUI::String& pLogicalName, const CEGUI::String& pFileName)
	{
		using namespace CEGUI;
 
		// Ensure that font names are registered only once
		if(mFontList.find(pLogicalName) == mFontList.end())
		{
			// Test the font so that only valid fonts are available
			String testFont = mChatFontName;
			if(mFontList.size() != 0)
			{
				// If the list is empty then attempt to create the font using the "real" font name
				// Otherwise use a "test" font name so as not to corrupt the "real" one
				testFont += "__test_font__";
			}
			Font* font = makeFont(testFont, pFileName, mDefaultFontSize);
			if(mFontList.size() != 0
				&& FontManager::getSingleton().isFontPresent(testFont))
			{
				// Since this was only a test font we destroy it
				FontManager::getSingleton().destroyFont(testFont);
			}
			if(!font)
			{
				// This font is invalid
				if(FontManager::getSingleton().isFontPresent(testFont))
					return;
				else
				return;
			}
 
			WindowManager& winMgr = WindowManager::getSingleton();
			Combobox* fontName = static_cast<Combobox*>(winMgr.getWindow("/ChatBox/FontName"));
			mFontList[pLogicalName] = pFileName;
			ListboxTextItem* fontNameItem = new ListboxTextItem(pLogicalName);
			fontNameItem->setSelectionBrushImage("TaharezLook", "MultiListSelectionBrush");
			fontName->addItem(fontNameItem);
			if(fontName->getItemCount() == 1)
			{
				// Enable widgets now that at least one valid font has been found
				Spinner* fontSize = static_cast<Spinner*>(winMgr.getWindow("/ChatBox/FontSize"));
				Window* chatText = winMgr.getWindow("/ChatBox/Text");
				fontName->setEnabled(true);
				fontSize->setEnabled(true);
				chatText->setEnabled(true);
 
				// The first registered font becomes the active font
				fontName->setText(pLogicalName); // This triggers a call to changeFont
				fontName->setItemSelectState(fontNameItem, true);
			}
		}
	}
 
protected:
	CEGUI::Font* makeFont(const CEGUI::String& pFontName, const CEGUI::String& pFileName, const CEGUI::String& pSize)
	{
		using namespace CEGUI;
 
		Font* font;
		try
		{
			if(FontManager::getSingleton().isFontPresent(pFontName))
			{
				// The chat font is reused rather than deleted and recreated
				// every time an attribute changes.  For this reason it is
				// important to use a unique logical name for the font.
				font = FontManager::getSingleton().getFont(pFontName);
				font->setProperty("FileName", pFileName);
				font->setProperty("PointSize", pSize);
			}
			else
			{
				// This is the first time we make the chat font so we need to create it
				XMLAttributes xmlAttributes;
 
				// CEGUIFont.cpp
				xmlAttributes.add("Name", pFontName);
				xmlAttributes.add("Filename", pFileName);
				xmlAttributes.add("ResourceGroup", "");
				xmlAttributes.add("AutoScaled", "true");
				xmlAttributes.add("NativeHorzRes", "800");
				xmlAttributes.add("NativeVertRes", "600");
 
				// CEGUIXMLAttributes.cpp
				xmlAttributes.add("Size", pSize);
				xmlAttributes.add("AntiAlias", "true");
 
				font = FontManager::getSingleton().createFont("FreeType", xmlAttributes);
			}
			font->load();
		}
		catch(Exception& e)
		{
			// Display the error message in the chat window
			addChatText(e.getMessage());
			font = 0;
		}
 
		return font;
	}
 
	void changeFont(const CEGUI::String& pFontLogicalName, const CEGUI::String& pFontSize)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
 
		if(!FontManager::getSingleton().isFontPresent(mChatFontName))
		{
			addChatText("You must call registerFont() at least once with a valid font");
			return;
		}
 
		FontList::iterator itFontList = mFontList.find(pFontLogicalName);
		if(itFontList == mFontList.end())
		{
			addChatText(pFontLogicalName + " has not been registered");
			return;
		}
 
		// Measure the height of the selected font
		Font* currentFont = makeFont(mChatFontName, (*itFontList).second, pFontSize);
		float fontHeight = currentFont->getFontHeight();
 
		/* Alter the area of the Editbox.  The original value is {{0.01,0},{1,-30},{0.99,0},{1,-5}}
		   The value we are altering is the "-30" within the second couplet, defining the position of
		   the upper y coordinate of the Editbox.  We base the new value on the position of the lower
		   y coordinate, which is "-5", and the height of the font.  To this we add some space "10" to
		   account for the Editbox's border. */
		Editbox* editBox = static_cast<Editbox*> (winMgr.getWindow("/ChatBox/Text"));
		URect chatTextArea = editBox->getArea();
		chatTextArea.d_min.d_y.d_offset = chatTextArea.d_max.d_y.d_offset
										- fontHeight
										- 10;
		editBox->setArea(chatTextArea);
		editBox->setFont(currentFont);
 
		/* Alther the area of the Listbox.  Here we only need the lower y coordinate.  Since this
		   value is the same as the upper y coordinate of the Editbox we do not need to calculate
		   it.  We also change the font of the Listbox and call upon handleUpdatedItemData() to
		   update the current Listbox items.  Finally we ensure that the last entry is still
		   visible. */
		Listbox* listBox = static_cast<Listbox*> (winMgr.getWindow("/ChatBox/List"));
		URect listTextArea = listBox->getArea();
		listTextArea.d_max.d_y.d_offset = chatTextArea.d_min.d_y.d_offset;
		listBox->setArea(listTextArea);
		listBox->setFont(currentFont);
		listBox->handleUpdatedItemData();
		size_t itemCount = listBox->getItemCount();
		if(itemCount)
		{
			ListboxItem* currentItem = listBox->getListboxItemFromIndex(itemCount - 1);
			listBox->ensureItemIsVisible(currentItem);
		}
	}
 
private:
	// Type of list for registered fonts
	typedef std::map<CEGUI::String, CEGUI::String> FontList;
 
	// List of registered fonts
	FontList mFontList;
 
	// Maximal number of entries to retain within the Listbox
	size_t mHistorySize;
 
	// Logical font name dedicated to the chat box
	// This allows us to modify the properties of that font and not affect the fonts used elsewhere
	CEGUI::String mChatFontName;
 
	// Default font size
	CEGUI::String mDefaultFontSize;
};
 
#endif // _ChatBox_h_

Main.cpp

#if defined( __WIN32__ ) || defined( _WIN32 )
	#define WIN32_LEAN_AND_MEAN
	#define NOMINMAX
	#include "windows.h"
#endif
 
#include "ChatBox_demo.h"
 
#if defined( __WIN32__ ) || defined( _WIN32 )
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)
#else
int main(int argc, char *argv[])
#endif
{
    DemoSample app;
    int i = app.run();
    return i;
}

ChatBox.layout

<?xml version="1.0" encoding="UTF-8"?>
 
<GUILayout >
    <Window Type="DefaultWindow" Name="Root" >
        <Property Name="InheritsAlpha" Value="False" />
        <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
        <Property Name="UnifiedAreaRect" Value="{{0,0},{0,0},{1,0},{1,0}}" />
        <Window Type="TaharezLook/FrameWindow" Name="/ChatBox" >
            <Property Name="Text" Value="Chat" />
            <Property Name="TitlebarFont" Value="Commonwealth-10" />
            <Property Name="InheritsAlpha" Value="False" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="TitlebarEnabled" Value="True" />
            <Property Name="UnifiedAreaRect" Value="{{0.01,0},{0.03,0},{0.6,0},{0.69375,0}}" />
            <Window Type="TaharezLook/Listbox" Name="/ChatBox/List" >
                <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                <Property Name="UnifiedAreaRect" Value="{{0.02,0},{0.078,0},{0.98,0},{1,-30}}" />
            </Window>
            <Window Type="TaharezLook/Editbox" Name="/ChatBox/Text" >
                <Property Name="Text" Value="Error: you did not register any font or none were valid" />
                <Property Name="MaxTextLength" Value="1073741823" />
                <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                <Property Name="UnifiedAreaRect" Value="{{0.02,0},{1,-30},{0.98,0},{1,-5}}" />
            </Window>
        </Window>
        <Window Type="TaharezLook/Editbox" Name="/ChatBox/History" >
            <Property Name="Text" Value="5" />
            <Property Name="MaxTextLength" Value="1073741823" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="UnifiedAreaRect" Value="{{0.77,0},{0.03,0},{0.81,0},{0.1,0}}" />
        </Window>
        <Window Type="TaharezLook/StaticText" Name="/ChatBox/HistoryLabel" >
            <Property Name="Text" Value="History size:" />
            <Property Name="HorzFormatting" Value="RightAligned" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="UnifiedAreaRect" Value="{{0.65,0},{0.03,0},{0.77,0},{0.1,0}}" />
        </Window>
        <Window Type="TaharezLook/StaticText" Name="/ChatBox/FonSizeLabel" >
            <Property Name="Font" Value="Commonwealth-10" />
            <Property Name="Text" Value="Font size:" />
            <Property Name="HorzFormatting" Value="RightAligned" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="UnifiedAreaRect" Value="{{0.65,0},{0.1,0},{0.77,0},{0.17,0}}" />
        </Window>
        <Window Type="TaharezLook/Spinner" Name="/ChatBox/FontSize" >
            <Property Name="Text" Value="10" />
            <Property Name="StepSize" Value="1" />
            <Property Name="CurrentValue" Value="10" />
            <Property Name="MaximumValue" Value="72" />
            <Property Name="MinimumValue" Value="-32768" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="UnifiedAreaRect" Value="{{0.77,0},{0.1,0},{0.83,0},{0.17,0}}" />
        </Window>
        <Window Type="TaharezLook/StaticText" Name="/ChatBox/FontNameLabel" >
            <Property Name="Font" Value="Commonwealth-10" />
            <Property Name="Text" Value="Font name:" />
            <Property Name="HorzFormatting" Value="RightAligned" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="UnifiedAreaRect" Value="{{0.65,0},{0.17,0},{0.77,0},{0.24,0}}" />
        </Window>
        <Window Type="TaharezLook/Combobox" Name="/ChatBox/FontName" >
            <Property Name="Text" Value="ChatBoxFont" />
            <Property Name="ReadOnly" Value="True" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="UnifiedAreaRect" Value="{{0.77,0},{0.17,0},{0.99,0},{0.45,0}}" />
            <Property Name="MaxEditTextLength" Value="1073741823" />
        </Window>
    </Window>
</GUILayout>