/*
* Copyright (c) 2002-2008 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include "scrollbar.h"
#include "window.h"
#include "primitive.h"
#include "text.h"
#include "gui_math.h"
#define TOTSIZE(sb) (((sb)->type==AG_SCROLLBAR_VERT) ? HEIGHT(sb) : WIDTH(sb))
AG_Scrollbar *
AG_ScrollbarNew(void *parent, enum ag_scrollbar_type type, Uint flags)
{
AG_Scrollbar *sb;
sb = Malloc(sizeof(AG_Scrollbar));
AG_ObjectInit(sb, &agScrollbarClass);
sb->type = type;
sb->flags |= flags;
if (flags & AG_SCROLLBAR_HFILL) { AG_ExpandHoriz(sb); }
if (flags & AG_SCROLLBAR_VFILL) { AG_ExpandVert(sb); }
AG_ObjectAttach(parent, sb);
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewInt(void *parent, enum ag_scrollbar_type type, Uint flags,
int *val, int *min, int *max, int *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindInt(sb, "value", val); }
if (min != NULL) { AG_BindInt(sb, "min", min); }
if (max != NULL) { AG_BindInt(sb, "max", max); }
if (vis != NULL) { AG_BindInt(sb, "visible", vis); }
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewUint(void *parent, enum ag_scrollbar_type type, Uint flags,
Uint *val, Uint *min, Uint *max, Uint *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindUint(sb, "value", val); }
if (min != NULL) { AG_BindUint(sb, "min", min); }
if (max != NULL) { AG_BindUint(sb, "max", max); }
if (vis != NULL) { AG_BindUint(sb, "visible", vis); }
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewUint8(void *parent, enum ag_scrollbar_type type, Uint flags,
Uint8 *val, Uint8 *min, Uint8 *max, Uint8 *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindUint8(sb, "value", val); }
if (min != NULL) { AG_BindUint8(sb, "min", min); }
if (max != NULL) { AG_BindUint8(sb, "max", max); }
if (vis != NULL) { AG_BindUint8(sb, "visible", vis); }
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewSint8(void *parent, enum ag_scrollbar_type type, Uint flags,
Sint8 *val, Sint8 *min, Sint8 *max, Sint8 *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindSint8(sb, "value", val); }
if (min != NULL) { AG_BindSint8(sb, "min", min); }
if (max != NULL) { AG_BindSint8(sb, "max", max); }
if (vis != NULL) { AG_BindSint8(sb, "visible", vis); }
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewUint16(void *parent, enum ag_scrollbar_type type, Uint flags,
Uint16 *val, Uint16 *min, Uint16 *max, Uint16 *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindUint16(sb, "value", val); }
if (min != NULL) { AG_BindUint16(sb, "min", min); }
if (max != NULL) { AG_BindUint16(sb, "max", max); }
if (vis != NULL) { AG_BindUint16(sb, "visible", vis); }
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewSint16(void *parent, enum ag_scrollbar_type type, Uint flags,
Sint16 *val, Sint16 *min, Sint16 *max, Sint16 *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindSint16(sb, "value", val); }
if (min != NULL) { AG_BindSint16(sb, "min", min); }
if (max != NULL) { AG_BindSint16(sb, "max", max); }
if (vis != NULL) { AG_BindSint16(sb, "visible", vis); }
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewUint32(void *parent, enum ag_scrollbar_type type, Uint flags,
Uint32 *val, Uint32 *min, Uint32 *max, Uint32 *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindUint32(sb, "value", val); }
if (min != NULL) { AG_BindUint32(sb, "min", min); }
if (max != NULL) { AG_BindUint32(sb, "max", max); }
if (vis != NULL) { AG_BindUint32(sb, "visible", vis); }
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewSint32(void *parent, enum ag_scrollbar_type type, Uint flags,
Sint32 *val, Sint32 *min, Sint32 *max, Sint32 *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindSint32(sb, "value", val); }
if (min != NULL) { AG_BindSint32(sb, "min", min); }
if (max != NULL) { AG_BindSint32(sb, "max", max); }
if (vis != NULL) { AG_BindSint32(sb, "visible", vis); }
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewFloat(void *parent, enum ag_scrollbar_type type, Uint flags,
float *val, float *min, float *max, float *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindFloat(sb, "value", val); }
if (min != NULL) { AG_BindFloat(sb, "min", min); }
if (max != NULL) { AG_BindFloat(sb, "max", max); }
if (vis != NULL) { AG_BindFloat(sb, "visible", vis); }
return (sb);
}
AG_Scrollbar *
AG_ScrollbarNewDouble(void *parent, enum ag_scrollbar_type type, Uint flags,
double *val, double *min, double *max, double *vis)
{
AG_Scrollbar *sb = AG_ScrollbarNew(parent, type, flags);
if (val != NULL) { AG_BindDouble(sb, "value", val); }
if (min != NULL) { AG_BindDouble(sb, "min", min); }
if (max != NULL) { AG_BindDouble(sb, "max", max); }
if (vis != NULL) { AG_BindDouble(sb, "visible", vis); }
return (sb);
}
/* Set an alternate handler for UP/LEFT button click. */
void
AG_ScrollbarSetIncFn(AG_Scrollbar *sb, AG_EventFn fn, const char *fmt, ...)
{
AG_ObjectLock(sb);
sb->buttonIncFn = AG_SetEvent(sb, NULL, fn, NULL);
AG_EVENT_GET_ARGS(sb->buttonIncFn, fmt);
AG_ObjectUnlock(sb);
}
/* Set an alternate handler for DOWN/RIGHT button click. */
void
AG_ScrollbarSetDecFn(AG_Scrollbar *sb, AG_EventFn fn, const char *fmt, ...)
{
AG_ObjectLock(sb);
sb->buttonDecFn = AG_SetEvent(sb, NULL, fn, NULL);
AG_EVENT_GET_ARGS(sb->buttonDecFn, fmt);
AG_ObjectUnlock(sb);
}
/* Set the base increment for integer bindings. */
void
AG_ScrollbarSetIntIncrement(AG_Scrollbar *sb, int inc)
{
AG_ObjectLock(sb);
sb->iInc = inc;
AG_ObjectUnlock(sb);
}
/* Set the base increment for real bindings. */
void
AG_ScrollbarSetRealIncrement(AG_Scrollbar *sb, double inc)
{
AG_ObjectLock(sb);
sb->rInc = inc;
AG_ObjectUnlock(sb);
}
/*
* Return the current position of the scrollbar, in terms of the position of
* the Left (or Top) edge in pixels. Returns 0 on success and -1 if the range
* is currently <= 0.
*/
#define GET_POSITION(type) \
if (*(type *)pMin >= (*(type *)pMax) - *(type *)pVis) { \
goto fail; \
} \
*x = (int)(((*(type *)pVal - *(type *)pMin) * sb->extent) / \
(*(type *)pMax - *(type *)pVis - *(type *)pMin))
static __inline__ int
GetPosition(AG_Scrollbar *sb, int *x)
{
AG_Variable *bMin, *bMax, *bVis, *bVal;
void *pMin, *pMax, *pVal, *pVis;
bVal = AG_GetVariable(sb, "value", &pVal);
bMin = AG_GetVariable(sb, "min", &pMin);
bMax = AG_GetVariable(sb, "max", &pMax);
bVis = AG_GetVariable(sb, "visible", &pVis);
switch (AG_VARIABLE_TYPE(bVal)) {
case AG_VARIABLE_INT: GET_POSITION(int); break;
case AG_VARIABLE_UINT: GET_POSITION(Uint); break;
case AG_VARIABLE_FLOAT: GET_POSITION(float); break;
case AG_VARIABLE_DOUBLE: GET_POSITION(double); break;
case AG_VARIABLE_UINT8: GET_POSITION(Uint8); break;
case AG_VARIABLE_SINT8: GET_POSITION(Sint8); break;
case AG_VARIABLE_UINT16: GET_POSITION(Uint16); break;
case AG_VARIABLE_SINT16: GET_POSITION(Sint16); break;
case AG_VARIABLE_UINT32: GET_POSITION(Uint32); break;
case AG_VARIABLE_SINT32: GET_POSITION(Sint32); break;
default: break;
}
if (sb->wBar == -1) {
*x = 0;
}
AG_UnlockVariable(bVis);
AG_UnlockVariable(bMax);
AG_UnlockVariable(bMin);
AG_UnlockVariable(bVal);
return (0);
fail:
AG_UnlockVariable(bVis);
AG_UnlockVariable(bMax);
AG_UnlockVariable(bMin);
AG_UnlockVariable(bVal);
return (-1);
}
#undef GET_POSITION
/*
* Set the value from a specified position in pixels.
*/
#define SEEK_TO_POSITION(type) \
if (x <= 0) { \
*(type *)pVal = *(type *)pMin; \
} else if (x >= sb->extent) { \
*(type *)pVal = *(type *)pMax - *(type *)pVis; \
} else { \
*(type *)pVal = x * \
(*(type *)pMax - *(type *)pVis - *(type *)pMin) / \
sb->extent; \
*(type *)pVal += *(type *)pMin; \
if (*(type *)pVal < *(type *)pMin) { \
*(type *)pVal = *(type *)pMin; \
} \
if (*(type *)pVal > *(type *)pMax) { \
*(type *)pVal = *(type *)pMax; \
} \
}
static __inline__ void
SeekToPosition(AG_Scrollbar *sb, int x)
{
AG_Variable *bMin, *bMax, *bVis, *bVal;
void *pMin, *pMax, *pVal, *pVis;
bVal = AG_GetVariable(sb, "value", &pVal);
bMin = AG_GetVariable(sb, "min", &pMin);
bMax = AG_GetVariable(sb, "max", &pMax);
bVis = AG_GetVariable(sb, "visible", &pVis);
switch (AG_VARIABLE_TYPE(bVal)) {
case AG_VARIABLE_INT: SEEK_TO_POSITION(int); break;
case AG_VARIABLE_UINT: SEEK_TO_POSITION(Uint); break;
case AG_VARIABLE_FLOAT: SEEK_TO_POSITION(float); break;
case AG_VARIABLE_DOUBLE: SEEK_TO_POSITION(double); break;
case AG_VARIABLE_UINT8: SEEK_TO_POSITION(Uint8); break;
case AG_VARIABLE_SINT8: SEEK_TO_POSITION(Sint8); break;
case AG_VARIABLE_UINT16: SEEK_TO_POSITION(Uint16); break;
case AG_VARIABLE_SINT16: SEEK_TO_POSITION(Sint16); break;
case AG_VARIABLE_UINT32: SEEK_TO_POSITION(Uint32); break;
case AG_VARIABLE_SINT32: SEEK_TO_POSITION(Sint32); break;
default: break;
}
AG_PostEvent(NULL, sb, "scrollbar-changed", NULL);
AG_UnlockVariable(bVis);
AG_UnlockVariable(bMax);
AG_UnlockVariable(bMin);
AG_UnlockVariable(bVal);
}
#undef SEEK_TO_POSITION
/*
* Decrement the value by the specified amount multiplied by iInc/rInc.
*/
#define DECREMENT_INT(type) \
if ((*(type *)pVal - ((type)sb->iInc)*v) < *(type *)pMin) \
*(type *)pVal = *(type *)pMin; \
else \
*(type *)pVal -= ((type)sb->iInc)*v
#define DECREMENT_REAL(type) \
if ((*(type *)pVal - ((type)v)*sb->rInc) < *(type *)pMin) \
*(type *)pVal = *(type *)pMin; \
else \
*(type *)pVal -= ((type)v)*sb->rInc
static void
Decrement(AG_Scrollbar *sb, int v)
{
AG_Variable *bMin, *bMax, *bVis, *bVal;
void *pMin, *pMax, *pVal, *pVis;
bVal = AG_GetVariable(sb, "value", &pVal);
bMin = AG_GetVariable(sb, "min", &pMin);
bMax = AG_GetVariable(sb, "max", &pMax);
bVis = AG_GetVariable(sb, "visible", &pVis);
switch (AG_VARIABLE_TYPE(bVal)) {
case AG_VARIABLE_INT: DECREMENT_INT(int); break;
case AG_VARIABLE_UINT: DECREMENT_INT(Uint); break;
case AG_VARIABLE_FLOAT: DECREMENT_REAL(float); break;
case AG_VARIABLE_DOUBLE: DECREMENT_REAL(double); break;
case AG_VARIABLE_UINT8: DECREMENT_INT(Uint8); break;
case AG_VARIABLE_SINT8: DECREMENT_INT(Sint8); break;
case AG_VARIABLE_UINT16: DECREMENT_INT(Uint16); break;
case AG_VARIABLE_SINT16: DECREMENT_INT(Sint16); break;
case AG_VARIABLE_UINT32: DECREMENT_INT(Uint32); break;
case AG_VARIABLE_SINT32: DECREMENT_INT(Sint32); break;
default: break;
}
AG_PostEvent(NULL, sb, "scrollbar-changed", NULL);
AG_UnlockVariable(bVis);
AG_UnlockVariable(bMax);
AG_UnlockVariable(bMin);
AG_UnlockVariable(bVal);
}
#undef DECREMENT_INT
#undef DECREMENT_REAL
/*
* Increment the value by the specified amount multiplied by iInc/rInc.
*/
#define INCREMENT_INT(type) \
if ((*(type *)pVal + ((type)sb->iInc)*v) > *(type *)pMax) \
*(type *)pVal = *(type *)pMax; \
else \
*(type *)pVal += ((type)sb->iInc)*v
#define INCREMENT_REAL(type) \
if ((*(type *)pVal + ((type)v)*sb->rInc) > *(type *)pMax) \
*(type *)pVal = *(type *)pMax; \
else \
*(type *)pVal += ((type)v)*sb->rInc
static void
Increment(AG_Scrollbar *sb, int v)
{
AG_Variable *bMin, *bMax, *bVis, *bVal;
void *pMin, *pMax, *pVal, *pVis;
bVal = AG_GetVariable(sb, "value", &pVal);
bMin = AG_GetVariable(sb, "min", &pMin);
bMax = AG_GetVariable(sb, "max", &pMax);
bVis = AG_GetVariable(sb, "visible", &pVis);
switch (AG_VARIABLE_TYPE(bVal)) {
case AG_VARIABLE_INT: INCREMENT_INT(int); break;
case AG_VARIABLE_UINT: INCREMENT_INT(Uint); break;
case AG_VARIABLE_FLOAT: INCREMENT_REAL(float); break;
case AG_VARIABLE_DOUBLE: INCREMENT_REAL(double); break;
case AG_VARIABLE_UINT8: INCREMENT_INT(Uint8); break;
case AG_VARIABLE_SINT8: INCREMENT_INT(Sint8); break;
case AG_VARIABLE_UINT16: INCREMENT_INT(Uint16); break;
case AG_VARIABLE_SINT16: INCREMENT_INT(Sint16); break;
case AG_VARIABLE_UINT32: INCREMENT_INT(Uint32); break;
case AG_VARIABLE_SINT32: INCREMENT_INT(Sint32); break;
default: break;
}
AG_PostEvent(NULL, sb, "scrollbar-changed", NULL);
AG_UnlockVariable(bVis);
AG_UnlockVariable(bMax);
AG_UnlockVariable(bMin);
AG_UnlockVariable(bVal);
}
#undef INCREMENT_INT
#undef INCREMENT_REAL
static void
MouseButtonUp(AG_Event *event)
{
AG_Scrollbar *sb = AG_SELF();
AG_DelTimeout(sb, &sb->scrollTo);
AG_DelTimeout(sb, &sb->incTo);
AG_DelTimeout(sb, &sb->decTo);
if (sb->curBtn == AG_SCROLLBAR_BUTTON_DEC && sb->buttonDecFn != NULL) {
AG_PostEvent(NULL, sb, sb->buttonDecFn->name, "%i", 0);
}
if (sb->curBtn == AG_SCROLLBAR_BUTTON_INC && sb->buttonIncFn != NULL) {
AG_PostEvent(NULL, sb, sb->buttonIncFn->name, "%i", 0);
}
if (sb->curBtn != AG_SCROLLBAR_BUTTON_NONE) {
sb->curBtn = AG_SCROLLBAR_BUTTON_NONE;
sb->xOffs = 0;
}
AG_PostEvent(NULL, sb, "scrollbar-drag-end", NULL);
}
static void
MouseButtonDown(AG_Event *event)
{
AG_Scrollbar *sb = AG_SELF();
int button = AG_INT(1);
int x = ((sb->type == AG_SCROLLBAR_HORIZ) ? AG_INT(2) : AG_INT(3)) -
sb->wButton;
int pos, posFound;
if (button != SDL_BUTTON_LEFT) {
return;
}
AG_WidgetFocus(sb);
posFound = (GetPosition(sb, &pos) == 0);
if (x < 0) {
/*
* Click on DECREMENT button. Unless user provided a handler
* function, we decrement once and start the timer.
*/
sb->curBtn = AG_SCROLLBAR_BUTTON_DEC;
if (sb->buttonDecFn != NULL) {
AG_PostEvent(NULL, sb, sb->buttonDecFn->name, "%i", 1);
} else {
Decrement(sb, 1);
AG_ScheduleTimeout(sb, &sb->scrollTo, agMouseSpinDelay);
AG_DelTimeout(sb, &sb->incTo);
AG_DelTimeout(sb, &sb->decTo);
}
} else if (x > TOTSIZE(sb) - sb->wButton*2) {
/*
* Click on INCREMENT button. Unless user provided a handler
* function, we increment once and start the timer.
*/
sb->curBtn = AG_SCROLLBAR_BUTTON_INC;
if (sb->buttonIncFn != NULL) {
AG_PostEvent(NULL, sb, sb->buttonIncFn->name, "%i", 1);
} else {
Increment(sb, 1);
AG_ScheduleTimeout(sb, &sb->scrollTo, agMouseSpinDelay);
AG_DelTimeout(sb, &sb->incTo);
AG_DelTimeout(sb, &sb->decTo);
}
} else if (!posFound || (x >= pos && x <= (pos + sb->wBar))) {
/*
* Click on the scrollbar itself. We don't do anything except
* saving the cursor position which we will use in future
* mousemotion events.
*/
sb->curBtn = AG_SCROLLBAR_BUTTON_SCROLL;
sb->xOffs = posFound ? (x - pos) : x;
} else if (sb->wBar != -1) {
/*
* Click outside of scrollbar. We seek to the absolute position
* described by the cursor.
*
* XXX TODO: Provide an option to scroll progressively to the
* position since many users will expect that.
*/
sb->curBtn = AG_SCROLLBAR_BUTTON_SCROLL;
sb->xOffs = sb->wBar/2;
SeekToPosition(sb, x - sb->xOffs);
}
AG_PostEvent(NULL, sb, "scrollbar-drag-begin", NULL);
}
static void
MouseMotion(AG_Event *event)
{
AG_Scrollbar *sb = AG_SELF();
if (sb->curBtn != AG_SCROLLBAR_BUTTON_SCROLL) {
return;
}
SeekToPosition(sb, ((sb->type == AG_SCROLLBAR_HORIZ) ?
AG_INT(1):AG_INT(2)) - sb->wButton - sb->xOffs);
}
static Uint32
ScrollTimeout(void *obj, Uint32 ival, void *arg)
{
AG_Scrollbar *sb = obj;
switch (sb->curBtn) {
case AG_SCROLLBAR_BUTTON_DEC:
Decrement(sb, 1);
break;
case AG_SCROLLBAR_BUTTON_INC:
Increment(sb, 1);
break;
default:
break;
}
return (agMouseSpinIval);
}
static void
KeyDown(AG_Event *event)
{
AG_Scrollbar *sb = AG_SELF();
int keysym = AG_INT(1);
switch (keysym) {
case SDLK_UP:
case SDLK_LEFT:
Decrement(sb, 1);
AG_DelTimeout(sb, &sb->incTo);
AG_ScheduleTimeout(sb, &sb->decTo, agKbdDelay);
break;
case SDLK_DOWN:
case SDLK_RIGHT:
Increment(sb, 1);
AG_DelTimeout(sb, &sb->decTo);
AG_ScheduleTimeout(sb, &sb->incTo, agKbdDelay);
break;
}
}
static void
KeyUp(AG_Event *event)
{
AG_Scrollbar *sb = AG_SELF();
int keysym = AG_INT(1);
switch (keysym) {
case SDLK_UP:
case SDLK_LEFT:
AG_DelTimeout(sb, &sb->decTo);
break;
case SDLK_DOWN:
case SDLK_RIGHT:
AG_DelTimeout(sb, &sb->incTo);
break;
}
}
static Uint32
IncrementTimeout(void *obj, Uint32 ival, void *arg)
{
AG_Scrollbar *sb = obj;
Increment(sb, 1);
return (agKbdRepeat);
}
static Uint32
DecrementTimeout(void *obj, Uint32 ival, void *arg)
{
AG_Scrollbar *sb = obj;
Decrement(sb, 1);
return (agKbdRepeat);
}
static void
LostFocus(AG_Event *event)
{
AG_Scrollbar *sb = AG_SELF();
AG_DelTimeout(sb, &sb->scrollTo);
AG_DelTimeout(sb, &sb->incTo);
AG_DelTimeout(sb, &sb->decTo);
}
static void
BoundValue(AG_Event *event)
{
AG_Scrollbar *sb = AG_SELF();
AG_Variable *bNew = AG_PTR(1);
AG_Variable *bValue;
void *pValue;
/*
* Require that "min", "max" and "visible" be of the same binding
* type as "value" to avoid derelict hell. Mixing of types could
* always be implemented if some application really requires it.
*/
if (!strcmp(bNew->name, "min") || !strcmp(bNew->name, "max") ||
!strcmp(bNew->name, "visible")) {
bValue = AG_GetVariable(sb, "value", &pValue);
if (bValue->type != bNew->type) {
AG_FatalError("Scrollbar \"%s\" binding type disagree "
"with \"value\" binding", bNew->name);
}
AG_UnlockVariable(bValue);
}
}
static void
Init(void *obj)
{
AG_Scrollbar *sb = obj;
WIDGET(sb)->flags |= AG_WIDGET_UNFOCUSED_BUTTONUP|
AG_WIDGET_UNFOCUSED_MOTION|
AG_WIDGET_FOCUSABLE;
AG_BindInt(sb, "value", &sb->value);
AG_BindInt(sb, "min", &sb->min);
AG_BindInt(sb, "max", &sb->max);
AG_BindInt(sb, "visible", &sb->visible);
sb->type = AG_SCROLLBAR_HORIZ;
sb->curBtn = AG_SCROLLBAR_BUTTON_NONE;
sb->flags = 0;
sb->value = 0;
sb->min = 0;
sb->max = 0;
sb->visible = 0;
sb->buttonIncFn = NULL;
sb->buttonDecFn = NULL;
sb->xOffs = 0;
sb->rInc = 1.0;
sb->iInc = 1;
sb->wBar = 8;
sb->wButton = 16;
sb->hArrow = sb->wButton*5/9;
AG_SetEvent(sb, "window-mousebuttondown", MouseButtonDown, NULL);
AG_SetEvent(sb, "window-mousebuttonup", MouseButtonUp, NULL);
AG_SetEvent(sb, "window-mousemotion", MouseMotion, NULL);
AG_SetEvent(sb, "window-keydown", KeyDown, NULL);
AG_SetEvent(sb, "window-keyup", KeyUp, NULL);
AG_SetEvent(sb, "widget-lostfocus", LostFocus, NULL);
AG_SetEvent(sb, "widget-hidden", LostFocus, NULL);
AG_SetEvent(sb, "bound", BoundValue, NULL);
AG_SetTimeout(&sb->scrollTo, ScrollTimeout, NULL, 0);
AG_SetTimeout(&sb->decTo, DecrementTimeout, NULL, 0);
AG_SetTimeout(&sb->incTo, IncrementTimeout, NULL, 0);
}
static void
SizeRequest(void *obj, AG_SizeReq *r)
{
AG_Scrollbar *sb = obj;
switch (sb->type) {
case AG_SCROLLBAR_HORIZ:
r->w = sb->wButton*2;
r->h = sb->wButton;
break;
case AG_SCROLLBAR_VERT:
r->w = sb->wButton;
r->h = sb->wButton*2;
break;
}
}
static int
SizeAllocate(void *obj, const AG_SizeAlloc *a)
{
AG_Scrollbar *sb = obj;
if (a->w < 4 || a->h < 4) {
return (-1);
}
sb->extent = ((sb->type==AG_SCROLLBAR_VERT) ? a->h : a->w) -
sb->wButton*2 - sb->wBar;
if (sb->extent < 0) { sb->extent = 0; }
return (0);
}
static void
DrawText(AG_Scrollbar *sb)
{
AG_Surface *txt;
char label[32];
AG_PushTextState();
AG_TextColor(TEXT_COLOR);
AG_TextBGColorHex(0xccccccff);
Snprintf(label, sizeof(label), "%d < %d < %d(%d)",
AG_GetInt(sb,"min"),
AG_GetInt(sb,"value"),
AG_GetInt(sb,"max"),
AG_GetInt(sb,"visible"));
/* XXX inefficient */
txt = AG_TextRender(label);
AG_WidgetBlit(sb, txt,
WIDTH(sb)/2 - txt->w/2,
HEIGHT(sb)/2 - txt->h/2);
AG_SurfaceFree(txt);
AG_PopTextState();
}
static void
Draw(void *obj)
{
AG_Scrollbar *sb = obj;
int x, size;
if (GetPosition(sb, &x) == 0) {
size = (sb->wBar == -1) ? sb->extent : sb->wBar;
if (size < 0) { size = 0; }
} else {
x = 0;
size = sb->extent + sb->wBar;
}
switch (sb->type) {
case AG_SCROLLBAR_VERT:
STYLE(sb)->ScrollbarVert(sb, x, size);
break;
case AG_SCROLLBAR_HORIZ:
STYLE(sb)->ScrollbarHoriz(sb, x, size);
break;
}
if (sb->flags & AG_SCROLLBAR_TEXT)
DrawText(sb);
}
/* Return 1 if it is useful to display the scrollbar given the current range. */
int
AG_ScrollbarVisible(AG_Scrollbar *sb)
{
int rv, x;
AG_ObjectLock(sb);
rv = (GetPosition(sb, &x) == -1) ? 0 : 1;
AG_ObjectUnlock(sb);
return (rv);
}
AG_WidgetClass agScrollbarClass = {
{
"Agar(Widget:Scrollbar)",
sizeof(AG_Scrollbar),
{ 0,0 },
Init,
NULL, /* free */
NULL, /* destroy */
NULL, /* load */
NULL, /* save */
NULL /* edit */
},
Draw,
SizeRequest,
SizeAllocate
};