T3: Overview of Gadget Features — HIRO 0.7.0 documentation

# 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

- `Initialize`

- `GenerateGui`

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_VERSION` was not increased, causing the stale old files to be considered new

Therefore, always take care when changing the `ReadFromStream`/`WriteToStream` methods.