Add smooth scrolling option to ugfx list.

ugfx_release_2.6
daid 2014-01-24 15:14:40 +01:00
parent 199b89e4dc
commit a059c6c59c
2 changed files with 123 additions and 40 deletions

View File

@ -46,22 +46,30 @@ typedef struct GEventGWinList {
typedef struct GListObject {
GWidgetObject w;
#if GINPUT_NEED_MOUSE
coord_t start_mouse_x;
coord_t start_mouse_y;
coord_t last_mouse_y;
#endif
#if GINPUT_NEED_TOGGLE
uint16_t t_up;
uint16_t t_dn;
#endif
int cnt; // Number of items currently in the list (quicker than counting each time)
int top; // The element at the top of the visible list area
int top; // Viewing offset in pixels from the top of the list
gfxQueueASync list_head; // The list of items
} GListObject;
/**
* @brief Enum to change the behaviour of the scroll bar
*
* @note This might be used with @p gwinListSetScroll()
* @note Used with @p gwinListSetScroll()
* @note @p scrollAlways always show the scrollbar
* @note @p scrollAuto show the scrollbar when there are more items on the list then fit on the screen
* @note @p scrollSmooth enable touch screen smooth scrolling
*/
typedef enum scroll_t { scrollAlways, scrollAuto } scroll_t;
typedef enum scroll_t { scrollAlways, scrollAuto, scrollSmooth } scroll_t;
#ifdef __cplusplus
extern "C" {
@ -97,7 +105,7 @@ GHandle gwinGListCreate(GDisplay *g, GListObject *widget, GWidgetInit *pInit, bo
/**
* @brief Change the behaviour of the scroll bar
*
* @note Current possible values: @p scrollAlways and @p scrollAuto
* @note Current possible values: @p scrollAlways, @p scrollAuto and @p scrollSmooth
*
* @param[in] gh The widget handle (must be a list handle)
* @param[in] flag The behaviour to be set

View File

@ -21,6 +21,7 @@
#include "gwin/class_gwin.h"
#include <string.h>
#include <stdlib.h>
// user for the default drawing routine
#define SCROLLWIDTH 16 // the border from the scroll buttons to the frame
@ -38,6 +39,7 @@
#define GLIST_FLG_MULTISELECT (GWIN_FIRST_CONTROL_FLAG << 0)
#define GLIST_FLG_HASIMAGES (GWIN_FIRST_CONTROL_FLAG << 1)
#define GLIST_FLG_SCROLLALWAYS (GWIN_FIRST_CONTROL_FLAG << 2)
#define GLIST_FLG_SCROLLSMOOTH (GWIN_FIRST_CONTROL_FLAG << 3)
// Flags on a ListItem.
#define GLIST_FLG_SELECTED 0x0001
@ -94,7 +96,17 @@ static void gwinListDefaultDraw(GWidgetObject* gw, void* param) {
x = 1;
// the scroll area
if ((gw2obj->cnt > (gw->g.height-2) / iheight) || (gw->g.flags & GLIST_FLG_SCROLLALWAYS)) {
if (gw->g.flags & GLIST_FLG_SCROLLSMOOTH) {
iwidth = gw->g.width - 2 - 4;
if (gw2obj->cnt > 0) {
int max_scroll_value = gw2obj->cnt * iheight - gw->g.height-2;
if (max_scroll_value > 0) {
int bar_height = (gw->g.height-2) * (gw->g.height-2) / (gw2obj->cnt * iheight);
gdispGFillArea(gw->g.display, gw->g.x + gw->g.width-4, gw->g.y + 1, 2, gw->g.height-1, gw->pstyle->background);
gdispGFillArea(gw->g.display, gw->g.x + gw->g.width-4, gw->g.y + gw2obj->top * ((gw->g.height-2)-bar_height) / max_scroll_value, 2, bar_height, ps->edge);
}
}
} else if ((gw2obj->cnt > (gw->g.height-2) / iheight) || (gw->g.flags & GLIST_FLG_SCROLLALWAYS)) {
iwidth = gw->g.width - (SCROLLWIDTH+3);
gdispGFillArea(gw->g.display, gw->g.x+iwidth+2, gw->g.y+1, SCROLLWIDTH, gw->g.height-2, gdispBlendColor(ps->fill, gw->pstyle->background, 128));
gdispGDrawLine(gw->g.display, gw->g.x+iwidth+1, gw->g.y+1, gw->g.x+iwidth+1, gw->g.y+gw->g.height-2, ps->edge);
@ -118,10 +130,16 @@ static void gwinListDefaultDraw(GWidgetObject* gw, void* param) {
// Find the top item
for (qi = gfxQueueASyncPeek(&gw2obj->list_head), i = 0; i < gw2obj->top && qi; qi = gfxQueueASyncNext(qi), i++);
for (qi = gfxQueueASyncPeek(&gw2obj->list_head), i = iheight - 1; i < gw2obj->top && qi; qi = gfxQueueASyncNext(qi), i+=iheight);
// the list frame
gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, ps->edge);
// Set the clipping region so we do not override the frame.
gdispGSetClip(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2);
// Draw until we run out of room or items
for (y=1; y+iheight < gw->g.height-1 && qi; qi = gfxQueueASyncNext(qi), y += iheight) {
for (y = 1-(gw2obj->top%iheight); y < gw->g.height-2 && qi; qi = gfxQueueASyncNext(qi), y += iheight) {
fill = (qi2li->flags & GLIST_FLG_SELECTED) ? ps->fill : gw->pstyle->background;
#if GWIN_NEED_LIST_IMAGES
if ((gw->g.flags & GLIST_FLG_HASIMAGES)) {
@ -146,12 +164,41 @@ static void gwinListDefaultDraw(GWidgetObject* gw, void* param) {
// Fill any remaining item space
if (y < gw->g.height-1)
gdispGFillArea(gw->g.display, gw->g.x+1, gw->g.y+y, iwidth, gw->g.height-1-y, gw->pstyle->background);
// the list frame
gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, ps->edge);
}
#if GINPUT_NEED_MOUSE
static void MouseSelect(GWidgetObject* gw, coord_t x, coord_t y) {
const gfxQueueASyncItem* qi;
int item, i;
coord_t iheight;
iheight = gdispGetFontMetric(gw->g.font, fontHeight) + TEXTGAP;
// Handle click over the list area
item = (gw2obj->top + y) / iheight;
if (item < 0 || item >= gw2obj->cnt)
return;
for(qi = gfxQueueASyncPeek(&gw2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
if ((gw->g.flags & GLIST_FLG_MULTISELECT)) {
if (item == i) {
qi2li->flags ^= GLIST_FLG_SELECTED;
break;
}
} else {
if (item == i)
qi2li->flags |= GLIST_FLG_SELECTED;
else
qi2li->flags &=~ GLIST_FLG_SELECTED;
}
}
_gwidgetRedraw(&gw->g);
sendListEvent(gw, item);
}
// a mouse down has occurred over the list area
static void MouseDown(GWidgetObject* gw, coord_t x, coord_t y) {
const gfxQueueASyncItem* qi;
@ -159,20 +206,32 @@ static void gwinListDefaultDraw(GWidgetObject* gw, void* param) {
coord_t iheight;
(void) x;
gw2obj->start_mouse_x = x;
gw2obj->start_mouse_y = y;
gw2obj->last_mouse_y = y;
iheight = gdispGetFontMetric(gw->g.font, fontHeight) + TEXTGAP;
pgsz = (gw->g.height-2)/iheight;
pgsz = (gw->g.height-2);
if (pgsz < 1) pgsz = 1;
// For smooth scrolling, scrolling is done in the MouseMove and selection is done on MouseUp
if (gw->g.flags & GLIST_FLG_SCROLLSMOOTH)
return;
// Handle click over the scroll bar
if (gw2obj->cnt > pgsz && x >= gw->g.width-(SCROLLWIDTH+2)) {
if (gw2obj->cnt > (pgsz / iheight) && x >= gw->g.width-(SCROLLWIDTH+2)) {
if (y < 2*ARROW) {
if (gw2obj->top > 0) {
gw2obj->top--;
gw2obj->top -= iheight;
if (gw2obj->top < 0)
gw2obj->top = 0;
_gwidgetRedraw(&gw->g);
}
} else if (y >= gw->g.height - 2*ARROW) {
if (gw2obj->top < gw2obj->cnt - pgsz) {
gw2obj->top++;
if (gw2obj->top < gw2obj->cnt * iheight - pgsz) {
gw2obj->top += iheight;
if (gw2obj->top > gw2obj->cnt * iheight - pgsz)
gw2obj->top = gw2obj->cnt * iheight - pgsz;
_gwidgetRedraw(&gw->g);
}
} else if (y < gw->g.height/2) {
@ -184,39 +243,51 @@ static void gwinListDefaultDraw(GWidgetObject* gw, void* param) {
_gwidgetRedraw(&gw->g);
}
} else {
if (gw2obj->top < gw2obj->cnt - pgsz) {
if (gw2obj->top < gw2obj->cnt - 2*pgsz)
if (gw2obj->top < gw2obj->cnt * iheight - pgsz) {
if (gw2obj->top < gw2obj->cnt * iheight - 2*pgsz)
gw2obj->top += pgsz;
else
gw2obj->top = gw2obj->cnt - pgsz;
gw2obj->top = gw2obj->cnt * iheight - pgsz;
_gwidgetRedraw(&gw->g);
}
}
return;
}
// Handle click over the list area
item = gw2obj->top + y / iheight;
MouseSelect(gw, x, y);
}
if (item < 0 || item >= gw2obj->cnt)
return;
static void MouseUp(GWidgetObject* gw, coord_t x, coord_t y) {
// Only act when we are a smooth scrolling list
if (!(gw->g.flags & GLIST_FLG_SCROLLSMOOTH))
return;
for(qi = gfxQueueASyncPeek(&gw2obj->list_head), i = 0; qi; qi = gfxQueueASyncNext(qi), i++) {
if ((gw->g.flags & GLIST_FLG_MULTISELECT)) {
if (item == i) {
qi2li->flags ^= GLIST_FLG_SELECTED;
break;
}
} else {
if (item == i)
qi2li->flags |= GLIST_FLG_SELECTED;
else
qi2li->flags &=~ GLIST_FLG_SELECTED;
}
}
// Only allow selection when we did not scroll
if (abs(gw2obj->start_mouse_x - x) > 4 || abs(gw2obj->start_mouse_y - y) > 4)
return;
_gwidgetRedraw(&gw->g);
sendListEvent(gw, item);
MouseSelect(gw, x, y);
}
static void MouseMove(GWidgetObject* gw, coord_t x, coord_t y) {
int iheight, oldtop;
(void) x;
if (!(gw->g.flags & GLIST_FLG_SCROLLSMOOTH)) return;
if (gw2obj->last_mouse_y != y) {
oldtop = gw2obj->top;
iheight = gdispGetFontMetric(gw->g.font, fontHeight) + TEXTGAP;
gw2obj->top -= y - gw2obj->last_mouse_y;
if (gw2obj->top >= gw2obj->cnt * iheight - (gw->g.height-2))
gw2obj->top = gw2obj->cnt * iheight - (gw->g.height-2) - 1;
if (gw2obj->top < 0)
gw2obj->top = 0;
gw2obj->last_mouse_y = y;
if (oldtop != gw2obj->top)
_gwidgetRedraw(&gw->g);
}
}
#endif
@ -295,8 +366,8 @@ static const gwidgetVMT listVMT = {
#if GINPUT_NEED_MOUSE
{
MouseDown,
0,
0,
MouseUp,
MouseMove,
},
#endif
#if GINPUT_NEED_TOGGLE
@ -340,14 +411,18 @@ void gwinListSetScroll(GHandle gh, scroll_t flag) {
if (gh->vmt != (gwinVMT *)&listVMT)
return;
((GListObject*)gh)->w.g.flags &=~(GLIST_FLG_SCROLLSMOOTH | GLIST_FLG_SCROLLALWAYS);
switch (flag) {
case scrollAlways:
((GListObject*)gh)->w.g.flags |= GLIST_FLG_SCROLLALWAYS;
break;
case scrollAuto:
((GListObject*)gh)->w.g.flags &=~ GLIST_FLG_SCROLLALWAYS;
break;
case scrollSmooth:
((GListObject*)gh)->w.g.flags |= GLIST_FLG_SCROLLSMOOTH;
break;
}
}