Code Sample Type: Display

Drawing 2-D That Matches the 3-D world

This example draws a 2-d label over the center of gravity of the user’s aircraft. It demonstrates how to implement 2-d OpenGL drawing over the screen (using a drawing callback) that matches locations in the 3-d world without using 3-d drawing callbacks. This technique works with OpenGL, Vulkan, or Metal drivers for X-Plane but requires X-Plane 11.50 or newer for datarefs.

We sometimes call this technique “coach marks” because it involves drawing markings over the 3-d world that match the 3-d world in location but do not interact with the depth buffer.

#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include "XPLMDefs.h"
#include "XPLMUtilities.h"
#include "XPLMDataAccess.h"
#include <string.h>

/* This example plugin demonstrates how to use a 2-d drawing callback to draw
 * to the screen in a way that matches the 3-d coordinate system.  Add-ons that
 * need to add 3-d labels, coach marks, or other non-3d graphics that "match"
 * the real world can use this technique to draw on with Metal and Vulkan. */

// Datarefs for the aircraft position.
static XPLMDataRef	s_pos_x = NULL; 
static XPLMDataRef	s_pos_y = NULL; 
static XPLMDataRef	s_pos_z = NULL; 

// Transform matrices - we will use these to figure out where we shuold should have drawn.
static XPLMDataRef	s_matrix_wrl = NULL;
static XPLMDataRef	s_matrix_proj = NULL;
static XPLMDataRef	s_screen_width = NULL;
static XPLMDataRef	s_screen_height = NULL;

// 4x4 matrix transform of an XYZW coordinate - this matches OpenGL matrix conventions.
static void mult_matrix_vec(float dst[4], const float m[16], const float v[4])
{
	dst[0] = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + v[3] * m[12];
	dst[1] = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + v[3] * m[13];
	dst[2] = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + v[3] * m[14];
	dst[3] = v[0] * m[3] + v[1] * m[7] + v[2] * m[11] + v[3] * m[15];
}

// This drawing callback will draw a label to the screen where the 

static int DrawCallback1(XPLMDrawingPhase inPhase, int inIsBefore, void * inRefcon)
{
	// Read the ACF's OpengL coordinates
	float acf_wrl[4] = {	
		XPLMGetDataf(s_pos_x),
		XPLMGetDataf(s_pos_y),
		XPLMGetDataf(s_pos_z),
		1.0f };
		
	float mv[16], proj[16];
	
	// Read the model view and projection matrices from this frame
	XPLMGetDatavf(s_matrix_wrl,mv,0,16);
	XPLMGetDatavf(s_matrix_proj,proj,0,16);
	
	float acf_eye[4], acf_ndc[4];
	
	// Simulate the OpenGL transformation to get screen coordinates.
	mult_matrix_vec(acf_eye, mv, acf_wrl);
	mult_matrix_vec(acf_ndc, proj, acf_eye);
	
	acf_ndc[3] = 1.0f / acf_ndc[3];
	acf_ndc[0] *= acf_ndc[3];
	acf_ndc[1] *= acf_ndc[3];
	acf_ndc[2] *= acf_ndc[3];
	
	float screen_w = XPLMGetDatai(s_screen_width);
	float screen_h = XPLMGetDatai(s_screen_height);
	
	float final_x = screen_w * (acf_ndc[0] * 0.5f + 0.5f);
	float final_y = screen_h * (acf_ndc[1] * 0.5f + 0.5f);

	// Now we have something in screen coordinates, which we can then draw a label on.

	XPLMDrawTranslucentDarkBox(final_x - 5, final_y + 10, final_x + 100, final_y - 10);

	float colWHT[] = { 1.0, 1.0, 1.0 };
	XPLMDrawString(colWHT, final_x, final_y, "TEST STRING 1", NULL, xplmFont_Basic);		
	
	return 1;
}

PLUGIN_API int XPluginStart(char * outName, char * outSig, char * outDesc)
{
	strcpy(outName,"Example label drawing");
	strcpy(outSig,"com.laminar.example_label_drawing");
	strcpy(outDesc,"A plugin that shows how to draw a 3-d-referenced label in 2-d");
	
	XPLMRegisterDrawCallback(DrawCallback1, xplm_Phase_Window, 0, NULL);
	
	s_pos_x = XPLMFindDataRef("sim/flightmodel/position/local_x");
	s_pos_y = XPLMFindDataRef("sim/flightmodel/position/local_y");
	s_pos_z = XPLMFindDataRef("sim/flightmodel/position/local_z");

	// These datarefs are valid to read from a 2-d drawing callback and describe the state
	// of the underlying 3-d drawing environment the 2-d drawing is layered on top of.
	s_matrix_wrl = XPLMFindDataRef("sim/graphics/view/world_matrix");
	s_matrix_proj = XPLMFindDataRef("sim/graphics/view/projection_matrix_3d");

	// This describes the size of the current monitor at the time we draw.
	s_screen_width = XPLMFindDataRef("sim/graphics/view/window_width");
	s_screen_height = XPLMFindDataRef("sim/graphics/view/window_height");
	
	return 1;
}

PLUGIN_API int XPluginEnable()
{
	return 1;
}

PLUGIN_API void XPluginStop()
{
}

PLUGIN_API void XPluginDisable()
{
}

PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMessage, void * inParam )
{
}
14 Comments

VR Window Sample

The following sample code, modeled after the X‑Plane 11 Window API sample, will create a “modern”-style window. Note that as of X‑Plane 11.20, control over windows in VR is quite limited—you can move a window in and out of VR and change the window’s size, but that’s about it.

Note that some of the APIs used here are new to X-Plane 11.20b5—if you get a runtime error, ensure you’re on the latest release.

#include "XPLMDisplay.h"    // for window creation and manipulation
#include "XPLMGraphics.h"   // for window drawing
#include "XPLMDataAccess.h" // for the VR dataref
#include "XPLMPlugin.h"     // for XPLM_MSG_ENTERED_VR and XPLM_MSG_EXITING_VR message
#include "XPLMMenus.h"
#include <string.h>
#include <stdio.h> // for sprintf()
#if IBM
	#include <windows.h>
#endif
#if LIN
	#include <GL/gl.h>
#elif __GNUC__
	#include <OpenGL/gl.h>
#else
	#include <GL/gl.h>
#endif

#ifndef XPLM301
	#error This is made to be compiled against the XPLM301 SDK
#endif

static XPLMWindowID	g_window;
static XPLMMenuID g_menu_id;

void				create_window();
void				get_default_window_pos(int position_lbrt[4]);
void				exit_vr_mode();
void				draw(XPLMWindowID in_window_id, void * in_refcon);
void				draw_button(const char * text, float in_out_lbrt[4] /* you set the left and bottom, we'll set the right and top */);
int					handle_mouse(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon);
void				handle_menu(void * mRef, void * iRef);

int					dummy_mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon) { return 0; }
XPLMCursorStatus	dummy_cursor_status_handler(XPLMWindowID in_window_id, int x, int y, void * in_refcon) { return xplm_CursorDefault; }
int					dummy_wheel_handler(XPLMWindowID in_window_id, int x, int y, int wheel, int clicks, void * in_refcon) { return 0; }
void				dummy_key_handler(XPLMWindowID in_window_id, char key, XPLMKeyFlags flags, char virtual_key, void * in_refcon, int losing_focus) { }

XPLMDataRef g_vr_dref;
static float g_vr_button_lbrt[4] = {0, 0, 0, 0}; // left, bottom, right, top
static float g_size_button_lbrt[4] = {0, 0, 0, 0}; // left, bottom, right, top
static int g_last_mouse_xy[2] = {0, 0};

static int	coord_in_rect(float x, float y, float * bounds_lbrt)  { return ((x >= bounds_lbrt[0]) && (x < bounds_lbrt[2]) && (y < bounds_lbrt[3]) && (y >= bounds_lbrt[1])); }


PLUGIN_API int XPluginStart(
						char *		outName,
						char *		outSig,
						char *		outDesc)
{
	strcpy(outName, "VRSamplePlugin");
	strcpy(outSig, "xpsdk.examples.vrsampleplugin");
	strcpy(outDesc, "A test plug-in that demonstrates moving a window into VR.");

	create_window();
	
	g_vr_dref = XPLMFindDataRef("sim/graphics/VR/enabled");
	
	g_menu_id = XPLMCreateMenu("VR Sample", NULL, 0, handle_menu, NULL);
	XPLMAppendMenuItem(g_menu_id, "Toggle Into/Out of VR", (void *)"cmd_vr", 1);
	
	// If this dataref is for some reason not available,
	// we won't be able to move the window to VR anyway,
	// so go ahead an disable us!
	return g_vr_dref != NULL;
}

PLUGIN_API void	XPluginStop(void)
{
	// Since we created the window, we'll be good citizens and clean it up
	XPLMDestroyWindow(g_window);
	g_window = NULL;
}

PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API int  XPluginEnable(void)  { return 1; }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam)
{
	if(inFrom == XPLM_PLUGIN_XPLANE && inMsg == XPLM_MSG_ENTERED_VR)
	{
		XPLMSetWindowPositioningMode(g_window, xplm_WindowVR, -1);
	}
	else if(inFrom == XPLM_PLUGIN_XPLANE && inMsg == XPLM_MSG_EXITING_VR && XPLMWindowIsInVR(g_window))
	{
		exit_vr_mode();
	}
}

void create_window()
{
	int position_lbrt[4];
	get_default_window_pos(position_lbrt);
	
	XPLMCreateWindow_t params = {};
	params.structSize = sizeof(params);
	params.left = position_lbrt[0];
	params.bottom = position_lbrt[1];
	params.right = position_lbrt[2];
	params.top = position_lbrt[3];
	params.visible = 1;
	params.drawWindowFunc = draw;
	params.handleMouseClickFunc = handle_mouse;
	params.handleRightClickFunc = dummy_mouse_handler;
	params.handleMouseWheelFunc = dummy_wheel_handler;
	params.handleKeyFunc = dummy_key_handler;
	params.handleCursorFunc = dummy_cursor_status_handler;
	params.refcon = NULL;
	params.layer = xplm_WindowLayerFloatingWindows;
	params.decorateAsFloatingWindow = xplm_WindowDecorationRoundRectangle;
	
	g_window = XPLMCreateWindowEx(&params);
	
	const int vr_is_enabled = XPLMGetDatai(g_vr_dref);
	XPLMSetWindowPositioningMode(g_window, vr_is_enabled ? xplm_WindowVR : xplm_WindowPositionFree, -1);
	
	XPLMSetWindowResizingLimits(g_window, 200, 200, 500, 500); // Limit resizing our window: maintain a minimum width/height of 200 boxels and a max width/height of 500
	XPLMSetWindowTitle(g_window, "Sample VR Window");
}

void get_default_window_pos(int position_lbrt[4])
{
	// We're not guaranteed that the main monitor's lower left is at (0, 0)...
	// we'll need to query for the global desktop bounds!
	int global_desktop_bounds[4]; // left, bottom, right, top
	XPLMGetScreenBoundsGlobal(&global_desktop_bounds[0], &global_desktop_bounds[3], &global_desktop_bounds[2], &global_desktop_bounds[1]);
	position_lbrt[0] = global_desktop_bounds[0] + 50;
	position_lbrt[1] = global_desktop_bounds[1] + 150;
	position_lbrt[2] = global_desktop_bounds[0] + 350;
	position_lbrt[3] = global_desktop_bounds[1] + 450;
}

void draw(XPLMWindowID in_window_id, void * in_refcon)
{
	XPLMSetGraphicsState(
			0 /* no fog */,
			0 /* 0 texture units */,
			0 /* no lighting */,
			0 /* no alpha testing */,
			1 /* do alpha blend */,
			1 /* do depth testing */,
			0 /* no depth writing */
	);

	int l, t, r, b;
	XPLMGetWindowGeometry(in_window_id, &l, &t, &r, &b);

	float col_white[] = {1.0, 1.0, 1.0};
	int char_height;
	XPLMGetFontDimensions(xplmFont_Proportional, NULL, &char_height, NULL);
	
	const int vr_is_enabled = XPLMGetDatai(g_vr_dref);
	if(vr_is_enabled)
	{
		// Position the button in the upper left of the window (sized to fit the button text)
		g_vr_button_lbrt[0] = l + 10;
		g_vr_button_lbrt[1] = t - char_height * 1.5;
		draw_button("Toggle VR", g_vr_button_lbrt);
		
		g_size_button_lbrt[0] = g_vr_button_lbrt[2] + 10;
		g_size_button_lbrt[1] = g_vr_button_lbrt[1];
		draw_button("Toggle Size", g_size_button_lbrt);
	}
	else // Draw some text to say VR isn't available
	{
		char * line1 = "VR must be enabled before you";
		char * line2 = "can toggle this window into VR.";
		XPLMDrawString(col_white, l, t - 2 * char_height, line1, NULL, xplmFont_Proportional);
		XPLMDrawString(col_white, l, t - 4 * char_height, line2, NULL, xplmFont_Proportional);
	}
}

void draw_button(const char * text, float in_out_lbrt[4] /* you set the left and bottom, we'll set the right and top */)
{
	// We draw our rudimentary button boxes based on the height of the button text
	int char_height;
	XPLMGetFontDimensions(xplmFont_Proportional, NULL, &char_height, NULL);
	
	in_out_lbrt[2] = in_out_lbrt[0] + XPLMMeasureString(xplmFont_Proportional, text, strlen(text)); // *just* wide enough to fit the button text
	in_out_lbrt[3] = in_out_lbrt[1] + (1.25f * char_height); // a bit taller than the button text
	
	// Draw the box around our rudimentary button
	float green[] = {0.0, 1.0, 0.0, 1.0};
	glColor4fv(green);
	glBegin(GL_LINE_LOOP);
	{
		glVertex2i(in_out_lbrt[0], in_out_lbrt[3]);
		glVertex2i(in_out_lbrt[2], in_out_lbrt[3]);
		glVertex2i(in_out_lbrt[2], in_out_lbrt[1]);
		glVertex2i(in_out_lbrt[0], in_out_lbrt[1]);
	}
	glEnd();
	
	// Draw the button text (pop in/pop out)
	float col_white[] = {1.0, 1.0, 1.0};
	XPLMDrawString(col_white, in_out_lbrt[0], in_out_lbrt[1] + 4, (char *)text, NULL, xplmFont_Proportional);
}

int	handle_mouse(XPLMWindowID in_window_id, int x, int y, XPLMMouseStatus mouse_status, void * in_refcon)
{
	if(mouse_status == xplm_MouseDown)
	{
		g_last_mouse_xy[0] = x;
		g_last_mouse_xy[1] = y;
		if(!XPLMIsWindowInFront(in_window_id))
		{
			XPLMBringWindowToFront(in_window_id);
		}
		else
		{
			const int vr_is_enabled = XPLMGetDatai(g_vr_dref);
			if(vr_is_enabled && coord_in_rect(x, y, g_vr_button_lbrt)) // user clicked the "toggle VR mode" button
			{
				if(XPLMWindowIsInVR(in_window_id))
				{
					exit_vr_mode();
				}
				else
				{
					XPLMSetWindowPositioningMode(in_window_id, xplm_WindowVR, 0);
				}
			}
			else if(vr_is_enabled && coord_in_rect(x, y, g_size_button_lbrt)) // user clicked "toggle size" btn
			{
				int width, height;
				XPLMGetWindowGeometryVR(in_window_id, &width, &height);
				width = width > 300 ? 300 : 500;
				height = height > 300 ? 300 : 500;
				XPLMSetWindowGeometryVR(in_window_id, width, height);
			}
		}
	}
	return 1;
}

void handle_menu(void * mRef, void * iRef)
{
	if(!strcmp((char *)iRef, "cmd_vr"))
	{
		if(XPLMWindowIsInVR(g_window))
		{
			exit_vr_mode();
		}
		else
		{
			XPLMSetWindowPositioningMode(g_window, xplm_WindowVR, 0);
		}
	}
}

void exit_vr_mode()
{
	XPLMSetWindowPositioningMode(g_window, xplm_WindowPositionFree, 0);
	// We need to also reposition the window; it doesn't get automatically moved back to the main monitor!
	int position_lbrt[4];
	get_default_window_pos(position_lbrt);
	XPLMSetWindowGeometry(g_window, position_lbrt[0], position_lbrt[3], position_lbrt[2], position_lbrt[1]);
}
9 Comments

SDK 3.0 Window Scissoring Sample

This sample is based on the XPLM300 window sample. It demonstrates the use of the modelview, viewport, and projection matrix datarefs (new to X-Plane 11.10) for applying scissoring to new-style plugin-created windows.

If you build the plugin, but comment out the call the glScissor(), you’ll see the fill pattern goes all the way to the edge of the green rectangle.

#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include "XPLMDataAccess.h"
#include <string.h>
#include <stdio.h>
#if IBM
	#include <windows.h>
#endif
#if LIN
	#include <GL/gl.h>
#elif __GNUC__
	#include <OpenGL/gl.h>
	#include <OpenGL/glu.h>
#else
	#include <GL/gl.h>
#endif

#ifndef XPLM300
	#error This is made to be compiled against the XPLM300 SDK
#endif

static XPLMWindowID	g_window;

void				draw(XPLMWindowID in_window_id, void * in_refcon);

int					dummy_mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon) { return 0; }
XPLMCursorStatus	dummy_cursor_status_handler(XPLMWindowID in_window_id, int x, int y, void * in_refcon) { return xplm_CursorDefault; }
int					dummy_wheel_handler(XPLMWindowID in_window_id, int x, int y, int wheel, int clicks, void * in_refcon) { return 0; }
void				dummy_key_handler(XPLMWindowID in_window_id, char key, XPLMKeyFlags flags, char virtual_key, void * in_refcon, int losing_focus) { }

PLUGIN_API int XPluginStart(
							char *		outName,
							char *		outSig,
							char *		outDesc)
{
	strcpy(outName, "ScissorsSamplePlugin");
	strcpy(outSig, "xpsdk.examples.scissorsampleplugin");
	strcpy(outDesc, "A test plug-in that demonstrates using GL scissors within an X-Plane 11 GUI window.");
	
	// We're not guaranteed that the main monitor's lower left is at (0, 0)... we'll need to query for the global desktop bounds!
	int global_desktop_bounds[4]; // left, bottom, right, top
	XPLMGetScreenBoundsGlobal(&global_desktop_bounds[0], &global_desktop_bounds[3], &global_desktop_bounds[2], &global_desktop_bounds[1]);
	
	XPLMCreateWindow_t params;
	params.structSize = sizeof(params);
	params.left = global_desktop_bounds[0] + 50;
	params.bottom = global_desktop_bounds[1] + 150;
	params.right = global_desktop_bounds[0] + 350;
	params.top = global_desktop_bounds[1] + 450;
	params.visible = 1;
	params.drawWindowFunc = draw;
	params.handleMouseClickFunc = dummy_mouse_handler;
	params.handleRightClickFunc = dummy_mouse_handler;
	params.handleMouseWheelFunc = dummy_wheel_handler;
	params.handleKeyFunc = dummy_key_handler;
	params.handleCursorFunc = dummy_cursor_status_handler;
	params.refcon = NULL;
	params.layer = xplm_WindowLayerFloatingWindows;
	params.decorateAsFloatingWindow = 1;
	
	g_window = XPLMCreateWindowEx(&params);
	XPLMSetWindowTitle(g_window, "Sample Window");
	return (g_window != NULL);
}

PLUGIN_API void	XPluginStop(void)
{
	// Since we created the window, we'll be good citizens and clean it up
	XPLMDestroyWindow(g_window);
	g_window = NULL;
}

PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API int  XPluginEnable(void)  { return 1; }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam) { }

static void multMatrixVec4f(GLfloat dst[4], const GLfloat m[16], const GLfloat v[4])
{
	dst[0] = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + v[3] * m[12];
	dst[1] = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + v[3] * m[13];
	dst[2] = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + v[3] * m[14];
	dst[3] = v[0] * m[3] + v[1] * m[7] + v[2] * m[11] + v[3] * m[15];
}

static void modelview_to_window_coords(int out_w[2], const GLfloat in_mv[4], const GLfloat mv[16], const GLfloat pr[16], const GLint viewport[4])
{
	GLfloat eye[4], ndc[4];
	multMatrixVec4f(eye, mv, in_mv);
	multMatrixVec4f(ndc, pr, eye);
	ndc[3] = 1.0f / ndc[3];
	ndc[0] *= ndc[3];
	ndc[1] *= ndc[3];
	
	out_w[0] = (ndc[0] * 0.5f + 0.5f) * viewport[2] + viewport[0];
	out_w[1] = (ndc[1] * 0.5f + 0.5f) * viewport[3] + viewport[1];
}

void draw(XPLMWindowID in_window_id, void * in_refcon)
{
	XPLMSetGraphicsState(
						 0 /* no fog */,
						 0 /* 0 texture units */,
						 0 /* no lighting */,
						 0 /* no alpha testing */,
						 1 /* do alpha blend */,
						 1 /* do depth testing */,
						 0 /* no depth writing */
						 );

	int l, t, r, b;
	XPLMGetWindowGeometry(in_window_id, &l, &t, &r, &b);
	
	// First, draw a green box at the edges of the window to illustrate the *full* bounds.
	glColor4f(0, 1, 0, 1);
	glBegin(GL_LINE_LOOP);
	{
		glVertex2f(l, b);
		glVertex2f(l, t);
		glVertex2f(r, t);
		glVertex2f(r, b);
	}
	glEnd();
	
	static XPLMDataRef mv_dref = XPLMFindDataRef("sim/graphics/view/modelview_matrix");
	static XPLMDataRef vp_dref = XPLMFindDataRef("sim/graphics/view/viewport");
	static XPLMDataRef pr_dref = XPLMFindDataRef("sim/graphics/view/projection_matrix");
	
	// Get the current modelview matrix, viewport, and projection matrix from X-Plane
	float mv[16], pr[16];
	int vp[4];
	XPLMGetDatavf(mv_dref, mv, 0, 16);
	XPLMGetDatavf(pr_dref, pr, 0, 16);
	XPLMGetDatavi(vp_dref, vp, 0, 4);
	
	// Our new modelview bounds: we'll bring the window in by 10 bx on all sides
	GLfloat top_right_modelview[4] = { r - 10, t - 10, 0, 1 };
	GLfloat btm_left_modelview[4] =  { l + 10, b + 10, 0, 1 };
	
	// Get our top-right and bottom-left window coordinates
	int top_right_window[2], btm_left_window[2];
	modelview_to_window_coords(top_right_window, top_right_modelview, mv, pr, vp);
	modelview_to_window_coords(btm_left_window,  btm_left_modelview,  mv, pr, vp);
	
	glEnable(GL_SCISSOR_TEST);
	glScissor(btm_left_window[0], btm_left_window[1], top_right_window[0] - btm_left_window[0], top_right_window[1] - btm_left_window[1]);
	{
		// Draw a fill pattern
		// If the scissors were disabled, this would fill the entire window, not just a subsection.
		glColor4f(1, 1, 1, 0.2);
		int x, y;
		glBegin(GL_LINES);
		for(x = l; x < r; x += 3)
		{
			glVertex2f(x, b);
			glVertex2f(x, t);
		}
		for(y = b; y < t; y += 3)
		{
			glVertex2f(l, y);
			glVertex2f(r, y);
		}
		glEnd();
	}
	glDisable(GL_SCISSOR_TEST);
}
5 Comments

X-Plane 11 Window Gravity Sample

This example demonstrates how the use of the XPLMSetWindowGravity() API to cause your window to dynamically resize based on the X-Plane window’s size.

The gravity API keeps the SDK-created window fixed to the left and right sides, so it grows & shrinks with the X‑Plane window.
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include <string.h>
#include <stdio.h>
#if IBM
	#include <windows.h>
#endif
#if LIN
	#include <GL/gl.h>
#elif __GNUC__
	#include <OpenGL/gl.h>
#else
	#include <GL/gl.h>
#endif

#ifndef XPLM300
	#error This is made to be compiled against the XPLM300 SDK
#endif

static XPLMWindowID	g_window;

void				draw(XPLMWindowID in_window_id, void * in_refcon);

int					dummy_mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon) { return 0; }
XPLMCursorStatus	dummy_cursor_status_handler(XPLMWindowID in_window_id, int x, int y, void * in_refcon) { return xplm_CursorDefault; }
int					dummy_wheel_handler(XPLMWindowID in_window_id, int x, int y, int wheel, int clicks, void * in_refcon) { return 0; }
void				dummy_key_handler(XPLMWindowID in_window_id, char key, XPLMKeyFlags flags, char virtual_key, void * in_refcon, int losing_focus) { }

PLUGIN_API int XPluginStart(
						char *		outName,
						char *		outSig,
						char *		outDesc)
{
	strcpy(outName, "GravitySamplePlugin");
	strcpy(outSig, "xpsdk.examples.gravitysampleplugin");
	strcpy(outDesc, "A test plug-in that demonstrates the gravity features of the X-Plane 11 GUI plugin API.");

	// We're not guaranteed that the main monitor's lower left is at (0, 0)... we'll need to query for the global desktop bounds!
	int global_desktop_bounds[4]; // left, bottom, right, top
	XPLMGetScreenBoundsGlobal(&global_desktop_bounds[0], &global_desktop_bounds[3], &global_desktop_bounds[2], &global_desktop_bounds[1]);

	XPLMCreateWindow_t params;
	params.structSize = sizeof(params);
	// Set the window bounds such that we stretch the full *width* of the global desktop, and cover the top 200 bx
	params.left = global_desktop_bounds[0];
	params.bottom = global_desktop_bounds[3] - 200;
	params.right = global_desktop_bounds[2];
	params.top = global_desktop_bounds[3];
	params.visible = 1;
	params.drawWindowFunc = draw;
	params.handleMouseClickFunc = dummy_mouse_handler;
	params.handleRightClickFunc = dummy_mouse_handler;
	params.handleMouseWheelFunc = dummy_wheel_handler;
	params.handleKeyFunc = dummy_key_handler;
	params.handleCursorFunc = dummy_cursor_status_handler;
	params.refcon = NULL;
	params.layer = xplm_WindowLayerFlightOverlay; // stick our window beneath all floating windows (like the X-Plane 11 map)
	params.decorateAsFloatingWindow = 0;
	
	g_window = XPLMCreateWindowEx(&params);
	
	XPLMSetWindowPositioningMode(g_window, xplm_WindowPositionFree, -1);
	// As the X-Plane window resizes, glue our left and right edges to the sides of the screen
	// (causing our width to grow and shrink to match the window size), but keep a constant
	// height for our window (with the same y position relative to the window's top).
	XPLMSetWindowGravity(g_window, 0, 1, 1, 1);
	
	return (g_window != NULL);
}

PLUGIN_API void	XPluginStop(void)
{
	// Since we created the window, we'll be good citizens and clean it up
	XPLMDestroyWindow(g_window);
	g_window = NULL;
}

PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API int  XPluginEnable(void)  { return 1; }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam) { }

void	draw(XPLMWindowID in_window_id, void * in_refcon)
{
	XPLMSetGraphicsState(
			0 /* no fog */,
			0 /* 0 texture units */,
			0 /* no lighting */,
			0 /* no alpha testing */,
			1 /* do alpha blend */,
			1 /* do depth testing */,
			0 /* no depth writing */
	);
	
	int b[4];
	XPLMGetWindowGeometry(in_window_id, &b[0], &b[3], &b[2], &b[1]);
	
	// Draw our window's translucent background overlay
	XPLMDrawTranslucentDarkBox(b[0], b[3], b[2], b[1]);
	
	// Display the window bounds (centered within the window)
	char scratch_buffer[150];
	sprintf(scratch_buffer, "Window bounds: %d %d %d %d", b[0], b[1], b[2], b[3]);
	float col_white[] = {1.0, 1.0, 1.0};
	int text_width = XPLMMeasureString(xplmFont_Proportional, scratch_buffer, strlen(scratch_buffer));
	float text_midpoint_x = (b[2] + b[0]) / 2;
	XPLMDrawString(col_white, text_midpoint_x - text_width / 2, (b[3] + b[1]) / 2, scratch_buffer, NULL, xplmFont_Proportional);
}
7 Comments

X-Plane 11 Window API Sample

The following sample code creates a new, “modern” window styled like an X-Plane 11 window. It demonstrates a number of the new XPLM300 APIs, including:


#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include <string.h>
#include <stdio.h>
#if IBM
	#include <windows.h>
#endif
#if LIN
	#include <GL/gl.h>
#elif __GNUC__
	#include <OpenGL/gl.h>
#else
	#include <GL/gl.h>
#endif

#ifndef XPLM300
	#error This is made to be compiled against the XPLM300 SDK
#endif

static XPLMWindowID	g_window;

void				draw(XPLMWindowID in_window_id, void * in_refcon);
int					handle_mouse(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon);
void				receive_main_monitor_bounds(int inMonitorIndex, int inLeftBx, int inTopBx, int inRightBx, int inBottomBx, void * refcon);

int					dummy_mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon) { return 0; }
XPLMCursorStatus	dummy_cursor_status_handler(XPLMWindowID in_window_id, int x, int y, void * in_refcon) { return xplm_CursorDefault; }
int					dummy_wheel_handler(XPLMWindowID in_window_id, int x, int y, int wheel, int clicks, void * in_refcon) { return 0; }
void				dummy_key_handler(XPLMWindowID in_window_id, char key, XPLMKeyFlags flags, char virtual_key, void * in_refcon, int losing_focus) { }

static const char * g_pop_out_label = "Pop Out";
static const char * g_pop_in_label = "Pop In";
static float g_pop_button_lbrt[4]; // left, bottom, right, top
static float g_position_button_lbrt[4]; // left, bottom, right, top

static int	coord_in_rect(float x, float y, float * bounds_lbrt)  { return ((x >= bounds_lbrt[0]) && (x < bounds_lbrt[2]) && (y < bounds_lbrt[3]) && (y >= bounds_lbrt[1])); }


PLUGIN_API int XPluginStart(
						char *		outName,
						char *		outSig,
						char *		outDesc)
{
	strcpy(outName, "GuiSamplePlugin");
	strcpy(outSig, "xpsdk.examples.guisampleplugin");
	strcpy(outDesc, "A test plug-in that demonstrates the X-Plane 11 GUI plugin API.");

	// We're not guaranteed that the main monitor's lower left is at (0, 0)... we'll need to query for the global desktop bounds!
	int global_desktop_bounds[4]; // left, bottom, right, top
	XPLMGetScreenBoundsGlobal(&global_desktop_bounds[0], &global_desktop_bounds[3], &global_desktop_bounds[2], &global_desktop_bounds[1]);

	XPLMCreateWindow_t params;
	params.structSize = sizeof(params);
	params.left = global_desktop_bounds[0] + 50;
	params.bottom = global_desktop_bounds[1] + 150;
	params.right = global_desktop_bounds[0] + 350;
	params.top = global_desktop_bounds[1] + 450;
	params.visible = 1;
	params.drawWindowFunc = draw;
	params.handleMouseClickFunc = handle_mouse;
	params.handleRightClickFunc = dummy_mouse_handler;
	params.handleMouseWheelFunc = dummy_wheel_handler;
	params.handleKeyFunc = dummy_key_handler;
	params.handleCursorFunc = dummy_cursor_status_handler;
	params.refcon = NULL;
	params.layer = xplm_WindowLayerFloatingWindows;
	params.decorateAsFloatingWindow = 1;
	
	g_window = XPLMCreateWindowEx(&params);
	
	XPLMSetWindowPositioningMode(g_window, xplm_WindowPositionFree, -1);
	XPLMSetWindowGravity(g_window, 0, 1, 0, 1); // As the X-Plane window resizes, keep our size constant, and our left and top edges in the same place relative to the window's left/top
	XPLMSetWindowResizingLimits(g_window, 200, 200, 500, 500); // Limit resizing our window: maintain a minimum width/height of 200 boxels and a max width/height of 500
	XPLMSetWindowTitle(g_window, "Sample Window");

	return (g_window != NULL);
}

PLUGIN_API void	XPluginStop(void)
{
	// Since we created the window, we'll be good citizens and clean it up
	XPLMDestroyWindow(g_window);
	g_window = NULL;
}

PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API int  XPluginEnable(void)  { return 1; }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam) { }

void	draw(XPLMWindowID in_window_id, void * in_refcon)
{
	char scratch_buffer[150];
	float col_white[] = {1.0, 1.0, 1.0};

	XPLMSetGraphicsState(
			0 /* no fog */,
			0 /* 0 texture units */,
			0 /* no lighting */,
			0 /* no alpha testing */,
			1 /* do alpha blend */,
			1 /* do depth testing */,
			0 /* no depth writing */
	);


	// We draw our rudimentary button boxes based on the height of the button text
	int char_height;
	XPLMGetFontDimensions(xplmFont_Proportional, NULL, &char_height, NULL);

	// We'll change the text of the pop-in/pop-out button based on our current state
	int is_popped_out = XPLMWindowIsPoppedOut(in_window_id);
	const char * pop_label = is_popped_out ? g_pop_in_label : g_pop_out_label;

	int l, t, r, b;
	XPLMGetWindowGeometry(in_window_id, &l, &t, &r, &b);

	// Draw our buttons
	{
		// Position the pop-in/pop-out button in the upper left of the window (sized to fit the
		g_pop_button_lbrt[0] = l + 10;
		g_pop_button_lbrt[3] = t - 15;
		g_pop_button_lbrt[2] = g_pop_button_lbrt[0] + XPLMMeasureString(xplmFont_Proportional, pop_label, strlen(pop_label)); // *just* wide enough to fit the button text
		g_pop_button_lbrt[1] = g_pop_button_lbrt[3] - (1.25f * char_height); // a bit taller than the button text

		// Position the "move to lower left" button just to the right of the pop-in/pop-out button
		const char * position_btn_text = "Move to Lower Left";
		g_position_button_lbrt[0] = g_pop_button_lbrt[2] + 30;
		g_position_button_lbrt[1] = g_pop_button_lbrt[1];
		g_position_button_lbrt[2] = g_position_button_lbrt[0] + XPLMMeasureString(xplmFont_Proportional, position_btn_text, strlen(position_btn_text));
		g_position_button_lbrt[3] = g_pop_button_lbrt[3];

		// Draw the boxes around our rudimentary buttons
		float green[] = {0.0, 1.0, 0.0, 1.0};
		glColor4fv(green);
		glBegin(GL_LINE_LOOP);
		{
			glVertex2i(g_pop_button_lbrt[0], g_pop_button_lbrt[3]);
			glVertex2i(g_pop_button_lbrt[2], g_pop_button_lbrt[3]);
			glVertex2i(g_pop_button_lbrt[2], g_pop_button_lbrt[1]);
			glVertex2i(g_pop_button_lbrt[0], g_pop_button_lbrt[1]);
		}
		glEnd();
		glBegin(GL_LINE_LOOP);
		{
			glVertex2i(g_position_button_lbrt[0], g_position_button_lbrt[3]);
			glVertex2i(g_position_button_lbrt[2], g_position_button_lbrt[3]);
			glVertex2i(g_position_button_lbrt[2], g_position_button_lbrt[1]);
			glVertex2i(g_position_button_lbrt[0], g_position_button_lbrt[1]);
		}
		glEnd();

		// Draw the button text (pop in/pop out)
		XPLMDrawString(col_white, g_pop_button_lbrt[0], g_pop_button_lbrt[1] + 4, (char *)pop_label, NULL, xplmFont_Proportional);

		// Draw the button text (reposition)
		XPLMDrawString(col_white, g_position_button_lbrt[0], g_position_button_lbrt[1] + 4, (char *)position_btn_text, NULL, xplmFont_Proportional);
	}

	// Draw a bunch of informative text
	{
		// Set the y position for the first bunch of text we'll draw to a little below the buttons
		int y = g_pop_button_lbrt[1] - 2 * char_height;

		// Display the total global desktop bounds
		{
			int global_desktop_lbrt[4];
			XPLMGetScreenBoundsGlobal(&global_desktop_lbrt[0], &global_desktop_lbrt[3], &global_desktop_lbrt[2], &global_desktop_lbrt[1]);
			sprintf(scratch_buffer, "Global desktop bounds: (%d, %d) to (%d, %d)", global_desktop_lbrt[0], global_desktop_lbrt[1], global_desktop_lbrt[2], global_desktop_lbrt[3]);
			XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
			y -= 1.5 * char_height;
		}

		// Display our bounds
		if(XPLMWindowIsPoppedOut(in_window_id)) // we are in our own first-class window, rather than "floating" within X-Plane's own window
		{
			int window_os_bounds[4];
			XPLMGetWindowGeometryOS(in_window_id, &window_os_bounds[0], &window_os_bounds[3], &window_os_bounds[2], &window_os_bounds[1]);
			sprintf(scratch_buffer, "OS Bounds: (%d, %d) to (%d, %d)", window_os_bounds[0], window_os_bounds[1], window_os_bounds[2], window_os_bounds[3]);
			XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
			y -= 1.5 * char_height;
		}
		else
		{
			int global_bounds[4];
			XPLMGetWindowGeometry(in_window_id, &global_bounds[0], &global_bounds[3], &global_bounds[2], &global_bounds[1]);
			sprintf(scratch_buffer, "Window bounds: %d %d %d %d", global_bounds[0], global_bounds[1], global_bounds[2], global_bounds[3]);
			XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
			y -= 1.5 * char_height;
		}

		// Display whether we're in front of our our layer
		{
			sprintf(scratch_buffer, "In front? %s", XPLMIsWindowInFront(in_window_id) ? "Y" : "N");
			XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
			y -= 1.5 * char_height;
		}

		// Display the mouse's position info text
		{
			int mouse_global_x, mouse_global_y;
			XPLMGetMouseLocationGlobal(&mouse_global_x, &mouse_global_y);
			sprintf(scratch_buffer, "Draw mouse (global): %d %d\n", mouse_global_x, mouse_global_y);
			XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
			y -= 1.5 * char_height;
		}
	}

}

int	handle_mouse(XPLMWindowID in_window_id, int x, int y, XPLMMouseStatus is_down, void * in_refcon)
{
	if(is_down == xplm_MouseDown)
	{
		const int is_popped_out = XPLMWindowIsPoppedOut(in_window_id);
		if (!XPLMIsWindowInFront(in_window_id))
		{
			XPLMBringWindowToFront(in_window_id);
		}
		else if(coord_in_rect(x, y, g_pop_button_lbrt)) // user clicked the pop-in/pop-out button
		{
			XPLMSetWindowPositioningMode(in_window_id, is_popped_out ? xplm_WindowPositionFree : xplm_WindowPopOut, 0);
		}
		else if(coord_in_rect(x, y, g_position_button_lbrt)) // user clicked the "move to lower left" button
		{
			// If we're popped out, and the user hits the "move to lower left" button,
			// we need to move them to the lower left of their OS's desktop space (units are pixels).
			// On the other hand, if we're a floating window inside of X-Plane, we need
			// to move to the lower left of the X-Plane global desktop (units are boxels).
			void (* get_geometry_fn)(XPLMWindowID, int *, int *, int *, int *) = is_popped_out ? &XPLMGetWindowGeometryOS : &XPLMGetWindowGeometry;
			int lbrt_current[4];
			get_geometry_fn(in_window_id, &lbrt_current[0], &lbrt_current[3], &lbrt_current[2], &lbrt_current[1]);

			int h = lbrt_current[3] - lbrt_current[1];
			int w = lbrt_current[2] - lbrt_current[0];
			void (* set_geometry_fn)(XPLMWindowID, int, int, int, int) = is_popped_out ? &XPLMSetWindowGeometryOS : &XPLMSetWindowGeometry;

			// Remember, the main monitor's origin is *not* guaranteed to be (0, 0), so we need to query for it in order to move the window to its lower left
			int bounds[4] = {0}; // left, bottom, right, top
			if(is_popped_out)
			{
				XPLMGetScreenBoundsGlobal(&bounds[0], &bounds[3], &bounds[2], &bounds[1]);
			}
			else
			{
				XPLMGetAllMonitorBoundsOS(receive_main_monitor_bounds, bounds);
			}

			set_geometry_fn(in_window_id, bounds[0], bounds[1] + h, bounds[0] + w, bounds[1]);
		}
	}
	return 1;
}

void receive_main_monitor_bounds(int inMonitorIndex, int inLeftBx, int inTopBx, int inRightBx, int inBottomBx, void * refcon)
{
	int * main_monitor_bounds = (int *)refcon;
	if(inMonitorIndex == 0) // the main monitor
	{
		main_monitor_bounds[0] = inLeftBx;
		main_monitor_bounds[1] = inBottomBx;
		main_monitor_bounds[2] = inRightBx;
		main_monitor_bounds[3] = inTopBx;
	}
}
55 Comments