1662 lines
39 KiB
C
1662 lines
39 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;
|
|
}
|
|
|
|
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:
|
|
// Update the widgets position
|
|
self->offset = widgetAnimationInterpolateTranslate(self, k1,
|
|
k2, time);
|
|
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 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;
|
|
|
|
// 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);
|
|
|
|
// 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 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);
|
|
|
|
// Re-layout the window
|
|
if (widgetDoLayout(widgetGetRoot(self)))
|
|
{
|
|
// Set ourself as its parent
|
|
child->parent = self;
|
|
|
|
return true;
|
|
}
|
|
// Not enough space to fit the widget
|
|
else
|
|
{
|
|
// Remove child *without* calling its destructor
|
|
vectorRemoveAt(self->children, vectorSize(self->children) - 1);
|
|
|
|
// Restore the layout
|
|
widgetDoLayout(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
|
|
widgetDoLayout(widgetGetRoot(self));
|
|
}
|
|
// See if it is one of its children
|
|
else
|
|
{
|
|
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;
|
|
|
|
entry->lastCalled = 0;
|
|
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.type = EVT_DESTRUCT;
|
|
|
|
handler->destructor(self, (event *) &evtDestruct, -1,
|
|
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.type = EVT_FOCUS;
|
|
evt.event.time = widgetGetTime();
|
|
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.type = EVT_BLUR;
|
|
evt.event.time = widgetGetTime();
|
|
widgetFireCallbacks(self, (event *) &evt);
|
|
}
|
|
|
|
void widgetResizeImpl(widget *self, int w, int h)
|
|
{
|
|
const size minSize = widgetGetMinSize(self);
|
|
const size maxSize = widgetGetMaxSize(self);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void widgetRepositionImpl(widget *self, int x, int y)
|
|
{
|
|
// Update our position
|
|
self->offset.x = x;
|
|
self->offset.y = y;
|
|
}
|
|
|
|
void widgetCompositeImpl(widget *self)
|
|
{
|
|
float blendColour[4];
|
|
int i;
|
|
|
|
// Do not composite unless we are visible
|
|
if (!self->isVisible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Scale if necessary
|
|
glScalef(self->scale.x, self->scale.y, 1.0f);
|
|
|
|
// Translate such that (0,0) is the top-left of ourself
|
|
glTranslatef(self->offset.x, self->offset.y, 0.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 matrix
|
|
glBlendColor(1.0f, 1.0f, 1.0f, blendColour[3]);
|
|
glRotatef(-self->rotate, 0.0f, 0.0f, 1.0f);
|
|
glTranslatef(-self->offset.x, -self->offset.y, 0.0f);
|
|
glScalef(1.0f / self->scale.x, 1.0f / self->scale.y, 1.0f);
|
|
}
|
|
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|