Create a CheckListboxItem

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Revision as of 01:37, 6 March 2011 by Capek (Talk | contribs) (because references rule)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Sometimes you need a list where the items are checkable. The ItemListbox introduced in CEGUI 0.5 accepts all sorts of items, as long as they inherit ItemEntry. I'm going to teach you how to create a CheckListboxItem to put in your list.

Getting started!

I'm assuming you are just using a standard imageset, with a standard scheme (currently TaharezLook and WindowsLook are supported).

We are first going to get started by writing the looknfeel entry. This is the most important part of this tutorial, as its basically 70% of what we need to do to achieve our goal. First of all, this entry has a few states:

  1. Enabled: How the item looks when the item is enabled.
  2. Disabled: How the item looks when the item is disabled.
  3. SelectedEnabled: How the item looks when the item is selected and is enabled.
  4. SelectedDisabled: How the item looks when the item is selected and is disabled.

Writing the requirements

Open the appriopiate looknfeel file (WindowsLook.looknfeel or TaharezLook.looknfeel), and place this at the bottom (inside the Falagard tags though!)

Our start

<WidgetLook name="WindowsLook/CheckListboxItem">
</WidgetLook>

We first need to define a WidgetLook, this is where all the action happens in. It takes one attribute, name. This is simply the name of the widget you are defining. Now we are going to add the states. These states are necessary for the item to respond to certain events. For example, when the item is disabled, the state Disabled is used.

<WidgetLook name="WindowsLook/CheckListboxItem">
    <StateImagery name="Enabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
    <StateImagery name="Disabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
</WidgetLook>

We define a state via StateImagery. It requires the attribute name which specifies what state its in. Don't worry about Layer for now. Section is always used. This part takes care of actually changing the look. It requires a attribute section, this is actually WHAT to change, in this case the label. TextColour is a simple colour (black), we will define it below. Now as you can see the Enabled state just sets the text colour to black, thats all it does. Same goes for Disabled (remember this is an item, items are not commonly disabled).

Now we are going to take a look at the other two states.

<WidgetLook name="WindowsLook/CheckListboxItem">
    <StateImagery name="Enabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
    <StateImagery name="Disabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
    <StateImagery name="SelectedEnabled">
        <Layer>
            <Section section="selection" />
            <Section section="label">
                 <ColourProperty name="SelectedTextColour" />
            </Section>
        </Layer>
    </StateImagery>
    <StateImagery name="SelectedDisabled">
        <Layer>
            <Section section="selection" />
            <Section section="label">
                <ColourProperty name="SelectedTextColour" />
            </Section>
        </Layer>
    </StateImagery>
</WidgetLook>

These states pretty much speak for themselves altough there are more sections listed. Section not only defines what to change, also defines what needs to be shown. In the Enabled state we dont want it to look like its selected, so there is no selection section. Note that the order of which you put the Sections are also the Z order. In this case we are putting the selection image first, and then text. If we would do it the other way around, we wouldn't see the text.

Now we are going to add the definitions of some colours and property's.

<WidgetLook name="WindowsLook/CheckListboxItem">
    <PropertyDefinition name="TextColour" initialValue="FF000000" redrawOnWrite="true" />
    <PropertyDefinition name="SelectedTextColour" initialValue="FFFFFFFF" redrawOnWrite="true" />
    <PropertyDefinition name="SelectionBrush" initialValue="set:WindowsLook image:Background" redrawOnWrite="true" />
    <PropertyDefinition name="SelectionColour" initialValue="FF3030FF" redrawOnWrite="true" />
    <Property name="Selectable" value="True" />
    <StateImagery name="Enabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
    <StateImagery name="Disabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
    <StateImagery name="SelectedEnabled">
        <Layer>
            <Section section="selection" />
            <Section section="label">
                 <ColourProperty name="SelectedTextColour" />
            </Section>
        </Layer>
    </StateImagery>
    <StateImagery name="SelectedDisabled">
        <Layer>
            <Section section="selection" />
            <Section section="label">
                <ColourProperty name="SelectedTextColour" />
            </Section>
        </Layer>
    </StateImagery>
</WidgetLook>

Our first PropertyDefinition just defines a text for a colour used in the StateImagery sections. Same goes for the other PropertyDefinitions except the SelectionBrush. Instead of defining a colour, it defines a image. In this case it uses the WindowsLook Background image defined in the imageset. Then we got a Property, which simply implies some sort of setting. In this case, we make it a item by adding the Property 'Selectable' and setting it to true as we want it to act like an item.

Writing the functional parts

We now need to add the parts where the background, text and the selection gets drawn. This seems hard, but isn't actually. We are going to start off with a NamedArea. This is required for a listbox item as the listbox needs to know how big the item is to fit it into the list.

<WidgetLook name="WindowsLook/CheckListboxItem">
    <PropertyDefinition name="TextColour" initialValue="FF000000" redrawOnWrite="true" />
    <PropertyDefinition name="SelectedTextColour" initialValue="FFFFFFFF" redrawOnWrite="true" />
    <PropertyDefinition name="SelectionBrush" initialValue="set:WindowsLook image:Background" redrawOnWrite="true" />
    <PropertyDefinition name="SelectionColour" initialValue="FF3030FF" redrawOnWrite="true" />
    <Property name="Selectable" value="True" />
    <NamedArea name="ContentSize">
        <Area>
            <Dim type="LeftEdge" >
                <AbsoluteDim value="0" />
            </Dim>
            <Dim type="TopEdge" >
                <AbsoluteDim value="0" />
            </Dim>
            <Dim type="Width" >
                <FontDim type="HorzExtent" padding="6" />
            </Dim>
            <Dim type="Height" >
                <FontDim type="LineSpacing" />
            </Dim>
        </Area>
    </NamedArea>
    <StateImagery name="Enabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
    <StateImagery name="Disabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
    <StateImagery name="SelectedEnabled">
        <Layer>
            <Section section="selection" />
            <Section section="label">
                 <ColourProperty name="SelectedTextColour" />
            </Section>
        </Layer>
    </StateImagery>
    <StateImagery name="SelectedDisabled">
        <Layer>
            <Section section="selection" />
            <Section section="label">
                <ColourProperty name="SelectedTextColour" />
            </Section>
        </Layer>
    </StateImagery>
</WidgetLook>

I am assuming you know what the basics like the Area, Dim and such. Though FontDim is used to change the size of the item depending on a label. The part we just added now defines the size of the item. Instead of adding the whole code here again, I'm only showing the additions from now on.

Ok! Now we are going to add the actual Checkbox. This is crucial for our goal, as it allows the item to be checked upon click. We dont have to write the states and all that for the Checkbox, we just use Child! Just place this under our NamedArea and above the first StateImagery.

<Child type ="WindowsLook/Checkbox" nameSuffix="__auto__checkbox">
    <Area>
        <Dim type="LeftEdge">
            <UnifiedDim scale="0" type="LeftEdge" />
            <AbsoluteDim value="3" />
        </Dim>
        <Dim type="TopEdge">
            <UnifiedDim scale="0" type="TopEdge" />
        </Dim>
        <Dim type="Width">
            <UnifiedDim scale="1" type="Width" />
        </Dim>
        <Dim type="Height">
            <UnifiedDim scale="1" type="Height"/>
        </Dim>
    </Area>
</Child>

This adds a WindowsLook/Checkbox to our item. The suffix is necessary to make the name unique. the rest is pretty straight forward - it adds the checkbox on the left side of the item, starting from 3 pixels to make it look smooth.

Now we need a label, this part is simple and I will not spend as much time on clarifying it, it basically explains itself.

<ImagerySection name="label">
    <TextComponent>
        <Area>
            <Dim type="TopEdge">
                <AbsoluteDim value="0" />
            </Dim>
            <Dim type="LeftEdge">
                <AbsoluteDim value="18" />
            </Dim>
            <Dim type="RightEdge">
                <UnifiedDim scale="1" offset="-3" type="RightEdge" />
            </Dim>
            <Dim type="BottomEdge">
                <UnifiedDim scale="1" type="BottomEdge" />
            </Dim>
        </Area>
    </TextComponent>
</ImagerySection>

The TextComponent simply states this this can contain text. The Dim's are just positioning. Note the LeftEdge, the label starts at 18 pixels from the left. This is because the first ~15 pixels are occupied by the checkbox.

Now something important! The selection image.

<ImagerySection name="selection">
    <ImageryComponent>
        <Area>
            <Dim type="TopEdge">
                <AbsoluteDim value="0" />
            </Dim>
            <Dim type="LeftEdge">
                <AbsoluteDim value="0" />
            </Dim>
            <Dim type="RightEdge">
                <UnifiedDim scale="1" type="RightEdge" />
            </Dim>
            <Dim type="BottomEdge">
                <UnifiedDim scale="1" type="BottomEdge" />
            </Dim>
        </Area>
        <ImageProperty name="SelectionBrush" />
        <ColourProperty name="SelectionColour" />
        <VertFormat type="Stretched" />
        <HorzFormat type="Stretched" />
    </ImageryComponent>
</ImagerySection>

Note that we give it the name selection, as used in the StateImagery. The Area part is just positioning and fitting again. The Dim's used there simply make sure the whole item is filled. ImageProperty defines the image to use. We have defined SelectionBrush at the top of our WidgetLook. It simply fills the Area with that image now. ColourProperty colours the image, we have also defined SelectionColour at the top of the WidgetLook. VertFormat and HorzFormat are stretched, if its not stretched, it will only draw one pixel of the image, making it near invisible. Stretching it just makes sure the whole area is covered.

That was it! We wrote our looknfeel entry!!

<WidgetLook name="WindowsLook/CheckListboxItem">
    <PropertyDefinition name="TextColour" initialValue="FF000000" redrawOnWrite="true" />
    <PropertyDefinition name="SelectedTextColour" initialValue="FFFFFFFF" redrawOnWrite="true" />
    <PropertyDefinition name="SelectionBrush" initialValue="set:WindowsLook image:Background" redrawOnWrite="true" />
    <PropertyDefinition name="SelectionColour" initialValue="FF3030FF" redrawOnWrite="true" />
    <Property name="Selectable" value="True" />
    <NamedArea name="ContentSize">
        <Area>
            <Dim type="LeftEdge" >
                <AbsoluteDim value="0" />
            </Dim>
            <Dim type="TopEdge" >
                <AbsoluteDim value="0" />
            </Dim>
            <Dim type="Width" >
                <FontDim type="HorzExtent" padding="6" />
            </Dim>
            <Dim type="Height" >
                <FontDim type="LineSpacing" />
            </Dim>
        </Area>
    </NamedArea>
    <Child type ="WindowsLook/Checkbox" nameSuffix="__auto__checkbox">
        <Area>
            <Dim type="LeftEdge">
                <UnifiedDim scale="0" type="LeftEdge" />
                <AbsoluteDim value="3" />
            </Dim>
            <Dim type="TopEdge">
                <UnifiedDim scale="0" type="TopEdge" />
            </Dim>
            <Dim type="Width">
                <UnifiedDim scale="1" type="Width" />
            </Dim>
            <Dim type="Height">
                <UnifiedDim scale="1" type="Height"/>
            </Dim>
        </Area>
    </Child>
    <ImagerySection name="label">
        <TextComponent>
            <Area>
                <Dim type="TopEdge">
                    <AbsoluteDim value="0" />
                </Dim>
                <Dim type="LeftEdge">
                    <AbsoluteDim value="18" />
                </Dim>
                <Dim type="RightEdge">
                    <UnifiedDim scale="1" offset="-3" type="RightEdge" />
                </Dim>
                <Dim type="BottomEdge">
                    <UnifiedDim scale="1" type="BottomEdge" />
                </Dim>
            </Area>
        </TextComponent>
    </ImagerySection>
    <ImagerySection name="selection">
        <ImageryComponent>
            <Area>
                <Dim type="TopEdge">
                    <AbsoluteDim value="0" />
                </Dim>
                <Dim type="LeftEdge">
                    <AbsoluteDim value="0" />
                </Dim>
                <Dim type="RightEdge">
                    <UnifiedDim scale="1" type="RightEdge" />
                </Dim>
                <Dim type="BottomEdge">
                    <UnifiedDim scale="1" type="BottomEdge" />
                </Dim>
            </Area>
            <ImageProperty name="SelectionBrush" />
            <ColourProperty name="SelectionColour" />
            <VertFormat type="Stretched" />
            <HorzFormat type="Stretched" />
        </ImageryComponent>
    </ImagerySection>
    <StateImagery name="Enabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
    <StateImagery name="Disabled">
         <Layer>
              <Section section="label">
                  <ColourProperty name="TextColour" />
              </Section>
         </Layer>
    </StateImagery>
    <StateImagery name="SelectedEnabled">
        <Layer>
            <Section section="selection" />
            <Section section="label">
                 <ColourProperty name="SelectedTextColour" />
            </Section>
        </Layer>
    </StateImagery>
    <StateImagery name="SelectedDisabled">
        <Layer>
            <Section section="selection" />
            <Section section="label">
                <ColourProperty name="SelectedTextColour" />
            </Section>
        </Layer>
    </StateImagery>
</WidgetLook>

Pfew am I glad thats done.

Coding our item

We have come to the part where we actually try out our new item. We need to do a few things to call this one finished. First, we need to create the CheckListboxItem class. Then we are going to add the window factory to the system. Last but not least is adding the Falagard mapping. We'll start with the CheckListboxItem class.

CheckListItem.h

#ifndef _CHECKLISTITEM_
#define _CHECKLISTITEM_
#include <stdio.h>
#include <stdlib.h>
#include <CEGUI.h>

namespace CEGUI { class CheckListboxItem : public ItemEntry { protected: public: CheckListboxItem(const String &type, const String &name); virtual ~CheckListboxItem(); static const String WidgetTypeName; }; CEGUI_DECLARE_WINDOW_FACTORY(CheckListboxItem) } #endif

As you can see, this class inherits ItemEntry. This is necessary as its THE thing that makes this item working properly. Pay special attention to CEGUI_DECLARE_WINDOW_FACTORY(CheckListboxItem). This tells CEGUI that we are declarating the window.

CheckListItem.cpp

#include "CheckListItem.h"

namespace CEGUI { CEGUI_DEFINE_WINDOW_FACTORY(CheckListboxItem) const String CheckListboxItem::WidgetTypeName("CEGUI/CheckListboxItem"); CheckListboxItem::CheckListboxItem(const String &type, const String &name) : ItemEntry(type, name) { } CheckListboxItem::~CheckListboxItem() { } }

CEGUI_DEFINE_WINDOW_FACTORY(CheckListboxItem) is quite important here. It tells CEGUI where we are placing the code that comes with the window. As you can see this class is quite empty, you are, of course, free to add anything you like to this class - these are just the mere basics.

Now, there is one more important thing to do. Register this window properly in CEGUI.

namespace CEGUI
{
void bindCEGUI()
{
    CEGUI::WindowFactoryManager& wfMgr = CEGUI::WindowFactoryManager::getSingleton();
    wfMgr.addFactory(&CEGUI_WINDOW_FACTORY(CheckListboxItem));
    wfMgr.addFalagardWindowMapping("WindowsLook/CheckListboxItem", "CEGUI/ItemEntry", "WindowsLook/CheckListboxItem", "Falagard/ItemEntry");
}
}

addFalagardWindowMappings parameters are as follows: Name, Type, LooknFeel, WindowRenderer. Just put this code somewhere in your main file or on another place that you are sure is executed before using our new item. Also note, in order to use CEGUI_WINDOW_FACTORY() you need to be in the CEGUI namespace.

Example

main.cpp

#include <stdio.h>
#include <stdlib.h>
#include "SDLLoader.h"
#include "CheckListItem.h"
void doStuff();

namespace CEGUI { void bindCEGUI() { CEGUI::WindowFactoryManager& wfMgr = CEGUI::WindowFactoryManager::getSingleton(); wfMgr.addFactory(&CEGUI_WINDOW_FACTORY(CheckListboxItem)); wfMgr.addFalagardWindowMapping("WindowsLook/CheckListboxItem", "CEGUI/ItemEntry", "WindowsLook/CheckListboxItem", "Falagard/ItemEntry"); } } int main (int argc, char **argv) { SDLLoader sdl; sdl.init(); CEGUI::bindCEGUI(); doStuff(); sdl.main_loop(); return 0; }
void doStuff() { CEGUI::System * m_System = CEGUI::System::getSingletonPtr(); CEGUI::Logger::getSingleton().setLoggingLevel(CEGUI::Insane); CEGUI::WindowManager * m_WndMgr = CEGUI::WindowManager::getSingletonPtr(); CEGUI::Window * m_Root = m_System->getGUISheet();
CEGUI::ItemListbox * lb = (CEGUI::ItemListbox*)m_WndMgr->createWindow("WindowsLook/ItemListbox", "Lol"); lb->setSize(CEGUI::UVector2(CEGUI::UDim(0.3f, 0.0f), CEGUI::UDim(0.3f, 0.0f))); lb->setPosition(CEGUI::UVector2(CEGUI::UDim(0.1f, 0.0f), CEGUI::UDim(0.1f, 0.0f))); m_Root->addChildWindow(lb);
CEGUI::CheckListboxItem * check = (CEGUI::CheckListboxItem*)m_WndMgr->createWindow("WindowsLook/CheckListboxItem", "Test"); check->setText("Test"); lb->addChildWindow(check); }

As you can see in the main function, the function that registers the window is called before actually creating it. Dont pay any attention to the sdl initiation - it's just my personal test environment.

Congratulations! You have just created a CheckListboxItem. This was a very good practice because you also learned the technique required to write a completely new widget.

Please contact me if you have any question, comments, feedback - anything, email me at levia at openfrag dot com.



--User:Levia 21:10, 5 April 2007 (GMT+1)