warzone2100/lib/betawidget/widget.c

1825 lines
43 KiB
C

/*
This file is part of Warzone 2100.
Copyright (C) 2008 Freddie Witherden
Copyright (C) 2008 Warzone Resurrection Project
Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <string.h>
#include "widget.h"
/**
* Performs linear interpolation between the points (x0,y0) and (x1,y1),
* returning the y co-ordinate of the line when the x co-ordinate is x.
*/
#define LINEAR_INTERPOLATE(x0, y0, x1, y1, x) ((y0) + ((x) - (x0)) * \
((y1) - (y0)) / ((x1) - (x0)))
const classInfo widgetClassInfo =
{
NULL, // Root class and therefore no parent
"widget"
};
static widgetVtbl vtbl;
/**
* Creates a new cairo context and places it in *cr. The context has a format of
* format and dimensions of w * h. Should *cr not be NULL it is assumed to be an
* existing cairo context and so is destroyed.
*
* @param cr The pointer to the region of memory to create the context in.
* @param format The format of the context; such as CAIRO_FORMAT_ARGB32.
* @param w The width of the context in pixels.
* @param h The height of the context in pixels.
* @return true if the context was created successfully; false otherwise.
*/
static bool widgetCairoCreate(cairo_t **cr, cairo_format_t format, int w, int h)
{
cairo_surface_t *surface;
// See if a context already exists
if (*cr)
{
// Destroy it
cairo_destroy(*cr);
// NULL the context
*cr = NULL;
}
// Create the surface
surface = cairo_image_surface_create(format, w, h);
// Make sure it was created
if (!surface)
{
return false;
}
// Creatr a context to draw on the surface
*cr = cairo_create(surface);
// Destroy the surface (*cr still maintains a reference to it)
cairo_surface_destroy(surface);
// Make sure the context was created
if (!*cr)
{
return false;
}
return true;
}
/**
* Checks to see if the widget, self, has the mouse over it. This consists of
* two checks; firstly seeing if the mouse is inside of the widgets bounding
* rectangle; secondly, if the widget has a mask, seeing if the location is
* masked or not.
*
* @param self The widget we want to see if the mouse is over.
* @param location The location of the mouse, in absolute terms.
* @return True if the mouse is over the widget, false otherwise.
*/
static bool widgetHasMouse(widget *self, point location)
{
const rect bounds = widgetAbsoluteBounds(self);
// Check to see if it is in the widgets bounding box
bool hasMouse = pointInRect(location, bounds);
// If the widget has a mask; ensure the location is not masked
if (hasMouse && self->maskEnabled)
{
// Work out where the mouse is relative to the widget
const point relativeLocation = pointSub(location, bounds.topLeft);
// We have the mouse if the point is NOT masked
hasMouse = !widgetPointMasked(self, relativeLocation);
}
return hasMouse;
}
/**
* Returns a pointer to the event handler with an id of id. Should id be invalid
* then NULL is returned.
*
* @param self The widget whose event handler table to search.
* @param id The id of the desired event handler.
* @return A pointer to the event handler, or NULL if id is invalid.
*/
static eventTableEntry *widgetGetEventHandlerById(const widget *self, int id)
{
int i;
eventTableEntry *entry = NULL;
// Search the event handler table
for (i = 0; i < vectorSize(self->eventVtbl); i++)
{
eventTableEntry *currEntry = vectorAt(self->eventVtbl, id);
// See if the id matches
if (currEntry->id == id)
{
entry = currEntry;
break;
}
}
return entry;
}
bool widgetIsA(const widget *self, const classInfo *instanceOf)
{
const classInfo *widgetClass;
// Transverse up the hierarchy
for (widgetClass = self->classInfo;
widgetClass->parentType;
widgetClass = widgetClass->parentType)
{
// self `is a' instanceOf
if (widgetClass == instanceOf)
{
return true;
}
}
return false;
}
event widgetCreateEvent(eventType type)
{
// Create the event
event e;
// Set the time of the event to now
e.time = widgetGetTime();
// Type of the event to the one provided
e.type = type;
return e;
}
bool widgetIsEventHandler(const widget *self, int id)
{
return widgetGetEventHandlerById(self, id) ? true : false;
}
static bool widgetAnimationTimerCallback(widget *self, const event *evt,
int handlerId, void *userData)
{
int i;
vector *frames = userData;
animationFrame *currFrame;
// Regular timer event
if (evt->type == EVT_TIMER_PERSISTENT)
{
bool didInterpolate = false;
// Currently active keyframes to be interpolated between
struct
{
animationFrame *pre;
animationFrame *post;
} interpolateFrames[ANI_TYPE_COUNT] = { { NULL, NULL } };
// Work out what frames we need to interpolate between
for (i = 0; i < vectorSize(frames); i++)
{
currFrame = vectorAt(frames, i);
/*
* We are only interested in the key frames which either directly
* precede now or come directly after. (As it is these frames which
* will be interpolated between.)
*/
if (currFrame->time <= evt->time)
{
interpolateFrames[currFrame->type].pre = currFrame;
}
else if (currFrame->time > evt->time
&& !interpolateFrames[currFrame->type].post)
{
interpolateFrames[currFrame->type].post = currFrame;
}
}
// Do the interpolation
for (i = 0; i < ANI_TYPE_COUNT; i++)
{
// If there are frames to interpolate between then do so
if (interpolateFrames[i].pre && interpolateFrames[i].post)
{
// Get the points to interpolate between
animationFrame k1 = *interpolateFrames[i].pre;
animationFrame k2 = *interpolateFrames[i].post;
int time = evt->time;
switch (i)
{
case ANI_TYPE_TRANSLATE:
{
// Get the new position
point pos = widgetAnimationInterpolateTranslate(self, k1,
k2, time);
// Set the new position
widgetReposition(self, pos.x, pos.y);
break;
}
case ANI_TYPE_ROTATE:
// Update the widgets rotation
self->rotate = widgetAnimationInterpolateRotate(self, k1,
k2, time);
break;
case ANI_TYPE_SCALE:
// Update the widgets scale factor
self->scale = widgetAnimationInterpolateScale(self, k1,
k2, time);
break;
case ANI_TYPE_ALPHA:
// Update the widgets alpha
self->alpha = widgetAnimationInterpolateAlpha(self, k1,
k2, time);
break;
}
// Make a note that we did interpolate
didInterpolate = true;
}
}
// If there was no interpolation then the animation is over
if (!didInterpolate)
{
// Remove ourself (the event handler)
widgetRemoveEventHandler(self, handlerId);
}
}
else if (evt->type == EVT_DESTRUCT)
{
animationFrame *lastFrame[ANI_TYPE_COUNT] = { NULL };
// Find the last frame of each animation type
for (i = 0; i < vectorSize(frames); i++)
{
currFrame = vectorAt(frames, i);
lastFrame[currFrame->type] = currFrame;
}
// Set the position/rotation/scale/alpha to that of the final frame
for (i = 0; i < ANI_TYPE_COUNT; i++)
{
if (lastFrame[i]) switch (i)
{
case ANI_TYPE_TRANSLATE:
self->offset = lastFrame[ANI_TYPE_TRANSLATE]->data.translate;
break;
case ANI_TYPE_ROTATE:
self->rotate = lastFrame[ANI_TYPE_ROTATE]->data.rotate;
break;
case ANI_TYPE_SCALE:
self->scale = lastFrame[ANI_TYPE_SCALE]->data.scale;
break;
case ANI_TYPE_ALPHA:
self->alpha = lastFrame[ANI_TYPE_ALPHA]->data.alpha;
break;
default:
break;
}
}
// Free the frames vector
vectorMapAndDestroy(frames, free);
}
return true;
}
static bool widgetToolTipTimerCallback(widget *self, const event *evt,
int handlerId, void *userData)
{
// So long as we still have the mouse, show the tool-tip
if (self->hasMouse)
{
widgetShowToolTip(self);
}
return true;
}
static bool widgetToolTipMouseEnterCallback(widget *self, const event *evt,
int handlerId, void *userData)
{
// Only relevant if we have a tool-tip
if (self->toolTip)
{
// Install a single-shot event handler to show the tip
widgetAddTimerEventHandler(self, EVT_TIMER_SINGLE_SHOT, 2000,
widgetToolTipTimerCallback, NULL, NULL);
}
return true;
}
static bool widgetToolTipMouseLeaveCallback(widget *self, const event *evt,
int handlerId, void *userData)
{
// If we have a tool tip and it is visible; hide it
if (self->toolTip && self->toolTipVisible)
{
widgetHideToolTip(self);
}
return true;
}
static void widgetInitVtbl(widget *self)
{
static bool initialised = false;
if (!initialised)
{
vtbl.addChild = widgetAddChildImpl;
vtbl.removeChild = widgetRemoveChildImpl;
vtbl.fireCallbacks = widgetFireCallbacksImpl;
vtbl.fireTimerCallbacks = widgetFireTimerCallbacksImpl;
vtbl.addEventHandler = widgetAddEventHandlerImpl;
vtbl.addTimerEventHandler = widgetAddTimerEventHandlerImpl;
vtbl.removeEventHandler = widgetRemoveEventHandlerImpl;
vtbl.handleEvent = widgetHandleEventImpl;
vtbl.animationInterpolateTranslate = widgetAnimationInterpolateTranslateImpl;
vtbl.animationInterpolateRotate = widgetAnimationInterpolateRotateImpl;
vtbl.animationInterpolateScale = widgetAnimationInterpolateScaleImpl;
vtbl.animationInterpolateAlpha = widgetAnimationInterpolateAlphaImpl;
vtbl.focus = widgetFocusImpl;
vtbl.blur = widgetBlurImpl;
vtbl.acceptDrag = widgetAcceptDragImpl;
vtbl.declineDrag = widgetDeclineDragImpl;
vtbl.enable = widgetEnableImpl;
vtbl.disable = widgetDisableImpl;
vtbl.show = widgetShowImpl;
vtbl.hide = widgetHideImpl;
vtbl.getMinSize = NULL;
vtbl.getMaxSize = NULL;
vtbl.resize = widgetResizeImpl;
vtbl.reposition = widgetRepositionImpl;
vtbl.composite = widgetCompositeImpl;
vtbl.doLayout = NULL;
vtbl.doDraw = NULL;
vtbl.doDrawMask = NULL;
vtbl.destroy = widgetDestroyImpl;
initialised = true;
}
// Set the classes vtable
self->vtbl = &vtbl;
}
void widgetInit(widget *self, const char *id)
{
// Prepare our vtable
widgetInitVtbl(self);
// Set our type
self->classInfo = &widgetClassInfo;
// Prepare our container
self->children = vectorCreate();
// Prepare our events table
self->eventVtbl = vectorCreate();
// Copy the ID of the widget
self->id = strdup(id);
// Default parent is none
self->parent = NULL;
// Default size is (-1,-1) 'NULL' size
self->size.x = -1.0f;
self->size.y = -1.0f;
// We are not rotated by default
self->rotate = 0.0f;
// Scale is (1,1)
self->scale.x = 1.0f;
self->scale.y = 1.0f;
// Alpha is 1 (no change)
self->alpha = 1.0f;
// Zero the user data
self->userData = 0;
self->pUserData = NULL;
// Create a dummy cairo context (getMin/MaxSize may depend on it)
self->cr = NULL;
widgetCairoCreate(&self->cr, CAIRO_FORMAT_ARGB32, 0, 0);
// Ask OpenGL for a texture id
glGenTextures(1, &self->textureId);
// No tool-tip by default
self->toolTip = NULL;
self->toolTipVisible = false;
// Install required event handlers for showing/hiding tool-tips
widgetAddEventHandler(self, EVT_MOUSE_ENTER,
widgetToolTipMouseEnterCallback, NULL, NULL);
widgetAddEventHandler(self, EVT_MOUSE_LEAVE,
widgetToolTipMouseLeaveCallback, NULL, NULL);
// Focus and mouse are false by default
self->hasFocus = false;
self->hasMouse = false;
self->hasMouseDown = false;
// By default we need drawing
self->needsRedraw = true;
// Enabled by default
self->isEnabled = true;
// Also by default we need to be shown (hence are invisible)
self->isVisible = false;
// By default the mouse-event mask is disabled
self->maskCr = NULL;
self->maskEnabled = false;
}
void widgetDestroyImpl(widget *self)
{
eventTableEntry *currEntry;
// Release the container
vectorMapAndDestroy(self->children, (mapCallback) widgetDestroy);
// Release the event handler table
while ((currEntry = vectorHead(self->eventVtbl)))
{
widgetRemoveEventHandler(self, currEntry->id);
}
vectorDestroy(self->eventVtbl);
// Destroy the cairo context
cairo_destroy(self->cr);
// Destroy the texture
glDeleteTextures(1, &self->textureId);
// If we use a mask, destroy it
if (self->maskEnabled)
{
cairo_destroy(self->maskCr);
}
// Free the ID
free((char *) self->id);
// Free the tool-tip (if any)
free((char *) self->toolTip);
// Free ourself
free(self);
}
void widgetDraw(widget *self)
{
int i;
// See if we need to be redrawn
if (self->needsRedraw)
{
void *bits = cairo_image_surface_get_data(cairo_get_target(self->cr));
self->needsRedraw = false;
// Clear the current context
cairo_set_operator(self->cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba(self->cr, 0.0, 0.0, 0.0, 0.0);
cairo_paint(self->cr);
// Restore the compositing operator back to the default
cairo_set_operator(self->cr, CAIRO_OPERATOR_OVER);
// Save (push) the current context
cairo_save(self->cr);
// Redaw ourself
widgetDoDraw(self);
// Restore the context
cairo_restore(self->cr);
// Update the texture
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self->textureId);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, self->size.x,
self->size.y, 0, GL_BGRA, GL_UNSIGNED_BYTE, bits);
}
// Draw our children (even if we did not need redrawing our children might)
for (i = 0; i < vectorSize(self->children); i++)
{
widget *child = vectorAt(self->children, i);
// Ask the child to re-draw itself
widgetDraw(child);
}
}
void widgetDrawMask(widget *self)
{
// Make sure masking is enabled and that the context is valid
assert(self->maskEnabled);
assert(self->maskCr);
// Make the context opaque (so it is all masked)
cairo_set_source_rgba(self->maskCr, 0.0, 0.0, 0.0, 1.0);
cairo_paint(self->maskCr);
// Change the blending mode so that the source overwrites the destination
cairo_set_operator(self->maskCr, CAIRO_OPERATOR_SOURCE);
// Change the source to transparency
cairo_set_source_rgba(self->maskCr, 0.0, 0.0, 0.0, 0.0);
// Draw the mask
widgetDoDrawMask(self);
}
point widgetAbsolutePosition(const widget *self)
{
// Get our own offset
point pos = self->offset;
// Add to this our parents offset
if (self->parent != NULL)
{
pos = pointAdd(pos, widgetAbsolutePosition(self->parent));
}
return pos;
}
rect widgetAbsoluteBounds(const widget *self)
{
// Get our position
const point p = widgetAbsolutePosition(self);
// Construct and return a rect from this
return rectFromPointAndSize(p, self->size);
}
widget *widgetGetRoot(widget *self)
{
// If we are the root widget, return early
if (self->parent == NULL)
{
return self;
}
// Otherwise search the hierarchy
else
{
widget *current;
for (current = self->parent; current->parent; current = current->parent);
return current;
}
}
widget *widgetFindById(widget *self, const char *id)
{
// See if we have that ID
if (strcmp(self->id, id) == 0)
{
return self;
}
// Try our children
else
{
int i;
for (i = 0; i < vectorSize(self->children); i++)
{
// Get the child widget
widget *child = vectorAt(self->children, i);
// Call its findById method
widget *match = widgetFindById(child, id);
// If it matched, return
if (match)
{
return match;
}
}
}
// If we found nothing return NULL
return NULL;
}
void *widgetGetEventHandlerUserData(const widget *self, int id)
{
eventTableEntry *entry = widgetGetEventHandlerById(self, id);
// Make sure we found something
assert(entry != NULL);
// Return the user-data
return entry->userData;
}
void widgetSetEventHandlerUserData(widget *self, int id, void *userData)
{
eventTableEntry *entry = widgetGetEventHandlerById(self, id);
// Make sure we found something
assert(entry != NULL);
// Set the user-data
entry->userData = userData;
}
bool widgetAddChildImpl(widget *self, widget *child)
{
// Make sure the id of the child is unquie
assert(widgetFindById(widgetGetRoot(self), child->id) == NULL);
// Make sure the child does not currently have a parent
assert(child->parent == NULL);
// Add the widget
vectorAdd(self->children, child);
// So long as our size is not (-1,-1) (NULL-size), re-layout the window
if ((self->size.x == -1.0f && self->size.y == -1.0f)
|| widgetDoLayout(widgetGetRoot(self)))
{
// Set ourself as its parent
child->parent = self;
return true;
}
// Not enough space to fit the widget (widgetDoLayout returned false)
else
{
// Remove child *without* calling its destructor
vectorRemoveAt(self->children, vectorSize(self->children) - 1);
// Restore the layout
widgetDoLayout(widgetGetRoot(self));
return false;
}
}
void widgetRemoveChildImpl(widget *self, widget *child)
{
int i;
for (i = 0; i < vectorSize(self->children); i++)
{
// If the child is the to-be-removed widget, remove it
if (vectorAt(self->children, i) == child)
{
// Call the destructor for the widget
widgetDestroy(vectorAt(self->children, i));
// Remove it from the list of children
vectorRemoveAt(self->children, i);
// Re-layout the window (so long as we are part of one)
if (self->size.x != -1.0f && self->size.y != -1.0f)
{
widgetDoLayout(widgetGetRoot(self));
}
}
// See if it is one of its children
else if (child->parent != self)
{
widgetRemoveChild(vectorAt(self->children, i), child);
}
}
}
int widgetAddEventHandlerImpl(widget *self, eventType type, callback handler,
callback destructor, void *userData)
{
eventTableEntry *entry = malloc(sizeof(eventTableEntry));
eventTableEntry *lastEntry = vectorHead(self->eventVtbl);
// Timer events should use addTimerEventHandler
assert(type != EVT_TIMER_SINGLE_SHOT && type != EVT_TIMER_PERSISTENT);
// Assign the handler an id which is one higher than the current highest
entry->id = (lastEntry) ? lastEntry->id + 1 : 1;
entry->type = type;
entry->callback = handler;
entry->destructor = destructor;
entry->userData = userData;
entry->lastCalled = 0; // We have never been called
entry->interval = -1; // We are not a timer event
// Add the handler to the table
vectorAdd(self->eventVtbl, entry);
// Return the id of the handler
return entry->id;
}
int widgetAddTimerEventHandlerImpl(widget *self, eventType type, int interval,
callback handler, callback destructor,
void *userData)
{
eventTableEntry *entry = malloc(sizeof(eventTableEntry));
eventTableEntry *lastEntry = vectorHead(self->eventVtbl);
// We should only be used to add timer events
assert(type == EVT_TIMER_SINGLE_SHOT || type == EVT_TIMER_PERSISTENT);
entry->id = (lastEntry) ? lastEntry->id + 1 : 1;
entry->type = type;
entry->callback = handler;
entry->destructor = destructor;
entry->userData = userData;
// the handler is called when lastCalled + interval >= current time
entry->lastCalled = widgetGetTime();
entry->interval = interval;
// Add the handler to the table
vectorAdd(self->eventVtbl, entry);
// Return the id of the handler
return entry->id;
}
void widgetRemoveEventHandlerImpl(widget *self, int id)
{
int i;
// Search for the handler with the id
for (i = 0; i < vectorSize(self->eventVtbl); i++)
{
eventTableEntry *handler = vectorAt(self->eventVtbl, i);
// If the handler matches, remove it
if (handler->id == id)
{
// If there is a destructor; call it
if (handler->destructor)
{
// Generate an EVT_DESTRUCT event
eventMisc evtDestruct;
evtDestruct.event = widgetCreateEvent(EVT_DESTRUCT);
handler->destructor(self, (event *) &evtDestruct, handler->id,
handler->userData);
}
// Release the handler
free(handler);
// Finally, remove the event handler from the table
vectorRemoveAt(self->eventVtbl, i);
break;
}
}
}
int widgetAddAnimation(widget *self, int nframes,
const animationFrame *frames)
{
int i;
int lastTime = 0;
int translateCount = 0, rotateCount = 0, scaleCount = 0, alphaCount = 0;
vector *ourFrames;
animationFrame *currFrame;
/*
* We need to make sure that the frames are in order and that there is
* enough information to do the required interpolation.
*/
for (i = 0; i < nframes; i++)
{
// Make sure the frame N+1 follows after N
assert(frames[i].time >= lastTime);
// Update the time
lastTime = frames[i].time;
// Update the animation frame count
switch (frames[i].type)
{
case ANI_TYPE_TRANSLATE:
translateCount++;
break;
case ANI_TYPE_ROTATE:
rotateCount++;
break;
case ANI_TYPE_SCALE:
scaleCount++;
break;
case ANI_TYPE_ALPHA:
alphaCount++;
break;
default:
assert(!"Invalid animation type");
break;
}
}
// Ensure the frame counts are valid
assert(translateCount != 1);
assert(rotateCount != 1);
assert(scaleCount != 1);
assert(alphaCount != 1);
// Copy the frames over to our own vector
ourFrames = vectorCreate();
for (i = 0; i < nframes; i++)
{
// Allocate space for the frame
currFrame = malloc(sizeof(animationFrame));
// Copy the frame
*currFrame = frames[i];
// De-normalise the frames time (so that it is absolute)
currFrame->time += widgetGetTime();
// Add the frame to the vector
vectorAdd(ourFrames, currFrame);
}
// Install the animation timer event handler
return widgetAddTimerEventHandler(self, EVT_TIMER_PERSISTENT, 10,
widgetAnimationTimerCallback,
widgetAnimationTimerCallback, ourFrames);
}
point widgetAnimationInterpolateTranslateImpl(widget *self, animationFrame k1,
animationFrame k2, int time)
{
point newOffset;
// Use linear interpolation to get the new (x,y) coords
newOffset.x = LINEAR_INTERPOLATE(k1.time, k1.data.translate.x,
k2.time, k2.data.translate.x, time);
newOffset.y = LINEAR_INTERPOLATE(k1.time, k1.data.translate.y,
k2.time, k2.data.translate.y, time);
return newOffset;
}
float widgetAnimationInterpolateRotateImpl(widget *self, animationFrame k1,
animationFrame k2, int time)
{
return LINEAR_INTERPOLATE(k1.time, k1.data.rotate,
k2.time, k2.data.rotate, time);
}
point widgetAnimationInterpolateScaleImpl(widget *self, animationFrame k1,
animationFrame k2, int time)
{
point newScale;
// Like with the translate method use linear interpolation
newScale.x = LINEAR_INTERPOLATE(k1.time, k1.data.scale.x,
k2.time, k2.data.scale.x, time);
newScale.y = LINEAR_INTERPOLATE(k1.time, k1.data.scale.y,
k2.time, k2.data.scale.y, time);
return newScale;
}
float widgetAnimationInterpolateAlphaImpl(widget *self, animationFrame k1,
animationFrame k2, int time)
{
return LINEAR_INTERPOLATE(k1.time, k1.data.alpha,
k2.time, k2.data.alpha, time);
}
bool widgetFireCallbacksImpl(widget *self, const event *evt)
{
int i;
bool ret;
for (i = 0; i < vectorSize(self->eventVtbl); i++)
{
eventTableEntry *handler = vectorAt(self->eventVtbl, i);
// If handler is registered to handle evt
if (handler->type == evt->type)
{
// Fire the callback
ret = handler->callback(self, evt, handler->id, handler->userData);
// Update the last called time
handler->lastCalled = evt->time;
// Break if the handler returned false
if (!ret)
{
break;
}
}
}
// FIXME
return true;
}
bool widgetFireTimerCallbacksImpl(widget *self, const event *evt)
{
int i;
bool ret;
eventTimer evtTimer = *((eventTimer *) evt);
// We should only be passed EVT_TIMER events
assert(evt->type == EVT_TIMER);
for (i = 0; i < vectorSize(self->eventVtbl); i++)
{
eventTableEntry *handler = vectorAt(self->eventVtbl, i);
// See if the handler is registered to handle timer events
if (handler->type == EVT_TIMER_SINGLE_SHOT
|| handler->type == EVT_TIMER_PERSISTENT)
{
// See if the event needs to be fired
if (evt->time >= (handler->lastCalled + handler->interval))
{
// Ensure the type of our custom event matches
evtTimer.event.type = handler->type;
// Fire the associated callback
ret = handler->callback(self, (event *) &evtTimer, handler->id,
handler->userData);
// Update the last called time
handler->lastCalled = evt->time;
// If the event is single shot then remove it
if (handler->type == EVT_TIMER_SINGLE_SHOT)
{
widgetRemoveEventHandler(self, handler->id);
}
}
}
}
// FIXME
return true;
}
void widgetAcceptDragImpl(widget *self)
{
// Make sure there is an active drag offer
assert(self->dragState == DRAG_PENDING);
// Accept the offer
self->dragState = DRAG_ACCEPTED;
}
void widgetDeclineDragImpl(widget *self)
{
// Make sure there is an active drag offer
assert(self->dragState == DRAG_PENDING);
// Decline the offer
self->dragState = DRAG_DECLINED;
}
void widgetEnableImpl(widget *self)
{
int i;
// First make sure our parent is enabled
if (self->parent && !self->parent->isEnabled)
{
return;
}
// Enable ourself
self->isEnabled = true;
// Enable all of our children
for (i = 0; i < vectorSize(self->children); i++)
{
widgetEnable(vectorAt(self->children, i));
}
}
void widgetDisableImpl(widget *self)
{
int i;
// If we are currently disabled, return
if (!self->isEnabled)
{
return;
}
// Disable ourself
self->isEnabled = false;
// Disable our children
for (i = 0; i < vectorSize(self->children); i++)
{
widgetDisable(WIDGET(vectorAt(self->children, i)));
}
}
void widgetShowImpl(widget *self)
{
// Make ourself visible
self->isVisible = true;
// We need redrawing
self->needsRedraw = true;
}
void widgetHideImpl(widget *self)
{
// Make ourself invisible
self->isVisible = false;
// NB: No effect on redrawing
}
void widgetFocusImpl(widget *self)
{
eventMisc evt;
// Check that we are not currently focused
if (self->hasFocus)
{
int i;
// Blur any of our currently focused child widgets
for (i = 0; i < vectorSize(self->children); i++)
{
widget *child = vectorAt(self->children, i);
if (child->hasFocus)
{
widgetBlur(child);
}
}
return;
}
// If we have a parent, focus it
if (self->parent)
{
widgetFocus(self->parent);
}
// Focus ourself
self->hasFocus = true;
// Fire our on-focus callbacks
evt.event = widgetCreateEvent(EVT_FOCUS);
widgetFireCallbacks(self, (event *) &evt);
}
void widgetBlurImpl(widget *self)
{
widget *current;
eventMisc evt;
// Make sure we have focus
if (!self->hasFocus)
{
// TODO: We should log this eventuality
return;
}
// First blur any focused child widgets
while ((current = widgetGetCurrentlyFocused(self)) != self)
{
widgetBlur(current);
}
// Blur ourself
self->hasFocus = false;
// Fire off the on-blur callbacks
evt.event = widgetCreateEvent(EVT_BLUR);
widgetFireCallbacks(self, (event *) &evt);
}
void widgetShowToolTip(widget *self)
{
// Create the event
eventToolTip evt;
evt.event = widgetCreateEvent(EVT_TOOL_TIP_SHOW);
evt.target = self;
// Dispatch the event to the root widget
widgetHandleEvent(widgetGetRoot(self), (event *) &evt);
// Note that the tool-tip is visible
self->toolTipVisible = true;
}
void widgetHideToolTip(widget *self)
{
// Create the event
eventToolTip evt;
evt.event = widgetCreateEvent(EVT_TOOL_TIP_HIDE);
evt.target = self;
// Dispatch the event to the root widget
widgetHandleEvent(widgetGetRoot(self), (event *) &evt);
// Note that the tool-tip is now hidden
self->toolTipVisible = false;
}
void widgetResizeImpl(widget *self, int w, int h)
{
const size minSize = widgetGetMinSize(self);
const size maxSize = widgetGetMaxSize(self);
// Create an event
eventResize evtResize;
evtResize.event = widgetCreateEvent(EVT_RESIZE);
// Save the current size in the event
evtResize.oldSize = self->size;
assert(minSize.x <= w);
assert(minSize.y <= h);
assert(w <= maxSize.x);
assert(h <= maxSize.y);
self->size.x = w;
self->size.y = h;
// Re-create the cairo context at this new size
widgetCairoCreate(&self->cr, CAIRO_FORMAT_ARGB32, w, h);
// If a mask is enabled; re-create it also
if (self->maskEnabled)
{
widgetCairoCreate(&self->maskCr, CAIRO_FORMAT_A1, w, h);
// Re-draw the mask (only done on resize)
widgetDrawMask(self);
}
// Set the needs redraw flag
self->needsRedraw = true;
// If we have any children, we need to redo their layout
if (vectorSize(self->children))
{
widgetDoLayout(self);
}
// Fire any EVT_RESIZE callbacks
widgetFireCallbacks(self, (event *) &evtResize);
}
void widgetRepositionImpl(widget *self, int x, int y)
{
// Generate a reposition event
eventReposition evtReposition;
evtReposition.event = widgetCreateEvent(EVT_REPOSITION);
// Save the current position in the event
evtReposition.oldPosition = self->offset;
evtReposition.oldAbsolutePosition = widgetAbsolutePosition(self);
// Update our position
self->offset.x = x;
self->offset.y = y;
// Fire any callbacks for EVT_REPOSITION
widgetFireCallbacks(self, (event *) &evtReposition);
}
void widgetCompositeImpl(widget *self)
{
float blendColour[4];
int i;
// Do not composite unless we are visible
if (!self->isVisible)
{
return;
}
// Save the current model-view matrix
glPushMatrix();
// Translate such that (0,0) is the top-left of ourself
glTranslatef(self->offset.x, self->offset.y, 0.0f);
// Scale if necessary
glScalef(self->scale.x, self->scale.y, 1.0f);
// Rotate ourself
glRotatef(self->rotate, 0.0f, 0.0f, 1.0f);
// Set our alpha (blend colour)
glGetFloatv(GL_BLEND_COLOR, blendColour);
glBlendColor(1.0f, 1.0f, 1.0f, blendColour[3] * self->alpha);
// Composite ourself
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, self->textureId);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, self->size.y);
glVertex2f(0.0f, self->size.y);
glTexCoord2f(0.0f, 0.0f);
glVertex2f(0.0f, 0.0f);
glTexCoord2f(self->size.x, self->size.y);
glVertex2f(self->size.x, self->size.y);
glTexCoord2f(self->size.x, 0.0f);
glVertex2f(self->size.x, 0.0f);
glEnd();
// Now our children
for (i = 0; i < vectorSize(self->children); i++)
{
widget *child = vectorAt(self->children, i);
// Composite
widgetComposite(child);
}
// Restore the model-view matrix
glPopMatrix();
// Restore the blend colour
glBlendColor(1.0f, 1.0f, 1.0f, blendColour[3]);
}
void widgetEnableMask(widget *self)
{
// Check if the mask is already enabled (worth asserting)
if (self->maskEnabled)
{
return;
}
// Make sure we implement the doDrawMask method
WIDGET_CHECK_METHOD(self, doDrawMask);
// Prepare the cairo surface
widgetCairoCreate(&self->maskCr, CAIRO_FORMAT_A1,
self->size.x, self->size.y);
// Note that the mask is now enabled
self->maskEnabled = true;
// Draw the mask
widgetDrawMask(self);
}
void widgetDisableMask(widget *self)
{
// NO-OP if the mask is not enabled
if (!self->maskEnabled)
{
return;
}
// Release the cairo context
cairo_destroy(self->maskCr);
// Note that the mask is now disabled
self->maskEnabled = false;
}
widget *widgetGetCurrentlyFocused(widget *self)
{
int i;
if (!self->hasFocus)
{
return NULL;
}
for (i = 0; i < vectorSize(self->children); i++)
{
widget *child = vectorAt(self->children, i);
if (child->hasFocus)
{
return widgetGetCurrentlyFocused(child);
}
}
// None of our children are focused, return ourself
return self;
}
widget *widgetGetCurrentlyMousedOver(widget *self)
{
int i;
// Make sure we have the mouse
if (!self->hasMouse)
{
return NULL;
}
// See if any of our children are moused over
for (i = 0; i < vectorSize(self->children); i++)
{
widget *child = vectorAt(self->children, i);
if (child->hasMouse)
{
return widgetGetCurrentlyMousedOver(child);
}
}
// None of our children have the mouse; return ourself
return self;
}
bool widgetHandleEventImpl(widget *self, const event *evt)
{
// If the event should be passed onto our children
bool relevant = true;
// If we are disabled or invisible then only timer events are relevant
if ((!self->isEnabled || !self->isVisible)
&& evt->type != EVT_TIMER)
{
return true;
}
switch (evt->type)
{
case EVT_MOUSE_MOVE:
{
eventMouse evtMouse = *((eventMouse *) evt);
bool newHasMouse = widgetHasMouse(self, evtMouse.loc);
/*
* Mouse motion events should not be dispatched if a mouse button
* is currently `down' on our parent but not ourself.
*/
if (self->parent
&& self->parent->hasMouseDown
&& !self->hasMouseDown)
{
relevant = false;
break;
}
// While dragging hasMouse irrelevant
if (self->dragState == DRAG_ACTIVE)
{
// Morph the event into a DRAG_TRACK event
evtMouse.event.type = EVT_DRAG_TRACK;
widgetFireCallbacks(self, (event *) &evtMouse);
// Not relevant to our children
relevant = false;
break;
}
// If we have or did have the mouse
if (newHasMouse || self->hasMouse)
{
// If we have just `got' the mouse
if (newHasMouse && !self->hasMouse)
{
// Generate an EVT_MOUSE_ENTER event instead
evtMouse.event.type = EVT_MOUSE_ENTER;
}
// If we have just lost the mouse
else if (!newHasMouse && self->hasMouse)
{
// Generate an EVT_MOUSE_LEAVE event
evtMouse.event.type = EVT_MOUSE_LEAVE;
}
// Fire any registered callbacks for evtMouse.event.type
widgetFireCallbacks(self, (event *) &evtMouse);
}
// Of no interest to us (and therefore not to our children either)
else
{
relevant = false;
}
// If the mouse is down offer a drag event
if (newHasMouse && self->hasMouseDown)
{
// We are awaiting a response to our drag offer
self->dragState = DRAG_PENDING;
// Morph the event
evtMouse.event.type = EVT_DRAG_BEGIN;
// Fire any EVT_DRAG_BEGIN callbacks
widgetFireCallbacks(self, (event *) &evtMouse);
// Check to see if the drag was accepted or not
if (self->dragState == DRAG_ACCEPTED)
{
self->dragState = DRAG_ACTIVE;
}
// Drag was declined or not handled (no BEGIN callback)
else
{
self->dragState = DRAG_NONE;
}
}
// Update the status of the mouse
self->hasMouse = newHasMouse;
break;
}
case EVT_MOUSE_DOWN:
case EVT_MOUSE_UP:
{
eventMouseBtn evtMouseBtn = *((eventMouseBtn *) evt);
// On mouse-up cancel any active drag events
if (evt->type == EVT_MOUSE_UP && self->dragState == DRAG_ACTIVE)
{
// Morph the event
evtMouseBtn.event.type = EVT_DRAG_END;
// Fire the callbacks
widgetFireCallbacks(self, (event *) &evtMouseBtn);
// No drag is active
self->dragState = DRAG_NONE;
// Mouse is no longer down
self->hasMouseDown = false;
// Of no relevance to our children
relevant = false;
break;
}
// If the mouse is inside of the widget
if (widgetHasMouse(self, evtMouseBtn.loc))
{
// If it is a mouse-down event set hasMouseDown to true
if (evt->type == EVT_MOUSE_DOWN)
{
self->hasMouseDown = true;
}
// Fire mouse down and mouse up callbacks
widgetFireCallbacks(self, (event *) &evtMouseBtn);
// Check for a click event
if (evt->type == EVT_MOUSE_UP && self->hasMouseDown)
{
evtMouseBtn.event.type = EVT_MOUSE_CLICK;
widgetFireCallbacks(self, (event *) &evtMouseBtn);
}
}
// If the mouse is no longer down
else if (evt->type == EVT_MOUSE_UP && self->hasMouseDown)
{
self->hasMouseDown = false;
}
// Of no interest to us or our children
else
{
relevant = false;
}
break;
}
case EVT_KEY_DOWN:
case EVT_KEY_UP:
case EVT_TEXT:
{
// Only relevant if we have focus
if (self->hasFocus)
{
widgetFireCallbacks(self, evt);
}
else
{
relevant = false;
}
break;
}
case EVT_TIMER:
{
// fireTimerCallbacks will handle this
widgetFireTimerCallbacks(self, evt);
break;
}
case EVT_TOOL_TIP_SHOW:
case EVT_TOOL_TIP_HIDE:
{
// Fire any callback handlers
widgetFireCallbacks(self, evt);
// Never relevant to our children
relevant = false;
break;
}
default:
break;
}
// If necessary pass the event onto our children
if (relevant)
{
int i;
for (i = 0; i < vectorSize(self->children); i++)
{
widgetHandleEvent(vectorAt(self->children, i), evt);
}
}
return true;
}
bool widgetPointMasked(const widget *self, point loc)
{
/*
* The format of the mask is CAIRO_FORMAT_A1, where:
* "each pixel is a 1-bit quantity holding an alpha value.
* Pixels are packed together into 32-bit quantities"
*
* TODO: An explanation worth reading.
*/
cairo_surface_t *surface = cairo_get_target(self->maskCr);
// The number of bytes to a row
const int stride = cairo_image_surface_get_stride(surface);
// The mask itself
const uint32_t *data = (uint32_t *) cairo_image_surface_get_data(surface);
// The 32-bit segment of data we are interested in
const uint32_t bits = data[(int) loc.y * (stride / 4) + ((int) loc.x / 32)];
// Where in the 32-bit segment the pixel is located
const uint32_t pixelMask = 1 << ((int) loc.x % 32);
// Check to see if the pixel is set or not
if (bits & pixelMask)
{
return true;
}
else
{
return false;
}
}
void widgetSetToolTip(widget *self, const char *tip)
{
// Free the current tip (if any)
free((char *) self->toolTip);
// If a new tip is being set, duplicate it
if (tip)
{
self->toolTip = strdup(tip);
}
// No new tip being set; disable tool-tips
else
{
self->toolTip = NULL;
}
}
const char *widgetGetToolTip(widget *self)
{
return self->toolTip;
}
bool widgetAddChild(widget *self, widget *child)
{
return WIDGET_GET_VTBL(self)->addChild(self, child);
}
void widgetRemoveChild(widget *self, widget *child)
{
WIDGET_GET_VTBL(self)->removeChild(self, child);
}
int widgetAddEventHandler(widget *self, eventType type, callback handler,
callback destructor, void *userData)
{
return WIDGET_GET_VTBL(self)->addEventHandler(self, type, handler,
destructor, userData);
}
int widgetAddTimerEventHandler(widget *self, eventType type, int interval,
callback handler, callback destructor,
void *userData)
{
return WIDGET_GET_VTBL(self)->addTimerEventHandler(self, type, interval,
handler, destructor,
userData);
}
void widgetRemoveEventHandler(widget *self, int id)
{
WIDGET_GET_VTBL(self)->removeEventHandler(self, id);
}
bool widgetFireCallbacks(widget *self, const event *evt)
{
return WIDGET_GET_VTBL(self)->fireCallbacks(self, evt);
}
bool widgetFireTimerCallbacks(widget *self, const event *evt)
{
return WIDGET_GET_VTBL(self)->fireTimerCallbacks(self, evt);
}
void widgetEnable(widget *self)
{
WIDGET_GET_VTBL(self)->enable(self);
}
void widgetDisable(widget *self)
{
WIDGET_GET_VTBL(self)->disable(self);
}
void widgetShow(widget *self)
{
WIDGET_GET_VTBL(self)->show(self);
}
void widgetHide(widget *self)
{
WIDGET_GET_VTBL(self)->hide(self);
}
void widgetFocus(widget *self)
{
WIDGET_GET_VTBL(self)->focus(self);
}
void widgetBlur(widget *self)
{
WIDGET_GET_VTBL(self)->blur(self);
}
void widgetAcceptDrag(widget *self)
{
WIDGET_GET_VTBL(self)->acceptDrag(self);
}
void widgetDeclineDrag(widget *self)
{
WIDGET_GET_VTBL(self)->declineDrag(self);
}
size widgetGetMinSize(widget *self)
{
WIDGET_CHECK_METHOD(self, getMinSize);
return WIDGET_GET_VTBL(self)->getMinSize(self);
}
size widgetGetMaxSize(widget *self)
{
WIDGET_CHECK_METHOD(self, getMaxSize);
return WIDGET_GET_VTBL(self)->getMaxSize(self);
}
void widgetResize(widget *self, int w, int h)
{
WIDGET_GET_VTBL(self)->resize(self, w, h);
}
void widgetReposition(widget *self, int x, int y)
{
WIDGET_GET_VTBL(self)->reposition(self, x, y);
}
void widgetComposite(widget *self)
{
WIDGET_GET_VTBL(self)->composite(self);
}
void widgetDoDraw(widget *self)
{
WIDGET_CHECK_METHOD(self, doDraw);
WIDGET_GET_VTBL(self)->doDraw(self);
}
void widgetDoDrawMask(widget *self)
{
WIDGET_CHECK_METHOD(self, doDrawMask);
WIDGET_GET_VTBL(self)->doDrawMask(self);
}
bool widgetDoLayout(widget *self)
{
WIDGET_CHECK_METHOD(self, doLayout);
return WIDGET_GET_VTBL(self)->doLayout(self);
}
bool widgetHandleEvent(widget *self, const event *evt)
{
return WIDGET_GET_VTBL(self)->handleEvent(self, evt);
}
point widgetAnimationInterpolateTranslate(widget *self, animationFrame k1,
animationFrame k2, int time)
{
return WIDGET_GET_VTBL(self)->animationInterpolateTranslate(self, k1, k2,
time);
}
float widgetAnimationInterpolateRotate(widget *self, animationFrame k1,
animationFrame k2, int time)
{
return WIDGET_GET_VTBL(self)->animationInterpolateRotate(self, k1, k2,
time);
}
point widgetAnimationInterpolateScale(widget *self, animationFrame k1,
animationFrame k2, int time)
{
return WIDGET_GET_VTBL(self)->animationInterpolateScale(self, k1, k2,
time);
}
float widgetAnimationInterpolateAlpha(widget *self, animationFrame k1,
animationFrame k2, int time)
{
return WIDGET_GET_VTBL(self)->animationInterpolateAlpha(self, k1, k2,
time);
}
void widgetDestroy(widget *self)
{
WIDGET_GET_VTBL(self)->destroy(self);
}