Skip to content

Latest commit

 

History

History
1181 lines (828 loc) · 53 KB

7.md

File metadata and controls

1181 lines (828 loc) · 53 KB

Chapter 7: UI

Overview

In Cocos2d-x v3.x, we have three ways to deal with GUI programming.

  • Label, Menu & MenuItem and TextFieldTTF, which have limited functions. But they are simple and easy to use.

  • Control Extensions under extension module plus a simple ScrollView and TableView implementation. It also has a handy EditBox for text input

Note:(Now, Since v3.3 beta, the EditBox class has been moved to UI module and the original cocos2d::extensions::EditBox has been marked as DEPRECATED. In the future release of cocos2d-x, the ScrollView and TableView will also be DEPRECATED and we will provide alternative replacement in UI module.).

The Control Extensions exists mainly for CocosBuilder. The parser of CocosBuilder uses this module heavily. There is very handy class in Control Extensions named Scale9Sprite which could help drawing slice 9 enabled sprite on screen. Now the Scale9Sprite is also marked as DEPRECATED and a replacement is ui::Scale9Sprite which has full functionality of the original one plus more features.

  • The brand new UI module. The recommended way to handling GUI programming in Cocos2d-x.

So which UI module should I choose when dealing with GUI programming in Cocos2d-x?

There is one rule:

Always stick to the new UI module, because we also have an UI editor to layout your UI elements visually.

Label, Menu & MenuItem and TextFieldTTF

Labels

Cocos2d-x v3 has unified the creation of Label into a single API. There are a few different types of Labels you can choose from.

BMFont

BMFont is a label type that uses a bitmap font. Bitmap fonts consist of a matrix of dots or pixels representing the image of each glyph in each face and size. It is very faster and easy to use, but non-scalable, requiring a separate font for each size.

In order to create a BMFont label, we should provide two files: A fooBar.fnt file and a fooBar.png file.

auto myLabel = cocos2d::Label::createWithBMFont("fooBar.fnt", "My Label Text");

TTF

TTF is a label type that uses a True Type Font. You must provide a True Type font file to feed the Label::createWithTTF function.

The primary strength of TrueType was originally that it offered font developers a high degree of control over precisely how their fonts are displayed, right down to particular pixels, at various font sizes.

When we create a TTF Label , it will create a 512 * 512 texture at first, when we change the Label text, it will search the character from the cached texture. If not found, it will create a new character and put it into the texture. When it reaches the limit of the texture, we will create another 512 * 512 texture and cache the new character in it. Thus we could keep the memory foot print as minimal as possible and also improve text rendering speed.

So it will not run as fast as BMFont when changing the Label content during game play frequently.

Here is the code to create a TTF Label with "myfont.ttf":

auto myLabel = cocos2d::Label::createWithTTF("myfont.ttf", "My Label Text", 16);

We could also use a more complex way to create TTF Labels:

TTFConfig labelConfig;
labelConfig.fontFilePath = "myfont.ttf";
labelConfig.fontSize = 16;
//choose the glyphs, which includes DYNAMIC, NEHE, ASCII and CUSTOM now.
labelConfig.glyphs = GlyphCollection::DYNAMIC;
labelConfig.outlineSize = 0;
labelConfig.distanceFieldEnabled = false;
auto mylabel = cocos2d::Label::createWithTTF(labelConfig, "My Label Text");

So it mainly be used for displaying Chart message or some static text information.

Label Atlas

Label Atlas is a label type that uses texture atlas.

auto mylabl = cocos2d::Label::createWithCharMap("fonts/labelatlasimg.png",24,32,'0');

Here is the charmap png file, its dimension is 240 * 32 and it contains 10 numbers from 0-9, so each number occupy 24*32.

The 2rd and 3rd arguments of Label::createWithCharMap is the itemWith and itemHeight.

charmap

You could consider Label Atlas as a simple BMFont with limited font customization. It runs as fast as BMFont and usually used for displaying rapid changing game scores.

SystemFont

SystemFont is a label type that usese the default system font and font size.

auto myLabel3 = cocos2d::Label::createWithSystemFont("My Label Text", "Arial", 16);

Shadow, Outline and Glow

The new Label supports Shadow, Outline and Glow effect, here are the APIs to enable this effect:

    /**
     * Enable shadow for the label
     *
     * @todo support blur for shadow effect
     */
    virtual void enableShadow(const Color4B& shadowColor = Color4B::BLACK,const Size &offset = Size(2,-2), int blurRadius = 0);

    /** only support for TTF, the System Font Outline effect only works on Android and IOS
    virtual void enableOutline(const Color4B& outlineColor,int outlineSize = -1);

    /** only support for TTF */
    virtual void enableGlow(const Color4B& glowColor);

As we see from the comments part, the Shadow effect is supported for all kind of Label types, but Outline effect only works well on all platform with TTF font, the SystemFont Outline effect only supports IOS and Android and the Glow effect only works with TTF font. That's because we use FreeType2 to rendering TTF font on various platform since v3.0.

Text Alignment

The new label support Horizontal and Vertical text alignment. The APIs are very straightforward:

    void setAlignment(TextHAlignment hAlignment) { setAlignment(hAlignment,_vAlignment);}
    TextHAlignment getTextAlignment() const { return _hAlignment;}

    void setAlignment(TextHAlignment hAlignment,TextVAlignment vAlignment);

    void setHorizontalAlignment(TextHAlignment hAlignment) { setAlignment(hAlignment,_vAlignment); }
    TextHAlignment getHorizontalAlignment() const { return _hAlignment; }

    void setVerticalAlignment(TextVAlignment vAlignment) { setAlignment(_hAlignment,vAlignment); }
    TextVAlignment getVerticalAlignment() const { return _vAlignment; }

Line Wrap

The new label also supports Line Wrap, you could specify a dimension to your label, when a text line reach the boundary of the dimension, it will automatically break the current line.

    /** Sets the untransformed size of the label.
     * The label's width be used for text align if the set value not equal zero.
     * The label's max line width will be equal to the same value.
     */
    void setWidth(unsigned int width) { setDimensions(width,_labelHeight);}
    unsigned int getWidth() const { return _labelWidth; }  

    /** Sets the untransformed size of the label.
     * The label's height be used for text align if the set value not equal zero.
     * The text will display of incomplete when the size of label not enough to support display all text.
     */
    void setHeight(unsigned int height){ setDimensions(_labelWidth,height);}
    unsigned int getHeight() const { return _labelHeight;}   

    /** Sets the untransformed size of the label in a more efficient way. */
    void setDimensions(unsigned int width,unsigned int height);
    const Size& getDimensions() const{ return _labelDimensions;}

There is one more thing we should keep in mind. On default, the line break are word-wise operation which means when a word is reaching the boundary of the dimension, if the remaining space is not enough for the word, the word will be put into the next new line. You could change this behavior by calling setLineBreakWithoutSpace(true) which is character-wise operation and the word might be divided into two parts.

Tips & Tricks

  • Please Don't use LabelTTF and LabelBMFont, they are marked as DEPRECATED classes and will be removed in the future.

  • When dealing with Label related programming, always stick to the new Label.

  • When you want to change the color of the label text ,please use setTextColor instead of setColor, the latter will change the text color as well as the Shadow/Outline/Glow color.

Menu&Menu Items

A Menu is a way to navigate through game options. Menus often contain options like 'Play', 'Quit', 'Settings' and 'About'. This is usually in the form of buttons that are pressed.

What makes up a menu

A Menu is made up of a base node called Menu. It is easy to create a Menu.

auto myMenu = cocos2d::Menu::create();

Menu Items and adding to a Menu

MenuItems are the meat of any Menu. Menu Items usually have a normal and a selected state as well as a callback...something that happens when the MenuItem is selected.

// creating a menu with a single item
auto closeItem = cocos2d::MenuItemImage::create("CloseNormal.png", "CloseSelected.png", CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));

cocos2d::Mneu* menu = cocos2d::Menu::create(closeItem, NULL);
this->addChild(menu, 1);

// creating a Menu from a Vector of items
cocos2d::Vector<cocos2d::MenuItem*> MenuItems;

auto closeItem = cocos2d::MenuItemImage::create("CloseNormal.png", "CloseSelected.png", CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));

MenuItems.pushBack(closeItem);

/* repeat for as many menu items as needed */

auto menu = cocos2d::Menu::createWithArray(MenuItems);
this->addChild(menu, 1);

Lambdas as Menu callback

A lambda function is a function that you can write inline in your source code. Cocos2d-x makes use of Lambdas. You can even use them as callbacks!

// a simple lambda
auto func = [] () { cout << "Hello world"; };
func(); // now call the function

// creating a CallFunc with a lambda
auto action1 = cocos2d::CallFunc::create(
[&](){
        std::cout << "using a Lambda callback" << std::endl;
    });
    
// creating an std::function using a lambda
std::function<void()> myFunction = []()
{
        std::cout << "From myFunction()" << std::endl;
};
auto action2 = cocos2d::CallFunc::create(myFunction);

// creating a MenuItemImage with a lambda callback
auto closeItem = cocos2d::MenuItemImage::create("CloseNormal.png", "CloseSelected.png", [&](cocos2d::Ref* sender){
                        // your code here
                     });

Limitations

Menu uses scene graph touch event listener to handle touch events, so the touch event could be handled properly when overlay occurs between menuItems and only the menuItem which has the highest ZOrder will receive the touch event.

But it works badly with ScrollView, PageView and ListView, when the menuItem is outside of the clipping area of these parent container, the touch is still handled which is really weird.

TextFieldTTF

TextFieldTTF is a simple class for inputting text. It's a OpenGL implementation and works on all platform that cocos2d-x supports. In fact, it's just a simple Label plus a few IME features.

Here is the class declaration:

class CC_DLL TextFieldTTF : public Label, public IMEDelegate
{
    //other 
}

So TextField supports all the features of Label, such as Text Alignment, Line Wrap and Outline/Shadow/Glow effect in theory.

We could use the following method to create a TextFieldTTF with a predefined place holder:

    _textField = TextFieldTTF::textFieldWithPlaceHolder("<click here for input>",
        FONT_NAME, //FONT_NAME is integer const define
        FONT_SIZE); //FONT_SIZE is also a integer const define
    //add _textField to the scene graph
    addChild(_textField);

    _textField->setDelegate(this);
    _textField->setPosition(Vec2(s.width / 2, s.height / 2));

After the TextFieldTTF instance has been added to the scene graph, you must call the following method to pop up the keyboard for inputting.

_textField->attachWithIME();

Similarly we could call the following method to close the keyboard.

_textField->detachWithIME();

We could also implement the TextFieldDelegate to get notifications when the the Keyboard is attached or detached and also the input text event and delete text event.

Here is the full interface explanation:

    /**
    @brief    If the sender doesn't want to attach to the IME, return true;
    */
    virtual bool onTextFieldAttachWithIME(TextFieldTTF * sender)
    
    /**
    @brief    If the sender doesn't want to detach from the IME, return true;
    */
    virtual bool onTextFieldDetachWithIME(TextFieldTTF * sender)

    /**
    @brief    If the sender doesn't want to insert the text, return true;
    */
    virtual bool onTextFieldInsertText(TextFieldTTF * sender, const char * text, size_t nLen)
    
    /**
    @brief    If the sender doesn't want to delete the delText, return true;
    */
    virtual bool onTextFieldDeleteBackward(TextFieldTTF * sender, const char * delText, size_t nLen)

Control Extensions, extension::EditBox, extension::ScrollView and extension::TableView

Overview

In this section, we will talk about all the Controls under extensions/GUI/CCControlExtension plus extra more discuss of extension::Scale9Sprite, extension::ScrollView, extension::TableView and extension::EditBox.

All of these classes are under cocos2d::extension namespace so if you want to use them ,please include the right header file and import the namespace:

    #include"cocos-ext.h"
    using namespace cocos2d::extension.

Control Extensions

The Control Extensions module was original designed for CocosBuilder and now it is not the recommended way to handling GUI programming and we don't plan to add any new features to these controls. So use them under your own risk.

There are many controls, such as ControlButton, ControlColorPicker, ControlSlider, ControlSwitch etc. Since they are not the recommended ways to handling UI programming, so we don't want to give more explanation to them. For more usage information, pleases refer to the tests.

Limitation: It doesn't handle touch event properly when controls become invisible only because they are outside of parent container's clipping area.

extension::Scale9Sprite

This class is marked as DEPRECATED since v3.3 beta and it is just a typedef of ui::Scale9Sprite.

CC_DEPRECATED_ATTRIBUTE typedef ui::Scale9Sprite Scale9Sprite;

For more document of extension::Scale9Sprite, please refer to ui::Scale9Sprite section.

extension::ScrollView

A ScrollView is a Layer which has a clipping area and an inner container with a fixed bounding area. We could add any node descendants to the ScrollView. In fact, all the nodes are added into the inner container. We could drag the inner container to move it around. When some elements become outside of the clipping area, they will become invisible automatically which is really handy when display content large than the current screen size.

Here we could use the following method to create a ScrollView:

    auto sprite = Sprite::create("HelloWorld.png");
    // the Size parameter is the clipping area size
    auto scrollView = ScrollView::create(Size(200,200));
    //we only create horizontal scroll enabled scrollview
    scrollView->setDirection(ScrollView::Direction::HORIZONTAL);
    scrollView->setPosition(Vec2(100,100));
    this->addChild(scrollView);
    //add a sprite to the scrollview, if the sprite's content size is larger than the clipping area of scrollview, only part of the sprite inside the clipping area will be visible
    scrollView->addChild(sprite);

The implementation of extension::ScrollView is not very godd and the scroll behavior is also not very natural. So we designed a new ui::ScrollView instead. It has more features than the extension::ScrollView. In the future, the class will also be DEPRECATED.

extension::TableView

The extension::TableView is a very basic, minimal implementation to bring UITableView-like component into cocos2d-x.

It's a subclass of extension::ScrollView. It needs a DataSource delegate as the data input.

When display data item, it will reuse TableCells when needed to increase the performance. So when you want to display a large portion of data, the extension::TableView is a good option here.

If you want to use extension::TableView, you must implement the TableViewDataSource delegate at first.

Note: Because we don't want to put UI code scatter cross the whole engine, so it will be merge into the UI module in the near future and more features will be added after the merging.

In the following code, besides the DataSource protocol, we have also implemented the TableViewDelegate protocol which is used for handling which TableCell is been touched.

class TableViewTestLayer : public cocos2d::Layer,public cocos2d::extension::TableViewDataSource, public cocos2d::extension::TableViewDelegate{
   //other stuff is omitted
    
    //TableViewDelegate method
    virtual void scrollViewDidScroll(cocos2d::extension::ScrollView* view) {};
    virtual void scrollViewDidZoom(cocos2d::extension::ScrollView* view) {}
    //TableViewDataSource method
    virtual void tableCellTouched(cocos2d::extension::TableView* table, cocos2d::extension::TableViewCell* cell);
    virtual cocos2d::Size tableCellSizeForIndex(cocos2d::extension::TableView *table, ssize_t idx);
    virtual cocos2d::extension::TableViewCell* tableCellAtIndex(cocos2d::extension::TableView *table, ssize_t idx);
    virtual ssize_t numberOfCellsInTableView(cocos2d::extension::TableView *table);
};

void TableViewTestLayer::tableCellTouched(TableView* table, TableViewCell* cell)
{
    CCLOG("cell touched at index: %ld", cell->getIdx());
}

Size TableViewTestLayer::tableCellSizeForIndex(TableView *table, ssize_t idx)
{
    if (idx == 2) {
        return Size(100, 100);
    }
    return Size(60, 60);
}

TableViewCell* TableViewTestLayer::tableCellAtIndex(TableView *table, ssize_t idx)
{
    auto string = String::createWithFormat("%ld", idx);
    TableViewCell *cell = table->dequeueCell();
    if (!cell) {
        cell = new (std::nothrow) CustomTableViewCell();
        cell->autorelease();
        auto sprite = Sprite::create("Images/Icon.png");
        sprite->setAnchorPoint(Vec2::ZERO);
        sprite->setPosition(Vec2(0, 0));
        cell->addChild(sprite);

        auto label = Label::createWithSystemFont(string->getCString(), "Helvetica", 20.0);
        label->setPosition(Vec2::ZERO);
		label->setAnchorPoint(Vec2::ZERO);
        label->setTag(123);
        cell->addChild(label);
    }
    else
    {
        auto label = (Label*)cell->getChildByTag(123);
        label->setString(string->getCString());
    }


    return cell;
}

ssize_t TableViewTestLayer::numberOfCellsInTableView(TableView *table)
{
    return 20;
}

Here is the code to create TableView: we create a TableView with size 60 * 250 and set the TableViewDataSource to the this pointer which is a TableViewTestLayer instance. Then we set the ScrollDirection to VERTICAL. we also the delegate class to the TableViewTestLayer. At last, we call reloadData method to tell the TableView to fill in the control with the data from TableViewdataSource.

	tableView = TableView::create(this, Size(60, 250));
	tableView->setDirection(ScrollView::Direction::VERTICAL);
	tableView->setPosition(Vec2(winSize.width-150,winSize.height/2-120));
	tableView->setDelegate(this);
	tableView->setVerticalFillOrder(TableView::VerticalFillOrder::TOP_DOWN);
	this->addChild(tableView);
	tableView->reloadData();

extension::EditBox

This class is marked as DEPRECATED since v3.3 beta and it is just a typedef of ui::EditBox:

CC_DEPRECATED_ATTRIBUTE typedef ui::EditBox EditBox;
CC_DEPRECATED_ATTRIBUTE typedef ui::EditBoxDelegate EditBoxDelegate;

For more document of extension::EditBox, please refer to ui::EditBox section.

New GUI Module

Overview

The new GUI module is a widget-based GUI framework, it was original designed for CocosStudio. It tries to break the stalemate that cocos2d-x lack a well defined, official supported featured GUI solution. We know GUI framework design is a hard topic for us, but we will improve it with the community.

Now it is the only GUI module under actively developed. New features will be added continually and bug report will be quickly responded. So when you are dealing with GUI programming, please give it a try.

In this section, we show you the architecture of the GUI module and the features it provides.

Architecture

The root class of the new GUI module is ui::Widget which is inherited from cocos2d::ProtectedNode. A ProtectedNode is designed for holding inner Node list such that when you are adding or removing child node from the ProtectedNode, the "Innder Node List" won't be touched which is safe to keep widget's inner rendering components.

We could simply divide the GUI module into two parts: the general Widgets part and the Container part.

Here is a figure to show you the big picture:

ui-architecture

From the picture, we can see that besides the container and widgets parts, we also have misc and experimental parts.

The misc part provide some auxiliary function for the rest of UI module, such as slice 9 enabled sprite drawing, find child widget by name recursively etc.

The experimental part is intent for putting none cross platform Widgets. The VideoPlyaer and WebView are two very handy and ready to use widget type, but they only works for iOS and Android. We will take about these two Widget later in the end of document.

Now, we will take a look at the Container part. Here is the figure:

ui-container

The Layout class is the root class of all containers. It is also inherited from Widget. Layout class exists mainly for arranging children widgets and clipping. It also supports background color and background image which is handy when layout it on a UI editor.

The LayoutManager, LayoutParameter and Margin classes are used for layout elements. HBox, VBox and RelativeBox are handy classes for layout children widgets horizontally, vertically and relatively.

The ScrolView, ListView and PageView are specific containers for certain usage scenario. We will talk it in another section.

At last, we will take a look at the Widgets part. It contains many handy widget to help our daily GUI programming.

Here is a figure contains all the widgets:

ui-detailed

We will discuss these widgets in detail in the next sections.

All of these Widget class share the same model below:

ui-model

They are all ProtectedNode with a few nodes as their inner rendering components. We will talk about the details of each Widget class in detail.

Feature List

  1. Touch event capturing mimic. The current touch event model of cocos2d-x only support bubble which means the touch event is received at the target node and bubble to its parents. There is no capture phase as compare to the DOM event model.

Consider a scenario, we have a ScrollView and a few buttons added, if we click the button, the touch event won't be passed to the ScrollView. If we want to click the button and drag to move the ScrollView, the ScrolView won't be dragged which is not the desired behavior.

The new UI module solve this problem and we could drag the element within ScrollView(ListView,PageView) to move the container.

  1. Clipping: stencil VS scissor. The Layout class and its descendants supports two types of clipping: stencil and scissor.

When we enable clipping, the child element in Layout will only be draw inside the bounds of Layout.

  1. UTF8 support When dealing with text display and text input, UTF8 support is a must have feature. All the text related classes in new UI module support UTF8.

  2. Percent positioning and percent content size. Percent positioning is a handy way to position widget. When you want to positioning the child widget always to the right top corner of its parents. We could simply set the child widget's position type to PositionType::PERCENT and set the PercentPosition to Vec2(1,1).

Percent content size is similar to percent position. If we want a child widget's content size always equal to the half content size of its partent, we could specify the child widget's size type to SizeType::PERCENT and set the SizePercent to Vec2(0.5,0.5).

  1. Widget state management. In general, a Widget will contains three state: Normal, Active(Pressed) and Disabled. All the widgets in the new UI module take care of the state management and change their appearance accordingly to the state switch.

  2. Widget focus. When you have a few widgets on the screen, you might want one of them to be focused and you could simply specify the focus change direction, the framework will do the rest for you to move the focus to the next proper widget. This feature is very helpful if you want to use cocos2d-x to program OTT UI interface.

  3. Layout widgets Layout is a must have feature in any UI framework. Currently the new UI module supports Horizontal, Vertical and Relative Layout. We could also specify the Margin property of each widget, these proper will be calculated during layouting.

If you don't want to implement these features by yourself, you might give the new UI module a try.

Scale9Sprite

This class is heavily used by the new UI module as well as the Control Extension module. When you want to stretch a image dynamically accordingly to the device screen resolution, the normal scale operation doesn't work very well, Scale9Sprite comes to rescue.

We could use the following code to create a patch 9 sprite easily:

        auto sprite_scaled_with_insets = Sprite::create("Images/blocks9.png");
        auto blocks_scaled_with_insets = ui::Scale9Sprite::create();
        blocks_scaled_with_insets->updateWithSprite(sprite_scaled_with_insets, Rect(0, 0, 96, 96), false, Rect(32, 32, 32, 32));
        blocks_scaled_with_insets->setContentSize(Size(96 * 4.5, 96 * 2.5));
        blocks_scaled_with_insets->setPosition(Vec2(x, y));
        this->addChild(blocks_scaled_with_insets);

This code create a patch 9 sprite with capInset equals to Rect(32, 32, 32, 32), the rectangle divide the whole sprite into 9 part. The internal implementation of ui::Scale9Sprite do use 9 sprites to represent each piece.

when the patch 9 sprite is stretching, the 4 sprite in the corner will stay the same, the 4 sprites in top,left,right and down will only stretch in one direction, either horizontal or vertical. The inner most sprite will stretch in two directions.

Here is the figure:

scale9sprie

You might also want to take a look at this article, it was written for CCScale9Sprite of cocos2d-iphone, but the theory behind is more or less the same as ui::Scale9Sprite.

For more usage of Scale9Sprite, please take a look at the UIScale9SpriteTest

Buttons

A button intercepts touch event and calls a predefined callback when tapped. It is inherited from ui::Widget. This class provide methods for setting the title, image and other properties to meet your own needs.

Architecture

A button is a ProtectedNode node along with a few inner rendering components, here is the figure:

ui-button

On default, the title of the button is displayed and only the normal state sprite will be displayed. When you tap the button, the state will change to pressed and its appearance will be changed accordingly.

Example

Here is the code to create a button with two images, a title and a callback.

    // the third parameter is optional
    Button* button = Button::create("cocosui/animationbuttonnormal.png",
                                        "cocosui/animationbuttonpressed.png");
    button->setTitleText("Text Button");
    button->setPosition(Point(widgetSize.width / 2.0f, widgetSize.height / 2.0f));
    // here we could also pass a lambda as callback
    button->addTouchEventListener(CC_CALLBACK_2(UIButtonTest::touchEvent, this));        
    this->addChild(button);

For more usage tests, please refer to UIButtonTest for more information.

Tips and tricks

On default, when you tap a button, it will zoom to a specified size. You could call setZoomScale to change the default behavior.

Architecture

Example

CheckBox

A checkbox permits the user to make a binary choice.

Architecture

A checkbox contains five node as its inner rendering components.

ui-checkobx

These five components are explained as below:

  • groundBoxNormal goundboxNormal

  • groundBoxSelected grondboxselectd

  • groundBoxDisabled groundboxdisabled

  • frontCrossNormal frontCrossNormal

  • frontCrossDisabled frontCrossDisabled

Example

Here is the code snippets to create a simple checkBox:

        // Create the checkbox
        CheckBox* checkBox = CheckBox::create("cocosui/check_box_normal.png",
                                              "cocosui/check_box_normal_press.png",
                                              "cocosui/check_box_active.png",
                                              "cocosui/check_box_normal_disable.png",
                                              "cocosui/check_box_active_disable.png");
        checkBox->setPosition(Vec2(widgetSize.width / 2.0f, widgetSize.height / 2.0f));
        //when user check or uncheck the widget, the following callback will be called. 
        checkBox->addEventListener(CC_CALLBACK_2(UICheckBoxTest::selectedEvent, this));
        _uiLayer->addChild(checkBox);

LoadingBar

A loading bar could be used to visualize the progression of an operation, such as download, file transfer etc. The loading bar has two direction(LEFT or RIGHT).

Architecture

The LoadingBar contains a Scale9Sprite as its progress indicator, here is the figure to show you the relationship:

loadingbar

Example

Here is the code snippet to create a loading bar:

        // Create the loading bar
        LoadingBar* loadingBar = LoadingBar::create("cocosui/sliderProgress.png");
        loadingBar->setDirection(LoadingBar::Direction::RIGHT);
        loadingBar->setPosition(Vec2(widgetSize.width / 2.0f,
                                      widgetSize.height / 2.0f + loadingBar->getContentSize().height / 4.0f));
        _uiLayer->addChild(loadingBar);

Slider

A Slider(track bar) allows user to set value by moving a indicator.

Architecture

A slider contains a Scale9Sprite as background and a Scale9Sprite as the progress indicator. It also contains a ball to allow user touch and drag. The ball renderer is just a Node with three Sprites as children.

Here is the figure to show you the inner rendering components of Slider:

slider

Example

Here is the code snippet to create a slider :

        Slider* slider = Slider::create();
        slider->loadBarTexture("cocosui/sliderTrack.png");
        slider->loadSlidBallTextures("cocosui/sliderThumb.png", "cocosui/sliderThumb.png", "");
        slider->loadProgressBarTexture("cocosui/sliderProgress.png");
        slider->setPosition(Vec2(widgetSize.width / 2.0f, widgetSize.height / 2.0f/* + slider->getSize().height * 2.0f*/));
        slider->addEventListener(CC_CALLBACK_2(UISliderTest::sliderEvent, this));
        _uiLayer->addChild(slider);

ImageView

A ImageView is a placeholder for displaying images. It wraps a Scale9Sprite as its inner renderer, so we could call setScale9Enabled method to draw 9-patch images. It it inherited from ui::Widget class, so it supports touch event, focus, percent positioning and percent content size.

Architecture

A ImageView contains a Scale9Sprite as inner rendering component.

ui-imageview

Example

The usage of ImageView is very straightforward, here is the code snippet:

  • Create a ImageView from a file:
        ImageView* imageView = ImageView::create("cocosui/ccicon.png");
        imageView->setPosition(Vec2(widgetSize.width / 2.0f,
                                     widgetSize.height / 2.0f));
        _uiLayer->addChild(imageView);
  • Create a ImageView from a SpriteFrame name:
        ImageView* imageView = ImageView::create("cocosui/ccicon.png", TextureResType::PLIST);
        imageView->setPosition(Vec2(widgetSize.width / 2.0f,
                                     widgetSize.height / 2.0f));
        _uiLayer->addChild(imageView);

Text

A Text widget is used for display texts. You could also use it as a Text-only button. It is also inherited from Widget class ,so it supports touch event, focus, percent positioning and percent content size.

Architecture

A Text contains a Label as inner rendering component, so it supports all the feature of Label, such as Shadow, Outline and Glow. It also supports TTF and SystemFont.

ui-text

Example

  • Create Text with SystemFont:
        Text* text = Text::create("Text", "AmericanTypewriter", 30);
        text->setPosition(Vec2(widgetSize.width / 2.0f, widgetSize.height / 2.0f + text->getContentSize().height / 4.0f));
        _uiLayer->addChild(text);
  • Create Text With TrueType Font:
        Text* text = Text::create("Text","fonts/A Damn Mess.ttf",30);
        text->setPosition(Vec2(widgetSize.width / 2.0f, widgetSize.height / 2.0f + text->getContentSize().height / 4.0f));
        _uiLayer->addChild(text);
  • Enable effects on Text:
        // create the shadow only label
        Text* shadow_label = Text::create();
        shadow_label->enableShadow();
        shadow_label->setString("Shadow");
        shadow_label->setPosition(Vec2(widgetSize.width / 2.0f, widgetSize.height / 2.0f + shadow_label->getContentSize().height));
        _uiLayer->addChild(shadow_label);
        
        // create the stroke only label
        Text* glow_label = Text::create();
        glow_label->setFontName("fonts/Marker Felt.ttf");
        glow_label->setString("Glow");
        glow_label->enableGlow(Color4B::RED);
        glow_label->setPosition(Vec2(widgetSize.width / 2.0f, widgetSize.height / 2.0f));
        
        _uiLayer->addChild(glow_label);
        
        
        // create the label stroke and shadow
        Text* outline_label = Text::create();
        outline_label->enableOutline(Color4B::BLUE, 2);
        outline_label->setString("Outline");
        outline_label->setPosition(Vec2(widgetSize.width / 2.0f, widgetSize.height / 2.0f - shadow_label->getContentSize().height));
        _uiLayer->addChild(outline_label);
  • Use Text as a text-only button:
    auto text = Text::create("Click Me", "Arial", 20);
    //call this method will zoom the text when tapped
    text->setTouchScaleChangeEnabled(true);
    // Text doesn't enable touch on default
    text->setTouchEnabled(true);
    text->addClickEventListener([=](Ref*){
        CCLOG("clicked");
    });
    text->setPosition(Vec2(100,100));
    this->addChild(text, 20);

TextBMFont

A TextBMFont widget is used for display BMFont texts. It is also inherited from Widget class, so it supports touch event, focus, percent positioning and percent content size.

Architecture

A TextBMFont contains a Label as inner rendering component.

ui-bmfont

Example

Here is a code snippet to create a TextBMFont from a Bitmap Font file:

        TextBMFont* textBMFont = TextBMFont::create("BMFont", "cocosui/bitmapFontTest2.fnt");
        textBMFont->setPosition(Vec2(widgetSize.width / 2, widgetSize.height / 2.0f + textBMFont->getContentSize().height / 8.0f));
        _uiLayer->addChild(textBMFont);

TextAtlas

A TextAtlas widget is used for display atlas font. It is also inherited from Widget class, so it supports touch event, focus, percent positioning and percent content size.

Architecture

A TextAtlas contains a Label as inner rendering component:

ui-textatlas

Example

Here is a code snippet to create a TextAtlas from a atlas font file:

        TextAtlas* textAtlas = TextAtlas::create("1234567890", "cocosui/labelatlas.png", 17, 22, "0");
        textAtlas->setPosition(Vec2((widgetSize.width) / 2, widgetSize.height / 2.0f));
        _uiLayer->addChild(textAtlas);                

RichText

A RichText widget is used for displaying text, image and custom node. It is inherited from Widget class, so it supports touch event, focus, percent positioning and percent content size. Note: the touch event is received by the whole RichText not the individual RichElement. The clickable RichElement will be supported in the future.

Architecture

RichText holds a vector of RichElement as its inner rendering components:

ui-richtext

Example

Here is a quick sample code to create the RichText:

        _richText = RichText::create();
        _richText->ignoreContentAdaptWithSize(false);
        _richText->setContentSize(Size(100, 100));
        
        RichElementText* re1 = RichElementText::create(1, Color3B::WHITE, 255, str1, "Marker Felt", 10);
        RichElementText* re2 = RichElementText::create(2, Color3B::YELLOW, 255, "And this is yellow. ", "Helvetica", 10);
        RichElementText* re3 = RichElementText::create(3, Color3B::GRAY, 255, str2, "Helvetica", 10);
        RichElementText* re4 = RichElementText::create(4, Color3B::GREEN, 255, "And green with TTF support. ", "fonts/Marker Felt.ttf", 10);
        RichElementText* re5 = RichElementText::create(5, Color3B::RED, 255, "Last one is red ", "Helvetica", 10);
        
        RichElementImage* reimg = RichElementImage::create(6, Color3B::WHITE, 255, "cocosui/sliderballnormal.png");
        
        cocostudio::ArmatureDataManager::getInstance()->addArmatureFileInfo("cocosui/100/100.ExportJson");
        cocostudio::Armature *pAr = cocostudio::Armature::create("100");
        pAr->getAnimation()->play("Animation1");
        
        RichElementCustomNode* recustom = RichElementCustomNode::create(1, Color3B::WHITE, 255, pAr);
        RichElementText* re6 = RichElementText::create(7, Color3B::ORANGE, 255, "Have fun!! ", "Helvetica", 10);
        _richText->pushBackElement(re1);
        _richText->insertElement(re2, 1);
        _richText->pushBackElement(re3);
        _richText->pushBackElement(re4);
        _richText->pushBackElement(re5);
        _richText->insertElement(reimg, 2);
        _richText->pushBackElement(recustom);
        _richText->pushBackElement(re6);
        
        _richText->setPosition(Vec2(widgetSize.width / 2, widgetSize.height / 2));
        _richText->setLocalZOrder(10);
        
        _widget->addChild(_richText);

Here is the screenshot:

sample

Limitations

  • It only supports manually RichElement arrangement now, the HTML tag based creation will be added in the future.
  • Various text effects are also not supported.
  • The clickable RichElement is also absent at the moment.

TextField

A TextField Widget is used for inputting text. It is inherited from Widget class, so it supports touch event, focus, percent positioning and percent content size.

Architecture

The TextField class contains a UICCTextFieldTTF as its inner rendering component, the UICCTextFieldTTF is inherited from TextFieldTTF and implements the TextFieldDelegate.

ui-textfiled

Example

Here is the code snippet to create a TextField:

        TextField* textField = TextField::create("input words here","Arial",30);
        textField->setPosition(Vec2(widgetSize.width / 2.0f, widgetSize.height / 2.0f));
        textField->addEventListener(CC_CALLBACK_2(UITextFieldTest::textFieldEvent, this));
        _uiLayer->addChild(textField);

        void UITextFieldTest::textFieldEvent(Ref *pSender, TextField::EventType type)
        {
            switch (type)
            {
                case TextField::EventType::ATTACH_WITH_IME:
                {
                     //do something
                }
                    break;

                case TextField::EventType::DETACH_WITH_IME:
                {
                }
                    break;

                case TextField::EventType::INSERT_TEXT:
                    break;

                case TextField::EventType::DELETE_BACKWARD:
                    break;

                default:
                    break;
            }
        }

ScrollView

A ScrollView widget is a container which could hold many child widget, the display area is usually smaller than its inner container. So it is a good option to display large portion of elements within a small screen size.

Architecture

A ScrollView is a Layout with clipping enabled, it contains another Layout as its inner container. All the elements will be added into the inner container instead of the ScrollView itself.

ui-scrollview

Example

        // Create the scrollview by vertical
        ui::ScrollView* scrollView = ui::ScrollView::create();
        scrollView->setContentSize(Size(280.0f, 150.0f));
        Size backgroundSize = background->getContentSize();
        scrollView->setPosition(Vec2((widgetSize.width - backgroundSize.width) / 2.0f +
                               (backgroundSize.width - scrollView->getContentSize().width) / 2.0f,
                               (widgetSize.height - backgroundSize.height) / 2.0f +
                               (backgroundSize.height - scrollView->getContentSize().height) / 2.0f));
        _uiLayer->addChild(scrollView);
        
        ImageView* imageView = ImageView::create("cocosui/ccicon.png");
        
        float innerWidth = scrollView->getContentSize().width;
        float innerHeight = scrollView->getContentSize().height + imageView->getContentSize().height;
        
        scrollView->setInnerContainerSize(Size(innerWidth, innerHeight));                
        
        Button* button = Button::create("cocosui/animationbuttonnormal.png", "cocosui/animationbuttonpressed.png");
        button->setPosition(Vec2(innerWidth / 2.0f, scrollView->getInnerContainerSize().height - button->getContentSize().height / 2.0f));
        scrollView->addChild(button);
        
        Button* titleButton = Button::create("cocosui/backtotopnormal.png", "cocosui/backtotoppressed.png");
        titleButton->setTitleText("Title Button");
        titleButton->setPosition(Vec2(innerWidth / 2.0f, button->getBottomBoundary() - button->getContentSize().height));
        scrollView->addChild(titleButton);
        
        Button* button_scale9 = Button::create("cocosui/button.png", "cocosui/buttonHighlighted.png");
        button_scale9->setScale9Enabled(true);
        button_scale9->setContentSize(Size(100.0f, button_scale9->getVirtualRendererSize().height));
        button_scale9->setPosition(Vec2(innerWidth / 2.0f, titleButton->getBottomBoundary() - titleButton->getContentSize().height));
        scrollView->addChild(button_scale9);
        
        imageView->setPosition(Vec2(innerWidth / 2.0f, imageView->getContentSize().height / 2.0f));
        scrollView->addChild(imageView);            

Here is the screenshot:

ListView

A ListView is a widget which show items in horizontal or vertical. ListView is inherited from ScrollView, but it only supports horizontal and vertical scrolling.

Architecture

Here is the figure to show you the class hierarchy of ListView:

ui-listview

Example

Here is a code snippet to show you how to create a ListView with few buttons. In the following code, we also enable bouncing and touch along side with a background image.

        // Create the list view ex
        ListView* listView = ListView::create();
        // set list view ex direction
        listView->setDirection(ui::ScrollView::Direction::HORIZONTAL);
        listView->setTouchEnabled(true);
        listView->setBounceEnabled(true);
        listView->setBackGroundImage("cocosui/green_edit.png");
        listView->setBackGroundImageScale9Enabled(true);
        listView->setContentSize(Size(240, 130));
        listView->setPosition(Vec2(240,160));
        listView->addEventListener((ui::ListView::ccListViewCallback)CC_CALLBACK_2(UIListViewTest_Horizontal::selectedItemEvent, this));
        this->addChild(listView);
        //add listview items into the ListView
        
        // add custom item
        for (int i = 0; i < count / 4; ++i)
        {
            Button* custom_button = Button::create("cocosui/button.png", "cocosui/buttonHighlighted.png");
            custom_button->setName("Title Button");
            custom_button->setScale9Enabled(true);
            custom_button->setContentSize(default_button->getContentSize());
            
            Layout* custom_item = Layout::create();
            custom_item->setContentSize(custom_button->getContentSize());
            custom_button->setPosition(Vec2(custom_item->getContentSize().width / 2.0f, custom_item->getContentSize().height / 2.0f));
            custom_item->addChild(custom_button);
            
            listView->pushBackCustomItem(custom_item);
        }
        // insert custom item

PageView

A PageView is used as a container to display contents which we could view them as tuning book pages.

Architecture

The PageView is inherited from Layout and it contains a vector of Layout as its pages.

ui-pageview

Example

Here is the code to create a PageView with three pages:

        // Create the page view
        PageView* pageView = PageView::create();
        pageView->setContentSize(Size(240.0f, 130.0f));
        Size backgroundSize = background->getContentSize();
        pageView->setPosition(Vec2(100,100));
        
        int pageCount = 3;
        for (int i = 0; i < pageCount; ++i)
        {
            Layout* layout = Layout::create();
            layout->setContentSize(Size(240.0f, 130.0f));
            
            ImageView* imageView = ImageView::create("cocosui/scrollviewbg.png");
            imageView->setScale9Enabled(true);
            imageView->setContentSize(Size(240, 130));
            imageView->setPosition(Vec2(layout->getContentSize().width / 2.0f, layout->getContentSize().height / 2.0f));
            layout->addChild(imageView);
            
            pageView->insertPage(layout,i);
        }
        
        pageView->addEventListener(CC_CALLBACK_2(UIPageViewTest::pageViewEvent, this));
        
        _uiLayer->addChild(pageView);

Layout

The Layout class exists mainly for three reasons: as a Layout container, as a clipping layer and as a focus helper class. In this section, we will mainly talk about the container and clipping part. The Focus functionality will be talked in a separated section.

In order to understand the Layout feature of Layout class, we should first take a loot at the LayoutParameter and LayoutManager class.

LayoutParameter

Every Widget class could have a LayoutParameter property. There are two types of LayoutParameter: LinearLayoutParameter and RelativeLayoutParameter, they are used for horizontal/vertical layout and Relative layout.

LayoutManger

LayoutManager is the class which takes care of the actually layout operation. There are LiearVerticalLayoutManager, LinearHorizontalLayoutManager and RelativeLayoutManger which are used for vertical, horizontal and relative layout respectively.

HBOX , VBox & RelativeBox

The HBox is just a Layout with layout type equal Layout::Type::HORIZONTAL, the VBox is also a layout with layout type equals Layout::Type::VERTICAL and the RelativeLayout is just a Layout with layout type equals Layout::Type::Relative.

There are all handy class for layout elements. And we also use HBox & VBox to manage the focus of our UI interface.

Example

Here is a code snippet to create a linear vertical layout.

        // Create the layout
        Layout* layout = Layout::create();
        layout->setLayoutType(LayoutType::VERTICAL);
        layout->setContentSize(Size(280, 150));
        Size backgroundSize = background->getContentSize();
        layout->setPosition(Vec2(200,150));
        _uiLayer->addChild(layout);
        
        
        Button* button = Button::create("cocosui/animationbuttonnormal.png", "cocosui/animationbuttonpressed.png");
        layout->addChild(button);
        
        LinearLayoutParameter* lp1 = LinearLayoutParameter::create();
        button->setLayoutParameter(lp1);
        lp1->setGravity(LinearLayoutParameter::LinearGravity::CENTER_HORIZONTAL);
        lp1->setMargin(Margin(0.0f, 5.0f, 0.0f, 10.0f));
        
        
        Button* titleButton = Button::create("cocosui/backtotopnormal.png", "cocosui/backtotoppressed.png");
        titleButton->setTitleText("Title Button");
        layout->addChild(titleButton);
        
        LinearLayoutParameter* lp2 = LinearLayoutParameter::create();
        titleButton->setLayoutParameter(lp2);
        lp2->setGravity(LinearLayoutParameter::LinearGravity::CENTER_HORIZONTAL);
        lp2->setMargin(Margin(0.0f, 10.0f, 0.0f, 10.0f));
        
        
        Button* button_scale9 = Button::create("cocosui/button.png", "cocosui/buttonHighlighted.png");
        button_scale9->setScale9Enabled(true);
        button_scale9->setContentSize(Size(100.0f, button_scale9->getVirtualRendererSize().height));
        layout->addChild(button_scale9);
        
        LinearLayoutParameter* lp3 = LinearLayoutParameter::create();
        button_scale9->setLayoutParameter(lp3);
        lp3->setGravity(LinearLayoutParameter::LinearGravity::CENTER_HORIZONTAL);
        lp3->setMargin(Margin(0.0f, 10.0f, 0.0f, 10.0f));

For more usage of Layout class, please refer to UILayoutTest for more information.

EditBox

EditBox is a widget for text input, the main difference between EditBox and TextField is EditBox calls system control to handle input task. We call the platform dependent API to rendering the DeitBox and do text input.

Here is the sample usage:

        _editName = ui::EditBox::create(editBoxSize, ui::Scale9Sprite::create(pNormalSprite));
        _editName->setPosition(Vec2(visibleOrigin.x+visibleSize.width/2, visibleOrigin.y+visibleSize.height*3/4));
        _editName->setFontName("Paint Boy");
        _editName->setFontSize(25);
        _editName->setFontColor(Color3B::RED);
        _editName->setPlaceHolder("Name:");
        _editName->setPlaceholderFontColor(Color3B::WHITE);
        _editName->setMaxLength(8);
        _editName->setReturnType(ui::EditBox::KeyboardReturnType::DONE);
        _editName->setDelegate(this);
        addChild(_editName);

For more usage of EditBox, please refer to UIEditBoxTest.

VideoPlyaer

ViedoPlayer is a widget to play videos inside your game.

Note: Currently it only supports iOS and Android, so we put it under the ui::experimental namespace which is not mean the feature we provided is not mature but only due to the lack of full cross platform support.

Here is the sample code to create a VideoPlayer control:

    _videoPlayer = VideoPlayer::create();
    _videoPlayer->setPosition(centerPos);
    _videoPlayer->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
    _videoPlayer->setContentSize(Size(widgetSize.width * 0.4f,widgetSize.height * 0.4f));
    _uiLayer->addChild(_videoPlayer);
    
    _videoPlayer->addEventListener(CC_CALLBACK_2(VideoPlayerTest::videoEventCallback, this));

To play a local video file, you could call setFileName,. To play a video from a remote machine, you could call setURL.

For more usage of VideoPlayer, please refer to UIVideoPlayerTest .

WebView

WebView is a widget to display web pages inside your game.

Note: Currently it only supports iOS and Android, so we put it under the ui::experimental namespace which is not mean the feature we provided is not mature but only due to the lack of full cross platform support.

Here is the sample code to create a WebView control, it will visit google after scene enters.

        _webView = cocos2d::experimental::ui::WebView::create();
        _webView->setPosition(winSize/2);
        _webView->setContentSize(winSize * 0.5);
        _webView->loadUrl("http://www.google.com");
        _webView->setScalesPageToFit(true);

For more usage of WebView, please refer to UIWebViewTest

Limitation

  1. Currently the RichText widget doesn't support clickable item and doesn't support manually line break.

  2. The TextField widget doesn't support "input cursor blink" and it won't move the content when keyboard appears. If you want a system control, you might want to use EditBox at the moment.

  3. ListView doesn't support DataSource and also doesn't support item reuse. When display large amount data, it will not performance good. At this time, you might want to use extension::TableView stead. (In the future, we will merge the extension::tableView to the new UI module)