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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Subtracts point q from point p.
|
||||
*
|
||||
* @param p
|
||||
* @param q
|
||||
* @return p - q.
|
||||
*/
|
||||
point pointSub(point p, point q);
|
||||
|
||||
|
||||
#endif /*GEOM_H_*/
|
||||
|
|
|
@ -10,6 +10,55 @@ const classInfo widgetClassInfo =
|
|||
|
||||
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)
|
||||
{
|
||||
const classInfo *widgetClass;
|
||||
|
@ -61,6 +110,7 @@ static void widgetInitVtbl(widget *self)
|
|||
|
||||
vtbl.doLayout = NULL;
|
||||
vtbl.doDraw = NULL;
|
||||
vtbl.doDrawMask = NULL;
|
||||
|
||||
vtbl.destroy = widgetDestroyImpl;
|
||||
|
||||
|
@ -76,8 +126,6 @@ static void widgetInitVtbl(widget *self)
|
|||
*/
|
||||
void widgetInit(widget *self, const char *id)
|
||||
{
|
||||
cairo_surface_t *surface;
|
||||
|
||||
// Prepare our vtable
|
||||
widgetInitVtbl(self);
|
||||
|
||||
|
@ -97,11 +145,8 @@ void widgetInit(widget *self, const char *id)
|
|||
self->parent = NULL;
|
||||
|
||||
// Create a dummy cairo context (getMin/MaxSize may depend on it)
|
||||
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
|
||||
self->cr = cairo_create(surface);
|
||||
|
||||
// `Destroy' the surface (self->cr maintains a reference to it)
|
||||
cairo_surface_destroy(surface);
|
||||
self->cr = NULL;
|
||||
widgetCairoCreate(&self->cr, CAIRO_FORMAT_ARGB32, 0, 0);
|
||||
|
||||
// Focus and mouse are false by default
|
||||
self->hasFocus = false;
|
||||
|
@ -110,6 +155,10 @@ void widgetInit(widget *self, const char *id)
|
|||
|
||||
// By default we need drawing
|
||||
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
|
||||
cairo_destroy(self->cr);
|
||||
|
||||
// If we use a mask, destroy it
|
||||
if (self->maskEnabled)
|
||||
{
|
||||
cairo_destroy(self->maskCr);
|
||||
}
|
||||
|
||||
// Free the 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)
|
||||
{
|
||||
// Get our own offset
|
||||
|
@ -461,7 +536,6 @@ void widgetResizeImpl(widget *self, int w, int h)
|
|||
{
|
||||
const size minSize = widgetGetMinSize(self);
|
||||
const size maxSize = widgetGetMaxSize(self);
|
||||
cairo_surface_t *surface;
|
||||
|
||||
assert(minSize.x < w);
|
||||
assert(minSize.y < h);
|
||||
|
@ -472,10 +546,16 @@ void widgetResizeImpl(widget *self, int w, int h)
|
|||
self->size.y = h;
|
||||
|
||||
// Re-create the cairo context at this new size
|
||||
cairo_destroy(self->cr);
|
||||
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
|
||||
self->cr = cairo_create(surface);
|
||||
cairo_surface_destroy(surface);
|
||||
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;
|
||||
|
@ -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)
|
||||
{
|
||||
int i;
|
||||
|
@ -540,7 +657,18 @@ bool widgetHandleEventImpl(widget *self, event *evt)
|
|||
case EVT_MOUSE_MOVE:
|
||||
{
|
||||
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
|
||||
|
@ -594,6 +722,7 @@ bool widgetHandleEventImpl(widget *self, event *evt)
|
|||
eventMouseBtn evtMouseBtn = *((eventMouseBtn *) evt);
|
||||
|
||||
// If the mouse is inside of the widget
|
||||
// TODO: Add mask support
|
||||
if (pointInRect(evtMouseBtn.loc, widgetAbsoluteBounds(self)))
|
||||
{
|
||||
// If it is a mouse-down event set hasMouseDown to true
|
||||
|
@ -657,6 +786,41 @@ bool widgetHandleEventImpl(widget *self, event *evt)
|
|||
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)
|
||||
{
|
||||
return WIDGET_GET_VTBL(self)->addChild(self, child);
|
||||
|
@ -734,6 +898,13 @@ void widgetDoDraw(widget *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)
|
||||
{
|
||||
WIDGET_CHECK_METHOD(self, doLayout);
|
||||
|
|
|
@ -185,6 +185,7 @@ struct _widgetVtbl
|
|||
void (*composite) (widget *self, cairo_t *comp);
|
||||
|
||||
void (*doDraw) (widget *self);
|
||||
void (*doDrawMask) (widget *self);
|
||||
bool (*doLayout) (widget *self);
|
||||
|
||||
void (*destroy) (widget *self);
|
||||
|
@ -224,6 +225,11 @@ struct _widget
|
|||
* The widgets cairo drawing context
|
||||
*/
|
||||
cairo_t *cr;
|
||||
|
||||
/*
|
||||
* The widgets mouse-event mask
|
||||
*/
|
||||
cairo_t *maskCr;
|
||||
|
||||
//--------------------------------------
|
||||
// Public members
|
||||
|
@ -274,6 +280,11 @@ struct _widget
|
|||
* If the widget is dirty (i.e., needs to be re-drawn)
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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_*/
|
||||
|
|
Loading…
Reference in New Issue