Add experimental support for masks in betawidget. This allows for non-rectangular widgets.
git-svn-id: svn+ssh://svn.gna.org/svn/warzone/trunk@5413 4a71c877-e1ca-e34f-864e-861f7616d084master
parent
dd86c02f97
commit
8895e7efba
|
@ -26,3 +26,12 @@ point pointAdd(point p, point q)
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
point pointSub(point p, point q)
|
||||||
|
{
|
||||||
|
point r;
|
||||||
|
r.x = p.x - q.x;
|
||||||
|
r.y = p.y - q.y;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
|
@ -64,5 +64,14 @@ bool pointInRect(point p, rect r);
|
||||||
*/
|
*/
|
||||||
point pointAdd(point p, point q);
|
point pointAdd(point p, point q);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts point q from point p.
|
||||||
|
*
|
||||||
|
* @param p
|
||||||
|
* @param q
|
||||||
|
* @return p - q.
|
||||||
|
*/
|
||||||
|
point pointSub(point p, point q);
|
||||||
|
|
||||||
|
|
||||||
#endif /*GEOM_H_*/
|
#endif /*GEOM_H_*/
|
||||||
|
|
|
@ -10,6 +10,55 @@ const classInfo widgetClassInfo =
|
||||||
|
|
||||||
static widgetVtbl vtbl;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
bool widgetIsA(widget *self, const classInfo *instanceOf)
|
bool widgetIsA(widget *self, const classInfo *instanceOf)
|
||||||
{
|
{
|
||||||
const classInfo *widgetClass;
|
const classInfo *widgetClass;
|
||||||
|
@ -61,6 +110,7 @@ static void widgetInitVtbl(widget *self)
|
||||||
|
|
||||||
vtbl.doLayout = NULL;
|
vtbl.doLayout = NULL;
|
||||||
vtbl.doDraw = NULL;
|
vtbl.doDraw = NULL;
|
||||||
|
vtbl.doDrawMask = NULL;
|
||||||
|
|
||||||
vtbl.destroy = widgetDestroyImpl;
|
vtbl.destroy = widgetDestroyImpl;
|
||||||
|
|
||||||
|
@ -76,8 +126,6 @@ static void widgetInitVtbl(widget *self)
|
||||||
*/
|
*/
|
||||||
void widgetInit(widget *self, const char *id)
|
void widgetInit(widget *self, const char *id)
|
||||||
{
|
{
|
||||||
cairo_surface_t *surface;
|
|
||||||
|
|
||||||
// Prepare our vtable
|
// Prepare our vtable
|
||||||
widgetInitVtbl(self);
|
widgetInitVtbl(self);
|
||||||
|
|
||||||
|
@ -97,11 +145,8 @@ void widgetInit(widget *self, const char *id)
|
||||||
self->parent = NULL;
|
self->parent = NULL;
|
||||||
|
|
||||||
// Create a dummy cairo context (getMin/MaxSize may depend on it)
|
// Create a dummy cairo context (getMin/MaxSize may depend on it)
|
||||||
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
|
self->cr = NULL;
|
||||||
self->cr = cairo_create(surface);
|
widgetCairoCreate(&self->cr, CAIRO_FORMAT_ARGB32, 0, 0);
|
||||||
|
|
||||||
// `Destroy' the surface (self->cr maintains a reference to it)
|
|
||||||
cairo_surface_destroy(surface);
|
|
||||||
|
|
||||||
// Focus and mouse are false by default
|
// Focus and mouse are false by default
|
||||||
self->hasFocus = false;
|
self->hasFocus = false;
|
||||||
|
@ -110,6 +155,10 @@ void widgetInit(widget *self, const char *id)
|
||||||
|
|
||||||
// By default we need drawing
|
// By default we need drawing
|
||||||
self->needsRedraw = true;
|
self->needsRedraw = true;
|
||||||
|
|
||||||
|
// By default the mouse-event mask is disabled
|
||||||
|
self->maskCr = NULL;
|
||||||
|
self->maskEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -126,6 +175,12 @@ void widgetDestroyImpl(widget *self)
|
||||||
// Destroy the cairo context
|
// Destroy the cairo context
|
||||||
cairo_destroy(self->cr);
|
cairo_destroy(self->cr);
|
||||||
|
|
||||||
|
// If we use a mask, destroy it
|
||||||
|
if (self->maskEnabled)
|
||||||
|
{
|
||||||
|
cairo_destroy(self->maskCr);
|
||||||
|
}
|
||||||
|
|
||||||
// Free the ID
|
// Free the ID
|
||||||
free(self->id);
|
free(self->id);
|
||||||
|
|
||||||
|
@ -159,6 +214,26 @@ void widgetDraw(widget *self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(widget *self)
|
point widgetAbsolutePosition(widget *self)
|
||||||
{
|
{
|
||||||
// Get our own offset
|
// Get our own offset
|
||||||
|
@ -461,7 +536,6 @@ void widgetResizeImpl(widget *self, int w, int h)
|
||||||
{
|
{
|
||||||
const size minSize = widgetGetMinSize(self);
|
const size minSize = widgetGetMinSize(self);
|
||||||
const size maxSize = widgetGetMaxSize(self);
|
const size maxSize = widgetGetMaxSize(self);
|
||||||
cairo_surface_t *surface;
|
|
||||||
|
|
||||||
assert(minSize.x < w);
|
assert(minSize.x < w);
|
||||||
assert(minSize.y < h);
|
assert(minSize.y < h);
|
||||||
|
@ -472,10 +546,16 @@ void widgetResizeImpl(widget *self, int w, int h)
|
||||||
self->size.y = h;
|
self->size.y = h;
|
||||||
|
|
||||||
// Re-create the cairo context at this new size
|
// Re-create the cairo context at this new size
|
||||||
cairo_destroy(self->cr);
|
widgetCairoCreate(&self->cr, CAIRO_FORMAT_ARGB32, w, h);
|
||||||
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
|
|
||||||
self->cr = cairo_create(surface);
|
// If a mask is enabled; re-create it also
|
||||||
cairo_surface_destroy(surface);
|
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
|
// Set the needs redraw flag
|
||||||
self->needsRedraw = true;
|
self->needsRedraw = true;
|
||||||
|
@ -507,6 +587,43 @@ void widgetCompositeImpl(widget *self, cairo_t *comp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
widget *widgetGetCurrentlyFocused(widget *self)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -540,7 +657,18 @@ bool widgetHandleEventImpl(widget *self, event *evt)
|
||||||
case EVT_MOUSE_MOVE:
|
case EVT_MOUSE_MOVE:
|
||||||
{
|
{
|
||||||
eventMouse evtMouse = *((eventMouse *) evt);
|
eventMouse evtMouse = *((eventMouse *) evt);
|
||||||
bool newHasMouse = pointInRect(evtMouse.loc, widgetAbsoluteBounds(self));
|
rect bounds = widgetAbsoluteBounds(self);
|
||||||
|
bool newHasMouse = pointInRect(evtMouse.loc, bounds);
|
||||||
|
|
||||||
|
// If the widget has a mask; ensure the location isn't masked
|
||||||
|
if (newHasMouse && self->maskEnabled)
|
||||||
|
{
|
||||||
|
// Work out where the mouse is relative to the widget
|
||||||
|
point relativeLoc = pointSub(evtMouse.loc, bounds.topLeft);
|
||||||
|
|
||||||
|
// We have the mouse if the point is NOT masked
|
||||||
|
newHasMouse = !widgetPointMasked(self, relativeLoc);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mouse motion events should not be dispatched if a mouse button
|
* Mouse motion events should not be dispatched if a mouse button
|
||||||
|
@ -594,6 +722,7 @@ bool widgetHandleEventImpl(widget *self, event *evt)
|
||||||
eventMouseBtn evtMouseBtn = *((eventMouseBtn *) evt);
|
eventMouseBtn evtMouseBtn = *((eventMouseBtn *) evt);
|
||||||
|
|
||||||
// If the mouse is inside of the widget
|
// If the mouse is inside of the widget
|
||||||
|
// TODO: Add mask support
|
||||||
if (pointInRect(evtMouseBtn.loc, widgetAbsoluteBounds(self)))
|
if (pointInRect(evtMouseBtn.loc, widgetAbsoluteBounds(self)))
|
||||||
{
|
{
|
||||||
// If it is a mouse-down event set hasMouseDown to true
|
// If it is a mouse-down event set hasMouseDown to true
|
||||||
|
@ -657,6 +786,41 @@ bool widgetHandleEventImpl(widget *self, event *evt)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool widgetPointMasked(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[loc.y * (stride / 4) + (loc.x / 32)];
|
||||||
|
|
||||||
|
// Where in the 32-bit segment the pixel is located
|
||||||
|
const uint32_t pixelMask = 1 << (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)
|
bool widgetAddChild(widget *self, widget *child)
|
||||||
{
|
{
|
||||||
return WIDGET_GET_VTBL(self)->addChild(self, child);
|
return WIDGET_GET_VTBL(self)->addChild(self, child);
|
||||||
|
@ -734,6 +898,13 @@ void widgetDoDraw(widget *self)
|
||||||
WIDGET_GET_VTBL(self)->doDraw(self);
|
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)
|
bool widgetDoLayout(widget *self)
|
||||||
{
|
{
|
||||||
WIDGET_CHECK_METHOD(self, doLayout);
|
WIDGET_CHECK_METHOD(self, doLayout);
|
||||||
|
|
|
@ -185,6 +185,7 @@ struct _widgetVtbl
|
||||||
void (*composite) (widget *self, cairo_t *comp);
|
void (*composite) (widget *self, cairo_t *comp);
|
||||||
|
|
||||||
void (*doDraw) (widget *self);
|
void (*doDraw) (widget *self);
|
||||||
|
void (*doDrawMask) (widget *self);
|
||||||
bool (*doLayout) (widget *self);
|
bool (*doLayout) (widget *self);
|
||||||
|
|
||||||
void (*destroy) (widget *self);
|
void (*destroy) (widget *self);
|
||||||
|
@ -224,6 +225,11 @@ struct _widget
|
||||||
* The widgets cairo drawing context
|
* The widgets cairo drawing context
|
||||||
*/
|
*/
|
||||||
cairo_t *cr;
|
cairo_t *cr;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The widgets mouse-event mask
|
||||||
|
*/
|
||||||
|
cairo_t *maskCr;
|
||||||
|
|
||||||
//--------------------------------------
|
//--------------------------------------
|
||||||
// Public members
|
// Public members
|
||||||
|
@ -274,6 +280,11 @@ struct _widget
|
||||||
* If the widget is dirty (i.e., needs to be re-drawn)
|
* If the widget is dirty (i.e., needs to be re-drawn)
|
||||||
*/
|
*/
|
||||||
bool needsRedraw;
|
bool needsRedraw;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the widget uses an mouse event mask
|
||||||
|
*/
|
||||||
|
bool maskEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -337,6 +348,20 @@ void widgetDraw(widget *self);
|
||||||
*/
|
*/
|
||||||
void widgetComposite(widget *self, cairo_t *comp);
|
void widgetComposite(widget *self, cairo_t *comp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the widgets mask.
|
||||||
|
*
|
||||||
|
* @param self The widget whose mask to enable.
|
||||||
|
*/
|
||||||
|
void widgetEnableMask(widget *self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the widgets mouse-event mask.
|
||||||
|
*
|
||||||
|
* @param self The widget whose mask to disable.
|
||||||
|
*/
|
||||||
|
void widgetDisableMask(widget *self);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively searches the child widgets of self for a widget whose ->id is
|
* Recursively searches the child widgets of self for a widget whose ->id is
|
||||||
* id. If no widget with such an id exists NULL is returned.
|
* id. If no widget with such an id exists NULL is returned.
|
||||||
|
@ -529,6 +554,23 @@ bool widgetHandleEvent(widget *self, event *evt);
|
||||||
*/
|
*/
|
||||||
void widgetDoDraw(widget *self);
|
void widgetDoDraw(widget *self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the mask context (self->maskCr) for drawing and then delegates the
|
||||||
|
* drawing to widgetDoDrawMask. This method is required as the mask context
|
||||||
|
* requires some additional initialisation to a regular Cairo context.
|
||||||
|
*
|
||||||
|
* @param self The widget whose mask to draw.
|
||||||
|
*/
|
||||||
|
void widgetDrawMask(widget *self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A protected `pure virtual` method which is called to draw the widgets mouse-
|
||||||
|
* event mask.
|
||||||
|
*
|
||||||
|
* @param self The widget that should draw its mask.
|
||||||
|
*/
|
||||||
|
void widgetDoDrawMask(widget *self);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -542,4 +584,14 @@ bool widgetDoLayout(widget *self);
|
||||||
*/
|
*/
|
||||||
bool widgetFireCallbacks(widget *self, event *evt);
|
bool widgetFireCallbacks(widget *self, event *evt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if the point loc is masked or not by the widgets mouse-event
|
||||||
|
* mask.
|
||||||
|
*
|
||||||
|
* @param self The widget to check the mask of.
|
||||||
|
* @param loc The point (x,y) to check the mask status of.
|
||||||
|
* @return true if loc is masked; false otherwise;
|
||||||
|
*/
|
||||||
|
bool widgetPointMasked(widget *self, point loc);
|
||||||
|
|
||||||
#endif /*WIDGET_H_*/
|
#endif /*WIDGET_H_*/
|
||||||
|
|
Loading…
Reference in New Issue