Developer masterclass: Creating 2-D panels (Part 3)

martins

Orbiter Founder
Orbiter Founder
This continues the tutorial from Part 2.

Lesson 6: Adding a switch (button, dial, etc.)
Apart from MFDs, the panel can contain many more active elements. A common class of elements has two or more discrete states (a switch that can be flipped up or down, a dial that can be turned to a set of discrete positions, a control light that can be on or off, etc.) Representative for all those objects, let’s add a 2-way switch now.

Active objects that have a number of discrete visual states are generally defined by one of a set of bitmaps representing the state. We can implement this in two ways: (a) blitting the bitmap displaying the required state into the background panel texture, or (b) putting a billboard texture group over the background mesh, and dynamically modifying the texture coordinates to point to a section of the texture containing an image of the required state.

There are advantages and disadvantages to both methods: (a) doesn’t increase the mesh complexity. Once the blitting operation is performed, the rendering process is as efficient as before. This method is best if the object state only changes very infrequently. (b) doesn’t require a blit, just a modification to the vertex texture coordinates. This method is best if the object state changes continuously (so that the overhead of the blitting operation becomes significant).

A switch usually changes its state only occasionally (on user input), so method (a) should be suitable and will be implemented here. I will discuss an example for method (b) in the next part.

As with the MFD button columns, the switch is defined as a class derived from PanelElement:
Code:
class MySwitch: public PanelElement {
public:
MySwitch (VESSEL3 *v);
bool Redraw2D (SURFHANDLE surf);
bool ProcessMouse2D (int event, int mx, int my);

private:
int switch_state;
};
As before, we have the Redraw2D and ProcessMouse2D functions to deal with updating the visual state and responding to user interaction. They can be implemented as follows:
Code:
MySwitch::MySwitch (VESSEL3 *v): PanelElement (v)
{
switch_state = 0;
// this is the base state as shown in the panel background
}

bool MySwitch::Redraw2D (SURFHANDLE surf)
{
int new_switch_state = GetMySwitchState();
if (new_switch_state != switch_state) {
switch_state = new_switch_state;
tx_x = tx_x0 + switch_state*tx_dx;
oapiBlt (surf, surf, btn_x, btn_y, tx_x, tx_y0, tx_w, tx_h);
}
return false;
}

bool MySwitch::ProcessMouse2D (int event, int mx, int my)
{
int curr_switch_state = GetMySwitchState();
int new_switch_state;
if (my < tx_h/2) new_switch_state = 0;
else             new_switch_state = 1;
if (new_switch_state != curr_switch_state) {
SetMySwitchState (new_switch_state);
return true;
} else
return false;
}
GetMySwitchState and SetMySwitchState are assumed to be implemented in MyVessel. In Redraw2D, btn_x and btn_y represent the target location of the button in the background texture. tx_x0 and tx_y0 are the locations of the button image in its “base state”, tx_w and tx_h are the width and height of the button in pixels. The images for the additional states are assumed to be in a horizontal row to the right of the base state, so that tx_x0 + switch_state*tx_dx picks out the required one.

In ProcessMouse2D, we flip the switch depending on whether the user clicks the top or bottom half of the switch. Returning true triggers a redraw event.

We add the switch as a new panel element in the constructor (after increasing the length of the pel array to 3):
Code:
MyVessel::MyVessel (...)
{
...
for (int i = 0; i < 2; i++)
pel[i] = new MFDButtonCol (this, i);
pel[2] = new MySwitch (this);
...
}
and register the switch as an active element inside DefineMainPanel:
Code:
void MyVessel::DefineMainPanel (PANELHANDLE hPanel)
{
...
RegisterPanelArea (hPanel, AID_MYSWITCH, _R(btn_x, btn_y, tx_w, tx_h),
PANEL_REDRAW_MOUSE, PANEL_MOUSE_LBDOWN, panel2dtex, pel[2]);
...
}
That’s it. The redraw and mouse event callback functions we have defined for the MFD button columns in the last section already take care of calling the appropriate MySwitch methods when needed.

Note: If you are implementing an entire row of switches, it is probably more efficient to define the row as a single panel element, rather than creating a new object for each individual switch.

The next part will implement a slider using mesh vertex transformations.

martins

Orbiter Founder
Orbiter Founder
Is anyone following this tutorial? Let me know if it actually works. I am writing this alongside updating the Shuttle-A panel code, but there is always a possibility of mistakes.

Out of interest: Have any existing vessel addons already adopted the new panel interface, or is the stock DG still the only pioneer out there?

Xyon

Puts the Fun in Dysfunctional
Moderator
Webmaster
GFX Staff
Donator
Beta Tester
Well, I'm giving it a go with one of my "unreleased, possibly never to be released" vessel addons, but I'm neither texturer nor modeller, so it's taking some time. :/

Thus far I am up to adding MFD areas, so far, so good.

Last edited:

n72.75

Tutorial Publisher
Donator
I've been really busy with school, but as soon as I have the time: I have some addon ideas that I've been wanting to try out. So I'll definitely use these tutorials then.

tblaxland

Webmaster
Reading, but not implementing currently. Very useful information anyway

Keatah

Active member
Invaluable information!! My first add-ons will be extra mfd displays and these masterclasses are quite useful.

Moach

Crazy dude with a rocket
i have plans to adapt the whole panel implementation for my G42 to accordance with the new methods... but i'm still waiting for further info, as it doesn't have a 2D panel, so i must have it all working on the VC :hmm:

but great reading so far! thanks

Bibi Uncle

50% Orbinaut, 50% Developer
Once again, thank you for this tutorial ! (and for this wonderful API !). However, there is something that I do not understand.

In the second code sample, I saw that you do not return true (so it returns false) when you are blitting. I found that weird because, in the documentation, it is said that we must return true when the event is process. I think (I'm not 100% sure about it) that in the 2006 version, when we didn't return true, the image was not blitted. However, I tested this code (in Orbiter 2010) and the switch is refreshing, but the function returns false.

Is it normal ? Or did I miss something ? If not, why does the callback has a boolean return if it doesn't change anything at all between a false or a true return ?

n122vu

Donator
I am attempting to implement this in my EA Cortez addon. Any chance you will continue this series with the tutorial to implement the slider using vertex transformations?

Also, any issues if I compile these lessons into a single PDF and make available? I have the PDF of the lessons thus far, created for myself so I can read it while coding.

Thanks for putting time into creating these.
Regards,
n122vu

PolliMatrix

New member
I'm developing the 2-D panel of my first add-on using the new mesh-based method. This tutorial is very helpful, especially since looking at the DeltaGlider examples by myself was a bit confusing at first.
I made some modifications, but aside from what Bibi Uncle found in part 2 I didn't notice any mistakes.
I'll try to make some custom displays and sliders myself, but are you still planning to do some more parts of this tutorial?