VGUI2: Creating a panel
Requirements
Have read and understood (or understand):
Can code:
- C++
- Script
This tutorial is about creating a simple interactive interface
Understanding how VGUI2 works
Every VGUI2 dialog that you see while using source based games is basically called a Panel.
Every Panel consists of three components:
- Scheme
- Control Settings
- Code
The Scheme
The scheme is a general configuration file which stores information about the colors of certain elements such as buttons, combo boxes, labels, etc. A typical scheme file is SourceScheme.res, for example. If you plan to create a panel which looks like the other panels in the menu you should use the same scheme file as they do.
Control Settings
The control settings file stores information about the relative position of your panel and its elements.
Every panel has a very own resource file. To create a resource file, there are two ways: Either you create one on your own using an editor like notepad, or you use Valves InGame Resource Editor.
Code
The code is the most important part of a panel, since the code decides what to do if the user clicks a button. To create and destroy the panel, you use the code. Fortunately you can set a lot more things than in the resource file(s). Code is the most important thing in this tutorial.
Creating a panel
Ok, let us assume we want to create a door, for real this time. Since we are not able to create a door from scratch, the first thing we do is to step by at the building centre. What we ask for, is a basic door. It works, but we have still plans to customize it.
Starting Off
The panel class is the base class of all VGUI2 elements. To get a rough overview about all the VGUI2 elements, have a look into the vgui_elements folder. Of course, we don't just buy some wood in our local building centre.
The important class is the EditablePanel class that inherits from the Panel class. Our panel will be a new class which inherits from the EditablePanel class. This results in several advantages: We can code methods related to the content of the panel, we can overwrite the methods of the base classes and do much more useful stuff.
You can create a new file MyPanel.cpp underneath the Source Files inside the client project.
MyPanel.cpp
//The following include files are necessary to allow your MyPanel.cpp to compile.
#include "cbase.h"
#include "IMyPanel.h"
using namespace vgui;
#include <vgui/IVGui.h>
#include <vgui_controls/Frame.h>
//CMyPanel class: Tutorial example class
class CMyPanel : public vgui::Frame
{
DECLARE_CLASS_SIMPLE(CMyPanel, vgui::Frame);
//CMyPanel : This Class / vgui::Frame : BaseClass
CMyPanel(vgui::VPANEL parent); // Constructor
~CMyPanel(){}; // Destructor
protected:
//VGUI overrides:
virtual void OnTick();
virtual void OnCommand(const char* pcCommand);
private:
//Other used VGUI control Elements:
};
The constructor: The argument is vgui::VPANEL parent. After reading the VGUI Documentation, you should know that every panel has a parent and why it has a parent.
Underneath the above code, add:
// Constuctor: Initializes the Panel
CMyPanel::CMyPanel(vgui::VPANEL parent)
: BaseClass(NULL, "MyPanel")
{
SetParent( parent );
SetKeyBoardInputEnabled( true );
SetMouseInputEnabled( true );
SetProportional( false );
SetTitleBarVisible( true );
SetMinimizeButtonVisible( false );
SetMaximizeButtonVisible( false );
SetCloseButtonVisible( false );
SetSizeable( false );
SetMoveable( false );
SetVisible( true );
SetScheme(vgui::scheme()->LoadSchemeFromFile("resource/SourceScheme.res", "SourceScheme"));
LoadControlSettings("resource/UI/mypanel.res");
vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
DevMsg("MyPanel has been constructed\n");
}
The first lines are pretty easy to understand. SetScheme is used to set the source scheme, which is the standard scheme for Half-Life 2. We get a pointer to the scheme by calling LoadSchemeFromFile(...). The LoadControlSettings function is used to load the control settings resolution file. The last line is explained in the VGUI2 documentation.
The SetProportional( false ); function decides to make the panel big or small, having it set to true will cause your panel to have big fonts, controls etc.
Finally, if you're going to edit it with the VGUI Build Mode Editor, you will need precreate the directory it goes into, and posibly give your resource an all-lower-case name [See also the Discussion page]
Underneath the above code, add:
//Class: CMyPanelInterface Class. Used for construction.
class CMyPanelInterface : public IMyPanel
{
private:
CMyPanel *MyPanel;
public:
CMyPanelInterface()
{
MyPanel = NULL;
}
void Create(vgui::VPANEL parent)
{
MyPanel = new CMyPanel(parent);
}
void Destroy()
{
if (MyPanel)
{
MyPanel->SetParent( (vgui::Panel *)NULL);
delete MyPanel;
}
}
};
static CMyPanelInterface g_MyPanel;
IMyPanel* mypanel = (IMyPanel*)&g_MyPanel;
void CMyPanel::OnTick()
{
BaseClass::OnTick();
}
void CMyPanel::OnCommand(const char* pcCommand)
{
BaseClass::OnCommand(pcCommand);
}
Next you can create IMyPanel.h in the same folder.
IMyPanel.h
// IMyPanel.h
class IMyPanel
{
public:
virtual void Create( vgui::VPANEL parent ) = 0;
virtual void Destroy( void ) = 0;
};
extern IMyPanel* mypanel;
Calling the panel
To call the panel, we add a few lines to the vgui_int.cpp file. Vgui_int.cpp includes functions which call all the panels. The VGui_CreateGlobalPanels() function is where the addition takes place.
This is the point, where you have to decide when the panel should show up. Either you create a panel that can be accessed during the game, or you create a panel for the main menu.
I assume that you want to create a panel for the game.
So, after including the panel, you should add at VGui_CreateGlobalPanels() the following:
mypanel->Create(gameParent);
and check to see if gameParent has been declared at the top of the function, if it hasn't then add
VPANEL gameParent = enginevgui->GetPanel( PANEL_CLIENTDLL );
Note: To have your screen appear in-game like the Counter-Strike buy menus or team selection menus, change PANEL_CLIENTDLL to PANEL_INGAMESCREENS , otherwise it will be visible only when you press Escape to go to the game menu.
Note: Dont forget to add
#include "IMyPanel.h"
to vgui_int.cpp file.
Then add this line into the VGui_Shutdown() function.
mypanel->Destroy();
If you plan to create a panel for the main menu, you need to put this into the construction function (VGui_CreateGlobalPanels()):
VPANEL GameUiDll = enginevgui->GetPanel( PANEL_GAMEUIDLL);
mypanel->Create(GameUiDll);
This will cause the panel to appear when you start your mod.
Adding elements
There are two ways to add new elements. One is to use the VGUI2 Builder (Press CTRL+SHIFT+ALT+B to open it). Since the VGUI2 Builder doesn’t come with all the essential elements, you should add elements in your code. Therefore, you have the choice in-between 50 elements, individually stored in the vgui_controls folder. Indeed, even this is pretty easy. You add a pointer into the class declaration. Here is an example:
vgui::TextEntry* m_pTime; // Panel class declaration, private section
Add this to the panels constructor:
m_pTime = new vgui::TextEntry(this, "MyTextEntry");
m_pTime->SetPos(15, 310);
m_pTime->SetSize(50, 20);
You'll also have to add the following include to get the constructors for TextEntry:
#include <vgui_controls/TextEntry.h>
It is time to make it appear after pressing a new option that we will add to the main menu. On this example, we will use a variable which allows us to set the state of our panel, combined with a button. Here is the example:
MyPanel.cpp
On start, add at the top under <vgui_controls/Frame.h> the following include, so we can add buttons at the code for our panel:
#include <vgui_controls/Button.h>
Add this variable into the private declaration. It will allows you to create later a button for the panel so it can close it:
private:
//Other used VGUI control Elements:
Button *m_pCloseButton;
Underneath all of the other code, add:
ConVar cl_showmypanel("cl_showmypanel", "0", FCVAR_CLIENTDLL, "Sets the state of myPanel <state>");
This line defines a new CVAR that it will make show or hide our new panel. Now continue by adding the following line at OnTick():
void CMyPanel::OnTick()
{
BaseClass::OnTick();
SetVisible(cl_showmypanel.GetBool()); //CL_SHOWMYPANEL / 1 BY DEFAULT
}
On next, add a command to toggle the panel on or off:
CON_COMMAND(ToggleMyPanel, "Toggles myPanel on or off")
{
cl_showmypanel.SetValue(!cl_showmypanel.GetBool());
};
In the last part of this tutorial we will add a button that it will make close the panel on pressing it. Add the following at the OnCommand() part:
void CMyPanel::OnCommand(const char* pcCommand)
{
BaseClass::OnCommand(pcCommand);
if(!Q_stricmp(pcCommand, "turnoff"))
cl_showmypanel.SetValue(0);
}
This will recieve the command "turnoff" that gets sent when we press the button, and on next, it will set cl_showmypanel to 0, closing the panel.
Create the button itself at the panels constructor (you can do it too at the RES file, but coding it will allow you to have more customization options):
m_pCloseButton = new Button(this, "Button", "Close", this, "turnoff");
m_pCloseButton->SetPos(433, 472);
m_pCloseButton->SetDepressedSound("common/bugreporter_succeeded.wav");
m_pCloseButton->SetReleasedSound("ui/buttonclick.wav");
We are creating a button that is parented with the panel, is called "Button", it has written on it "Close", it sends the command "turnoff", it is set to appear at botton right of our panel, and it will make two sounds, one when pressed, and another when released.
Now add
mypanel->Activate();
underneath
cl_showmypanel.SetValue(!cl_showmypanel.GetBool());
. This will
focus (bring it at the front of the screen, over all the panels) on your panel. To be able use the Activate() command, you will need to add in MyPanel.cpp.:
void Activate( void )
{
if ( MyPanel )
{
MyPanel->Activate();
}
}
Underneath:
void Destroy( void )
{
if ( MyPanel )
{
MyPanel->SetParent( (vgui::Panel *)NULL );
delete MyPanel;
}
}
And add in IMyPanel.h.:
virtual void Activate( void ) = 0;
Underneath:
virtual void Destroy( void ) = 0;
Main Menu and the panel res file
To open to your new panel from the main menu, open up GameMenu.res in your /resource/ folder, and add:
"5"//Actually this number must be in order with the rest of elements { "label" "My Panel" "command" "engine ToggleMyPanel" "notmulti" "1" //Add this only if you don't want to show this option while on a multiplayer game ("notsingle" "1" for singleplayer) }
(There are more examples of the possible variables here).
This adds on the position 5 a new option called My Panel, that on press, it will send the command "ToggleMyPanel".
And for the last step, create your panel RES design (this tutorial creates one at the ui folder inside resource). You can use the following as example:
"resource/ui/mypanel.res" { "MyPanel" { "ControlName" "CMyPanel" "fieldName" "MyPanel" "xpos" "797" "ypos" "301" "wide" "512" "tall" "512" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" "settitlebarvisible" "1" "title" "MOD OPTIONS" } "frame_topGrip" { "ControlName" "Panel" "fieldName" "frame_topGrip" "xpos" "8" "ypos" "0" "wide" "496" "tall" "5" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_bottomGrip" { "ControlName" "Panel" "fieldName" "frame_bottomGrip" "xpos" "8" "ypos" "507" "wide" "486" "tall" "5" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_leftGrip" { "ControlName" "Panel" "fieldName" "frame_leftGrip" "xpos" "0" "ypos" "8" "wide" "5" "tall" "496" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_rightGrip" { "ControlName" "Panel" "fieldName" "frame_rightGrip" "xpos" "507" "ypos" "8" "wide" "5" "tall" "486" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_tlGrip" { "ControlName" "Panel" "fieldName" "frame_tlGrip" "xpos" "0" "ypos" "0" "wide" "8" "tall" "8" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_trGrip" { "ControlName" "Panel" "fieldName" "frame_trGrip" "xpos" "504" "ypos" "0" "wide" "8" "tall" "8" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_blGrip" { "ControlName" "Panel" "fieldName" "frame_blGrip" "xpos" "0" "ypos" "504" "wide" "8" "tall" "8" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_brGrip" { "ControlName" "Panel" "fieldName" "frame_brGrip" "xpos" "494" "ypos" "494" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_caption" { "ControlName" "Panel" "fieldName" "frame_caption" "xpos" "3" "ypos" "-16" "wide" "502" "tall" "23" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_minimize" { "ControlName" "Button" "fieldName" "frame_minimize" "xpos" "0" "ypos" "0" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "0" "enabled" "1" "tabPosition" "0" "labelText" "0" "textAlignment" "north-west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "2" "textinsety" "1" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "Default" "0" } "frame_maximize" { "ControlName" "Button" "fieldName" "frame_maximize" "xpos" "0" "ypos" "0" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "0" "enabled" "1" "tabPosition" "0" "labelText" "1" "textAlignment" "north-west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "2" "textinsety" "1" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "Default" "0" } "frame_mintosystray" { "ControlName" "Button" "fieldName" "frame_mintosystray" "xpos" "0" "ypos" "0" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "0" "enabled" "1" "tabPosition" "0" "labelText" "o" "textAlignment" "north-west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "2" "textinsety" "1" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "command" "MinimizeToSysTray" "Default" "0" } "frame_close" { "ControlName" "Button" "fieldName" "frame_close" "xpos" "487" "ypos" "8" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" "labelText" "r" "textAlignment" "north-west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "2" "textinsety" "1" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "Default" "0" "command" "turnoff" } "frame_menu" { "ControlName" "FrameSystemButton" "fieldName" "frame_menu" "xpos" "7" "ypos" "8" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" "textAlignment" "west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "0" "textinsety" "0" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "Default" "0" } "BuildModeDialog" { "ControlName" "BuildModeDialog" "fieldName" "BuildModeDialog" "xpos" "327" "ypos" "301" "wide" "300" "tall" "420" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" "settitlebarvisible" "1" "title" "#Frame_Untitled" } }
Final result
This is how it should look at the end both your Cpp and header files:
MyPanel.cpp
//The following include files are necessary to allow your MyPanel.cpp to compile.
#include "cbase.h"
#include "IMyPanel.h"
using namespace vgui;
#include <vgui/IVGui.h>
#include <vgui_controls/Frame.h>
#include <vgui_controls/Button.h>
//CMyPanel class: Tutorial example class
class CMyPanel : public vgui::Frame
{
DECLARE_CLASS_SIMPLE(CMyPanel, vgui::Frame);
//CMyPanel : This Class / vgui::Frame : BaseClass
CMyPanel(vgui::VPANEL parent); // Constructor
~CMyPanel(){}; // Destructor
protected:
//VGUI overrides:
virtual void OnTick();
virtual void OnCommand(const char* pcCommand);
private:
//Other used VGUI control Elements:
Button *m_pCloseButton;
};
// Constuctor: Initializes the Panel
CMyPanel::CMyPanel(vgui::VPANEL parent)
: BaseClass(NULL, "MyPanel")
{
SetParent(parent);
SetKeyBoardInputEnabled(true);
SetMouseInputEnabled(true);
SetProportional(false);
SetTitleBarVisible(true);
SetMinimizeButtonVisible(false);
SetMaximizeButtonVisible(false);
SetCloseButtonVisible(false);
SetSizeable(false);
SetMoveable(false);
SetVisible(true);
SetScheme(vgui::scheme()->LoadSchemeFromFile("resource/SourceScheme.res", "SourceScheme"));
LoadControlSettings("resource/UI/playermodelsel.res");
vgui::ivgui()->AddTickSignal(GetVPanel(), 100);
DevMsg("MyPanel has been constructed\n");
//Button done
m_pCloseButton = new Button(this, "Button", "Close", this, "turnoff");
m_pCloseButton->SetPos(433, 472);
m_pCloseButton->SetDepressedSound("common/bugreporter_succeeded.wav");
m_pCloseButton->SetReleasedSound("ui/buttonclick.wav");
}
//Class: CMyPanelInterface Class. Used for construction.
class CMyPanelInterface : public MyPanel
{
private:
CMyPanel *MyPanel;
public:
CMyPanelInterface()
{
MyPanel = NULL;
}
void Create(vgui::VPANEL parent)
{
MyPanel = new CMyPanel(parent);
}
void Destroy()
{
if (MyPanel)
{
MyPanel->SetParent((vgui::Panel *)NULL);
delete MyPanel;
}
}
void Activate(void)
{
if (MyPanel)
{
MyPanel->Activate();
}
}
};
static CMyPanelInterface g_MyPanel;
MyPanel* mypanel = (MyPanel*)&g_MyPanel;
ConVar cl_showmypanel("cl_showmypanel", "0", FCVAR_CLIENTDLL, "Sets the state of myPanel <state>");
void CMyPanel::OnTick()
{
BaseClass::OnTick();
SetVisible(cl_showmypanel.GetBool());
}
CON_COMMAND(OpenTestPanelFenix, "Toggles testpanelfenix on or off")
{
cl_showmypanel.SetValue(!cl_showmypanel.GetBool());
mypanel->Activate();
};
void CMyPanel::OnCommand(const char* pcCommand)
{
BaseClass::OnCommand(pcCommand);
if (!Q_stricmp(pcCommand, "turnoff"))
cl_showmypanel.SetValue(0);
}
IMyPanel.h
class MyPanel
{
public:
virtual void Create(vgui::VPANEL parent) = 0;
virtual void Destroy(void) = 0;
virtual void Activate(void) = 0;
};
extern MyPanel* mypanel;
Now compile your files, boot your mod, and press the new option that it should appear at the main menu. If everything goes fine, a panel will open, and pressing the button "Close" will close it. You can also close it by pressing again the main menu option, since we made it toggle.
Makefile changes on Linux
In source-sdk-2013/mp/src/game/client/client_linux32_hl2mp.mak (for multiplayer), you need to add a mention of your new file to the list of files to be built, and then a recipe to build it.
First, add the .cpp file to the list to be built:
CPPFILES= \
MyPanel.cpp \
../../common/compiledcaptionswap.cpp \
../../common/language.cpp \
...
Then, down near the "recipe" for vgui_int.cpp, add the following recipe to build MyPanel:
ifneq (clean, $(findstring clean, $(MAKECMDGOALS)))
-include $(OBJ_DIR)/MyPanel.P
endif
$(OBJ_DIR)/MyPanel.o : $(PWD)/MyPanel.cpp $(PWD)/client_linux32_hl2mp.mak $(SRCROOT)/devtools/makefile_base_posix.mak
$(PRE_COMPILE_FILE)
$(COMPILE_FILE) $(POST_COMPILE_FILE)
See also
- Modifying Source GameUI is a lighter dose of creating your own UI.
- Non RES-File Controls are controls that can be compiled in code instead of RES files like in Half-Life 1