-
Notifications
You must be signed in to change notification settings - Fork 19
User Interface
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 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!
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.