Difference between revisions of "The Main Menu"
m |
|||
(36 intermediate revisions by 6 users not shown) | |||
Line 1: | Line 1: | ||
+ | {{VersionBadge|0.5}} {{VersionBadge|0.6}} | ||
+ | |||
So, you've probably read all the Beginner Guide to X tutorials (if not then go back and read them !), your sitting there still feeling lost, and you can't find exactly how to bring all the stuff you've read together to make a simple GUI. I too found it specially hard to wrap everything together when starting out and hopefully this article should help address this problem for others, I'm still a beginner so there might be a few errors, but for the most part the code has been tested. This tutorial will be rather Ogre oriented since thats what I use, I'll try to keep things a little generic though. | So, you've probably read all the Beginner Guide to X tutorials (if not then go back and read them !), your sitting there still feeling lost, and you can't find exactly how to bring all the stuff you've read together to make a simple GUI. I too found it specially hard to wrap everything together when starting out and hopefully this article should help address this problem for others, I'm still a beginner so there might be a few errors, but for the most part the code has been tested. This tutorial will be rather Ogre oriented since thats what I use, I'll try to keep things a little generic though. | ||
+ | |||
+ | This tutorial was last updated on: 7th January 2008 by CrazyEddie | ||
== Getting started == | == Getting started == | ||
− | For tutorial purposes, we will quickly run over the Initialisation part (as this has been addressed in other tutorials more thoroughly and is usually engine specific). I will not litter the tutorial with unlrelated code like framelisteners, We will be making a main menu which is basicly just a collection of buttons and a StaticImage for background, but it is cruical that you have read the following tutorials and understood them, specially the file types. | + | For tutorial purposes, we will quickly run over the Initialisation part (as this has been addressed in other tutorials more thoroughly and is usually engine specific). I will not litter the tutorial with unlrelated code like framelisteners, includes and other stuff. We will be making a main menu which is basicly just a collection of buttons and a StaticImage for background, but it is cruical that you have read the following tutorials and understood them, specially the file types. |
[[http://www.cegui.org.uk/wiki/index.php/Tutorials#CrazyEddie.27s_Beginner_Guides CrazyEddie's Beginner Guides ]] | [[http://www.cegui.org.uk/wiki/index.php/Tutorials#CrazyEddie.27s_Beginner_Guides CrazyEddie's Beginner Guides ]] | ||
[[http://www.ogre3d.org/wiki/index.php/Basic_Tutorial_6 Ogre's Basic Tutorial 6]] | [[http://www.ogre3d.org/wiki/index.php/Basic_Tutorial_6 Ogre's Basic Tutorial 6]] | ||
− | == A plan of action == | + | == Creating the Menu: A plan of action == |
There are 3 ways to go about this, one way is to hardcode the menu (which is a bad idea unless you don't want your menu to be customisable), the 2nd way is to use although uses an xml based approach by writing layout files, imageset files..etc. The 3rd way is to use the falagard system which is subset of the 2nd approach, but expands even more on the functionality by offering the .looknfeel files which allow you to skin any existing scheme or even create your own. | There are 3 ways to go about this, one way is to hardcode the menu (which is a bad idea unless you don't want your menu to be customisable), the 2nd way is to use although uses an xml based approach by writing layout files, imageset files..etc. The 3rd way is to use the falagard system which is subset of the 2nd approach, but expands even more on the functionality by offering the .looknfeel files which allow you to skin any existing scheme or even create your own. | ||
Line 12: | Line 16: | ||
Ok, before we get started we need to have a few things initialised first, other then Ogre root, scene manager, framelistener....etc. If you've read [[http://www.ogre3d.org/wiki/index.php/Basic_Tutorial_6 basic tutorial 6]] you'll see that we need to have the following defiendone of the looks loaded as well as a font. I'll be using TaharezLook and tahoma-12 for this tutorial, but I'll load the font at a later stage in the tutorial. I'll only use CEGUI:: in this area, but I'll drop using it since it'll be obvious where to use it after a while. Just assume I've used using namespace CEGUI; | Ok, before we get started we need to have a few things initialised first, other then Ogre root, scene manager, framelistener....etc. If you've read [[http://www.ogre3d.org/wiki/index.php/Basic_Tutorial_6 basic tutorial 6]] you'll see that we need to have the following defiendone of the looks loaded as well as a font. I'll be using TaharezLook and tahoma-12 for this tutorial, but I'll load the font at a later stage in the tutorial. I'll only use CEGUI:: in this area, but I'll drop using it since it'll be obvious where to use it after a while. Just assume I've used using namespace CEGUI; | ||
− | + | <source lang="cpp"> | |
− | + | // Initialisation Area | |
− | + | CEGUI::OgreCEGUIRenderer* mGUIRenderer = new CEGUI::OgreCEGUIRenderer(Root::getSingleton().getAutoCreatedWindow(), Ogre::RENDER_QUEUE_OVERLAY, false, 3000); | |
− | + | CEGUI::System* mGUISystem = new CEGUI::System(mGUIRenderer); | |
− | + | CEGUI::Logger::getSingleton().setLoggingLevel(CEGUI::Informative); // this is recommended to help with debugging, but not neccessary | |
− | + | ||
− | + | CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLook.scheme"); | |
− | + | mGUISystem->setDefaultMouseCursor((CEGUI::utf8*)"TaharezLook", (CEGUI::utf8*)"MouseArrow"); | |
− | + | CEGUI::Window* mRootWindow= CEGUI::WindowManager::getSingleton().createWindow((CEGUI::utf8*)"DefaultWindow", (CEGUI::utf8*)"RootWindow"); | |
+ | mGUISystem->setGUISheet(mRootWindow); // set active Window | ||
+ | </source> | ||
*Note 1: Casting string params to UTF8* isn't neccessary, if your application will only use english (latin based languages), it shouldn't be of any use. But if you want to use other languages which are not covered by the ascii chart, you can use the utf8 cast. I won't be using it for this tutorial, but it should be obvious where you can use it | *Note 1: Casting string params to UTF8* isn't neccessary, if your application will only use english (latin based languages), it shouldn't be of any use. But if you want to use other languages which are not covered by the ascii chart, you can use the utf8 cast. I won't be using it for this tutorial, but it should be obvious where you can use it | ||
*Note 2: While its totally legal to use different windows and have children assigned to them, I prefer to have a single root window in which all other windows are childs to it. I initialise it as DefaultWindow* to set it apart from other window types | *Note 2: While its totally legal to use different windows and have children assigned to them, I prefer to have a single root window in which all other windows are childs to it. I initialise it as DefaultWindow* to set it apart from other window types | ||
*Note 3: You can start your app without initialising a font, so long as you use a Window that doesn't use fonts (e.g: StaticImages) | *Note 3: You can start your app without initialising a font, so long as you use a Window that doesn't use fonts (e.g: StaticImages) | ||
+ | |||
+ | ==== Includes ==== | ||
+ | As I said earlier, I won't be adding #include lines to the tutorial's code to make it smaller. But here is a helpful tip that should spare you wasted time looking for which header to include. | ||
+ | Unless you #included "CEGUI.h", you'll need to #include a header when you use a CEGUI class. Since the naming convention of CEGUI is rather helpful. You can almost always count on that class being in "CEGUI'''ClassName'''.h". Make sure to make the first letter of the word upper case. If the class is 2 words, then each first letter is upper case. Like so | ||
+ | <source lang="cpp"> | ||
+ | #include "CEGUISchemeManager.h" | ||
+ | #include "CEGUIFontManager.h" | ||
+ | </source> | ||
+ | ....etc :) | ||
=== The Code Approach === | === The Code Approach === | ||
Line 30: | Line 45: | ||
There are a few things you'll constantly need when writing a menu, assuming your gui code is in a different class, we'll attempt to get pointers to them at the beginning of the code. | There are a few things you'll constantly need when writing a menu, assuming your gui code is in a different class, we'll attempt to get pointers to them at the beginning of the code. | ||
− | + | <source lang="cpp"> | |
− | + | WindowManager* Wmgr = WindowManager::getSingletonPtr(); | |
− | + | System* mGUISystem = System::getSingletonPtr(); | |
+ | Window* myRoot = Wmgr->getWindow("RootWindow"); // get default window | ||
+ | </source> | ||
==== The Menu code ==== | ==== The Menu code ==== | ||
Anyways, now that we got that out of the way, lets get started. I'll write code, then explain it below | Anyways, now that we got that out of the way, lets get started. I'll write code, then explain it below | ||
− | + | <source lang="cpp"> | |
− | + | // Menu Background | |
− | + | Window* MenuBackground = Wmgr->createWindow("TaharezLook/StaticImage", "Background"); | |
− | + | myRoot->addChildWindow( MenuBackground ); | |
− | + | MenuBackground->setPosition( UVector2( UDim( 0.0f, 0.0f ), UDim( 0.0f, 0.0f) ) ); | |
+ | MenuBackground->setSize( UVector2( UDim( 1.0f, 0.0f ), UDim( 1.0f, 0.0f ) ) ); // full screen | ||
− | + | // New game Button | |
− | + | PushButton* NewGame = (PushButton*)Wmgr->createWindow("TaharezLook/Button", "NewGame"); | |
− | + | MenuBackground->addChildWindow( NewGame ); | |
− | + | NewGame->setPosition( UVector2( UDim( 0.2f, 0.0f), UDim( 0.2f, 0.0f ) ) ); | |
− | + | NewGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); | |
− | + | ||
− | + | // Load game Button | |
− | + | PushButton* LoadGame= (PushButton*)Wmgr->createWindow("TaharezLook/Button", "LoadGame"); | |
− | + | MenuBackground->addChildWindow( LoadGame ); | |
− | + | LoadGame->setPosition( UVector2( UDim( 0.2f, 0.0f ), UDim( 0.45f, 0.0f ) ) ); | |
− | + | LoadGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); | |
− | + | ||
− | + | // Quit game Button | |
− | + | PushButton* QuitGame= (PushButton*)Wmgr->createWindow("TaharezLook/Button", "QuitGame"); | |
− | + | MenuBackground->addChildWindow( QuitGame ); | |
− | + | QuitGame->setPosition( UVector2( UDim( 0.2f, 0.0f ), UDim( 0.7f, 0.0f ) ) ); | |
− | + | QuitGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); | |
− | + | ||
− | + | mGUISystem->setGUISheet(myRoot); // this line is redundant since you didn't change gui sheets, but its here to make sure | |
+ | </source> | ||
This will create a menu with 3 empty buttons positioned below each other with an empty background. You always need to define the starting position of the window (the top left edge) and how big the window will be. If you don't you'll never see the window. | This will create a menu with 3 empty buttons positioned below each other with an empty background. You always need to define the starting position of the window (the top left edge) and how big the window will be. If you don't you'll never see the window. | ||
Line 71: | Line 90: | ||
===== Using Fonts ===== | ===== Using Fonts ===== | ||
Before we use text, we'll need to define a font. If you've already done that then skip forward. If not then please add this line to your initialisation code in Prerequisites area. | Before we use text, we'll need to define a font. If you've already done that then skip forward. If not then please add this line to your initialisation code in Prerequisites area. | ||
− | + | <source lang="cpp"> | |
+ | if(!FontManager::getSingleton().isFontPresent("Tahoma-12")) | ||
+ | FontManager::getSingleton().createFont("Tahoma-12.font"); | ||
+ | </source> | ||
To add a text line for a button you use: | To add a text line for a button you use: | ||
− | + | <source lang="cpp"> | |
+ | ButtonPtr->setText("foo"); | ||
+ | </source> | ||
===== Using Tooltips ===== | ===== Using Tooltips ===== | ||
Now that we've defined a font, we should define tooltips as well in initialisation area. | Now that we've defined a font, we should define tooltips as well in initialisation area. | ||
− | + | <source lang="cpp"> | |
+ | myGUISystem->setDefaultTooltip("TaharezLook/Tooltip"); | ||
+ | </source> | ||
You should usually just leave this with initialisation along with initialisation. You'll need to inject time pulses each frame though, that was explained here already [[ToolTips]]. | You should usually just leave this with initialisation along with initialisation. You'll need to inject time pulses each frame though, that was explained here already [[ToolTips]]. | ||
To add a tooltip for a button you use: | To add a tooltip for a button you use: | ||
− | + | <source lang="cpp"> | |
+ | ButtonPtr->setTooltipText("foo"); | ||
+ | </source> | ||
===== Using Images ===== | ===== Using Images ===== | ||
− | Before using images, we need to define an imageset. You can skip forward to the imageset explanation in the XML area to understand what it means. | + | Before using images, we need to define an imageset. You can skip forward to the [[http://www.cegui.org.uk/wiki/index.php/The_Main_Menu#Imagesets imageset explanation in the XML area]] to understand what it means. |
− | Note that you could easily create a full image from a texture using the line below, the created image will be called "full_image" | + | Note that you could easily create a full image from a texture using the line below, the auto-created image will be called "full_image" |
− | + | <source lang="cpp"> | |
+ | Imageset* foo = ImagesetManager::getSingleton().createImagesetFromImageFile("NameOfImageset", "Image.jpg"); | ||
+ | </source> | ||
But if you want to split it up it'll be alittle harder. You'll need to define a texture | But if you want to split it up it'll be alittle harder. You'll need to define a texture | ||
− | + | <source lang="cpp"> | |
− | + | Texture* texturePtr = System::getSingleton().getRenderer()->createTexture("ImageFile.jpg", ""); | |
+ | Imageset* MenuImageset = ImagesetManager::getSingleton().createImageset("ImageName", texturePtr); | ||
+ | </source> | ||
− | Since I'm no artist, we'll just assume we have 2 | + | Since I'm no artist, we'll just assume we have 2 image files, one with the background image and the other with the different button states (make sure they're loaded in resources.cfg). While there are 4 different states( Normal, Hover, pushed & Disabled) we'll just use 2 different looks (to avoid writing more similar code), one for normal & the other will be shared amongst Hover, Pushed & disabled. The 2nd image will contain the different looks for the button, indicating Up or Down states, check the illustration figure below. Each of the 2 images will have its own imageset. |
− | + | ||
− | + | {| cellpadding="15" border="1" | |
− | + | |+ MenuButtons.jpg | |
+ | | align="center" | Button Up | ||
+ | |- | ||
+ | | align="center" | Button Down | ||
+ | |} | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | Imageset* MenuImageset = ImagesetManager::getSingleton().createImagesetFromImageFile("Background","MenuBackground.jpg"); | ||
+ | Texture* texturePtr = System::getSingleton().getRenderer()->createTexture("MenuButtons.jpg", ""); | ||
+ | Imageset* MenuImageset = ImagesetManager::getSingleton().createImageset("Buttons", texturePtr); | ||
+ | </source> | ||
Now we have our imagesets, we'll need define images in each. The first we'll need to define an the Background image, which will be full size. For the 2nd imageset we'll need to split it up and define 2 images inside of it. One for Button Up, and one for Button Down. | Now we have our imagesets, we'll need define images in each. The first we'll need to define an the Background image, which will be full size. For the 2nd imageset we'll need to split it up and define 2 images inside of it. One for Button Up, and one for Button Down. | ||
− | + | <source lang="cpp"> | |
− | + | MenuImageset->defineImage("Background", Point(0.0f,0.0f), Size( 1.0f, 1.0f ), Point(0.0f,0.0f)); // Whole Image | |
− | + | ButtonsImageset->defineImage("ButtonUp", Point(0.0f,0.0f), Size( 1.0f, 0.5f ), Point(0.0f,0.0f)); // Top half of image | |
+ | ButtonsImageset->defineImage("ButtonDown", Point(0.0f,0.5f), Size( 1.0f, 0.5f ), Point(0.0f,0.0f)); // Bottom Half | ||
+ | </source> | ||
− | now that we have images | + | now that we have images defined, we can easily apply them to a window that supports images. Note the value (second) string used here - is is of the format "set:<imageset name> image:<image name>" |
− | + | ||
− | + | <source lang="cpp"> | |
− | + | Window->setProperty( "Image", "set:ImagesetName image:ImageName" ); | |
+ | </source> | ||
==== Wrapping It all up ==== | ==== Wrapping It all up ==== | ||
Now lets write the code above again with the new adjustments | Now lets write the code above again with the new adjustments | ||
− | + | ||
− | + | <source lang="cpp"> | |
− | + | /*** Stuff you need to do in the initialisation phase ***/ | |
− | + | if(!FontManager::getSingleton().isFontPresent("Tahoma-12")) | |
− | + | FontManager::getSingleton().createFont("Tahoma-12.font"); | |
− | + | mGUISystem->setDefaultTooltip("TaharezLook/Tooltip"); | |
− | + | ||
− | + | // Creating Imagesets and defining images | |
− | + | Imageset* MenuImageset = ImagesetManager::getSingleton().createImagesetFromImageFile("Background","MenuBackground.jpg"); | |
− | + | Texture* texturePtr = System::getSingleton().getRenderer()->createTexture("MenuButtons.jpg", ""); // default resource group | |
− | + | Imageset* ButtonsImageset = ImagesetManager::getSingleton().createImageset("Buttons", texturePtr); | |
− | + | ButtonsImageset->defineImage("ButtonUp", Point(0.0f,0.0f), Size( 0.5f, 0.5f ), Point(0.0f,0.0f)); | |
− | + | ButtonsImageset->defineImage("ButtonDown", Point(0.0f,0.5f), Size( 0.5f, 0.5f ), Point(0.0f,0.0f)); | |
− | + | ||
− | + | /*** the menu code ***/ | |
− | + | Window* MenuBackground = Wmgr->createWindow("TaharezLook/StaticImage", "Background"); | |
− | + | myRoot->addChildWindow( MenuBackground ); | |
− | + | MenuBackground->setPosition( UVector2( UDim( 0.0f, 0.0f), UDim( 0.0f, 0.0f ) ) ); | |
− | + | MenuBackground->setSize( UVector2( UDim( 1.0f, 0.0f), UDim( 1.0f, 0.0f ) ) ); // full screen | |
− | + | // this is the preferred way to set the image. | |
− | + | MenuBackground->setProperty( "Image", "set:Background image:full_image" ); | |
− | + | ||
− | + | PushButton* NewGame = (PushButton*)Wmgr->createWindow("TaharezLook/Button", "NewGame"); | |
− | + | MenuBackground->addChildWindow( NewGame ); | |
− | + | NewGame->setPosition( UVector2( UDim( 0.2f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); | |
− | + | NewGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); | |
− | + | NewGame->setText("New Game"); | |
− | + | NewGame->setProperty( "NormalImage", "set:Buttons image:ButtonUp" ); | |
− | + | NewGame->setProperty( "HoverImage", "set:Buttons image:ButtonDown" ); | |
− | + | NewGame->setProperty( "PushedImage", "set:Buttons image:ButtonDown" ); | |
− | + | ||
− | + | PushButton* LoadGame = (PushButton*)Wmgr->createWindow("TaharezLook/Button", "LoadGame"); | |
− | + | MenuBackground->addChildWindow( LoadGame ); | |
− | + | LoadGame->setPosition( UVector2( UDim(0.2f, 0.0f ), UDim( 0.45f, 0.0f ) ) ); | |
− | + | LoadGame->setSize( UVector2( UDim(0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); | |
− | + | LoadGame->setText("Load Game"); | |
− | + | LoadGame->setTooltipText("Disabled, not implemented yet"); | |
− | + | LoadGame->disable(); | |
− | + | LoadGame->setProperty( "NormalImage", "set:Buttons image:ButtonUp" ); | |
− | + | LoadGame->setProperty( "HoverImage", "set:Buttons image:ButtonDown" ); | |
− | + | LoadGame->setProperty( "PushedImage", "set:Buttons image:ButtonDown" ); | |
− | + | LoadGame->setProperty( "DisabledImage", "set:Buttons image:ButtonDown" ); | |
− | + | ||
− | + | PushButton* QuitGame= (PushButton*)Wmgr->createWindow("TaharezLook/Button", "QuitGame"); | |
− | + | MenuBackground->addChildWindow( QuitGame ); | |
− | + | QuitGame->setPosition( UVector2( UDim( 0.2f, 0.0f ), UDim( 0.7f, 0.0f ) ) ); | |
− | + | QuitGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); | |
− | + | QuitGame->setText("Quit Game"); | |
− | + | QuitGame->setProperty( "NormalImage", "set:Buttons image:ButtonUp" ); | |
− | + | QuitGame->setProperty( "HoverImage", "set:Buttons image:ButtonDown" ); | |
− | + | QuitGame->setProperty( "PushedImage", "set:Buttons image:ButtonDown" ); | |
− | + | </source> | |
− | + | ||
− | + | ||
Now go ahead and test, doesn't that look much better ? It looks more like a proper menu now. You could start handling events from here on (explained later). | Now go ahead and test, doesn't that look much better ? It looks more like a proper menu now. You could start handling events from here on (explained later). | ||
Line 181: | Line 224: | ||
A code line is usually encapsulated in < >. To start a declare a new type you can use <Type/> or <Type> </Type>. The first way should be used to declare objects that don't have too many settings. e.g: | A code line is usually encapsulated in < >. To start a declare a new type you can use <Type/> or <Type> </Type>. The first way should be used to declare objects that don't have too many settings. e.g: | ||
+ | <source lang="xml"> | ||
<Image Name="Background" XPos="0.0" YPos="0.0" Width="800" Height="600" /> | <Image Name="Background" XPos="0.0" YPos="0.0" Width="800" Height="600" /> | ||
+ | </source> | ||
As you've probably noticed, you can set its settings in the same line. As for the second way, it should be used to open up bigger types. e.g: | As you've probably noticed, you can set its settings in the same line. As for the second way, it should be used to open up bigger types. e.g: | ||
+ | <source lang="xml"> | ||
<Window Type="TaharezLook/Button" Name="NewGame"> | <Window Type="TaharezLook/Button" Name="NewGame"> | ||
− | + | <Property Name="UnifiedAreaRect" Value="{{0.2,0.0},{0.45,0.0},{0.6,0.0},{0.65,0.0}}" /> | |
</Window> | </Window> | ||
+ | </source> | ||
You can start nesting types in other types, you should be able to pickup the rest of the basics from xml files included with CEGUI and the ones in this tutorial. | You can start nesting types in other types, you should be able to pickup the rest of the basics from xml files included with CEGUI and the ones in this tutorial. | ||
Line 199: | Line 246: | ||
Effectively, an Imageset is just a collection of defined regions upon the source image / texture file (which is also specified in the Imageset definition). Each of these defined regions has a unique name and is known within the system as an Image. An Image as defined in an Imageset is the basic level of imagery used by CEGUI. By modifying the source image / texture file and also the position and size of the defined regions within the Imageset files you can easily change the appearance of what gets drawn by CEGUI. | Effectively, an Imageset is just a collection of defined regions upon the source image / texture file (which is also specified in the Imageset definition). Each of these defined regions has a unique name and is known within the system as an Image. An Image as defined in an Imageset is the basic level of imagery used by CEGUI. By modifying the source image / texture file and also the position and size of the defined regions within the Imageset files you can easily change the appearance of what gets drawn by CEGUI. | ||
− | Since each imageset cannot contain more then one Image file. We'll have to make 2 imageset files. For both images, we'll assume the width and hieght are 512x512. But for the second image, we'll assume it has 4 different button images inside instead of just 2 like the code part of tutorial, each button image is 1/4 the size of the full image. The only reason behind this is its much easier to assign & define images now. | + | Since each imageset cannot contain more then one Image file. We'll have to make 2 imageset files. For both images, we'll assume the width and hieght are 512x512. But for the second image, we'll assume it has 4 different button images inside instead of just 2 like the code part of tutorial, each button image is 1/4 the size of the full image. The only reason behind this is its much easier to assign & define images now. Here is an illustration figure for the 2nd image |
+ | |||
+ | {| cellpadding="10" border="1" | ||
+ | |+ MenuButtons.jpg | ||
+ | | align="center" | Button Up | ||
+ | |- | ||
+ | | align="center" | Button Down | ||
+ | |- | ||
+ | | align="center" | Button Highlighted | ||
+ | |- | ||
+ | | align="center" | Button Greyed Out | ||
+ | |} | ||
+ | |||
* MenuBackground.imageset | * MenuBackground.imageset | ||
+ | <source lang="xml"> | ||
<?xml version="1.0" ?> | <?xml version="1.0" ?> | ||
<Imageset Name="Background" Imagefile="MenuBackground.jpg" NativeHorzRes="800" NativeVertRes="600" AutoScaled="true" ResourceGroup="General"> | <Imageset Name="Background" Imagefile="MenuBackground.jpg" NativeHorzRes="800" NativeVertRes="600" AutoScaled="true" ResourceGroup="General"> | ||
<Image Name="Background" XPos="0.0" YPos="0.0" Width="512" Height="512" /> | <Image Name="Background" XPos="0.0" YPos="0.0" Width="512" Height="512" /> | ||
</Imageset> | </Imageset> | ||
+ | </source> | ||
+ | |||
* MenuButtons.imageset | * MenuButtons.imageset | ||
+ | <source lang="xml"> | ||
<?xml version="1.0" ?> | <?xml version="1.0" ?> | ||
<Imageset Name="Buttons" Imagefile="MenuButtons.jpg" NativeHorzRes="800" NativeVertRes="600" AutoScaled="true"> | <Imageset Name="Buttons" Imagefile="MenuButtons.jpg" NativeHorzRes="800" NativeVertRes="600" AutoScaled="true"> | ||
Line 213: | Line 276: | ||
<Image Name="ButtonHighlighted" XPos="0.0" YPos="384.14" Width="512" Height="128" /> | <Image Name="ButtonHighlighted" XPos="0.0" YPos="384.14" Width="512" Height="128" /> | ||
</Imageset> | </Imageset> | ||
+ | </source> | ||
− | While the ResourceGroup="" line isn't | + | While the ResourceGroup="" line isn't necessary, I just added it so you can see where to use it. |
To load Imagesets by code you do | To load Imagesets by code you do | ||
− | ImagesetManager:: | + | ImagesetManager::getSingleton().createImageset( "MenuBackground.imageset"); |
===== Layouts ===== | ===== Layouts ===== | ||
Line 223: | Line 287: | ||
* Menu.layout | * Menu.layout | ||
+ | <source lang="xml"> | ||
<?xml version="1.0" ?> | <?xml version="1.0" ?> | ||
<GUILayout> | <GUILayout> | ||
− | + | <Window Type="WindowsLook/StaticImage" Name="Menu/Background"> | |
− | + | <Property Name="UnifiedSize" Value="{{1.0,0},{1.0,0}}" /> | |
− | + | <Property Name="Image" Value="set:Background image:Background" /> | |
− | + | <Window Type="WindowsLook/Button" Name="Menu/NewGame"> | |
− | + | <Property Name="UnifiedAreaRect" Value="{{0.2,0.0},{0.2,0.0},{0.6,0.0},{0.4,0.0}}" /> | |
− | + | <Property Name="NormalImage" Value="set:Buttons image:ButtonUp" /> | |
− | + | <Property Name="HoverImage" Value="set:Buttons image:ButtonDisabled" /> | |
− | + | <Property Name="PushedImage" Value="set:Buttons image:ButtonHighlighted" /> | |
− | + | <Property Name="DisabledImage" Value="set:Buttons image:ButtonDisabled" /> | |
− | + | <Property Name="Tooltip" Value="Start a new game"/> | |
− | + | </Window> | |
− | + | <Window Type="WindowsLook/Button" Name="Menu/LoadGame"> | |
− | + | <Property Name="UnifiedAreaRect" Value="{{0.2,0.0},{0.45,0.0},{0.6,0.0},{0.65,0.0}}" /> | |
− | + | <Property Name="NormalImage" Value="set:Buttons image:ButtonUp" /> | |
− | + | <Property Name="HoverImage" Value="set:Buttons image:ButtonDisabled" /> | |
− | + | <Property Name="PushedImage" Value="set:Buttons image:ButtonHighlighted" /> | |
− | + | <Property Name="DisabledImage" Value="set:Buttons image:ButtonDisabled" /> | |
− | + | <Property Name="Tooltip" Value="Not implemented yet"/> | |
− | + | </Window> | |
− | + | <Window Type="WindowsLook/Button" Name="Menu/QuitGame"> | |
− | + | <Property Name="UnifiedAreaRect" Value="{{0.2,0.0},{0.7,0.0},{0.6,0.0},{0.9,0.0}}" /> | |
− | + | <Property Name="NormalImage" Value="set:Buttons image:ButtonUp" /> | |
− | + | <Property Name="HoverImage" Value="set:Buttons image:ButtonDisabled" /> | |
− | + | <Property Name="PushedImage" Value="set:Buttons image:ButtonHighlighted" /> | |
− | + | <Property Name="DisabledImage" Value="set:Buttons image:ButtonDisabled" /> | |
− | + | <Property Name="Tooltip" Value="Exit To Desktop"/> | |
− | + | </Window> | |
− | + | </Window> | |
</GUILayout> | </GUILayout> | ||
+ | </source> | ||
This does basicly all the stuff we did in the code portion of the tutorial. The only new term here is UnifiedAreaRect, the first pair defines where the XPos starts, the 3rd defines where it ends. Same goes for 2nd & 4th but for YPos | This does basicly all the stuff we did in the code portion of the tutorial. The only new term here is UnifiedAreaRect, the first pair defines where the XPos starts, the 3rd defines where it ends. Same goes for 2nd & 4th but for YPos | ||
To load a .layout file in code you do | To load a .layout file in code you do | ||
− | Window* Menu = WindowManager:: | + | <source lang="cpp"> |
+ | Window* Menu = WindowManager::getSingleton().loadWindowLayout("Menu.layout"); | ||
+ | </source> | ||
*Note 1: There is an editor file for layouts written by scriptkid, its preferable if you get it from CVS and build it, but its possible to get the binaries from the downloads area in the cegui website. While its rather limited atm, it allows you to create and place the various widgets and export into a .layout file. It doesn't support custom images as of yet (8/12/2005). | *Note 1: There is an editor file for layouts written by scriptkid, its preferable if you get it from CVS and build it, but its possible to get the binaries from the downloads area in the cegui website. While its rather limited atm, it allows you to create and place the various widgets and export into a .layout file. It doesn't support custom images as of yet (8/12/2005). | ||
*Note 2: Since the layout file is usually the most error prone, I usually encase it in a try catch statement to catch any parsing exceptions and pause on them. If you don't do that you won't get informed of errors when they happen, which could lead to other errors happening and crashing ultimatley. | *Note 2: Since the layout file is usually the most error prone, I usually encase it in a try catch statement to catch any parsing exceptions and pause on them. If you don't do that you won't get informed of errors when they happen, which could lead to other errors happening and crashing ultimatley. | ||
− | + | <source lang="cpp"> | |
− | + | try | |
− | + | { | |
− | + | Window* Menu = WindowManager::getSingleton().loadWindowLayout("Menu.layout"); | |
− | + | } | |
− | + | catch(CEGUI::Exception &e) | |
− | + | { | |
− | + | OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, string(e.getMessage().c_str()), "Error Parsing Menu"); | |
+ | } | ||
+ | </source> | ||
===== Schemes ===== | ===== Schemes ===== | ||
Line 283: | Line 353: | ||
A 'Window Set' basically specifies the name of a loadable module or skin(.dll , .so, .looknfeel), and a set of widgets contained within that modules that you wish to register with the system. A 'Window Alias' provides a means to refer to a window / widget type by alternative names, it can also be used to 'hide' an already registered widget type with another widget type (so that other widget type gets used instead). | A 'Window Set' basically specifies the name of a loadable module or skin(.dll , .so, .looknfeel), and a set of widgets contained within that modules that you wish to register with the system. A 'Window Alias' provides a means to refer to a window / widget type by alternative names, it can also be used to 'hide' an already registered widget type with another widget type (so that other widget type gets used instead). | ||
− | This file isn't totally needed in the current approach, we're just using it here as a means of grouping imagesets for loading. However this file will be used in greater detail in the Falagard system. | + | This file isn't totally needed in the current approach, we're just using it here as a means of grouping imagesets for loading. However this file will be used in greater detail in the [[Falagard]] system. |
* GameGUI.scheme | * GameGUI.scheme | ||
+ | <source lang="xml"> | ||
<?xml version="1.0" ?> | <?xml version="1.0" ?> | ||
<GUIScheme Name="GameGUI"> | <GUIScheme Name="GameGUI"> | ||
Line 291: | Line 362: | ||
<Imageset Name="Buttons" Filename="MenuButtons.imageset"/> | <Imageset Name="Buttons" Filename="MenuButtons.imageset"/> | ||
</GUIScheme> | </GUIScheme> | ||
+ | </source> | ||
This simply just loads the 2 imagesets. To load the scheme by code you use | This simply just loads the 2 imagesets. To load the scheme by code you use | ||
− | + | <source lang="cpp"> | |
+ | SchemeManager::getSingleton().loadScheme("GameGUI.scheme); | ||
+ | </source> | ||
==== Wrapping It all up ==== | ==== Wrapping It all up ==== | ||
Line 300: | Line 374: | ||
Now for the code portion, the initialisation area will remain the same. However, you'll need to have this line before your menu code | Now for the code portion, the initialisation area will remain the same. However, you'll need to have this line before your menu code | ||
− | + | <source lang="cpp"> | |
− | + | SchemeManager::getSingleton().loadScheme("GameGUI.scheme); | |
− | + | // Or you could do this if you skip the scheme part | |
− | + | //ImagesetManager::getSingleton().createImageset( "MenuBackground.imageset"); | |
+ | //ImagesetManager::getSingleton().createImageset( "MenuButtons.imageset"); | ||
+ | </source> | ||
Now its time to write the menu code | Now its time to write the menu code | ||
− | + | <source lang="cpp"> | |
− | + | WindowManager* Wmgr = WindowManager::getSingletonPtr(); | |
− | + | System* mGUISystem = System::getSingletonPtr(); | |
− | + | Window* myRoot = Wmgr->getWindow("RootWindow"); // get default window | |
− | + | ||
− | + | try | |
− | + | { | |
− | + | Window* Menu = Wmgr->loadWindowLayout("Menu.layout"); | |
− | + | } | |
− | + | catch(CEGUI::Exception &e) | |
− | + | { | |
− | + | OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, string(e.getMessage().c_str()), "Error Parsing Menu"); | |
− | + | } | |
− | + | myRoot->addChildWindow(Menu); | |
+ | GUISys->setGUISheet(myRoot); // this line is redundant since you didn't change gui sheets, but its here to make sure | ||
+ | </source> | ||
And thats it ! 50 lines of code are now alittle less then 10 lines. The menu is now easily editable by editing the xml files without the need of recompiling. Should any errors occur, checking the cegui.log should be very handy. | And thats it ! 50 lines of code are now alittle less then 10 lines. The menu is now easily editable by editing the xml files without the need of recompiling. Should any errors occur, checking the cegui.log should be very handy. | ||
− | === Falagard Way & the looknfeel system === | + | === [[Falagard]] Way & the looknfeel system === |
TO DO | TO DO | ||
− | + | == Handling events for the menu == | |
− | + | Ok, so we've seen how to make our menu. But so far we can only look at it, we can't use it. Here we'll learn how to start subscribing events and handle them. To start handling events we need to have pointers for each menu button you want handled (even the background if you want to do anything special). If you've followed the code approach, you should already have them. Otherwise, we'll need to start getting them. Simply do this for each button | |
+ | |||
+ | <source lang="cpp"> | ||
+ | PushButton* Button1 = (PushButton*)wmgr->getWindow("Widget Reference Name"); | ||
+ | </source> | ||
+ | |||
+ | Now that we have pointers to the buttons we wan't to handle. You need to subscribe it to a function that will do the "handling". If you're function is a global function (or not part of a class). you do this | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | Button1 ->subscribeEvent(PushButton::EventClicked, HandleButton1); | ||
+ | </source> | ||
+ | |||
+ | if it is part of a class you do this | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | Button1->subscribeEvent(PushButton::EventClicked, Event::Subscriber(&ClassName::HandleButton1, this)); | ||
+ | </source> | ||
+ | |||
+ | What this does is tell the Window to trigger function HandleButton1() should EventClicked happen. There are much more events which you can find out about them from the API reference. | ||
+ | |||
+ | Now here is a typical action | ||
+ | <source lang="cpp"> | ||
+ | bool HandleButton1(const EventArgs& e) | ||
+ | { | ||
+ | //Code you want Button1 to do | ||
+ | return true; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | Once the event is triggered, HandleButton1 is called and an EventArgs is passed to it. This has some useful info you might need to operate on, | ||
+ | |||
+ | *Note 1 : the function needs to be of type bool and return true. it also needs to take one parameter only and thats const CEGUI::EventArgs& | ||
+ | |||
+ | === What you need in your Frame Listener === | ||
+ | |||
+ | But wait a minute, CEGUI doesn't detect keyboard & mouse input itself. This means none of the code above will work just yet, we'll need to inject Input as we get it. So you'll have to modify your framelistener to do so. This part is based in portion from [[http://www.ogre3d.org/wiki/index.php/Basic_Tutorial_6 basic tutorial 6]]. This area is heavily ogre specific btw for those who didn't notice | ||
+ | |||
+ | First you'll need to subclass your framelistener (or whatever class handles your input) from KeyListener, MouseMotionListener & MouseListener then overload the pure virtual functions(check the ogre api to see which functions you'll need to overload) | ||
+ | Here is a list off all of them (might be some more), some of them you don't need to inject so provide empty | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | // Helper function | ||
+ | ConvertOgreButtonToCEGUI(int ButtonID); | ||
+ | |||
+ | //key listener | ||
+ | void keyPressed(KeyEvent *e); | ||
+ | void keyClicked(KeyEvent *e); | ||
+ | void keyReleased(KeyEvent *e); | ||
+ | |||
+ | //mouse motion listener | ||
+ | void mouseMoved(MouseEvent *e); | ||
+ | void mouseDragMoved(MouseEvent *e){} | ||
+ | void mouseDragged(MouseEvent *e); | ||
+ | |||
+ | // mouse listener | ||
+ | void mouseReleased (MouseEvent *e); | ||
+ | void mousePressed (MouseEvent *e); | ||
+ | void mouseClicked (MouseEvent *e){}; | ||
+ | void mouseEntered (MouseEvent *e){}; | ||
+ | void mouseExited (MouseEvent *e){}; | ||
+ | </source> | ||
+ | |||
+ | here is thier implementations | ||
+ | <source lang="cpp"> | ||
+ | MouseButton YourInputListenerClass::ConvertOgreButtonToCEGUI(int ButtonID) | ||
+ | { | ||
+ | switch (ButtonID) | ||
+ | { | ||
+ | case MouseEvent::BUTTON0_MASK: | ||
+ | return CEGUI::LeftButton; | ||
+ | case MouseEvent::BUTTON1_MASK: | ||
+ | return CEGUI::RightButton; | ||
+ | case MouseEvent::BUTTON2_MASK: | ||
+ | return CEGUI::MiddleButton; | ||
+ | case MouseEvent::BUTTON3_MASK: | ||
+ | return CEGUI::X1Button; | ||
+ | default: | ||
+ | return CEGUI::LeftButton; | ||
+ | } | ||
+ | } | ||
+ | void YourInputListenerClass::keyPressed(KeyEvent *e) | ||
+ | { | ||
+ | mGUISystem->injectKeyDown(e->getKey()); // I'm not totally sure about this area, can someone confirm ? | ||
+ | mGUISystem->injectChar(e->getKeyChar()); | ||
+ | e->consume(); | ||
+ | } | ||
+ | void YourInputListenerClass::keyReleased(KeyEvent *e) | ||
+ | { | ||
+ | mGUISystem->injectKeyUp(e->getKey()); | ||
+ | e->consume(); | ||
+ | } | ||
+ | void YourInputListenerClass::mouseMoved (MouseEvent *e) | ||
+ | { | ||
+ | mGUISystem->injectMouseMove(e->getRelX() * mGUIRenderer ->getWidth(),e->getRelY() * GuiRenderer->getHeight()); | ||
+ | e->consume(); | ||
+ | } | ||
+ | void YourInputListenerClass::mousePressed (MouseEvent *e) | ||
+ | { | ||
+ | mGUISystem->injectMouseButtonDown(ConvertOgreButtonToCEGUI(e->getButtonID())); | ||
+ | e->consume(); | ||
+ | } | ||
+ | void YourInputListenerClass::mouseReleased(MouseEvent *e) | ||
+ | { | ||
+ | mGUISystem->injectMouseButtonUp(ConvertOgreButtonToCEGUI(e->getButtonID())); | ||
+ | e->consume(); | ||
+ | } | ||
+ | void YourInputListenerClass::mouseDragged (MouseEvent *e) | ||
+ | { | ||
+ | mouseMoved(e); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | === Handling events for the layout approach === | ||
+ | I'll show an example based on the layout approach, you could easily adapt it to any of the other approaches. | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | // handler functions declarations | ||
+ | bool handleNewGame(const EventArgs& e); | ||
+ | bool handleQuit(const EventArgs& e); | ||
+ | bool handleHover(const EventArgs& e); | ||
+ | |||
+ | WindowManager* Wmgr = WindowManager::getSingletonPtr(); | ||
+ | System* mGUISystem = System::getSingletonPtr(); | ||
+ | Window* myRoot = Wmgr->getWindow("RootWindow"); // get default window | ||
+ | |||
+ | try | ||
+ | { | ||
+ | Window* Menu = Wmgr->loadWindowLayout("Menu.layout"); | ||
+ | } | ||
+ | catch(CEGUI::Exception &e) | ||
+ | { | ||
+ | OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, string(e.getMessage().c_str()), "Error Parsing Menu"); | ||
+ | } | ||
+ | myRoot->addChildWindow(Menu); | ||
+ | mGUISystem->setGUISheet(myRoot); | ||
+ | |||
+ | bool handleNewGame(const EventArgs& e) | ||
+ | { | ||
+ | // your project specific code to start game | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | bool handleHover(const EventArgs& e) | ||
+ | { | ||
+ | // Play a beep sound | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | bool handleQuit(const EventArgs& e) | ||
+ | { | ||
+ | // your engine specific code to shut down | ||
+ | Root::getSingleton().queueEndRendering(); // ogre specific | ||
+ | return true; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | Thats it. Theoritically it should work. Now go ahead and try :) | ||
+ | |||
+ | == Conclusion == | ||
+ | |||
+ | Now it should be easy to and clear to beginner users how to begin making use of CEGUI, you can easily extend what you've learnt here to start making more complex GUIs. | ||
+ | If you find that something isn't clear or you want help with it you can post on the forums or join the #cegui channel on IRC (freenode server). I usually idle on IRC often, nick MandM. | ||
+ | |||
+ | [[Category:Tutorials]] | ||
+ | [[Category:Update requested]] |
Latest revision as of 01:39, 6 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)
So, you've probably read all the Beginner Guide to X tutorials (if not then go back and read them !), your sitting there still feeling lost, and you can't find exactly how to bring all the stuff you've read together to make a simple GUI. I too found it specially hard to wrap everything together when starting out and hopefully this article should help address this problem for others, I'm still a beginner so there might be a few errors, but for the most part the code has been tested. This tutorial will be rather Ogre oriented since thats what I use, I'll try to keep things a little generic though.
This tutorial was last updated on: 7th January 2008 by CrazyEddie
Contents
Getting started
For tutorial purposes, we will quickly run over the Initialisation part (as this has been addressed in other tutorials more thoroughly and is usually engine specific). I will not litter the tutorial with unlrelated code like framelisteners, includes and other stuff. We will be making a main menu which is basicly just a collection of buttons and a StaticImage for background, but it is cruical that you have read the following tutorials and understood them, specially the file types. [CrazyEddie's Beginner Guides ] [Ogre's Basic Tutorial 6]
Creating the Menu: A plan of action
There are 3 ways to go about this, one way is to hardcode the menu (which is a bad idea unless you don't want your menu to be customisable), the 2nd way is to use although uses an xml based approach by writing layout files, imageset files..etc. The 3rd way is to use the falagard system which is subset of the 2nd approach, but expands even more on the functionality by offering the .looknfeel files which allow you to skin any existing scheme or even create your own.
Prerequisites
Ok, before we get started we need to have a few things initialised first, other then Ogre root, scene manager, framelistener....etc. If you've read [basic tutorial 6] you'll see that we need to have the following defiendone of the looks loaded as well as a font. I'll be using TaharezLook and tahoma-12 for this tutorial, but I'll load the font at a later stage in the tutorial. I'll only use CEGUI:: in this area, but I'll drop using it since it'll be obvious where to use it after a while. Just assume I've used using namespace CEGUI;
// Initialisation Area CEGUI::OgreCEGUIRenderer* mGUIRenderer = new CEGUI::OgreCEGUIRenderer(Root::getSingleton().getAutoCreatedWindow(), Ogre::RENDER_QUEUE_OVERLAY, false, 3000); CEGUI::System* mGUISystem = new CEGUI::System(mGUIRenderer); CEGUI::Logger::getSingleton().setLoggingLevel(CEGUI::Informative); // this is recommended to help with debugging, but not neccessary CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLook.scheme"); mGUISystem->setDefaultMouseCursor((CEGUI::utf8*)"TaharezLook", (CEGUI::utf8*)"MouseArrow"); CEGUI::Window* mRootWindow= CEGUI::WindowManager::getSingleton().createWindow((CEGUI::utf8*)"DefaultWindow", (CEGUI::utf8*)"RootWindow"); mGUISystem->setGUISheet(mRootWindow); // set active Window
- Note 1: Casting string params to UTF8* isn't neccessary, if your application will only use english (latin based languages), it shouldn't be of any use. But if you want to use other languages which are not covered by the ascii chart, you can use the utf8 cast. I won't be using it for this tutorial, but it should be obvious where you can use it
- Note 2: While its totally legal to use different windows and have children assigned to them, I prefer to have a single root window in which all other windows are childs to it. I initialise it as DefaultWindow* to set it apart from other window types
- Note 3: You can start your app without initialising a font, so long as you use a Window that doesn't use fonts (e.g: StaticImages)
Includes
As I said earlier, I won't be adding #include lines to the tutorial's code to make it smaller. But here is a helpful tip that should spare you wasted time looking for which header to include. Unless you #included "CEGUI.h", you'll need to #include a header when you use a CEGUI class. Since the naming convention of CEGUI is rather helpful. You can almost always count on that class being in "CEGUIClassName.h". Make sure to make the first letter of the word upper case. If the class is 2 words, then each first letter is upper case. Like so
#include "CEGUISchemeManager.h" #include "CEGUIFontManager.h"
....etc :)
The Code Approach
There are a few things you'll constantly need when writing a menu, assuming your gui code is in a different class, we'll attempt to get pointers to them at the beginning of the code.
WindowManager* Wmgr = WindowManager::getSingletonPtr(); System* mGUISystem = System::getSingletonPtr(); Window* myRoot = Wmgr->getWindow("RootWindow"); // get default window
The Menu code
Anyways, now that we got that out of the way, lets get started. I'll write code, then explain it below
// Menu Background Window* MenuBackground = Wmgr->createWindow("TaharezLook/StaticImage", "Background"); myRoot->addChildWindow( MenuBackground ); MenuBackground->setPosition( UVector2( UDim( 0.0f, 0.0f ), UDim( 0.0f, 0.0f) ) ); MenuBackground->setSize( UVector2( UDim( 1.0f, 0.0f ), UDim( 1.0f, 0.0f ) ) ); // full screen // New game Button PushButton* NewGame = (PushButton*)Wmgr->createWindow("TaharezLook/Button", "NewGame"); MenuBackground->addChildWindow( NewGame ); NewGame->setPosition( UVector2( UDim( 0.2f, 0.0f), UDim( 0.2f, 0.0f ) ) ); NewGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); // Load game Button PushButton* LoadGame= (PushButton*)Wmgr->createWindow("TaharezLook/Button", "LoadGame"); MenuBackground->addChildWindow( LoadGame ); LoadGame->setPosition( UVector2( UDim( 0.2f, 0.0f ), UDim( 0.45f, 0.0f ) ) ); LoadGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); // Quit game Button PushButton* QuitGame= (PushButton*)Wmgr->createWindow("TaharezLook/Button", "QuitGame"); MenuBackground->addChildWindow( QuitGame ); QuitGame->setPosition( UVector2( UDim( 0.2f, 0.0f ), UDim( 0.7f, 0.0f ) ) ); QuitGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); mGUISystem->setGUISheet(myRoot); // this line is redundant since you didn't change gui sheets, but its here to make sure
This will create a menu with 3 empty buttons positioned below each other with an empty background. You always need to define the starting position of the window (the top left edge) and how big the window will be. If you don't you'll never see the window. You'll see that the 3 buttons are childs of the Background Menu window, which is a child of the RootWindow.
Changing the look
But this is no fun, we want to set Images to the buttons, display text on them, perhaps even use tooltips. The menu isn't really usable this way. But before we start jumping into things, we must do a few things first.
Using Fonts
Before we use text, we'll need to define a font. If you've already done that then skip forward. If not then please add this line to your initialisation code in Prerequisites area.
if(!FontManager::getSingleton().isFontPresent("Tahoma-12")) FontManager::getSingleton().createFont("Tahoma-12.font");
To add a text line for a button you use:
ButtonPtr->setText("foo");
Using Tooltips
Now that we've defined a font, we should define tooltips as well in initialisation area.
myGUISystem->setDefaultTooltip("TaharezLook/Tooltip");
You should usually just leave this with initialisation along with initialisation. You'll need to inject time pulses each frame though, that was explained here already ToolTips. To add a tooltip for a button you use:
ButtonPtr->setTooltipText("foo");
Using Images
Before using images, we need to define an imageset. You can skip forward to the [imageset explanation in the XML area] to understand what it means.
Note that you could easily create a full image from a texture using the line below, the auto-created image will be called "full_image"
Imageset* foo = ImagesetManager::getSingleton().createImagesetFromImageFile("NameOfImageset", "Image.jpg");
But if you want to split it up it'll be alittle harder. You'll need to define a texture
Texture* texturePtr = System::getSingleton().getRenderer()->createTexture("ImageFile.jpg", ""); Imageset* MenuImageset = ImagesetManager::getSingleton().createImageset("ImageName", texturePtr);
Since I'm no artist, we'll just assume we have 2 image files, one with the background image and the other with the different button states (make sure they're loaded in resources.cfg). While there are 4 different states( Normal, Hover, pushed & Disabled) we'll just use 2 different looks (to avoid writing more similar code), one for normal & the other will be shared amongst Hover, Pushed & disabled. The 2nd image will contain the different looks for the button, indicating Up or Down states, check the illustration figure below. Each of the 2 images will have its own imageset.
Button Up |
Button Down |
Imageset* MenuImageset = ImagesetManager::getSingleton().createImagesetFromImageFile("Background","MenuBackground.jpg"); Texture* texturePtr = System::getSingleton().getRenderer()->createTexture("MenuButtons.jpg", ""); Imageset* MenuImageset = ImagesetManager::getSingleton().createImageset("Buttons", texturePtr);
Now we have our imagesets, we'll need define images in each. The first we'll need to define an the Background image, which will be full size. For the 2nd imageset we'll need to split it up and define 2 images inside of it. One for Button Up, and one for Button Down.
MenuImageset->defineImage("Background", Point(0.0f,0.0f), Size( 1.0f, 1.0f ), Point(0.0f,0.0f)); // Whole Image ButtonsImageset->defineImage("ButtonUp", Point(0.0f,0.0f), Size( 1.0f, 0.5f ), Point(0.0f,0.0f)); // Top half of image ButtonsImageset->defineImage("ButtonDown", Point(0.0f,0.5f), Size( 1.0f, 0.5f ), Point(0.0f,0.0f)); // Bottom Half
now that we have images defined, we can easily apply them to a window that supports images. Note the value (second) string used here - is is of the format "set:<imageset name> image:<image name>"
Window->setProperty( "Image", "set:ImagesetName image:ImageName" );
Wrapping It all up
Now lets write the code above again with the new adjustments
/*** Stuff you need to do in the initialisation phase ***/ if(!FontManager::getSingleton().isFontPresent("Tahoma-12")) FontManager::getSingleton().createFont("Tahoma-12.font"); mGUISystem->setDefaultTooltip("TaharezLook/Tooltip"); // Creating Imagesets and defining images Imageset* MenuImageset = ImagesetManager::getSingleton().createImagesetFromImageFile("Background","MenuBackground.jpg"); Texture* texturePtr = System::getSingleton().getRenderer()->createTexture("MenuButtons.jpg", ""); // default resource group Imageset* ButtonsImageset = ImagesetManager::getSingleton().createImageset("Buttons", texturePtr); ButtonsImageset->defineImage("ButtonUp", Point(0.0f,0.0f), Size( 0.5f, 0.5f ), Point(0.0f,0.0f)); ButtonsImageset->defineImage("ButtonDown", Point(0.0f,0.5f), Size( 0.5f, 0.5f ), Point(0.0f,0.0f)); /*** the menu code ***/ Window* MenuBackground = Wmgr->createWindow("TaharezLook/StaticImage", "Background"); myRoot->addChildWindow( MenuBackground ); MenuBackground->setPosition( UVector2( UDim( 0.0f, 0.0f), UDim( 0.0f, 0.0f ) ) ); MenuBackground->setSize( UVector2( UDim( 1.0f, 0.0f), UDim( 1.0f, 0.0f ) ) ); // full screen // this is the preferred way to set the image. MenuBackground->setProperty( "Image", "set:Background image:full_image" ); PushButton* NewGame = (PushButton*)Wmgr->createWindow("TaharezLook/Button", "NewGame"); MenuBackground->addChildWindow( NewGame ); NewGame->setPosition( UVector2( UDim( 0.2f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); NewGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); NewGame->setText("New Game"); NewGame->setProperty( "NormalImage", "set:Buttons image:ButtonUp" ); NewGame->setProperty( "HoverImage", "set:Buttons image:ButtonDown" ); NewGame->setProperty( "PushedImage", "set:Buttons image:ButtonDown" ); PushButton* LoadGame = (PushButton*)Wmgr->createWindow("TaharezLook/Button", "LoadGame"); MenuBackground->addChildWindow( LoadGame ); LoadGame->setPosition( UVector2( UDim(0.2f, 0.0f ), UDim( 0.45f, 0.0f ) ) ); LoadGame->setSize( UVector2( UDim(0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); LoadGame->setText("Load Game"); LoadGame->setTooltipText("Disabled, not implemented yet"); LoadGame->disable(); LoadGame->setProperty( "NormalImage", "set:Buttons image:ButtonUp" ); LoadGame->setProperty( "HoverImage", "set:Buttons image:ButtonDown" ); LoadGame->setProperty( "PushedImage", "set:Buttons image:ButtonDown" ); LoadGame->setProperty( "DisabledImage", "set:Buttons image:ButtonDown" ); PushButton* QuitGame= (PushButton*)Wmgr->createWindow("TaharezLook/Button", "QuitGame"); MenuBackground->addChildWindow( QuitGame ); QuitGame->setPosition( UVector2( UDim( 0.2f, 0.0f ), UDim( 0.7f, 0.0f ) ) ); QuitGame->setSize( UVector2( UDim( 0.4f, 0.0f ), UDim( 0.2f, 0.0f ) ) ); QuitGame->setText("Quit Game"); QuitGame->setProperty( "NormalImage", "set:Buttons image:ButtonUp" ); QuitGame->setProperty( "HoverImage", "set:Buttons image:ButtonDown" ); QuitGame->setProperty( "PushedImage", "set:Buttons image:ButtonDown" );
Now go ahead and test, doesn't that look much better ? It looks more like a proper menu now. You could start handling events from here on (explained later). But this way isn't really very good. Should you want to change anything in the menu, you'll have to edit several lines of code & recompile your application. Besides, just making a menu as simple as that took about 50 lines of code, there must be a simpler way.
XML based approach (non-falagard way)
This way is much more efficient then hard-coding the menu. Its less code, easier to edit and use as well. I personally think this way is even better then using the falagard system if you're just after doing small and simple menu's, despite the fact falagard is supposed to be better performance wise and more flexible. Before we start we need
- Note 1: The .xsd files included in the cegui datafiles aren't needed if you're using Ogre. Ogre uses a different XML parser (tinyXML) which doesn't use these files so feel free to remove them, but if you're not using ogre and you didn't specify a different xml parser, you'll need those files.
XML Crash Course
If you're familiar with xml, feel free to skip this area (or better yet, rewrite it to make it more helpful).
A code line is usually encapsulated in < >. To start a declare a new type you can use <Type/> or <Type> </Type>. The first way should be used to declare objects that don't have too many settings. e.g:
<Image Name="Background" XPos="0.0" YPos="0.0" Width="800" Height="600" />
As you've probably noticed, you can set its settings in the same line. As for the second way, it should be used to open up bigger types. e.g:
<Window Type="TaharezLook/Button" Name="NewGame"> <Property Name="UnifiedAreaRect" Value="{{0.2,0.0},{0.45,0.0},{0.6,0.0},{0.65,0.0}}" /> </Window>
You can start nesting types in other types, you should be able to pickup the rest of the basics from xml files included with CEGUI and the ones in this tutorial.
- Note 1: Please use a proper text editor and not notepad or wordpad, there are many available for free online, using google would be a good time here :).
XML File types
Now I'll explain the filetypes (copied from the beginner tutorial to loading data) and thier uses, there are 3 types. .layout .imageset .scheme ( there is a 4th one .looknfeel used with falagard) While you can just call them .xml and be done with it, this way makes it more clearer.
Imagesets
Effectively, an Imageset is just a collection of defined regions upon the source image / texture file (which is also specified in the Imageset definition). Each of these defined regions has a unique name and is known within the system as an Image. An Image as defined in an Imageset is the basic level of imagery used by CEGUI. By modifying the source image / texture file and also the position and size of the defined regions within the Imageset files you can easily change the appearance of what gets drawn by CEGUI.
Since each imageset cannot contain more then one Image file. We'll have to make 2 imageset files. For both images, we'll assume the width and hieght are 512x512. But for the second image, we'll assume it has 4 different button images inside instead of just 2 like the code part of tutorial, each button image is 1/4 the size of the full image. The only reason behind this is its much easier to assign & define images now. Here is an illustration figure for the 2nd image
Button Up |
Button Down |
Button Highlighted |
Button Greyed Out |
- MenuBackground.imageset
<?xml version="1.0" ?> <Imageset Name="Background" Imagefile="MenuBackground.jpg" NativeHorzRes="800" NativeVertRes="600" AutoScaled="true" ResourceGroup="General"> <Image Name="Background" XPos="0.0" YPos="0.0" Width="512" Height="512" /> </Imageset>
- MenuButtons.imageset
<?xml version="1.0" ?> <Imageset Name="Buttons" Imagefile="MenuButtons.jpg" NativeHorzRes="800" NativeVertRes="600" AutoScaled="true"> <Image Name="ButtonUp" XPos="0.0" YPos="0.0" Width="512" Height="128" /> <Image Name="ButtonDown" XPos="0.0" YPos="128.0" Width="512" Height="128" /> <Image Name="ButtonDisabled" XPos="0.0" YPos="256.0" Width="512" Height="128" /> <Image Name="ButtonHighlighted" XPos="0.0" YPos="384.14" Width="512" Height="128" /> </Imageset>
While the ResourceGroup="" line isn't necessary, I just added it so you can see where to use it. To load Imagesets by code you do
ImagesetManager::getSingleton().createImageset( "MenuBackground.imageset");
Layouts
A layout file contains an XML representation of a window layout. Each nested 'Window' element defines a window or widget to be created, the 'Property' elements define the desired settings and property values for each window defined.
- Menu.layout
<?xml version="1.0" ?> <GUILayout> <Window Type="WindowsLook/StaticImage" Name="Menu/Background"> <Property Name="UnifiedSize" Value="{{1.0,0},{1.0,0}}" /> <Property Name="Image" Value="set:Background image:Background" /> <Window Type="WindowsLook/Button" Name="Menu/NewGame"> <Property Name="UnifiedAreaRect" Value="{{0.2,0.0},{0.2,0.0},{0.6,0.0},{0.4,0.0}}" /> <Property Name="NormalImage" Value="set:Buttons image:ButtonUp" /> <Property Name="HoverImage" Value="set:Buttons image:ButtonDisabled" /> <Property Name="PushedImage" Value="set:Buttons image:ButtonHighlighted" /> <Property Name="DisabledImage" Value="set:Buttons image:ButtonDisabled" /> <Property Name="Tooltip" Value="Start a new game"/> </Window> <Window Type="WindowsLook/Button" Name="Menu/LoadGame"> <Property Name="UnifiedAreaRect" Value="{{0.2,0.0},{0.45,0.0},{0.6,0.0},{0.65,0.0}}" /> <Property Name="NormalImage" Value="set:Buttons image:ButtonUp" /> <Property Name="HoverImage" Value="set:Buttons image:ButtonDisabled" /> <Property Name="PushedImage" Value="set:Buttons image:ButtonHighlighted" /> <Property Name="DisabledImage" Value="set:Buttons image:ButtonDisabled" /> <Property Name="Tooltip" Value="Not implemented yet"/> </Window> <Window Type="WindowsLook/Button" Name="Menu/QuitGame"> <Property Name="UnifiedAreaRect" Value="{{0.2,0.0},{0.7,0.0},{0.6,0.0},{0.9,0.0}}" /> <Property Name="NormalImage" Value="set:Buttons image:ButtonUp" /> <Property Name="HoverImage" Value="set:Buttons image:ButtonDisabled" /> <Property Name="PushedImage" Value="set:Buttons image:ButtonHighlighted" /> <Property Name="DisabledImage" Value="set:Buttons image:ButtonDisabled" /> <Property Name="Tooltip" Value="Exit To Desktop"/> </Window> </Window> </GUILayout>
This does basicly all the stuff we did in the code portion of the tutorial. The only new term here is UnifiedAreaRect, the first pair defines where the XPos starts, the 3rd defines where it ends. Same goes for 2nd & 4th but for YPos
To load a .layout file in code you do
Window* Menu = WindowManager::getSingleton().loadWindowLayout("Menu.layout");
- Note 1: There is an editor file for layouts written by scriptkid, its preferable if you get it from CVS and build it, but its possible to get the binaries from the downloads area in the cegui website. While its rather limited atm, it allows you to create and place the various widgets and export into a .layout file. It doesn't support custom images as of yet (8/12/2005).
- Note 2: Since the layout file is usually the most error prone, I usually encase it in a try catch statement to catch any parsing exceptions and pause on them. If you don't do that you won't get informed of errors when they happen, which could lead to other errors happening and crashing ultimatley.
try { Window* Menu = WindowManager::getSingleton().loadWindowLayout("Menu.layout"); } catch(CEGUI::Exception &e) { OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, string(e.getMessage().c_str()), "Error Parsing Menu"); }
Schemes
A Scheme is a means to group other data files together, it's also the most convenient way to load and register widget types. A Scheme can contain one or more of the following (which will be loaded and initialised when the scheme is loaded): Imageset Window Set Window Alias A 'Window Set' basically specifies the name of a loadable module or skin(.dll , .so, .looknfeel), and a set of widgets contained within that modules that you wish to register with the system. A 'Window Alias' provides a means to refer to a window / widget type by alternative names, it can also be used to 'hide' an already registered widget type with another widget type (so that other widget type gets used instead).
This file isn't totally needed in the current approach, we're just using it here as a means of grouping imagesets for loading. However this file will be used in greater detail in the Falagard system.
- GameGUI.scheme
<?xml version="1.0" ?> <GUIScheme Name="GameGUI"> <Imageset Name="Background" Filename="MenuButtons.imageset" ResourceGroup="General"/> <Imageset Name="Buttons" Filename="MenuButtons.imageset"/> </GUIScheme>
This simply just loads the 2 imagesets. To load the scheme by code you use
SchemeManager::getSingleton().loadScheme("GameGUI.scheme);
Wrapping It all up
Save the xml files above and add them somewhere in your Resource hierarchy (which should be your media folder unless you changed it).
Now for the code portion, the initialisation area will remain the same. However, you'll need to have this line before your menu code
SchemeManager::getSingleton().loadScheme("GameGUI.scheme); // Or you could do this if you skip the scheme part //ImagesetManager::getSingleton().createImageset( "MenuBackground.imageset"); //ImagesetManager::getSingleton().createImageset( "MenuButtons.imageset");
Now its time to write the menu code
WindowManager* Wmgr = WindowManager::getSingletonPtr(); System* mGUISystem = System::getSingletonPtr(); Window* myRoot = Wmgr->getWindow("RootWindow"); // get default window try { Window* Menu = Wmgr->loadWindowLayout("Menu.layout"); } catch(CEGUI::Exception &e) { OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, string(e.getMessage().c_str()), "Error Parsing Menu"); } myRoot->addChildWindow(Menu); GUISys->setGUISheet(myRoot); // this line is redundant since you didn't change gui sheets, but its here to make sure
And thats it ! 50 lines of code are now alittle less then 10 lines. The menu is now easily editable by editing the xml files without the need of recompiling. Should any errors occur, checking the cegui.log should be very handy.
Falagard Way & the looknfeel system
TO DO
Ok, so we've seen how to make our menu. But so far we can only look at it, we can't use it. Here we'll learn how to start subscribing events and handle them. To start handling events we need to have pointers for each menu button you want handled (even the background if you want to do anything special). If you've followed the code approach, you should already have them. Otherwise, we'll need to start getting them. Simply do this for each button
PushButton* Button1 = (PushButton*)wmgr->getWindow("Widget Reference Name");
Now that we have pointers to the buttons we wan't to handle. You need to subscribe it to a function that will do the "handling". If you're function is a global function (or not part of a class). you do this
Button1 ->subscribeEvent(PushButton::EventClicked, HandleButton1);
if it is part of a class you do this
Button1->subscribeEvent(PushButton::EventClicked, Event::Subscriber(&ClassName::HandleButton1, this));
What this does is tell the Window to trigger function HandleButton1() should EventClicked happen. There are much more events which you can find out about them from the API reference.
Now here is a typical action
bool HandleButton1(const EventArgs& e) { //Code you want Button1 to do return true; }
Once the event is triggered, HandleButton1 is called and an EventArgs is passed to it. This has some useful info you might need to operate on,
- Note 1 : the function needs to be of type bool and return true. it also needs to take one parameter only and thats const CEGUI::EventArgs&
What you need in your Frame Listener
But wait a minute, CEGUI doesn't detect keyboard & mouse input itself. This means none of the code above will work just yet, we'll need to inject Input as we get it. So you'll have to modify your framelistener to do so. This part is based in portion from [basic tutorial 6]. This area is heavily ogre specific btw for those who didn't notice
First you'll need to subclass your framelistener (or whatever class handles your input) from KeyListener, MouseMotionListener & MouseListener then overload the pure virtual functions(check the ogre api to see which functions you'll need to overload) Here is a list off all of them (might be some more), some of them you don't need to inject so provide empty
// Helper function ConvertOgreButtonToCEGUI(int ButtonID); //key listener void keyPressed(KeyEvent *e); void keyClicked(KeyEvent *e); void keyReleased(KeyEvent *e); //mouse motion listener void mouseMoved(MouseEvent *e); void mouseDragMoved(MouseEvent *e){} void mouseDragged(MouseEvent *e); // mouse listener void mouseReleased (MouseEvent *e); void mousePressed (MouseEvent *e); void mouseClicked (MouseEvent *e){}; void mouseEntered (MouseEvent *e){}; void mouseExited (MouseEvent *e){};
here is thier implementations
MouseButton YourInputListenerClass::ConvertOgreButtonToCEGUI(int ButtonID) { switch (ButtonID) { case MouseEvent::BUTTON0_MASK: return CEGUI::LeftButton; case MouseEvent::BUTTON1_MASK: return CEGUI::RightButton; case MouseEvent::BUTTON2_MASK: return CEGUI::MiddleButton; case MouseEvent::BUTTON3_MASK: return CEGUI::X1Button; default: return CEGUI::LeftButton; } } void YourInputListenerClass::keyPressed(KeyEvent *e) { mGUISystem->injectKeyDown(e->getKey()); // I'm not totally sure about this area, can someone confirm ? mGUISystem->injectChar(e->getKeyChar()); e->consume(); } void YourInputListenerClass::keyReleased(KeyEvent *e) { mGUISystem->injectKeyUp(e->getKey()); e->consume(); } void YourInputListenerClass::mouseMoved (MouseEvent *e) { mGUISystem->injectMouseMove(e->getRelX() * mGUIRenderer ->getWidth(),e->getRelY() * GuiRenderer->getHeight()); e->consume(); } void YourInputListenerClass::mousePressed (MouseEvent *e) { mGUISystem->injectMouseButtonDown(ConvertOgreButtonToCEGUI(e->getButtonID())); e->consume(); } void YourInputListenerClass::mouseReleased(MouseEvent *e) { mGUISystem->injectMouseButtonUp(ConvertOgreButtonToCEGUI(e->getButtonID())); e->consume(); } void YourInputListenerClass::mouseDragged (MouseEvent *e) { mouseMoved(e); }
Handling events for the layout approach
I'll show an example based on the layout approach, you could easily adapt it to any of the other approaches.
// handler functions declarations bool handleNewGame(const EventArgs& e); bool handleQuit(const EventArgs& e); bool handleHover(const EventArgs& e); WindowManager* Wmgr = WindowManager::getSingletonPtr(); System* mGUISystem = System::getSingletonPtr(); Window* myRoot = Wmgr->getWindow("RootWindow"); // get default window try { Window* Menu = Wmgr->loadWindowLayout("Menu.layout"); } catch(CEGUI::Exception &e) { OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, string(e.getMessage().c_str()), "Error Parsing Menu"); } myRoot->addChildWindow(Menu); mGUISystem->setGUISheet(myRoot); bool handleNewGame(const EventArgs& e) { // your project specific code to start game return true; } bool handleHover(const EventArgs& e) { // Play a beep sound return true; } bool handleQuit(const EventArgs& e) { // your engine specific code to shut down Root::getSingleton().queueEndRendering(); // ogre specific return true; }
Thats it. Theoritically it should work. Now go ahead and try :)
Conclusion
Now it should be easy to and clear to beginner users how to begin making use of CEGUI, you can easily extend what you've learnt here to start making more complex GUIs. If you find that something isn't clear or you want help with it you can post on the forums or join the #cegui channel on IRC (freenode server). I usually idle on IRC often, nick MandM.