shithub: tinygl

ref: eddb423a236660b32768f10d63b376e53254b505
dir: /include-demo/openimgui.h/

View raw version
#include <math.h>
//PROTOTYPE FOR THE OPENIMGUISTANDARD PROPOSAL

//Licensed to you under the CC0 license.



//This is the standard for an intuitive immediate-mode gui specification which gracefully solves many of the shortcomings of 
//other immediate mode gui standards.

//1) How elements are drawn across different environments
//2) How keyboard/gamepad cursor navigation is handled
//3) How the same GUI rendering code can be transported between backends.

//This is a standard for immediate mode GUI elements which can be implemented anywhere and gracefully decreases in feature level based on platform.

//If your target platform can render text and it can render boxes, then it can run openimgui.

// The screen's top left corner is 0,0 and bottom right is 1,1

// All coordinates and dimensions are specified relative to that.

//HOW CURSOR BUTTON IS HANDLED~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//Beginning of your frame...

//omg_cb = 0; 
//if(just_touched || just_mouseleftbuttondown || just_button_down) omg_cb = 1; //Pressed!
//if(just_released_touch || just_mouseleftbutton up || just_button_up) omg_cb = 2; //Released!

//Gui code this frame...

//HOW CURSOR POSITION IS HANDLED:
// On platforms with touch or mouse input, the polling of cursor position will occur like this~~~~~~~~~~~~~
// omg_cursor_has_been_sucked = 0;
// omg_cursorpos[0] = device_cursorpos.x / (float) screenWidth;
// omg_cursorpos[1] = device_cursorpos.x / (float) screenHeight;
// Clamp the cursorpos (if necessary)
// omg_cursorpos[0] = omg_clampf(omg_cursorpos[0]);
// omg_cursorpos[1] = omg_clampf(omg_cursorpos[1]);
// omg_cursorpos_presuck[0] = -1;
// omg_cursorpos_presuck[1] = -1;

// On platforms which use buttons to navigate menu elements...~~~~~~~~~~~~~
// omg_cursor_has_been_sucked = 0;
// if(buttonleft) omg_cursorpos[0] -= omg_buttonjump[0];
// if(buttonright) omg_cursorpos[0] += omg_buttonjump[0];
// if(buttonup) omg_cursorpos[1] -= omg_buttonjump[1];
// if(buttondown) omg_cursorpos[1] += omg_buttonjump[1];
// Clamp the cursorpos
// omg_cursorpos[0] = omg_wrapf(omg_cursorpos[0]);
// omg_cursorpos[1] = omg_wrapf(omg_cursorpos[1]);
// omg_cursorpos_presuck[0] = omg_cursorpos[0];
// omg_cursorpos_presuck[1] = omg_cursorpos[1];

// HOW BUTTON SUCKING WORKS ~~~~~~~~~~~~~~

// On platforms without cursor input such as game consoles, there needs to be an ergonomic way to navigate menus.

// This is achieved by simulating a virtual mouse cursor in the game and "Sucking" it into the closest sucking box.

// We keep track of the cursorposition every frame as well as the position before an attempt to "suck" it has been made.
// This allows us to determine (By testing, for every graphical object) whether or not the cursorposition should be "sucked" into
// the graphical object.

// Normalized cursor position
#ifndef OPENIMGUI_IMPL
extern float omg_cursorpos[2]; //Defaults to zero
extern float omg_cursorpos_presuck[2]; //Defaults to zero
extern int omg_cursor_has_been_sucked;
extern int omg_cursor_was_inside;  //Set 
extern float omg_buttonjump[2]; //Defaults to zero
// Setting for users using 
extern int omg_bstate_old;
extern int omg_udlr_old[4];
extern int omg_udlr[4];
// cursor button
extern int omg_cb; //Set to zero every iteration.
#else
float omg_cursorpos[2]; //Defaults to zero
float omg_cursorpos_presuck[2]; //Defaults to zero
int omg_cursor_has_been_sucked;
int omg_cursor_was_inside;  //Set 
float omg_buttonjump[2]; //Defaults to zero
// Setting for users using 
int omg_bstate_old = 0;
int omg_udlr_old[4] = {0,0,0,0};
int omg_udlr[4];
// cursor button
int omg_cb; //Set to zero every iteration.
#endif
//Used for determining the closest button in sucking mode.
static inline float omg_sqrlinelength(float x1, float y1, float x2, float y2){
	return ((x1-x2) * (x1-x2) + (y1-y2) * (y1-y2));
}
//Used for clamping cursor position to the screen.
static inline float omg_clampf(float x){
	return (x>1.0)?1.0: (x<0.0)?0.0:x;
}
//Used for wrapping the cursor position to the screen in button cursor mode.
static inline float omg_wrapf(float x){
	float f = fmod(x, 1);
	if(f<0.0) (f = 1.0 + f);
	return f;
}

static inline void omg_update_keycursor(int _up, int _down, int _left, int _right, int bstate){
	
	omg_cursor_was_inside = 0;
	int up = _up && ! omg_udlr_old[0];
	int down = _down && ! omg_udlr_old[1];
	int left = _left && ! omg_udlr_old[2];
	int right = _right && ! omg_udlr_old[3];
	omg_udlr[0] = up;
	omg_udlr[1] = down;
	omg_udlr[2] = left;
	omg_udlr[3] = right;
	omg_udlr_old[0] = _up;
	omg_udlr_old[1] = _down;
	omg_udlr_old[2] = _left;
	omg_udlr_old[3] = _right;
	omg_cursor_has_been_sucked = 0;
	omg_cursorpos_presuck[0] = omg_cursorpos[0];
	omg_cursorpos_presuck[1] = omg_cursorpos[1];
	if(up)   omg_cursorpos[1] -= omg_buttonjump[1];
	if(down) omg_cursorpos[1] += omg_buttonjump[1];
	if(left) omg_cursorpos[0] -= omg_buttonjump[0];
	if(right)omg_cursorpos[0] += omg_buttonjump[0];
	
	//Clamp the cursorpos
	omg_cursorpos[0] = omg_wrapf(omg_cursorpos[0]);
	omg_cursorpos[1] = omg_wrapf(omg_cursorpos[1]);
	omg_cursorpos_presuck[0] = omg_cursorpos[0];
	omg_cursorpos_presuck[1] = omg_cursorpos[1];
	//printf("BEGIN! Cx = %f, Cy = %f\n", omg_cursorpos[0], omg_cursorpos[1]);
	omg_cb = 0; 
	if(bstate && !omg_bstate_old) omg_cb = 1;
	else if (!bstate && omg_bstate_old) omg_cb = 2;
	omg_bstate_old = bstate;
}

//for mouse cursors and touch input.
static inline void omg_update_mcursor(float ncx, float ncy, int bstate){
	omg_cursor_has_been_sucked = 0;
	omg_cursor_was_inside = 0;
	omg_cursorpos[0] = ncx;
	omg_cursorpos[1] = ncy;
	// Clamp the cursorpos (if necessary)
	omg_cursorpos[0] = omg_clampf(omg_cursorpos[0]);
	omg_cursorpos[1] = omg_clampf(omg_cursorpos[1]);
	omg_cursorpos_presuck[0] = -1;
	omg_cursorpos_presuck[1] = -1;

	omg_cb = 0; 
	if(bstate && !omg_bstate_old) omg_cb = 1;
	else if (!bstate && omg_bstate_old) omg_cb = 2;
	omg_bstate_old = bstate;
}
static inline int omg_boxtest(float x, float y, float xdim, float ydim, float cx, float cy){
	if((x <= cx) &&
			(x+xdim >= cx) &&
			(y <= cy) &&
			(y+ydim >= cy))
		return 1;
	return 0;
}
static inline int omg_box_retval(float x, float y, float xdim, float ydim){
	if(omg_cursorpos_presuck[0] == -1) 
		return omg_boxtest(x,y,xdim,ydim,	omg_cursorpos[0],omg_cursorpos[1]);
	return omg_boxtest(x,y,xdim,ydim,		omg_cursorpos_presuck[0],omg_cursorpos_presuck[1]);
}
static inline void omg_box_suck(float x, float y, float xdim, float ydim, int sucks, float buttonjumpx, float buttonjumpy){
	 if(omg_cursorpos_presuck[0] != -1 && sucks){ //Do not attempt to suck if this graphical element does not suck or sucking is not enabled.
		int btest = omg_boxtest(x,y,xdim,ydim, omg_cursorpos_presuck[0], omg_cursorpos_presuck[1]);
		 if(!omg_cursor_has_been_sucked){
		 	//We are free to try to suck up the cursor without a check.
			omg_cursorpos[0] = x + xdim/2.0;
			omg_cursorpos[1] = y + ydim/2.0;
			omg_cursor_has_been_sucked = 1;
		  	omg_buttonjump[0] = buttonjumpx;
		  	omg_buttonjump[1] = buttonjumpy;
		  	if(btest) omg_cursor_was_inside = 1;
		  	//puts("Initial grab...\n");
		  	//printf("Cx = %f, Cy = %f\n", omg_cursorpos[0], omg_cursorpos[1]);
		} else if (
		(!omg_cursor_was_inside && //Cursor was not inside.
		omg_sqrlinelength(x+xdim/2.0, y+ydim/2.0, 			omg_cursorpos_presuck[0],  omg_cursorpos_presuck[1]) < 
		           omg_sqrlinelength(omg_cursorpos[0], omg_cursorpos[1], omg_cursorpos_presuck[0],  omg_cursorpos_presuck[1])
		 ) || //Cursor was inside, if it's inside this one as well, pick the closest.
		 (!omg_cursor_was_inside && btest) ||
		  (
		  	btest && 
			omg_sqrlinelength(x+xdim/2.0, y+ydim/2.0, 			omg_cursorpos_presuck[0],  omg_cursorpos_presuck[1]) < 
		    omg_sqrlinelength(omg_cursorpos[0], omg_cursorpos[1], omg_cursorpos_presuck[0],  omg_cursorpos_presuck[1])
		  )
		           ){
		           //The box is closer than the current suck position.
			omg_cursorpos[0] = x+xdim/2.0;
			omg_cursorpos[1] = y+ydim/2.0;
			omg_cursor_has_been_sucked = 1;
		  omg_buttonjump[0] = buttonjumpx;
		  omg_buttonjump[1] = buttonjumpy;
		  //if(boxtest(x,y,xdim,ydim)) omg_cursor_was_inside = 1;
		  omg_cursor_was_inside = omg_boxtest(x,y,xdim,ydim, omg_cursorpos_presuck[0], omg_cursorpos_presuck[1]);
		  //puts("Found a different button!\n");
		  //printf("Cx = %f, Cy = %f\n", omg_cursorpos[0], omg_cursorpos[1]);
		}
	}
}
// OMG_BOX:
// Draws a box on the screen.
// Returns whether or not the cursor was inside it this frame (NOT IF IT GOT __SUCKED__ INSIDE IT!)
// x,y are the top left corner.
// xdim, ydim, are the width and height of the box.
// hints is a set of implementation-specific parameters describing the nature of how the box is drawn,
// sucks indicates whether or not the cursor position is "sucked" into the button (See: HOW BUTTON SUCKING WORKS)
// buttonjumpx and buttonjumpy are the amount by which the cursor will jump in X and Y when pressing the menu navigation arrows.
// The return value is determined like this:
// if(omg_cursorpos_presuck[0] == -1) return omg_boxtest(omg_cursorpos) else
//	return boxtest(omg_cursorpos_presuck)
// The suck test works like this:
// if(omg_cursorpos_presuck[0] != -1 && sucks){ //Do not attempt to suck if this graphical element does not suck or sucking is not enabled.
// if(!omg_cursor_has_been_sucked){ //We are free to try to suck up the cursor without a check.
//	omg_cursorpos[0] = x+xdim/2.0;
//	omg_cursorpos[1] = y+ydim/2.0;
//	omg_cursor_has_been_sucked = 1;
//  omg_buttonjump[0] = buttonjumpx;
//  omg_buttonjump[1] = buttonjumpy;
//} else if (omg_sqrlinelength(x+xdim/2.0, y+ydim/2.0, omg_cursorpos_presuck[0],  omg_cursorpos_presuck[1]) < 
//           omg_sqrlinelength(omg_cursorpos[0], omg_cursorpos[1], omg_cursorpos_presuck[0],  omg_cursorpos_presuck[1])){ //The box is closer than the current suck position.
//	omg_cursorpos[0] = x+xdim/2.0;
//	omg_cursorpos[1] = y+ydim/2.0;
//	omg_cursor_has_been_sucked = 1;
//  omg_buttonjump[0] = buttonjumpx;
//  omg_buttonjump[1] = buttonjumpy;
//}}
//When sucking is enabled (omg_cursorpos_presuck[0] != -1) the box test will be performed on cursorpos_presuck.
//You can use the above static inline functions as a reference for your implementation.

int omg_box(float x, float y, float xdim, float ydim, int sucks, float buttonjumpx, float buttonjumpy, int hints);

// OMG_TEXTBOX:
// Draws a box... with text in it
// All the args are the same, and its return value is the same, except now it can draw text.
// It should handle all the same hints as omg_box.
// the hintstext variable should handle all 
// The textsize is an implementation-specific indication of how large the text in the box should be.
// The x and y dimensions of the box are automatically deduced from text.
// Text containing newlines will extend the Y dimension of the box,
// and the longest line of text will determine the x dimension of the box.
// Otherwise, it is functionally identical to omg_box.
int omg_textbox(float x, float y, const char* text, int textsize, int sucks, float buttonjumpx, float buttonjumpy, int hints, int hintstext);