317 lines
8.3 KiB
C++
317 lines
8.3 KiB
C++
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 2009 Elio Gubser
|
|
Copyright (C) 2008 Freddie Witherden
|
|
Copyright (C) 2008-2013 Warzone 2100 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 "button.h"
|
|
|
|
static buttonVtbl vtbl;
|
|
|
|
static const float borderRadius = 10;
|
|
|
|
const classInfo buttonClassInfo =
|
|
{
|
|
&widgetClassInfo,
|
|
"button"
|
|
};
|
|
|
|
static bool buttonSetButtonStateHandler(widget *selfWidget, const event *evt,
|
|
int handlerId, void *userData)
|
|
{
|
|
// Cast ourself to a button
|
|
button *self = BUTTON(selfWidget);
|
|
|
|
// Request a redraw
|
|
WIDGET(self)->needsRedraw = true;
|
|
|
|
switch(evt->type)
|
|
{
|
|
case EVT_MOUSE_DOWN: self->state = BUTTON_STATE_MOUSEDOWN; break;
|
|
case EVT_MOUSE_ENTER: self->state = BUTTON_STATE_MOUSEOVER; break;
|
|
case EVT_MOUSE_UP: self->state = BUTTON_STATE_MOUSEOVER; break;
|
|
case EVT_MOUSE_LEAVE:
|
|
case EVT_ENABLE: self->state = BUTTON_STATE_NORMAL; break;
|
|
case EVT_DISABLE: self->state = BUTTON_STATE_DISABLED; break;
|
|
default: assert(0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void buttonInitVtbl(button *self)
|
|
{
|
|
static bool initialised = false;
|
|
|
|
if (!initialised)
|
|
{
|
|
// Copy our parents vtable into ours
|
|
vtbl.widgetVtbl = *(WIDGET(self)->vtbl);
|
|
|
|
// Overload the widget's destroy and doDraw methods
|
|
vtbl.widgetVtbl.destroy = buttonDestroyImpl;
|
|
vtbl.widgetVtbl.doDraw = buttonDoDrawImpl;
|
|
vtbl.widgetVtbl.doDrawMask = buttonDoDrawMaskImpl;
|
|
|
|
vtbl.doButtonPath = buttonDoButtonPathImpl;
|
|
vtbl.doDrawNormal = buttonDoDrawNormalImpl;
|
|
vtbl.doDrawDisabled = buttonDoDrawDisabledImpl;
|
|
vtbl.doDrawMouseOver = buttonDoDrawMouseOverImpl;
|
|
vtbl.doDrawMouseDown = buttonDoDrawMouseDownImpl;
|
|
|
|
vtbl.widgetVtbl.getMinSize = buttonGetMinSizeImpl;
|
|
vtbl.widgetVtbl.getMaxSize = buttonGetMaxSizeImpl;
|
|
|
|
initialised = true;
|
|
}
|
|
|
|
// Replace our parents vtable with our own
|
|
WIDGET(self)->vtbl = &vtbl.widgetVtbl;
|
|
|
|
// Set our vtable
|
|
self->vtbl = &vtbl;
|
|
}
|
|
|
|
button *buttonCreate(const char *id, int w, int h)
|
|
{
|
|
button *instance = malloc(sizeof(button));
|
|
|
|
if (instance == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Call the constructor
|
|
buttonInit(instance, id, w, h);
|
|
|
|
// Return the new object
|
|
return instance;
|
|
}
|
|
|
|
void buttonInit(button *self, const char *id, int w, int h)
|
|
{
|
|
// Buttons need to be redrawn when the following events fire
|
|
int i, handlers[] = {
|
|
EVT_ENABLE,
|
|
EVT_DISABLE,
|
|
EVT_MOUSE_ENTER,
|
|
EVT_MOUSE_LEAVE,
|
|
EVT_MOUSE_DOWN,
|
|
EVT_MOUSE_UP
|
|
};
|
|
|
|
// Init our parent
|
|
widgetInit((widget *)self, id);
|
|
|
|
// Prepare our vtable
|
|
buttonInitVtbl(self);
|
|
|
|
// Set our type
|
|
((widget *)self)->classInfo = &buttonClassInfo;
|
|
|
|
// initialise button state
|
|
self->state = BUTTON_STATE_NORMAL;
|
|
|
|
// Mask for exact mouse events
|
|
widgetEnableMask(WIDGET(self));
|
|
|
|
// Install necessary event handlers
|
|
for (i = 0; i < sizeof(handlers) / sizeof(int); i++)
|
|
{
|
|
widgetAddEventHandler(WIDGET(self), handlers[i],
|
|
buttonSetButtonStateHandler, NULL, NULL);
|
|
}
|
|
|
|
buttonSetPatternsForState(self, BUTTON_STATE_NORMAL, "button/normal/fill", "button/normal/contour");
|
|
buttonSetPatternsForState(self, BUTTON_STATE_DISABLED, "button/disabled/fill", "button/disabled/contour");
|
|
buttonSetPatternsForState(self, BUTTON_STATE_MOUSEOVER, "button/mouseover/fill", "button/mouseover/contour");
|
|
buttonSetPatternsForState(self, BUTTON_STATE_MOUSEDOWN, "button/mousedown/fill", "button/mousedown/contour");
|
|
|
|
widgetResize(WIDGET(self), w, h);
|
|
}
|
|
|
|
|
|
void buttonDestroyImpl(widget *self)
|
|
{
|
|
// Call our parents destructor
|
|
widgetDestroyImpl(self);
|
|
}
|
|
|
|
void buttonDoDrawImpl(widget *widgetSelf)
|
|
{
|
|
button *self;
|
|
|
|
self = BUTTON(widgetSelf);
|
|
switch(self->state)
|
|
{
|
|
case BUTTON_STATE_NORMAL: buttonDoDrawNormal(self); break;
|
|
case BUTTON_STATE_DISABLED: buttonDoDrawDisabled(self); break;
|
|
case BUTTON_STATE_MOUSEOVER: buttonDoDrawMouseOver(self); break;
|
|
case BUTTON_STATE_MOUSEDOWN: buttonDoDrawMouseDown(self); break;
|
|
default: assert(0); break;
|
|
}
|
|
}
|
|
|
|
void buttonDoDrawMaskImpl(widget *self)
|
|
{
|
|
// Get the mask context
|
|
cairo_t *cr = self->maskCr;
|
|
|
|
// Do the rounded rectangle path
|
|
buttonDoButtonPath(BUTTON(self), cr);
|
|
|
|
// We don't have to specify the color, it's already set in our parent
|
|
cairo_fill(cr);
|
|
}
|
|
|
|
size buttonGetMinSizeImpl(widget *self)
|
|
{
|
|
const size minSize = { 60.0f, 40.0f };
|
|
|
|
return minSize;
|
|
}
|
|
size buttonGetMaxSizeImpl(widget *self)
|
|
{
|
|
// Note the int => float conversion
|
|
const size maxSize = { 60.f, 40.f };
|
|
|
|
return maxSize;
|
|
}
|
|
|
|
/**
|
|
* Draws the button's shape and background using the delivered patterns.
|
|
*
|
|
* @param self The button object which will be drawn.
|
|
* @param fillPattern The pattern to fill its shape.
|
|
* @param contourPattern The pattern to outline its shape.
|
|
*/
|
|
static void buttonDrawFinally(button *self, pattern *fillPattern, pattern *contourPattern)
|
|
{
|
|
// Get drawing context
|
|
cairo_t *cr = WIDGET(self)->cr;
|
|
|
|
assert(self != NULL);
|
|
assert(fillPattern != NULL);
|
|
assert(contourPattern != NULL);
|
|
|
|
// Do the rounded rectangle path
|
|
buttonDoButtonPath(self, cr);
|
|
|
|
// Select normal state fill gradient
|
|
patternManagerSetAsSource(cr, fillPattern, WIDGET(self)->size.x, WIDGET(self)->size.y);
|
|
// Fill the path, preserving the path
|
|
cairo_fill_preserve(cr);
|
|
|
|
// Select normal state contour gradient
|
|
patternManagerSetAsSource(cr, contourPattern, WIDGET(self)->size.x, WIDGET(self)->size.y);
|
|
// Finally stroke the path
|
|
cairo_stroke(cr);
|
|
}
|
|
|
|
void buttonDoDrawNormalImpl(button *self)
|
|
{
|
|
buttonDrawFinally(self, self->fillPatterns[BUTTON_STATE_NORMAL], self->contourPatterns[BUTTON_STATE_NORMAL]);
|
|
}
|
|
|
|
void buttonDoDrawDisabledImpl(button *self)
|
|
{
|
|
buttonDrawFinally(self, self->fillPatterns[BUTTON_STATE_DISABLED], self->contourPatterns[BUTTON_STATE_DISABLED]);
|
|
}
|
|
|
|
void buttonDoDrawMouseOverImpl(button *self)
|
|
{
|
|
buttonDrawFinally(self, self->fillPatterns[BUTTON_STATE_MOUSEOVER], self->contourPatterns[BUTTON_STATE_MOUSEOVER]);
|
|
}
|
|
|
|
void buttonDoDrawMouseDownImpl(button *self)
|
|
{
|
|
buttonDrawFinally(self, self->fillPatterns[BUTTON_STATE_MOUSEDOWN], self->contourPatterns[BUTTON_STATE_MOUSEDOWN]);
|
|
}
|
|
|
|
void buttonDoButtonPathImpl(button *self, cairo_t *cr)
|
|
{
|
|
size ourSize = WIDGET(self)->size;
|
|
|
|
// Do the rounded rectangle
|
|
cairo_arc_negative(cr, borderRadius, borderRadius, borderRadius, -M_PI_2, -M_PI);
|
|
cairo_line_to(cr, 0, ourSize.y-borderRadius);
|
|
cairo_arc_negative(cr, borderRadius, ourSize.y-borderRadius, borderRadius, M_PI, M_PI_2);
|
|
cairo_line_to(cr, ourSize.x-borderRadius, ourSize.y);
|
|
cairo_arc_negative(cr, ourSize.x-borderRadius, ourSize.y-borderRadius, borderRadius, M_PI_2, 0);
|
|
cairo_line_to(cr, ourSize.x, borderRadius);
|
|
cairo_arc_negative(cr, ourSize.x-borderRadius, borderRadius, borderRadius, 0, -M_PI_2);
|
|
cairo_close_path(cr);
|
|
}
|
|
|
|
void buttonDoDrawNormal(button *self)
|
|
{
|
|
BUTTON_CHECK_METHOD(self, doDrawNormal);
|
|
|
|
BUTTON_GET_VTBL(self)->doDrawNormal(self);
|
|
}
|
|
|
|
void buttonDoDrawDisabled(button *self)
|
|
{
|
|
BUTTON_CHECK_METHOD(self, doDrawDisabled);
|
|
|
|
BUTTON_GET_VTBL(self)->doDrawDisabled(self);
|
|
}
|
|
|
|
void buttonDoDrawMouseOver(button *self)
|
|
{
|
|
BUTTON_CHECK_METHOD(self, doDrawMouseOver);
|
|
|
|
BUTTON_GET_VTBL(self)->doDrawMouseOver(self);
|
|
}
|
|
|
|
void buttonDoDrawMouseDown(button *self)
|
|
{
|
|
BUTTON_CHECK_METHOD(self, doDrawMouseDown);
|
|
|
|
BUTTON_GET_VTBL(self)->doDrawMouseDown(self);
|
|
}
|
|
|
|
void buttonDoButtonPath(button *self, cairo_t *cr)
|
|
{
|
|
BUTTON_CHECK_METHOD(self, doButtonPath);
|
|
|
|
BUTTON_GET_VTBL(self)->doButtonPath(self, cr);
|
|
}
|
|
|
|
void buttonSetPatternsForState(button *self, buttonState state, const char *fillPatternId, const char *contourPatternId)
|
|
{
|
|
assert(self != NULL);
|
|
assert(state < BUTTON_STATE_COUNT);
|
|
|
|
if(fillPatternId)
|
|
{
|
|
if(fillPatternId[0] != '\0')
|
|
{
|
|
self->fillPatterns[state] = patternManagerGetPattern(fillPatternId);
|
|
}
|
|
}
|
|
|
|
if(contourPatternId)
|
|
{
|
|
if(contourPatternId[0] != '\0')
|
|
{
|
|
self->contourPatterns[state] = patternManagerGetPattern(contourPatternId);
|
|
}
|
|
}
|
|
}
|