Skip to content

User Interface

Matt Styles edited this page May 6, 2020 · 2 revisions

User interface (or UI) elements in xygine can be created in one of two ways, depending on their intended usage. Immediate mode UIs are created via Dear ImGui which is embedded in xygine. A simple subset of functions are exported via the xy::ui namespace, but Dear ImGui's functions can also be used directly. Callback UIs are created via the ECS and are ideal for in-game menu systems which interect with the rest of the Scene.

Immediate Mode UIs

Immediate mode UIs are are very useful for creating interfaces which works on 'live' data, such as debug output, or creating development tools where the user interface updates object properties, such as the values of shader uniforms. There is an example of this kind of usage in the SimpleParticleEditor project in the xygine repository.

To use the immediate mode UI in xygine include <xyginext/gui/GuiClient.hpp> and <xyginext/gui/Gui.hpp>. The UI can either draw a stand alone window, or create a new tab which appears in the console window, opened by pressing F1. In either case the class which declares the UI must inherit GuiClient. As immediate mode UIs tend to work on live data, their lifetime has to be correctly managed so that it matches the lifetime of the data with which they work. To do this use either

registerConsoleTab("Some Tab", myTabFunction);

or

registerWindow(myWindowFunction);

Both of these are supplied by the GuiClient interface, and make sure that any class that uses them will also automatically remove any registered UI elements. The functions myTabFunction and myWindowFunction supplied as arguments contain the code required to draw either of the UI elements, and work in exactly the same way as Dear ImGui. Functions from the xy::ui namespace can be used to create the UI, as well as being able to use Dear ImGui directly.

auto myTabFunction = []()
{
    ImGui::Text("Hello World!");
};

auto myWindowFunction = []()
{
    xy::ui::begin();
    xy::ui::slider("My Slider", output, 1.f, 100.f);

    if(xy::ui::button("Click Me!"))
    {
        LogI << "button clicked!" << std::endl;
    }

    xy::ui::end();
}

Note that the window function requires begin() and end() - but the tab function does not!

Callback Based UIs

Game menus are usually more suited to callback UIs. These are created via a Scene using the ECS. To create a button from a Sprite entity all that is needed is to add a UIHitBox component, and add a UISystem to the active Scene. The UIHitbox component provides an active 'area' within which mouse events are received, and an array of callback IDs that refer to the callback to be performed on specific events. Such callbacks are registered via the UISystem and can be applied to one or more UIHitBox components.

std::uint32_t clickOne = scene.getSystem<xy::UISystem>().addMouseButtonCallback(
    [](xy::Entity e, sf::Uint64 flags)
    {
        if(flags & xy::UISystem::LeftMouse)
        {
            LogI << "Click One!" << std::endl;
        }
    });

std::uint32_t clickTwo = scene.getSystem<xy::UISystem>().addMouseButtonCallback(
    [](xy::Entity e, sf::Uint64 flags)
    {
        if(flags & xy::UISystem::LeftMouse)
        {
            LogI << "Click Two!" << std::endl;
        }
    });

std::uint32_t mouseEnter = scene.getSystem<xy::UISystem>().addMouseMoveCallback(
    [](xy::Entity e, sf::Vector2f delta)
    {
        e.getComponent<xy::Sprite>().setColour(sf::Color::Red);
    });

std::uint32_t mouseExit = scene.getSystem<xy::UISystem>().addMouseMoveCallback(
    [](xy::Entity e, sf::Vector2f delta)
    {
        e.getComponent<xy::Sprite>().setColour(sf::Color::White);
    });

auto entity = scene.createEntity();
entity.addComponent<xy::Transform>().setPosition(positionOne);
entity.addComponent<xy::Drawable>();
auto bounds = entity.addComponent<xy::Sprite>(spriteTexture).getTextureBounds();
entity.addComponent<xy::UIHitBox>().area = bounds;
entity.getComponent<xy::UIHitBox>().callbacks[xy::UIHitBox::MouseUp] = clickOne;
entity.getComponent<xy::UIHitBox>().callbacks[xy::UIHitBox::MouseEnter] = mouseEnter;
entity.getComponent<xy::UIHitBox>().callbacks[xy::UIHitBox::MouseExit] = mouseExit;

entity = scene.createEntity();
entity.addComponent<xy::Transform>().setPosition(positionTwo);
entity.addComponent<xy::Drawable>();
bounds = entity.addComponent<xy::Sprite>(spriteTexture).getTextureBounds();
entity.addComponent<xy::UIHitBox>().area = bounds;
entity.getComponent<xy::UIHitBox>().callbacks[xy::UIHitBox::MouseDown] = clickTwo;
entity.getComponent<xy::UIHitBox>().callbacks[xy::UIHitBox::MouseEnter] = mouseEnter;
entity.getComponent<xy::UIHitBox>().callbacks[xy::UIHitBox::MouseExit] = mouseExit;

In the above example the bounds of the sprite is used to define the UIHitBox area. Notice that the two buttons have different mouse click callbacks assigned - but also both share the same enter/exit callbacks used highlight the currently hovered button.

As the system also requires access to the current input event it should be forwarded via

scene.getSystem<xy::UISystem>().handleEvent(evt);

The UIHitbox component can also be assigned a group ID. Only one group of UIHitbox controls can be active at a time, which is set with UISystem::setActiveGroup(). This is useful for managing multiple menus created with a single UISystem.

For a full list of available callbacks for mouse, keyboard and joystick events, see the doxygen documentation.

Clone this wiki locally