T3: Overview of Gadget Features
We have already mentioned the HIRO_DRAW library in the T2: Creating Custom Modules. Let’s now look at more possibilities to render your data beyond simple geometries.
Important
Whenever you change the visualization without user interaction, the method Gadget::DrawOnNextUpdate should be called to notify the system about the change.
The method is required when you are using the Optimized rendering setting,
otherwise, the system might not notice the change, and HIRO will not redraw the screen.
Camera Focus
Have you seen and tried the button with magnifying glass icon in the Gadget GUI title bar?
That is the camera focus feature.
It lets users immediately set the camera pose to view the scene’s data correctly.
Override the method Gadget::FocusCamera to define the camera’s custom pose.
GUI Generator
Another functionality that HIRO offers for custom gadgets is a simple creation of an interactive GUI. One of the possible uses is manipulation with the visual settings of rendered objects.
First, the function Gadget::GenerateGui must be overridden for your derived gadget class.
The system automatically calls the method, and the gui parameter is a reference to the GuiGenerator object that corresponds to this gadget.
Options in the sidebar panel can be modified using this reference.
Explore possibilities of each GUI element in namespace hiro::gui documentation and use them to your advantage.
1void MyGadget::GenerateGui(hiro::GuiGenerator &gui)
2{
3 hiro::Gadget::GenerateGui(gui);
4 gui.AddCheckbox("Material visible") // creates a checkbox with specified caption
5 ->SetLink(&is_mat_visible_) // makes the variable to reflect the checkbox state
6 ->Set(true) // changes the state of checkbox to true
7 ->Subscribe([](const hiro::gui::Checkbox *checkbox) {
8 // this lambda function will be invoked every time a checkbox state is changed
9 });
10};
It is possible to set up an element to be visible only under some conditions.
This can be done using SetConditionFunc or SetConditionBool methods.
1...
2 // Class MyGadget has a boolean member variable called "is_mat_visible_".
3 // There are two ways to set an element to be visible only when the value is true.
4
5 gui.AddNumericInt("Material id 1")
6 ->SetConditionBool(&is_mat_visible_);
7
8 gui.AddNumericInt("Material id 2")
9 ->SetConditionFunc([this](){
10 return is_mat_visible_;
11 });
12...
Method GenerateGui is called automatically and cannot be called multiple times.
If you wish to re-generate your GUI, use the method Resource::ResetGadgets - it refreshes all gadgets created by the resource that is called this method.
Hint
During the creation of a gadget object, the following functions are called in this order:
Constructor
InitializeGenerateGui
When overriding method Initialize, be sure to also call superclass method Gadget::Initialize at the beginning.
Otherwise, some features of your Gadget will not work correctly.
Text Printing
HIRO provides a straightforward interface enabling the rendering of any 2D text in the application window.
The Gadget class has a RenderTexts method.
You can override it and feed your text to be rendered every frame.
1...
2void MyGadget::RenderTexts(hiro::draw::TextRenderer &t_renderer)
3{
4 hiro::Gadget::RenderTexts(t_renderer);
5 t_renderer.SetAlignment(hiro::draw::TextAlignment::center);
6 t_renderer.SetColor(cogs::color::RED);
7
8 const auto proj = GetProjectionParams();
9 t_renderer.Print( {proj.width / 2, proj.height / 2}, "Your text here!");
10 DrawOnNextUpdate();
11}
12...
The engine automatically calls the method RenderTexts``on every draw, passing in a ``TextRenderer reference.
When overriding a method, make sure to call the parent’s method as well.
You can set various attributes, such as text alignment and color.
Finally, call the Print method, providing a 2D position vector and your text.
Method TextRenderer::Print takes text position as the first parameter.
The position is relative to the top left corner of the viewarea in which the gadget exists.
Hint
Method GetProjectionParams returns ProjectionParams structure that describes the projection used in the current viewarea. It holds helpful information about viewport, camera view, and projection and can be used, for example, to cast camera rays via ProjectionParams::CastRay.
Events
You may find yourself in a situation where you wish to affect the renderers and their styles by the events produced by the user. Gadget class provides several options for processing input, such as:
keyboard key press/release
mouse button press/release
mouse wheel rotate
mouse move
view resize
Override these methods to define your custom behavior.
This example shows processing keyboard key press event by overriding the method Gadget::KeyPressed.
1...
2hiro::EventStatus MyGadget::KeyPressed (
3 hiro::Key key,
4 const hiro::ModKeys &mods,
5 hiro::EventStatus status
6)
7{
8 if (hiro::Gadget::KeyPressed(key, mods, status) == hiro::EventStatus::processed)
9 {
10 return hiro::EventStatus::processed;
11 }
12 if (status == hiro::EventStatus::processed)
13 {
14 return hiro::EventStatus::unprocessed;
15 }
16 if (key == hiro::Key::space)
17 {
18 std::cout << "Space key was pressed!" << std::endl;
19 return hiro::EventStatus::processed;
20 }
21 return hiro::EventStatus::unprocessed;
22}
23...
The KeyPressed method comes with various parameters.
The parameter key describes which key was pressed by the user.
You can use the const hiro::ModKeys &mods parameter to check if any modifier keys, such as CTRL or Shift, were pressed.
Finally, the status is a state flag specifying whether HIRO has already processed this event.
Good manners are that you should not process the event when it has the status hiro::EventStatus::processed
since another object already processed the event. However, there may be exceptions.
As before, always remember to call the parent’s method when overriding.
Hint
The event functions should return hiro::EventStatus::processed if they processed the event successfully.
Auto-save System
During algorithm debugging, the programmer may frequently introduce changes in the code, and the application is started over and over. Also, during application runtime, users can adjust rendering, show/hide some elements, or change parameter values. To spare the debugging time, we recommend storing the state of the gadget, which HIRO can restore after the next application startup. Lucky you! HIRO offers a save/load system that is very simple to use.
If you wish to use an auto-save feature, call Gadget::LoadState in your Initialization method. This method ensures two things.
First, it loads the previously-stored state file and invokes the ReadFromStream method.
Second, it tells the system that you wish to use the auto-save feature, and from now on, WriteToStream will be triggered whenever a change to the generated GUI is introduced.
To define custom state values that should be stored/loaded, override the ReadFromStream and WriteToStream methods.
Check the superclass load method as shown in the example below.
The boolean result of the method tells the system whether the data has been loaded correctly.
1...
2void MyGadget::Initialize()
3{
4 Gadget::Initialize();
5 LoadState();
6}
7
8uint32_t MyGadget::STATE_VERSION = 0;
9
10bool MyGadget::ReadFromStream(std::istream &str)
11{
12 // Read super class state. If it fails, do not continue.
13 if (!Gadget::ReadFromStream(str))
14 return false;
15
16 // Read style. If it fails, do not continue.
17 if (!style_->ReadFromStream(str))
18 return false;
19
20 // Check if the file data is not deprecated. If it is, do not continue.
21 if (!ReadStateVersion(str, STATE_VERSION))
22 return false;
23
24 // Read custom properties.
25 std_ext::Read(is_mat_visible_, str);
26
27 // Everything went successfuly.
28 return true;
29}
30
31void MyGadget::WriteToStream(std::ostream &str)
32{
33 // This method should reflect reading, except... it is writing.
34 Gadget::WriteToStream(str);
35 style_->WriteToStream(str);
36 WriteStateVersion(str, STATE_VERSION);
37 std_ext::Write(is_mat_visible_, str);
38}
39...
Constant STATE_VERSION is a custom number that defines the version of your read/write code.
It is recommended to use ReadStateVersion and WriteStateVersion to ensure that the values you read will not be corrupted when you read from a stale file.
You should increase the STATE_VERSION number every time the changes are introduced to the read/write methods.
Attention
When you introduced changes to the ReadFromStream/WriteToStream method, a common issue with state saving can be caused by the following:
changes were not reflected correctly in the other of the two methods, causing the inconsistency between the read and write code
STATE_VERSIONwas not increased, causing the stale old files to be considered new
Therefore, always take care when changing the ReadFromStream/WriteToStream methods.