This was a really intriguing assignment - I thought it would be fun to try making a few more GUI elements in order to extend the basic functionality with the following features:
Given an arbitrary color, buttons are automatically drawn with a gradient to create the impression of protrusion. The gradient is achieved by drawing a rectangle using GL_TRIANGLE_STRIP and using colors slightly lighter than the input color for the top two vertices, and colors slightly darker than the input color for bottom two vertices. On hover, vertices colors are brightened to give the button a lighter appearance. On press, the top vertices' color and bottom vertices' color are inverted to create the impression of concavity. The actual formula for button color went something like this:
// Shine values
float shine = 0.15;
int hoverShine = 0;
// Button pressed - reverse vertex colors
if (down) {
shine *= -1;
}
// Hover - brighten vertex colors
if (hover) {
hoverShine += 30;
}
unsigned char colors[12] = {
// Bottom left vertex
(int)(mBackgroundColor->r * (1 - shine) * 255 + hoverShine),
(int)(mBackgroundColor->g * (1 - shine) * 255 + hoverShine),
(int)(mBackgroundColor->b * (1 - shine) * 255 + hoverShine),
// Bottom right vertex
(int)(mBackgroundColor->r * (1 - shine) * 255 + hoverShine),
(int)(mBackgroundColor->g * (1 - shine) * 255 + hoverShine),
(int)(mBackgroundColor->b * (1 - shine) * 255 + hoverShine),
// Top left vertex
(int)(mBackgroundColor->r * (1 + shine) * 255 + hoverShine),
(int)(mBackgroundColor->g * (1 + shine) * 255 + hoverShine),
(int)(mBackgroundColor->b * (1 + shine) * 255 + hoverShine),
// Top right vertex
(int)(mBackgroundColor->r * (1 + shine) * 255 + hoverShine),
(int)(mBackgroundColor->g * (1 + shine) * 255 + hoverShine),
(int)(mBackgroundColor->b * (1 + shine) * 255 + hoverShine),
};
Button text was rendered with an inset style by first drawing the text with a light shade of the button's color, then drawing it again with dark shade of the button's color, and offset from the previous text's position by a few pixels.
Checkboxes were pretty easy to implement - essentially a UILabel and a UIButton with a slightly modified transition diagram. Clicking once goes to an "on" state and clicking again goes to an "off" state; delegates are notified of state changes with an optional callback function. When "on", the checkbox is filled with a gradient drawn using the same method as for buttons.
Sliders were also pretty simple - a guiding line and a draggable UICircle. The UICircle is moved only when dragging occurs within the bounds of the slider widget, and the y-height of the dragger is locked to the guiding line's height (i.e. changes in the y-coordinate of the mouse are ignored). On hover, the dragger is also made to "pop out" by redrawing it with a larger radius.
Textfields were basically an extension of text labels. When clicked on by a mouse, a textfield first "captures" the keyboard focus using a callback function. This is necessary since usually, only one GUI element handles keyboard input. The textfield then converts subsequent keypresses detected by glutKeyboardFunc and glutSpecialFunc from keycodes into the corresponding character or special action (enter, delete, moving the cursor around with arrow keys). The updated text and cursor position is then redrawn.