Пользовательский интерфейс
In UNIGINE a Graphical User Interface (GUI) is composed of different types of widgets added to it. Basically, there are two ways of creating GUI:В UNIGINE графический пользовательский интерфейс (GUI) состоит из добавленных к нему виджетов различных типов. Cуществует два основных способа создания графического интерфейса:
- By adding widgets to the system GUI (UNIGINE user interface) that is rendered on top of application window. In this case we use the Gui class.Добавление виджетов в системный графический интерфейс (UNIGINE Gui), который отображается поверх окна приложения. В этом случае мы используем класс Gui.
- By adding widgets to a GUI object positioned in the world. In this case, any postprocessing filter can be applied. By creating a specific Gui object (or GuiMesh for arbitrary geometry) that can be placed anywhere in the scene, and adding widgets to it. This can be useful, for example, to implement interaction with the interface displayed on a computer screen in a room (i.e. UI bound to an object that can be viewed from different angles). In this case, a post-processing filter can be applied.Создание отдельного объекта Gui (или GuiMesh для произвольной геометрии), который можно расположить где угодно в сцене, и добавления виджетов к нему. Это может пригодиться, к примеру, чтобы реализовать взаимодействие с интерфейсом, отображаемым на экране компьютера в помещении (т.е. UI с привязкой к объекту, на который можно посмотреть под разным углом). В этом случае может быть применен любой фильтр постобработки.
There are 2 ways to create the GUI layout:Существует 2 способа создания макета графического интерфейса:
- Directly from code via GUI classesНепосредственно из кода через GUI-классы.
- Using UI files with the description of user interface. Such a description file in XML-like format can be created for a complex interface to write less code.С помощью ui-файлов с описанием пользовательского интерфейса. Такой файл описания в xml-подобном формате можно подготовить для сложного интерфейса, чтобы писать меньше кода.
The following code demonstrates how to add Label and Slider widgets to the system GUI:Следующий код демонстрирует, как добавить виджеты Label и Slider в системный графический интерфейс:
// получаем ссылку на системный GUI
gui = Gui::getCurrent();
// создаем виджет Label и устанавливаем его параметры
WidgetLabelPtr widget_label = WidgetLabel::create(gui, "Label text:");
widget_label->setToolTip("This is my label!");
widget_label->arrange();
widget_label->setPosition(10, 10);
// создаем виджет Slider и устанавливаем его параметры
WidgetSliderPtr widget_slider = WidgetSlider::create(gui, 0, 360, 90);
widget_slider->setToolTip("This is my slider!");
widget_slider->arrange();
widget_slider->setPosition(100, 10);
// добавляем виджеты к системному GUI
gui->addChild(widget_label, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);
gui->addChild(widget_slider, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);
In order to use GUI elements (not just watch them rendered) we must specify handlers for various events (click, change, etc.). The following code demonstrates how to set event handlers:Чтобы элементы графического интерфейса не просто рисовались, а работали, нужно указать обработчики для различных событий (щелчок, изменение и т.д.). Пример ниже показывает, как установить обработчики событий:
// экземпляр класса EventConnections для управления подписками на события
EventConnections econnections;
/// функция-обработчик, вызываемая по щелчку на кнопке button1
int onButton1Clicked(const WidgetPtr &button)
{
/* .. */
}
/// метод-обработчик, вызываемый при изменении положения слайдера
int AppWorldLogic::onSliderChanged(const WidgetPtr &slider)
{
/* .. */
}
int AppWorldLogic::init()
{
// получаем ссылку на системный GUI
GuiPtr gui = Gui::getCurrent();
/* .. */
// подписываемся на событие 'Clicked' с обработчиком onButton1Clicked
buttonwidget1->getEventClicked().connect(econnections, onButton1Clicked);
// подписываемся на событие 'Changed' у виджета widget_slider с обработчиком AppWorldLogic::onSliderChanged
widget_slider->getEventChanged().connect(econnections, this, &AppWorldLogic::onSliderChanged);
/* .. */
return 1;
}
int AppWorldLogic::shutdown()
{
// удаляем все подписки
econnections.disconnectAll();
return 1;
}
PracticeПрактика#
In architectural visualization projects, a widespread option is changing materials on various objects, for example, to preview different wallpapers on the walls and match them with the decor of the furniture. For our project, let's create a component (also inherit it from Interactable) that displays a list of available materials for a selected object in the UI based on ObjectGui with the ability to select and automatically apply them.В проектах архитектурной визуализации часто добавляют возможность изменения материала на различных объектах, например, чтобы посмотреть, как будут выглядеть стены с разными обоями, а заодно и подобрать к ним декор гарнитура. Для нашего проекта напишем компонент (также унаследуем его от Interactable) отображающий в UI на основе ObjectGui список доступных материалов для выделенного объекта с возможностью выбора и автоматического применения.
-
Create a new component called MaterialCustomizer and add the following code to it:Создадим новый компонент и назовем его MaterialCustomizer, добавим в него следующий код:
#pragma once #include <UnigineComponentSystem.h> #include "Interactable.h" class MaterialCustomizer : public Interactable { public: // Объявляем конструктор и деструктор для нашего класса, а также задаем имя связанного с компонентом свойства (property). // Файл MaterialCustomizer.prop со всеми описанными ниже параметрами будет сгенерирован Компонентной системой в папке 'data' вашего проекта при первом запуске приложения COMPONENT_DEFINE(MaterialCustomizer, Interactable); // список материалов PROP_ARRAY(Material, MaterialsList); // регистрация методов, которые будут вызываться на соответствующих этапах жизненного цикла приложения (сами методы объявляются в protected-блоке ниже) COMPONENT_INIT(init); // перегрузка метода Action реализующая вызов контекстного меню void action(int num = 0); protected: // объявление методов, которые будут вызываться на соответствующих этапах жизненного цикла приложения void init(); private: // ссылка на GUI-объект Unigine::ObjectGuiPtr gui = nullptr; // обработчик события выбора материала в списке void select_material_handler(const Unigine::WidgetPtr& w); // вспомогательная переменная для хранения подписок на события Unigine::EventConnections econn; };
#include "MaterialCustomizer.h" #include "InputProcessor.h" // Регистрация компонента MaterialCustomizer REGISTER_COMPONENT(MaterialCustomizer); using namespace Unigine; using namespace Math; // функция, применяющая выбранный в меню материал к выделенной поверхности кастомизируемого объекта void MaterialCustomizer::select_material_handler(const WidgetPtr& w) { Unigine::ObjectPtr object = checked_ptr_cast<Unigine::Object>(node); Unigine::WidgetComboBoxPtr combo_box = checked_ptr_cast<Unigine::WidgetComboBox>(w); object->setMaterial(MaterialsList[combo_box->getCurrentItem()], ComponentSystem::get()->getComponentInWorld<InputProcessor>()->intersection->getSurface()); } void MaterialCustomizer::init() { // задаем текст подсказки, которая будет отображаться при наведении курсора на объект tooltip = "По правому щелчку мыши отображается меню, в котором можно выбрать материал для объекта."; // создаем объект GUI для отображения интерфейса выбора материала и задаем его размеры и разрешение gui = ObjectGui::create(0.5f, 0.5f); gui->setScreenSize(600, 600); // включаем обнаружение пересечений, для взаимодействия с UI при помощи мыши gui->setIntersection(true, 0); // делаем так, чтобы интерфейс всегда поворачивался к пользователю и не загораживался другими объектами gui->setBillboard(true); gui->getMaterialInherit(0)->setDepthTest(false); // отключаем черный фон и временно скрываем UI gui->setBackground(false); gui->setEnabled(false); // задаем дистанцию, с которой возможно взаимодействие с интерфейсом gui->setControlDistance(10); if (MaterialsList.size() > 0) { // добавляем виджет-контейнер WidgetVBox для вертикальной компоновки элементов, устанавливаем отступы WidgetVBoxPtr vbox = WidgetVBox::create(gui->getGui()); vbox->setSpace(20, 20); // включаем фон и задаем его цвет vbox->setBackground(1); vbox->setBackgroundColor(vec4(0.1f, 0.5f, 0.1f, 0.4f)); // создаем виджет WidgetLabel для отображения заголовка, устанавливаем его положение размер шрифта WidgetLabelPtr label = WidgetLabel::create(gui->getGui(), "Выбор материала:"); label->setFontSize(50); // добавляем виджет Label в контейнер vbox->addChild(label, Gui::ALIGN_TOP); // создаем виджет WidgetComboBox для отображения выпадающего списка материалов, WidgetComboBoxPtr mat_menu = WidgetComboBox::create(gui->getGui()); mat_menu->setFontSize(40); // устанавливаем обработчик на выбор элемента из списка mat_menu->getEventChanged().connect(econn, this, &MaterialCustomizer::select_material_handler); // добавляем элементы в меню в соответствии со списком материалов for (int i = 0; i < MaterialsList.size(); i++) { Unigine::String str = FileSystem::getVirtualPath(MaterialsList[i].get()->getFilePath()); mat_menu->addItem(str.substr(str.rfind("/") + 1)); } // добавляем виджет ComboBox в контейнер vbox->addChild(mat_menu, Gui::ALIGN_TOP); // добавляем виджет-контейнер в GUI gui->getGui()->addChild(vbox, Gui::ALIGN_EXPAND | Gui::ALIGN_OVERLAP); } } // перегрузка действия для дочернего компонента void MaterialCustomizer::action(int num) { // индексы действий, отличные от нуля, для данного компонента невалидны, поэтому игнорируются if (num != 0) return; // располагаем интерфейс в точке на кастомизируемом объекте, где была нажата правая клавиша мыши gui->setWorldPosition(ComponentSystem::get()->getComponentInWorld<InputProcessor>()->intersection->getPoint()); // скрываем интерфейс выбора материала для остальных объектов Unigine::Vector<MaterialCustomizer*> customizer_components; ComponentSystem::get()->getComponentsInWorld(customizer_components); for (MaterialCustomizer* customizer : customizer_components) if (this != customizer) customizer->gui->setEnabled(false); // отображаем или скрываем интерфейс выбора материала для данного объекта gui->setEnabled(!gui->isEnabled()); }
Let's save our files and then build and run our application by hitting Ctrl + F5 to make the Component System generate a property to be used to assign our component to nodes. Close the application after running it and switch to UnigineEditor.Сохраним файлы, а затем соберем и запустим приложение нажав Ctrl + F5, чтобы Компонентная система сгенерировала property для связи компонента с нодой. После запуска приложения закроем его и вернемся в UnigineEditor.
- Assign our new property to the bedside_table_1 object in our scene.Назначим свойство (property) на объект bedside_table_1 в нашей сцене.
-
Fill in the list of materials. To do this, change the number of items in the list from 0 to 3 and then drag three materials into the corresponding fields.Затем заполним список материалов. Для этого изменим количество элементов в списке с 0 на 3 и после этого перетащим три материала в соответствующие поля.
Upon right-clicking an object a list of available materials will be displayed, you can choose the desired one, and it will be applied automatically right as you click it:При нажатии правой кнопки мыши будет отображаться список доступных материалов, в котором щелчком левой кнопки можно выбрать нужный и он применится автоматически: