ugfx/src/gwin/gwin_tabset.c

570 lines
17 KiB
C

/*
* This file is subject to the terms of the GFX License. If a copy of
* the license was not distributed with this file, you can obtain one at:
*
* http://ugfx.io/license.html
*/
/**
* @file src/gwin/gwin_tabset.c
* @brief GWIN sub-system tabset code.
*/
#include "../../gfx.h"
#if GFX_USE_GWIN && GWIN_NEED_TABSET
#include "gwin_class.h"
#include <string.h>
// Some position values
#define GTABSET_BORDER 2
#define GTABSET_TXT_PAD 5
// Some color blending
#define GTABSET_TAB_CNR 8 // Diagonal corner on active tab
#define GTABSET_TOP_FADE 50 // (GTABSET_TOP_FADE/255)% fade to white for top of tab/button
#define GTABSET_BOTTOM_FADE 25 // (GTABSET_BOTTOM_FADE/255)% fade to black for bottom of tab/button
#define GTABSET_OUTLINE_FADE 128 // (GTABSET_OUTLINE_FADE/255)% fade to background for active tab edge
/* Internal state flags */
#define GWIN_TABSET_USER_FLAGS (GWIN_TABSET_BORDER)
#if GWIN_TABSET_BORDER < GWIN_FIRST_CONTROL_FLAG
#error "GWIN Tabset: - Flag definitions don't match"
#endif
#if GWIN_TABSET_BORDER > GWIN_LAST_CONTROL_FLAG
#error "GWIN Tabset: - Flag definitions don't match"
#endif
/********************************************************************************************************************
* Tab-page stuff
*/
static void FixTabSizePos(GHandle gh);
typedef GContainerObject GTabpageObject;
static gCoord TabpageBorderSize(GHandle gh) { (void)gh; return 0; }
static void gwinTabpageDraw_Std(GWidgetObject *gw, void *param) {
(void)gw;
(void)param;
// The page is effectively transparent
}
static void TabpageDestroy(GHandle gh) {
_gcontainerDestroy(gh);
FixTabSizePos(gh->parent);
}
static const gcontainerVMT tabpageVMT = {
{
{
"Tabpage", // The classname
sizeof(GTabpageObject), // The object size
TabpageDestroy, // The destroy routine
_gcontainerRedraw, // The redraw routine
0, // The after-clear routine
},
gwinTabpageDraw_Std, // The default drawing routine
#if GINPUT_NEED_MOUSE
{
0, // Process mouse down event
0, // Process mouse up events
0, // Process mouse move events
},
#endif
#if GINPUT_NEED_KEYBOARD || GWIN_NEED_KEYBOARD
{
0 // Process keyboard events
},
#endif
#if GINPUT_NEED_TOGGLE
{
0, // 1 toggle role
0, // Assign Toggles
0, // Get Toggles
0, // Process toggle off events
0, // Process toggle on events
},
#endif
#if GINPUT_NEED_DIAL
{
0, // 1 dial roles
0, // Assign Dials
0, // Get Dials
0, // Process dial move events
},
#endif
},
TabpageBorderSize, // The size of the left border (mandatory)
TabpageBorderSize, // The size of the top border (mandatory)
TabpageBorderSize, // The size of the right border (mandatory)
TabpageBorderSize, // The size of the bottom border (mandatory)
0, // A child has been added (optional)
0, // A child has been deleted (optional)
};
void gwinTabsetSetTitle(GHandle gh, const char *title, gBool useAlloc) {
if (gh->vmt != (gwinVMT *)&tabpageVMT)
return;
gwinSetText(gh, title, useAlloc);
FixTabSizePos(gh->parent);
gwinRedraw(gh->parent);
}
/********************************************************************************************************************
* Tab-set stuff
*/
static gCoord CalcTabHeight(GHandle gh) {
GHandle ph;
gCoord x, y, w;
x = w = 0;
y = GWIN_TABSET_TABHEIGHT;
for(ph = gwinGetFirstChild(gh); ph; ph = gwinGetSibling(ph)) {
if (ph->vmt == (gwinVMT *)&tabpageVMT) {
w = gdispGetStringWidth(((GWidgetObject *)ph)->text, gh->font) + GTABSET_TXT_PAD*2;
x += w;
if (x > gh->width) {
y += GWIN_TABSET_TABHEIGHT;
x = w;
}
}
}
return y;
}
static void FixTabSizePos(GHandle gh) {
gCoord w, h, oldth;
GHandle vis, ph;
oldth = ((GTabsetObject *)gh)->border_top;
((GTabsetObject *)gh)->border_top = CalcTabHeight(gh);
oldth -= ((GTabsetObject *)gh)->border_top;
if (oldth == 0)
return;
vis = 0;
w = gwinGetInnerWidth(gh);
h = gwinGetInnerHeight(gh);
for(ph = gwinGetFirstChild(gh); ph; ph = gwinGetSibling(ph)) {
if (ph->vmt == (gwinVMT *)&tabpageVMT) {
if (!vis || (ph->flags & GWIN_FLG_VISIBLE))
vis = ph;
gwinMove(ph, 0, 0);
gwinResize(ph, w, h);
} else {
gwinMove(ph, ph->x-gh->x-((gh->flags & GWIN_TABSET_BORDER) ? GTABSET_BORDER : 0) , ph->y-oldth-gh->y);
}
}
if (vis && !(vis->flags & GWIN_FLG_VISIBLE)) {
vis->flags |= GWIN_FLG_VISIBLE;
_gwinRippleVisibility();
}
}
static gCoord TabSetBorderSize(GHandle gh) { return (gh->flags & GWIN_TABSET_BORDER) ? GTABSET_BORDER : 0; }
static gCoord TabSetBorderTop(GHandle gh) { return ((GTabsetObject *)gh)->border_top; }
#if GINPUT_NEED_MOUSE
static void mouseDown(GWidgetObject *gw, gCoord mx, gCoord my) {
GHandle ph, gh;
int cnt;
if (my < 0 || my > ((GTabsetObject *)gw)->border_top)
return;
// Work out which tab was pressed
{
gCoord x, w, y;
cnt = 0;
x = w = 0;
y = GWIN_TABSET_TABHEIGHT;
gh = 0;
for(ph = gwinGetFirstChild(&gw->g); ph; ph = gwinGetSibling(ph)) {
if (ph->vmt == (gwinVMT *)&tabpageVMT) {
w = gdispGetStringWidth(((GWidgetObject *)ph)->text, gw->g.font) + GTABSET_TXT_PAD*2;
x += w;
if (x > gw->g.width) {
y += GWIN_TABSET_TABHEIGHT;
x = w;
}
if (my < y && mx < x) {
gh = ph;
break;
}
cnt++;
}
}
if (!gh || (gh->flags & GWIN_FLG_VISIBLE))
return;
}
// Mark the existing tab as not visible
for(ph = gwinGetFirstChild(&gw->g); ph; ph = gwinGetSibling(ph)) {
if (ph->vmt == (gwinVMT *)&tabpageVMT && (ph->flags & GWIN_FLG_VISIBLE)) {
// Mark this page invisible
ph->flags &= ~GWIN_FLG_VISIBLE;
break;
}
}
// Mark this tab as visible
gh->flags |= GWIN_FLG_VISIBLE;
_gwinRippleVisibility();
// Force a redraw of the whole tabset
_gwinUpdate(&gw->g);
// Send the Tabset Event
{
GSourceListener * psl;
GEventGWinTabset * pge;
psl = 0;
while ((psl = geventGetSourceListener(GWIDGET_SOURCE, psl))) {
if (!(pge = (GEventGWinTabset *)geventGetEventBuffer(psl)))
continue;
pge->type = GEVENT_GWIN_TABSET;
pge->gwin = &gw->g;
#if GWIN_WIDGET_TAGS
pge->tag = gw->tag;
#endif
pge->ghPage = gh;
pge->nPage = cnt;
geventSendEvent(psl);
}
}
}
#endif
static const gcontainerVMT tabsetVMT = {
{
{
"Tabset", // The classname
sizeof(GTabsetObject), // The object size
_gcontainerDestroy, // The destroy routine
_gcontainerRedraw, // The redraw routine
0, // The after-clear routine
},
gwinTabsetDraw_Std, // The default drawing routine
#if GINPUT_NEED_MOUSE
{
mouseDown, // Process mouse down event
0, // Process mouse up events
0, // Process mouse move events
},
#endif
#if GINPUT_NEED_TOGGLE
{
0, // 1 toggle role
0, // Assign Toggles
0, // Get Toggles
0, // Process toggle off events
0, // Process toggle on events
},
#endif
#if GINPUT_NEED_DIAL
{
0, // 1 dial roles
0, // Assign Dials
0, // Get Dials
0, // Process dial move events
},
#endif
},
TabSetBorderSize, // The size of the left border (mandatory)
TabSetBorderTop, // The size of the top border (mandatory)
TabSetBorderSize, // The size of the right border (mandatory)
TabSetBorderSize, // The size of the bottom border (mandatory)
0, // A child has been added (optional)
0, // A child has been deleted (optional)
};
GHandle gwinGTabsetCreate(GDisplay *g, GTabsetObject *fo, GWidgetInit *pInit, gU32 flags) {
if (!(fo = (GTabsetObject *)_gcontainerCreate(g, (GContainerObject *)fo, pInit, &tabsetVMT)))
return 0;
// Set Tabset specific stuff
fo->c.g.flags |= flags & GWIN_TABSET_USER_FLAGS;
fo->border_top = GWIN_TABSET_TABHEIGHT;
gwinSetVisible(&fo->c.g, pInit->g.show);
return &fo->c.g;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// API calls
///////////////////////////////////////////////////////////////////////////////////////////////////
GHandle gwinTabsetAddTab(GHandle gh, const char *title, gBool useAlloc) {
GWidgetInit wi;
if (gh->vmt != (gwinVMT *)&tabsetVMT)
return 0;
// Set up the init structure
gwinWidgetClearInit(&wi);
wi.g.x = wi.g.y = 0;
wi.g.width = gwinGetInnerWidth(gh);
wi.g.height = gwinGetInnerHeight(gh);
wi.g.show = !gwinTabsetCountTabs(gh);
wi.g.parent = gh;
// Create the page
if (!(gh = _gcontainerCreate(gh->display, 0, &wi, &tabpageVMT)))
return 0;
// Set the text and visibility
gwinSetText(gh, title, useAlloc);
FixTabSizePos(gh->parent);
gwinSetVisible(gh, wi.g.show);
gwinRedraw(gh->parent);
return gh;
}
int gwinTabsetCountTabs(GHandle gh) {
int cnt;
if (gh->vmt != (gwinVMT *)&tabsetVMT)
return 0;
for(cnt = 0, gh = gwinGetFirstChild(gh); gh; gh = gwinGetSibling(gh)) {
if (gh->vmt == (gwinVMT *)&tabpageVMT)
cnt++;
}
return cnt;
}
GHandle gwinTabsetGetTabByIndex(GHandle gh, int index) {
if (gh->vmt != (gwinVMT *)&tabsetVMT)
return 0;
for(gh = gwinGetFirstChild(gh); gh; gh = gwinGetSibling(gh)) {
if (gh->vmt == (gwinVMT *)&tabpageVMT && !index--)
return gh;
}
return 0;
}
GHandle gwinTabsetGetTabByTitle(GHandle gh, const char *title) {
if (gh->vmt != (gwinVMT *)&tabsetVMT)
return 0;
for(gh = gwinGetFirstChild(gh); gh; gh = gwinGetSibling(gh)) {
if (gh->vmt == (gwinVMT *)&tabpageVMT && !strcmp(title, ((GWidgetObject *)gh)->text))
return gh;
}
return 0;
}
void gwinTabsetSetTab(GHandle gh) {
GHandle ph;
if (gh->vmt != (gwinVMT *)&tabpageVMT || (gh->flags & GWIN_FLG_VISIBLE))
return;
// We alter the visibility flags here directly as we know we are going to redraw everything
for(ph = gwinGetFirstChild(gh->parent); ph; ph = gwinGetSibling(ph)) {
if (ph->vmt == (gwinVMT *)&tabpageVMT && (ph->flags & GWIN_FLG_VISIBLE)) {
// Mark this page invisible
ph->flags &= ~GWIN_FLG_VISIBLE;
break;
}
}
// Mark this tab as visible
gh->flags |= GWIN_FLG_VISIBLE;
_gwinRippleVisibility();
// Force a redraw of the tabset
gwinRedraw(gh->parent);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Default render routines //
///////////////////////////////////////////////////////////////////////////////////////////////////
#if GWIN_FLAT_STYLING
static void fgarea(GWidgetObjset *gw, const char *text, gCoord y, gCoord x, gCoord w) {
const GColorSet * pcol;
pcol = (gw->g.flags & GWIN_FLG_SYSENABLED) ? &gw->pstyle->pressed : &gw->pstyle->disabled;
gdispGDrawBox(gw->g.display, gw->g.x+x, gw->g.y+y, w, GWIN_TABSET_TABHEIGHT, pcol->edge);
gdispGFillStringBox(gw->g.display, gw->g.x+x+1, gw->g.y+y+1, w-2, GWIN_TABSET_TABHEIGHT-1, text, gw->g.font, pcol->text, pcol->fill, gJustifyCenter);
}
static void bgarea(GWidgetObjset *gw, const char *text, gCoord y, gCoord x, gCoord w) {
const GColorSet * pcol;
pcol = (gw->g.flags & GWIN_FLG_SYSENABLED) ? &gw->pstyle->enabled : &gw->pstyle->disabled;
gdispGFillStringBox(gw->g.display, gw->g.x+x, gw->g.y+y, w-1, GWIN_TABSET_TABHEIGHT, text, gw->g.font, pcol->text, pcol->fill, gJustifyCenter);
gdispGDrawLine(gw->g.display, gw->g.x+x+w-1, gw->g.y+y, gw->g.x+x+w-1, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, pcol->edge);
gdispGDrawLine(gw->g.display, gw->g.x+x, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, gw->g.x+x+w-2, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, pcol->edge);
}
static void ntarea(GWidgetObjset *gw, gCoord y, gCoord x, gCoord w) {
const GColorSet * pcol;
pcol = (gw->g.flags & GWIN_FLG_SYSENABLED) ? &gw->pstyle->pressed : &gw->pstyle->disabled;
gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y, w+y, GWIN_TABSET_TABHEIGHT-1, gw->g.bgcolor);
gdispGDrawLine(gw->g.display, gw->g.x+x, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, gw->g.x+x+w-1, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, pcol->edge);
}
#else
static void fgarea(GWidgetObject *gw, const char *text, gCoord y, gCoord x, gCoord w) {
const GColorSet * pcol;
gColor tcol;
pcol = (gw->g.flags & GWIN_FLG_SYSENABLED) ? &gw->pstyle->pressed : &gw->pstyle->disabled;
tcol = gdispBlendColor(pcol->edge, gw->pstyle->background, GTABSET_OUTLINE_FADE);
gdispGFillStringBox(gw->g.display, gw->g.x+x, gw->g.y+y, w, GWIN_TABSET_TABHEIGHT, text, gw->g.font, pcol->text, gw->g.bgcolor, gJustifyCenter);
gdispGDrawLine(gw->g.display, gw->g.x+x, gw->g.y+y, gw->g.x+x+w-(GTABSET_TAB_CNR+1), gw->g.y+y, tcol);
gdispGDrawLine(gw->g.display, gw->g.x+x+w-(GTABSET_TAB_CNR+1), gw->g.y+y, gw->g.x+x+w-1, gw->g.y+y+GTABSET_TAB_CNR, tcol);
gdispGDrawLine(gw->g.display, gw->g.x+x+w-1, gw->g.y+y+GTABSET_TAB_CNR, gw->g.x+x+w-1, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, tcol);
if (!x)
gdispGDrawLine(gw->g.display, gw->g.x, gw->g.y+y, gw->g.x, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, tcol);
}
static void bgarea(GWidgetObject *gw, const char *text, gCoord y, gCoord x, gCoord w) {
const GColorSet * pcol;
fixed alpha;
gCoord i;
gColor tcol, bcol;
pcol = (gw->g.flags & GWIN_FLG_SYSENABLED) ? &gw->pstyle->enabled : &gw->pstyle->disabled;
/* Fill the box blended from variants of the fill color */
tcol = gdispBlendColor(GFX_WHITE, pcol->fill, GTABSET_TOP_FADE);
bcol = gdispBlendColor(GFX_BLACK, pcol->fill, GTABSET_BOTTOM_FADE);
for(alpha = 0, i = 0; i < GWIN_TABSET_TABHEIGHT; i++, alpha += FIXED(255)/GWIN_TABSET_TABHEIGHT)
gdispGDrawLine(gw->g.display, gw->g.x+x, gw->g.y+y+i, gw->g.x+x+w-2, gw->g.y+y+i, gdispBlendColor(bcol, tcol, NONFIXED(alpha)));
gdispGDrawLine(gw->g.display, gw->g.x+x+w-1, gw->g.y+y, gw->g.x+x+w-1, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, pcol->edge);
gdispGDrawStringBox(gw->g.display, gw->g.x+x+1, gw->g.y+y+1, w-2, GWIN_TABSET_TABHEIGHT-2, text, gw->g.font, pcol->text, gJustifyCenter);
}
static void ntarea(GWidgetObject *gw, gCoord y, gCoord x, gCoord w) {
const GColorSet * pcol;
pcol = (gw->g.flags & GWIN_FLG_SYSENABLED) ? &gw->pstyle->pressed : &gw->pstyle->disabled;
gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, w, GWIN_TABSET_TABHEIGHT-1, gw->g.bgcolor);
gdispGDrawLine(gw->g.display, gw->g.x+x, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, gw->g.x+x+w-1, gw->g.y+y+GWIN_TABSET_TABHEIGHT-1, pcol->edge);
}
#endif
static gCoord drawtabs(GWidgetObject *gw) {
GHandle ph;
gCoord x, y, w;
x = w = 0;
y = 0;
for(ph = gwinGetFirstChild(&gw->g); ph; ph = gwinGetSibling(ph)) {
if (ph->vmt == (gwinVMT *)&tabpageVMT) {
w = gdispGetStringWidth(((GWidgetObject *)ph)->text, gw->g.font) + GTABSET_TXT_PAD*2;
if (x+w > gw->g.width) {
ntarea(gw, y, x, gw->g.width - x);
y += GWIN_TABSET_TABHEIGHT;
x = 0;
}
if (ph->flags & GWIN_FLG_VISIBLE)
fgarea(gw, ((GWidgetObject *)ph)->text, y, x, w);
else
bgarea(gw, ((GWidgetObject *)ph)->text, y, x, w);
x += w;
}
}
if (x < gw->g.width)
ntarea(gw, y, x, gw->g.width - x);
return y + GWIN_TABSET_TABHEIGHT;
}
static void drawborder(GWidgetObject *gw, gCoord y) {
if ((gw->g.flags & GWIN_CONTAINER_BORDER)) {
const GColorSet * pcol;
gCoord x, w;
pcol = (gw->g.flags & GWIN_FLG_SYSENABLED) ? &gw->pstyle->enabled : &gw->pstyle->disabled;
x = gw->g.x+gw->g.width-1;
w = gw->g.y+gw->g.height-1;
gdispGDrawLine(gw->g.display, gw->g.x, gw->g.y+y, gw->g.x, w-1, pcol->edge);
gdispGDrawLine(gw->g.display, gw->g.x, w, x, w, pcol->edge);
gdispGDrawLine(gw->g.display, x, gw->g.y+y, x, w-1, pcol->edge);
}
}
void gwinTabsetDraw_Transparent(GWidgetObject *gw, void *param) {
(void) param;
if (gw->g.vmt != (gwinVMT *)&tabsetVMT)
return;
drawborder(gw, drawtabs(gw));
// Don't touch the client area
}
void gwinTabsetDraw_Std(GWidgetObject *gw, void *param) {
gCoord y;
(void) param;
if (gw->g.vmt != (gwinVMT *)&tabsetVMT)
return;
// Draw the frame
y = drawtabs(gw);
drawborder(gw, y);
// Draw the client area
if ((gw->g.flags & GWIN_CONTAINER_BORDER))
gdispGFillArea(gw->g.display, gw->g.x+1, gw->g.y+y, gw->g.width-2, gw->g.height-y-1, gw->pstyle->background);
else
gdispGFillArea(gw->g.display, gw->g.x, gw->g.y+y, gw->g.width, gw->g.height-y, gw->pstyle->background);
}
#if GDISP_NEED_IMAGE
void gwinTabsetDraw_Image(GWidgetObject *gw, void *param) {
#define gi ((gImage *)param)
gCoord x, y, iw, ih, mx, my;
if (gw->g.vmt != (gwinVMT *)&tabsetVMT)
return;
// Draw the frame
y = drawtabs(gw);
drawborder(gw, y);
// Draw the client area by tiling the image
mx = gw->g.x+gw->g.width;
my = gw->g.y+gw->g.height;
if ((gw->g.flags & GWIN_CONTAINER_BORDER)) {
mx -= 2;
my -= 1;
}
for(y = gw->g.y+y, ih = gi->height; y < my; y += ih) {
if (ih > my - y)
ih = my - y;
x = gw->g.x;
if ((gw->g.flags & GWIN_CONTAINER_BORDER))
x++;
for(iw = gi->width; x < mx; x += iw) {
if (iw > mx - x)
iw = mx - x;
gdispGImageDraw(gw->g.display, gi, x, y, iw, ih, 0, 0);
}
}
#undef gi
}
#endif
#endif /* (GFX_USE_GWIN && GWIN_NEED_TABSET) || defined(__DOXYGEN__) */