I came up with a really simple but flexible button system for my C projects. This post is going to be pretty short, so here's a diagram to summarize everything:
As you can see, the button object takes a function pointer as a parameter during construction. This function is what I refer to as exec. The exec function allows for a button to perform any action on-click. The exec function can take a general purpose parameter, perhaps defaulted to type int or void pointer. Within the exec function any necessary typecasting can be hidden away within it.
The exec functions will often times have additional dependencies throughout your program, in order to provide their desired functionality. For example I might need my exec function to create a live game object on-click. I'd need to include any headers required for creating game objects into my button system in order for this to work. For this reason the exec functions should be abstracted into their own separate file.
Alternatively one can make use of a map of identifiers to exec pointers in order to cut down on file dependencies. By mapping, of perhaps a string identifier, to an exec pointer the button class can then take the identifier upon creation rather than the pointer itself. The various exec functions can then be placed into file systems that pertain to the type of functionality the exec function itself provides. A registration of each exec function into the table would be required.
As for button behavior, I decided to create a few different types of Update functions for each button. Once a button has detected an on-click and has called its exec function, the Update function is called each cycle until the button is ready to detect another click and call its exec again. Since there can be multiple types of buttons each requiring its own unique update, the button object also takes a pointer to an Update function on creation. Here's a couple examples of button updates I've written:
//
// ButtonUpdateStandard
// Purpose: Standard update function for a generic button.
//
RETURN_TYPE ButtonUpdateStandard( BUTTON *self, float dt )
{
// Update dt only if non-zero.
// For example when exec is called, set dt to 0.001 (seconds)
// to begin debounce timer.
if(self->dt != 0)
{
self->dt += dt;
}
// Debounce
if(self->dt > STANDARD_BUTTON_DEBOUNCE && !IsKeyPressed( VK_LBUTTON ))
{
self->dt = 0;
}
if(self->dt == 0)
{
VECTOR2D pos = { 0 };
pos.x_ = (float)GLOBAL_INPUTS.xPos;
pos.y_ = (float)GLOBAL_INPUTS.yPos;
// Test for button press
if(IsKeyPressed( VK_LBUTTON ))
{
if(StaticPointToStaticRect( &pos, &self->rect ))
{
{
self->exec( self );
// Initiate debounce
self->dt = 0.0001f;
// End list caller routine if blocking
if(self->isBlocking)
return RETURN_FAILURE;
}
}
}
}
return RETURN_SUCCESS;
}
//
// ButtonUpdateBrush
// Purpose: Standard update function for a generic button.
//
RETURN_TYPE ButtonUpdateBrush( BUTTON *self, float dt )
{
// Update dt only if non-zero.
// For example when exec is called, set dt to 0.001 (seconds)
// to begin debounce timer.
if(self->dt != 0)
{
self->dt += dt;
}
// Debounce
if(self->dt > BRUSH_BUTTON_DEBOUNCE)
{
self->dt = 0;
}
if(self->dt == 0)
{
VECTOR2D pos = { 0 };
pos.x_ = (float)GLOBAL_INPUTS.xPos;
pos.y_ = (float)GLOBAL_INPUTS.yPos;
// Test for button press
if(IsKeyPressed( VK_LBUTTON ))
{
if(StaticPointToStaticRect( &pos, &self->rect ))
{
{
self->exec( self );
// Initiate debounce
self->dt = 0.000001f;
// End list caller routine if blocking
if(self->isBlocking)
return RETURN_FAILURE;
}
}
}
}
return RETURN_SUCCESS;
}
These two buttons act like a standard button press in most GUI systems. One has a slight delay for how fast you can actually click the button, and a debouncer that requires a release of the mouse key before update actually starts using DT to calculate the current time duration. The other acts more like a spray brush where you can just hold down the click button on have exec called repeatedly with a pause between calls.