API Question How to draw custom elements on HUD?

zachary77

New member
Joined
Aug 31, 2017
Messages
80
Reaction score
1
Points
0
Hi,
I'm currently using the debug string to show flap/trim settings for my glider. This is a problem because it conflicts if two gliders are in one scenario:uhh:
So I'd like to learn how to print stuff on the HUD. Pretty simple things like FLAPS 1/2 etc. on top left.
I've read the Sketchpad documentation but don't understand what exactly is required:facepalm:
Any help would be appreciated.
Thanks,
zachary77
 

GLS

Well-known member
Orbiter Contributor
Addon Developer
Joined
Mar 22, 2008
Messages
5,914
Reaction score
2,908
Points
188
Website
github.com
Your vessel class needs to implement clbkDrawHUD( int mode, const HUDPAINTSPEC *hps, oapi::Sketchpad *skp ). The hps pointer gives info about the HUD (size, etc) and the skp allows you to draw lines, print text, etc.
 

Notebook

Addon Developer
Addon Developer
News Reporter
Donator
Joined
Nov 20, 2007
Messages
11,816
Reaction score
640
Points
188
I've been working for years trying to get an engine test-stand and bunker for one of my add-ons. This is how far I got:

Code:
// ==============================================================
//                 ORBITER MODULE: DialogTemplate
//                  Part of the ORBITER SDK
//          Copyright (C) 2003-2010 Martin Schweiger
//                   All rights reserved
//
// MFDTemplate.cpp
//
// This module demonstrates how to build an Orbiter plugin which
// inserts a new MFD (multi-functional display) mode. The code
// is not very useful in itself, but it can be used as a starting
// point for your own MFD developments.
// ==============================================================

#define STRICT
#define ORBITER_MODULE
#include "windows.h"
#include "orbitersdk.h"
#include "MFDTemplate.h"

// ==============================================================
// Global variables

int g_MFDmode; // identifier for new MFD mode

// ==============================================================
// API interface

DLLCLBK void InitModule (HINSTANCE hDLL)
{
	static char *name = "MFD Template";   // MFD mode name
	MFDMODESPECEX spec;
	spec.name = name;
	spec.key = OAPI_KEY_T;                // MFD mode selection key
	spec.context = NULL;
	spec.msgproc = MFDTemplate::MsgProc;  // MFD mode callback function

	// Register the new MFD mode with Orbiter
	g_MFDmode = oapiRegisterMFDMode (spec);
}

DLLCLBK void ExitModule (HINSTANCE hDLL)
{
	// Unregister the custom MFD mode when the module is unloaded
	oapiUnregisterMFDMode (g_MFDmode);
}

// ==============================================================
// MFD class implementation

// Constructor
MFDTemplate::MFDTemplate (DWORD w, DWORD h, VESSEL *vessel)
: MFD2 (w, h, vessel)
{
	font = oapiCreateFont (w/20, true, "Arial", FONT_NORMAL, 0);
	
	pRed = oapiCreatePen(1, 1, MFD_RED);
	pGreen = oapiCreatePen(1, 1, MFD_GREEN);
	pBlue = oapiCreatePen(1,1, MFD_BLUE);
	pWhite = oapiCreatePen(1,1, MFD_WHITE);
	pGaslox = oapiCreatePen(2,1, MFD_GASLOX);
	pControl = oapiCreatePen(2,1, MFD_Control);
	
	bBlack = oapiCreateBrush(MFD_BLACK);
	bWhite = oapiCreateBrush(MFD_WHITE);
	bRed = oapiCreateBrush(MFD_RED);
	bGreen = oapiCreateBrush(MFD_GREEN);
	bBlue = oapiCreateBrush(MFD_BLUE);
	bGaslox = oapiCreateBrush(MFD_GASLOX);
	bGasnit = oapiCreateBrush(MFD_GASNIT);
	bLox = oapiCreateBrush(MFD_LOX);
	// Add MFD initialisation here
}

// Destructor
MFDTemplate::~MFDTemplate ()
{
	oapiReleaseFont (font);
	
	oapiReleasePen  (pRed);
	oapiReleasePen  (pGreen);
	oapiReleasePen  (pBlue);
	oapiReleasePen  (pGaslox);
	oapiReleasePen	(pControl);

	oapiReleaseBrush(bBlack);
	oapiReleaseBrush(bWhite);
	oapiReleaseBrush(bRed);
	oapiReleaseBrush(bGreen);
	oapiReleaseBrush(bBlue);
	oapiReleaseBrush(bGaslox);
	oapiReleaseBrush(bGasnit);
	oapiReleaseBrush(bLox);
	// Add MFD cleanup code here
}

// Return button labels
char *MFDTemplate::ButtonLabel (int bt)
{
	// The labels for the four buttons used by our MFD mode
	static char *label[4] = {"PRE", "HYD", "ELE", "PNE"};
	return (bt < 4 ? label[bt] : 0);
}

// Return button menus
int MFDTemplate::ButtonMenu (const MFDBUTTONMENU **menu) const
{
	// The menu descriptions for the four buttons
	static const MFDBUTTONMENU mnu[4] = {
		{"Pressurisation", 0, '['},
		{"Hydraulic", 0, '['},
		{"Electrical", 0, '['},
		{"Pneumatic", 0, ']'}
	};
	if (menu) *menu = mnu;
	return 4; // return the number of buttons used
}


// Repaint the MFD
bool MFDTemplate::Update (oapi::Sketchpad *skp)
{
	Title (skp, "PACK A");
	// Draws the MFD title
		
	skp->SetFont (font);
	skp->SetTextAlign (oapi::Sketchpad::CENTER, oapi::Sketchpad::BASELINE);
	skp->SetTextColor (MFD_WHITE);
	
	skp->Rectangle (W/4, 0, (3*W)/4, H/15);
	skp->Text (W/2, H/20,"LOX PRESSURISATION", 18);

	
	skp->Rectangle (W/80, H/12, (21*W)/64, H/7);
	skp->Text (W/6, H/8,"Main Sequencer", 14);

	skp->Rectangle ((3*W)/8, H/12, (5*W)/8, H/7);
	skp->Text (W/2, H/8,"- 00:00:10", 10);
	
	skp->Rectangle ((45*W)/64, H/12, (63*W)/64, (34*H)/128);
	skp->Text((27*W)/32,(9*H)/64, "Pressurisation",14);
	skp->Text((27*W)/32,(12*H)/64, "Control",7);
	skp->Text((27*W)/32,(30*H)/128, "Unit",4);

	skp->Rectangle((11*W)/32, H/6, (21*W)/32, (27*H)/128);
	skp->Text(W/2,H/5,"Ground Nitrogen",15);

	skp->Rectangle((11*W)/32, (55*H)/256, (21*W)/32, (34*H)/128);
	skp->Text(W/2,H/4,"Heat Exchanger", 14);
	
	skp->SetPen(pRed);	
	skp->Line((21*W)/64,H/9,(3*W/8),H/9);
	skp->Line((5*W)/8,H/9,(45*W/64),H/9);
	skp->Line((21*W)/32,12*H/64, (45*W/64),12*H/64);
	skp->Line((21*W)/32,15*H/64, (45*W/64),15*H/64); 
	//	----------- end of control graphics -------------------------
	//  ------------- Tanks and valves ------------------------------
	//  --------------- Start Tank Assembly -------------------------
	skp->SetPen(pWhite);
	skp->Rectangle((1*W)/32, (12*H)/32, (8*W)/32, (24*H)/32);	// Start Tank
	skp->Text((9*W)/64, (51*H)/64, "Start Tank",10);
	skp->Rectangle((2*W)/32, (9*H)/32, (3*W)/32, (12*H)/32);	// Start Tank filler
	skp->Rectangle((1*W)/32, (17*H)/64, (4*W)/32, (9*H)/32);
	//Start Tank Pressurisation Pipe
	skp->SetBrush(bGasnit);
	skp->Rectangle((48*W)/64,(17*H)/64,(49*W)/64,(21*H)/64);	//	Pipe 1
	skp->Rectangle((8*W)/64,(20*H)/64,(49*W)/64,(21*H)/64);		//	Pipe 2 horiz
	skp->Rectangle((8*W)/64,(20*H)/64,(9*W)/64,(24*H)/64);		//	Pipe 3
	//skp->Rectangle((8*W)/32, (27*H)/64, (10*W)/32, (14*H)/32);		// Vent pipe
	skp->SetBrush(NULL);										// back to unfilled rectangles
	skp->Rectangle((8*W)/32, (12*H)/32, (17*W)/32, (27*H)/64);	//	Valve border
	skp->Text((23*W)/64,(53*H)/128, "Vent Valve",10);
	skp->SetBrush(bGreen);
	skp->Rectangle((15*W)/32, (12*H)/32, (17*W)/32, (27*H)/64);	//	Start Tank Vent
	skp->SetBrush(NULL);										// back to unfilled rectangles
	skp->SetPen(pWhite);
	//  -------------------- Run Tank Asembly -------------------------
	skp->Rectangle((16*W)/32, (16*H)/32, (24*W)/32, (24*H)/32);	// Run Tank
	skp->Text((10*W/16), (51*H)/64, "Run Tank",8);
	//Run Tank Pressurisation Pipes
	skp->SetBrush(bGaslox);
	skp->Rectangle((59*W)/64,(17*H)/64,(60*W)/64,(34*H)/64);	//	Pipe 1 vert
	skp->Rectangle((24*W)/32,(33*H)/64,(60*W)/64,(34*H)/64);	//	Pipe 2 horiz
	skp->SetBrush(NULL);										// back to unfilled rectangles
	skp->Rectangle((35*W)/64,(29*H)/64,(52*W)/64,(32*H)/64);	//	Valve border
	skp->Text((21*W)/32,(63*H)/128, "Vent Valve",10);
	skp->SetBrush(bGreen);
	skp->Rectangle((48*W)/64,(29*H)/64,(52*W)/64,(32*H)/64);	//	Run Tank Vent
	skp->SetBrush(NULL);										// back to unfilled rectangles
	// ------------------ Valve Control -----------------------------
	skp->SetPen(pControl);
	skp->Line((51*W)/64,(17*H)/64,(51*W)/64,(25*H)/64);
	skp->LineTo((33*W)/64,(25*H)/64);
	skp->Line((57*W)/64,(17*H)/64,(57*W)/64,(30*H)/64);
	skp->LineTo((52*W)/64,(30*H)/64);
	skp->Line((54*W)/64,(17*H)/64,(54*W)/64,(28*H)/64);
	skp->LineTo((24*W)/64,(28*H)/64);
	skp->LineTo((24*W)/64,(40*H)/64);
	// ------------------- end of valve control ----------------------
	// ------------------- Main Pipework -----------------------------
	skp->SetPen(pWhite);
	skp->Rectangle((8*W)/32, (20*H)/32, (16*W)/32, (22*H)/32);
	skp->Rectangle((24*W)/32, (20*H)/32, (28*W)/32, (22*H)/32);
	skp->Rectangle((28*W)/32, (19*H)/32, (57*W)/64, (23*H)/32);
	skp->Rectangle((57*W)/64, (19*H)/32, (58*W)/64, (23*H)/32);
	skp->Line((58*W)/64, (20*H)/32, (62*W)/64, (20*H)/32);
	skp->Line((58*W)/64, (22*H)/32, (62*W)/64, (22*H)/32);
	skp->Ellipse((11*W)/32, (20*H)/32, (13*W)/32, (22*H)/32);
	skp->Text((12*W)/32, (47*H)/64, "Fill/Check",10);
	skp->Text((55*W)/64, (49*H)/64, "To Engines",10);
	// ----------------------------------------------------------------
	// -------------------- Data Area ---------------------------------
	skp->Line(0,(52*H)/64,W, (52*H)/64);
	skp->Text(W/2, (55*H)/64, "Ullage Pressure",15);
	skp->Text(W/2, (58*H)/64, "Tank Volume",11);
	skp->Text(W/2, (61*H)/64, "Tank Flow",9);

	//--------------------- Fill/Check Valve draw ----------------------
	skp->SetBrush(bGreen);
	skp->Ellipse((11*W)/32, (20*H)/32, (13*W)/32, (22*H)/32);
	skp->SetBrush(bBlack);
	skp->Ellipse((11*W)/32, (20*H)/32, (13*W)/32, (22*H)/32);
	skp->SetBrush(bRed);
	skp->Rectangle((23*W)/64, (20*H)/32, (25*W)/64, (22*H)/32);
	skp->SetBrush(bBlack);
	skp->Rectangle((23*W)/64, (20*H)/32, (25*W)/64, (22*H)/32);

	// Add MFD display routines here.
	// Use the device context (hDC) for Windows GDI paint functions.

	return true;
}

// MFD message parser
int MFDTemplate::MsgProc (UINT msg, UINT mfd, WPARAM wparam, LPARAM lparam)
{
	switch (msg) {
	case OAPI_MSG_MFD_OPENED:
		// Our new MFD mode has been selected, so we create the MFD and
		// return a pointer to it.
		return (int)(new MFDTemplate (LOWORD(wparam), HIWORD(wparam), (VESSEL*)lparam));
	}
	return 0;
}

and the .h file for it:
Code:
// ==============================================================
//                 ORBITER MODULE: DialogTemplate
//                  Part of the ORBITER SDK
//            Copyright (C) 2003 Martin Schweiger
//                   All rights reserved
//
// MFDTemplate.h
//
// This module demonstrates how to build an Orbiter plugin which
// inserts a new MFD (multi-functional display) mode. The code
// is not very useful in itself, but it can be used as a starting
// point for your own MFD developments.
// ==============================================================

#ifndef __MFDTEMPLATE_H
#define __MFDTEMPLATE_H

#define MFD_WHITE   0xFFFFFF
#define	MFD_BLACK	0x000000
#define MFD_RED		0x0000FF
#define MFD_GREEN   0x00FF00
#define MFD_BLUE	0xFF0000
#define	MFD_GASLOX	0x888888
#define MFD_GASNIT	0x88FF88
#define	MFD_Control	0x8888FF
#define	MFD_LOX		0xCCFFCC

class MFDTemplate: public MFD2 {
public:
	MFDTemplate (DWORD w, DWORD h, VESSEL *vessel);
	~MFDTemplate ();
	char *ButtonLabel (int bt);
	int ButtonMenu (const MFDBUTTONMENU **menu) const;
	bool Update (oapi::Sketchpad *skp);
	static int MsgProc (UINT msg, UINT mfd, WPARAM wparam, LPARAM lparam);

protected:
	oapi::Font *font;
	
	oapi::Pen *pRed;
	oapi::Pen *pGreen;
	oapi::Pen *pBlue;
	oapi::Pen *pWhite;
	oapi::Pen *pGaslox;
	oapi::Pen *pControl;

	oapi::Brush	*bBlack;
	oapi::Brush *bGreen;
	oapi::Brush *bRed;
	oapi::Brush *bBlue;
	oapi::Brush *bWhite;
	oapi::Brush *bGaslox;
	oapi::Brush *bGasnit;
	oapi::Brush	*bLox;
};

#endif // !__MFDTEMPLATE_H

It did work in Orbiter2010, haven't tried it in Orbiter2016.

EDIT: Always read the question...I thought you were asking about MFD drawing, but you are asking about the HUD!
Sorry, haven't done anything with those.
Maybe this code can be converted?
Either way hope it helps

N.
 
Last edited:

zachary77

New member
Joined
Aug 31, 2017
Messages
80
Reaction score
1
Points
0
Your vessel class needs to implement clbkDrawHUD( int mode, const HUDPAINTSPEC *hps, oapi::Sketchpad *skp ). The hps pointer gives info about the HUD (size, etc) and the skp allows you to draw lines, print text, etc.

Yes, but I don't understand the skp... thing.
 

BrianJ

Addon Developer
Addon Developer
Joined
Apr 19, 2008
Messages
1,678
Reaction score
902
Points
128
Location
Code 347
Yes, but I don't understand the skp... thing.
Some code for your clbkDrawHUD{} section that might get you started.

Code:
bool my_vessel::clbkDrawHUD (int mode, const HUDPAINTSPEC *hps, oapi::Sketchpad *skp)
{
// draw the default HUD, assuming you're using VESSEL3 class
VESSEL3::clbkDrawHUD (mode, hps, skp);

int x_coord = 300; // x coordinate from top left
int y_coord = 300; // y coordinate from top left
double my_variable = 0.1234; // a number you want to display

  {
  skp->SetTextColor(0x0066FF66); // 0x00BBGGRR
  char abuf[256];
  sprintf(abuf,"My Variable %0.4f", my_variable);
  skp->Text (x_coord, y_coord, abuf,strlen(abuf));
  }

return true;
}
Figuring out the x and y coords for different screen resolutions is gnarly, I have a clunky method you could use but there must be a better way.....
Hope this helps.
 
Last edited:

ADSWNJ

Scientist
Addon Developer
Joined
Aug 5, 2011
Messages
1,667
Reaction score
3
Points
38
If you are NOT the vessel author, you can hook into the HUD via an MFD, via Enjo's HUDDrawer SDK. Works a charm!
 

zachary77

New member
Joined
Aug 31, 2017
Messages
80
Reaction score
1
Points
0
Some code for your clbkDrawHUD{} section that might get you started.

Code:
bool my_vessel::clbkDrawHUD (int mode, const HUDPAINTSPEC *hps, oapi::Sketchpad *skp)
{
// draw the default HUD, assuming you're using VESSEL3 class
VESSEL3::clbkDrawHUD (mode, hps, skp);

int x_coord = 300; // x coordinate from top left
int y_coord = 300; // y coordinate from top left
double my_variable = 0.1234; // a number you want to display

  {
  skp->SetTextColor(0x0066FF66); // 0x00BBGGRR
  char abuf[256];
  sprintf(abuf,"My Variable %0.4f", my_variable);
  skp->Text (x_coord, y_coord, abuf,strlen(abuf));
  }

return true;
}
Figuring out the x and y coords for different screen resolutions is gnarly, I have a clunky method you could use but there must be a better way.....
Hope this helps.

Thanks SO much BrianJ, it works!
I can finally get rid of the debug string.
Only issue is that the text is too small... how'd I fix that?
 

BrianJ

Addon Developer
Addon Developer
Joined
Apr 19, 2008
Messages
1,678
Reaction score
902
Points
128
Location
Code 347
Only issue is that the text is too small... how'd I fix that?
Not sure about that. I think you may need to use
Code:
skp->SetFont(...)
But you may need to create a font before you can set it.....I never had to do that....anyone else know how?
 

dbeachy1

O-F Administrator
Administrator
Orbiter Contributor
Addon Developer
Donator
Beta Tester
Joined
Jan 14, 2008
Messages
9,216
Reaction score
1,562
Points
203
Location
VA
Website
alteaaerospace.com
Preferred Pronouns
he/him
Here are some simplified snippets from the XR codebase:

Code:
.h vessel (this defines a member variable that contains the new font)
    // new HUD font for normal text
    oapi::Font *m_pHudNormalFont;

.cpp constructor 
    // Note: the new Orbiter 2010 core HUD text uses Arial bold; however, our custom text looks better in a fixed-width font,
    // so we use "Lucida Console".
    m_pHudNormalFont = oapiCreateFont(fontSize, false, "Lucida Console", FONT_BOLD);  // fixed-width (prop = false)

.cpp vessel destructor
    oapiReleaseFont(m_pHudNormalFont);

.cpp vessel clbkDrawHUD
    oapi::Font *pOrgHUDFont = skp->SetFont(m_pHudNormalFont);   // sets new font and saves the existing font so we can restore it later
    // …draw with font as desired

    // at end of clkbDrawHUD method:
    skp->SetFont(pOrgHUDFont);   // restore original HUD font
 

zachary77

New member
Joined
Aug 31, 2017
Messages
80
Reaction score
1
Points
0
Here are some simplified snippets from the XR codebase:

Code:
.h vessel (this defines a member variable that contains the new font)
    // new HUD font for normal text
    oapi::Font *m_pHudNormalFont;

.cpp constructor 
    // Note: the new Orbiter 2010 core HUD text uses Arial bold; however, our custom text looks better in a fixed-width font,
    // so we use "Lucida Console".
    m_pHudNormalFont = oapiCreateFont(fontSize, false, "Lucida Console", FONT_BOLD);  // fixed-width (prop = false)

.cpp vessel destructor
    oapiReleaseFont(m_pHudNormalFont);

.cpp vessel clbkDrawHUD
    oapi::Font *pOrgHUDFont = skp->SetFont(m_pHudNormalFont);   // sets new font and saves the existing font so we can restore it later
    // …draw with font as desired

    // at end of clkbDrawHUD method:
    skp->SetFont(pOrgHUDFont);   // restore original HUD font

Thanks a lot! It works like a charm. Next release will have the HUD readouts thanks to you guys:tiphat:
 
Top