From 8275c8820f230342939a2410dd0b24c0f26a14e5 Mon Sep 17 00:00:00 2001 From: Andrew Hannam Date: Mon, 26 Nov 2012 18:45:26 +1000 Subject: Ginput and structure changes GINPUT Touch including drivers GTIMER fixes GEVENT fixes GWIN button completion Structure changes to better seperate sections of a sub-system --- src/gdisp.c | 10 +- src/gdisp_fonts.c | 3 +- src/gevent.c | 54 ++++- src/ginput.c | 37 ---- src/ginput/dial.c | 35 +++ src/ginput/ginput.mk | 4 + src/ginput/keyboard.c | 35 +++ src/ginput/mouse.c | 554 +++++++++++++++++++++++++++++++++++++++++++++++ src/ginput/toggle.c | 161 ++++++++++++++ src/gtimer.c | 54 +++-- src/gwin.c | 368 +------------------------------ src/gwin/button.c | 331 ++++++++++++++++++++++++++++ src/gwin/console.c | 210 ++++++++++++++++++ src/gwin/gwin.mk | 2 + src/gwin/gwin_internal.h | 53 +++++ 15 files changed, 1480 insertions(+), 431 deletions(-) delete mode 100644 src/ginput.c create mode 100644 src/ginput/dial.c create mode 100644 src/ginput/ginput.mk create mode 100644 src/ginput/keyboard.c create mode 100644 src/ginput/mouse.c create mode 100644 src/ginput/toggle.c create mode 100644 src/gwin/button.c create mode 100644 src/gwin/console.c create mode 100644 src/gwin/gwin.mk create mode 100644 src/gwin/gwin_internal.h (limited to 'src') diff --git a/src/gdisp.c b/src/gdisp.c index 92bb060d..51a43b49 100644 --- a/src/gdisp.c +++ b/src/gdisp.c @@ -32,7 +32,7 @@ #if GFX_USE_GDISP || defined(__DOXYGEN__) #ifdef GDISP_NEED_TEXT - #include "gdisp_fonts.h" + #include "gdisp/fonts.h" #endif /*===========================================================================*/ @@ -487,13 +487,13 @@ * * @api */ - void gdispDrawArc(coord_t x, coord_t y, coord_t radius, uint16_t start, uint16_t end, color_t color) { + void gdispDrawArc(coord_t x, coord_t y, coord_t radius, coord_t start, coord_t end, color_t color) { chMtxLock(&gdispMutex); GDISP_LLD(drawarc)(x, y, radius, start, end, color); chMtxUnlock(); } #elif GDISP_NEED_ARC && GDISP_NEED_ASYNC - void gdispDrawArc(coord_t x, coord_t y, coord_t radius, uint16_t start, uint16_t end, color_t color) { + void gdispDrawArc(coord_t x, coord_t y, coord_t radius, coord_t start, coord_t end, color_t color) { gdisp_lld_msg_t *p = gdispAllocMsg(GDISP_LLD_MSG_DRAWARC); p->drawarc.x = x; p->drawarc.y = y; @@ -518,13 +518,13 @@ * * @api */ - void gdispFillArc(coord_t x, coord_t y, coord_t radius, uint16_t start, uint16_t end, color_t color) { + void gdispFillArc(coord_t x, coord_t y, coord_t radius, coord_t start, coord_t end, color_t color) { chMtxLock(&gdispMutex); GDISP_LLD(fillarc)(x, y, radius, start, end, color); chMtxUnlock(); } #elif GDISP_NEED_ARC && GDISP_NEED_ASYNC - void gdispFillArc(coord_t x, coord_t y, coord_t radius, uint16_t start, uint16_t end, color_t color) { + void gdispFillArc(coord_t x, coord_t y, coord_t radius, coord_t start, coord_t end, color_t color) { gdisp_lld_msg_t *p = gdispAllocMsg(GDISP_LLD_MSG_FILLARC); p->fillarc.x = x; p->fillarc.y = y; diff --git a/src/gdisp_fonts.c b/src/gdisp_fonts.c index 4384a72e..6a6cd910 100644 --- a/src/gdisp_fonts.c +++ b/src/gdisp_fonts.c @@ -29,7 +29,8 @@ #if GDISP_NEED_TEXT -#include "gdisp_fonts.h" +#include "gdisp/fonts.h" + /* fontSmall - for side buttons */ #if 1 diff --git a/src/gevent.c b/src/gevent.c index d9c7e4f1..0a18eca2 100644 --- a/src/gevent.c +++ b/src/gevent.c @@ -71,6 +71,7 @@ static void deleteAssignments(GListener *pl, GSourceHandle gsh) { void geventListenerInit(GListener *pl) { chSemInit(&pl->waitqueue, 0); // Next wait'er will block chBSemInit(&pl->eventlock, FALSE); // Only one thread at a time looking at the event buffer + pl->callback = 0; // No callback active pl->event.type = GEVENT_NULL; // Always safety } @@ -165,9 +166,45 @@ void geventDetachSource(GListener *pl, GSourceHandle gsh) { * @return NULL on timeout */ GEvent *geventEventWait(GListener *pl, systime_t timeout) { + if (pl->callback || chSemGetCounterI(&pl->waitqueue) < 0) + return 0; return chSemWaitTimeout(&pl->waitqueue, timeout) == RDY_OK ? &pl->event : 0; } +/* @brief Register a callback for an event on a listener from an assigned source. + * @details The type of the event should be checked (pevent->type) and then pevent should be typecast to the + * actual event type if it needs to be processed. + * + * @params[in] pl The Listener + * @params[in] fn The function to call back + * @params[in] param A parameter to pass the callback function + * + * @note The GEvent buffer is valid only during the time of the callback. The callback MUST NOT save + * a pointer to the buffer for use outside the callback. + * @note An existing callback function is de-registered by passing a NULL for 'fn'. Any existing + * callback function is replaced. Any thread currently waiting using geventEventWait will be sent the exit event. + * @note Callbacks occur in a thread context but stack space must be kept to a minumum and + * the callback must process quickly as all other events are performed on a single thread. + * @note In the callback function you should never call ANY event functions using your own GListener handle + * as it WILL create a deadlock and lock the system up. + * @note Applications should not use this call - geventEventWait() is the preferred mechanism for an + * application. This call is provided for GUI objects that may not have their own thread. + */ +void geventRegisterCallback(GListener *pl, GEventCallbackFn fn, void *param) { + if (pl) { + chMtxLock(&geventMutex); + chBSemWait(&pl->eventlock); // Obtain the buffer lock + pl->param = param; // Set the param + pl->callback = fn; // Set the callback function + if (chSemGetCounterI(&pl->waitqueue) < 0) { + pl->event.type = GEVENT_EXIT; // Set up the EXIT event + chSemSignal(&pl->waitqueue); // Wake up the listener + } + chBSemSignal(&pl->eventlock); // Release the buffer lock + chMtxUnlock(); + } +} + /** * @brief Called by a source with a possible event to get a listener record. * @details @p lastlr should be NULL on the first call and thereafter the result of the previous call. @@ -215,7 +252,7 @@ GSourceListener *geventGetSourceListener(GSourceHandle gsh, GSourceListener *las */ GEvent *geventGetEventBuffer(GSourceListener *psl) { // We already know we have the event lock - return chSemGetCounterI(&psl->pListener->waitqueue) < 0 ? &psl->pListener->event : 0; + return &psl->pListener->callback || chSemGetCounterI(&psl->pListener->waitqueue) < 0 ? &psl->pListener->event : 0; } /** @@ -226,10 +263,17 @@ GEvent *geventGetEventBuffer(GSourceListener *psl) { */ void geventSendEvent(GSourceListener *psl) { chMtxLock(&geventMutex); - // Wake up the listener - if (chSemGetCounterI(&psl->pListener->waitqueue) < 0) - chSemSignal(&psl->pListener->waitqueue); - chMtxUnlock(); + if (psl->pListener->callback) { // This test needs to be taken inside the mutex + chMtxUnlock(); + // We already know we have the event lock + psl->pListener->callback(psl->pListener->param, &psl->pListener->event); + + } else { + // Wake up the listener + if (chSemGetCounterI(&psl->pListener->waitqueue) < 0) + chSemSignal(&psl->pListener->waitqueue); + chMtxUnlock(); + } } /** diff --git a/src/ginput.c b/src/ginput.c deleted file mode 100644 index 9b6b180a..00000000 --- a/src/ginput.c +++ /dev/null @@ -1,37 +0,0 @@ -/* - ChibiOS/GFX - Copyright (C) 2012 - Joel Bodenmann aka Tectu - - This file is part of ChibiOS/GFX. - - ChibiOS/GFX 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 3 of the License, or - (at your option) any later version. - - ChibiOS/GFX 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 this program. If not, see . -*/ - -/** - * @file src/ginput.c - * @brief GINPUT Driver code. - * - * @addtogroup GINPUT - * @{ - */ -#include "ch.h" -#include "hal.h" -#include "ginput.h" - -#if GFX_USE_GINPUT || defined(__DOXYGEN__) - -#error "GINPUT: Not Implemented Yet" - -#endif /* GFX_USE_GINPUT */ -/** @} */ diff --git a/src/ginput/dial.c b/src/ginput/dial.c new file mode 100644 index 00000000..cb6799a9 --- /dev/null +++ b/src/ginput/dial.c @@ -0,0 +1,35 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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 this program. If not, see . +*/ + +/** + * @file src/ginput/dial.c + * @brief GINPUT dial code. + * + * @addtogroup GINPUT + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "ginput.h" + +#if GINPUT_NEED_DIAL || defined(__DOXYGEN__) + #error "GINPUT: GINPUT_NEED_DIAL - Not Implemented Yet" +#endif /* GINPUT_NEED_DIAL */ +/** @} */ diff --git a/src/ginput/ginput.mk b/src/ginput/ginput.mk new file mode 100644 index 00000000..06bcfc07 --- /dev/null +++ b/src/ginput/ginput.mk @@ -0,0 +1,4 @@ +GFXSRC += $(GFXLIB)/src/ginput/mouse.c \ + $(GFXLIB)/src/ginput/keyboard.c \ + $(GFXLIB)/src/ginput/toggle.c \ + $(GFXLIB)/src/ginput/dial.c diff --git a/src/ginput/keyboard.c b/src/ginput/keyboard.c new file mode 100644 index 00000000..1c38a408 --- /dev/null +++ b/src/ginput/keyboard.c @@ -0,0 +1,35 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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 this program. If not, see . +*/ + +/** + * @file src/ginput/keyboard.c + * @brief GINPUT keyboard code. + * + * @addtogroup GINPUT + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "ginput.h" + +#if GINPUT_NEED_KEYBOARD || defined(__DOXYGEN__) + #error "GINPUT: GINPUT_NEED_KEYBOARD - Not Implemented Yet" +#endif /* GINPUT_NEED_KEYBOARD */ +/** @} */ diff --git a/src/ginput/mouse.c b/src/ginput/mouse.c new file mode 100644 index 00000000..35872a6c --- /dev/null +++ b/src/ginput/mouse.c @@ -0,0 +1,554 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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 this program. If not, see . +*/ + +/** + * @file src/ginput/mouse.c + * @brief GINPUT mouse/touch code. + * + * @addtogroup GINPUT_MOUSE + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "gtimer.h" +#include "ginput.h" + +#if GINPUT_NEED_MOUSE || defined(__DOXYGEN__) + +#include "lld/ginput/mouse.h" + +#if GINPUT_MOUSE_NEED_CALIBRATION + #if !defined(GFX_USE_GDISP) || !GFX_USE_GDISP + #error "GINPUT: GFX_USE_GDISP must be defined when mouse or touch calibration is required" + #endif + + #define GINPUT_MOUSE_CALIBRATION_FONT &fontUI2Double + #define GINPUT_MOUSE_CALIBRATION_TEXT "Calibration" + + #if GINPUT_MOUSE_MAX_CALIBRATION_ERROR < 0 + #define GINPUT_MOUSE_CALIBRATION_POINTS 3 + #else + #define GINPUT_MOUSE_CALIBRATION_POINTS 4 + #endif + + typedef struct Calibration_t { + float ax; + float bx; + float cx; + float ay; + float by; + float cy; + } Calibration; +#endif + +typedef struct MousePoint_t { + coord_t x, y; + } MousePoint; + +static GTIMER_DECL(MouseTimer); + +static struct MouseConfig_t { + MouseReading t; + MousePoint movepos; + MousePoint clickpos; + systime_t clicktime; + uint16_t last_buttons; + uint16_t flags; + #define FLG_INIT_DONE 0x8000 + #define FLG_CLICK_TIMER 0x0001 + #define FLG_IN_CAL 0x0010 + #define FLG_CAL_OK 0x0020 + #define FLG_CAL_SAVED 0x0040 + #define FLG_CAL_FREE 0x0080 + #if GINPUT_MOUSE_NEED_CALIBRATION + GMouseCalibrationSaveRoutine fnsavecal; + GMouseCalibrationLoadRoutine fnloadcal; + Calibration caldata; + #endif + } MouseConfig; + +#if GINPUT_MOUSE_NEED_CALIBRATION + static __inline void _tsDrawCross(const MousePoint *pp) { + gdispDrawLine(pp->x-15, pp->y, pp->x-2, pp->y, White); + gdispDrawLine(pp->x+2, pp->y, pp->x+15, pp->y, White); + gdispDrawLine(pp->x, pp->y-15, pp->x, pp->y-2, White); + gdispDrawLine(pp->x, pp->y+2, pp->x, pp->y+15, White); + + gdispDrawLine(pp->x-15, pp->y+15, pp->x-7, pp->y+15, RGB2COLOR(184,158,131)); + gdispDrawLine(pp->x-15, pp->y+7, pp->x-15, pp->y+15, RGB2COLOR(184,158,131)); + + gdispDrawLine(pp->x-15, pp->y-15, pp->x-7, pp->y-15, RGB2COLOR(184,158,131)); + gdispDrawLine(pp->x-15, pp->y-7, pp->x-15, pp->y-15, RGB2COLOR(184,158,131)); + + gdispDrawLine(pp->x+7, pp->y+15, pp->x+15, pp->y+15, RGB2COLOR(184,158,131)); + gdispDrawLine(pp->x+15, pp->y+7, pp->x+15, pp->y+15, RGB2COLOR(184,158,131)); + + gdispDrawLine(pp->x+7, pp->y-15, pp->x+15, pp->y-15, RGB2COLOR(184,158,131)); + gdispDrawLine(pp->x+15, pp->y-15, pp->x+15, pp->y-7, RGB2COLOR(184,158,131)); + } + + static __inline void _tsClearCross(const MousePoint *pp) { + gdispFillArea(pp->x - 15, pp->y - 15, 42, 42, Blue); + } + + static __inline void _tsTransform(MouseReading *pt, const Calibration *c) { + pt->x = (coord_t) (c->ax * pt->x + c->bx * pt->y + c->cx); + pt->y = (coord_t) (c->ay * pt->x + c->by * pt->y + c->cy); + } + + static __inline void _tsDo3PointCalibration(const MousePoint *cross, const MousePoint *points, Calibration *c) { + float dx, dx0, dx1, dx2, dy0, dy1, dy2; + + /* Compute all the required determinants */ + dx = ((float)(points[0].x - points[2].x)) * ((float)(points[1].y - points[2].y)) + - ((float)(points[1].x - points[2].x)) * ((float)(points[0].y - points[2].y)); + + dx0 = ((float)(cross[0].x - cross[2].x)) * ((float)(points[1].y - points[2].y)) + - ((float)(cross[1].x - cross[2].x)) * ((float)(points[0].y - points[2].y)); + + dx1 = ((float)(cross[1].x - cross[2].x)) * ((float)(points[0].x - points[2].x)) + - ((float)(cross[0].x - cross[2].x)) * ((float)(points[1].x - points[2].x)); + + dx2 = cross[0].x * ((float)points[1].x * (float)points[2].y - (float)points[2].x * (float)points[1].y) - + cross[1].x * ((float)points[0].x * (float)points[2].y - (float)points[2].x * (float)points[0].y) + + cross[2].x * ((float)points[0].x * (float)points[1].y - (float)points[1].x * (float)points[0].y); + + dy0 = ((float)(cross[0].y - cross[2].y)) * ((float)(points[1].y - points[2].y)) + - ((float)(cross[1].y - cross[2].y)) * ((float)(points[0].y - points[2].y)); + + dy1 = ((float)(cross[1].y - cross[2].y)) * ((float)(points[0].x - points[2].x)) + - ((float)(cross[0].y - cross[2].y)) * ((float)(points[1].x - points[2].x)); + + dy2 = cross[0].y * ((float)points[1].x * (float)points[2].y - (float)points[2].x * (float)points[1].y) - + cross[1].y * ((float)points[0].x * (float)points[2].y - (float)points[2].x * (float)points[0].y) + + cross[2].y * ((float)points[0].x * (float)points[1].y - (float)points[1].x * (float)points[0].y); + + /* Now, calculate all the required coefficients */ + c->ax = dx0 / dx; + c->bx = dx1 / dx; + c->cx = dx2 / dx; + + c->ay = dy0 / dx; + c->by = dy1 / dx; + c->cy = dy2 / dx; + } +#endif + +#if GINPUT_MOUSE_READ_CYCLES > 1 + static void get_raw_reading(MouseReading *pt) { + int32_t x, y, z; + unsigned i; + + x = y = z = 0; + for(i = 0; i < GINPUT_MOUSE_READ_CYCLES; i++) { + ginput_lld_mouse_get_reading(pt); + x += pt->x; + y += pt->y; + z += pt->z; + } + + /* Take the average of the readings */ + pt->x = x / GINPUT_MOUSE_READ_CYCLES; + pt->y = y / GINPUT_MOUSE_READ_CYCLES; + pt->z = z / GINPUT_MOUSE_READ_CYCLES; + } +#else + #define get_raw_reading(pt) ginput_lld_mouse_get_reading(pt) +#endif + +static void get_calibrated_reading(MouseReading *pt) { + #if GINPUT_MOUSE_NEED_CALIBRATION || GDISP_NEED_CONTROL + coord_t w, h; + #endif + + get_raw_reading(pt); + + #if GINPUT_MOUSE_NEED_CALIBRATION || GDISP_NEED_CONTROL + w = gdispGetWidth(); + h = gdispGetHeight(); + #endif + + #if GINPUT_MOUSE_NEED_CALIBRATION + _tsTransform(pt, &MouseConfig.caldata); + #endif + + #if GDISP_NEED_CONTROL + switch(gdispGetOrientation()) { + case GDISP_ROTATE_0: + break; + case GDISP_ROTATE_90: + { + coord_t t = pt->y; + pt->y = h - 1 - pt->x; + pt->x = t; + } + break; + case GDISP_ROTATE_180: + pt->x = w - 1 - pt->x; + pt->y = h - 1 - pt->y; + break; + case GDISP_ROTATE_270: + { + coord_t t = pt->x; + pt->x = w - 1 - pt->y; + pt->y = t; + } + break; + } + #endif + + #if GINPUT_MOUSE_NEED_CALIBRATION + if (pt->x < 0) pt->x = 0; + else if (pt->x >= w) pt->x = w-1; + if (pt->y < 0) pt->y = 0; + else if (pt->y >= h) pt->y = h-1; + #endif +} + +static void MousePoll(void *param) { + (void) param; + GSourceListener *psl; + GEventMouse *pe; + unsigned meta; + uint16_t tbtns; + uint32_t cdiff; + uint32_t mdiff; + + // Save the last mouse state + MouseConfig.last_buttons = MouseConfig.t.buttons; + + // Get the new mouse reading + get_calibrated_reading(&MouseConfig.t); + + // Calculate out new event meta value and handle CLICK and CXTCLICK + meta = GMETA_NONE; + + // Calculate the position difference from our movement reference (update the reference if out of range) + mdiff = (MouseConfig.t.x - MouseConfig.movepos.x) * (MouseConfig.t.x - MouseConfig.movepos.x) + + (MouseConfig.t.y - MouseConfig.movepos.y) * (MouseConfig.t.y - MouseConfig.movepos.y); + if (mdiff > GINPUT_MOUSE_MAX_MOVE_JITTER * GINPUT_MOUSE_MAX_MOVE_JITTER) { + MouseConfig.movepos.x = MouseConfig.t.x; + MouseConfig.movepos.y = MouseConfig.t.y; + } + + // Check if the click has moved outside the click area and if so cancel the click + if ((MouseConfig.flags & FLG_CLICK_TIMER)) { + cdiff = (MouseConfig.t.x - MouseConfig.clickpos.x) * (MouseConfig.t.x - MouseConfig.clickpos.x) + + (MouseConfig.t.y - MouseConfig.clickpos.y) * (MouseConfig.t.y - MouseConfig.clickpos.y); + if (cdiff > GINPUT_MOUSE_MAX_CLICK_JITTER * GINPUT_MOUSE_MAX_CLICK_JITTER) + MouseConfig.flags &= ~FLG_CLICK_TIMER; + } + + // Mouse down + tbtns = MouseConfig.t.buttons & ~MouseConfig.last_buttons; + if ((tbtns & GINPUT_MOUSE_BTN_LEFT)) + meta |= GMETA_MOUSE_DOWN; + if ((tbtns & (GINPUT_MOUSE_BTN_LEFT|GINPUT_MOUSE_BTN_RIGHT))) { + MouseConfig.clickpos.x = MouseConfig.t.x; + MouseConfig.clickpos.y = MouseConfig.t.y; + MouseConfig.clicktime = chTimeNow(); + MouseConfig.flags |= FLG_CLICK_TIMER; + } + + // Mouse up + tbtns = ~MouseConfig.t.buttons & MouseConfig.last_buttons; + if ((tbtns & GINPUT_MOUSE_BTN_LEFT)) + meta |= GMETA_MOUSE_UP; + if ((tbtns & (GINPUT_MOUSE_BTN_LEFT|GINPUT_MOUSE_BTN_RIGHT))) { + if ((MouseConfig.flags & FLG_CLICK_TIMER)) { + if ((tbtns & GINPUT_MOUSE_BTN_LEFT) + #if GINPUT_MOUSE_CLICK_TIME != TIME_INFINITE + && chTimeNow() - MouseConfig.clicktime < MS2ST(GINPUT_MOUSE_CLICK_TIME) + #endif + ) + meta |= GMETA_MOUSE_CLICK; + else + meta |= GMETA_MOUSE_CXTCLICK; + MouseConfig.flags &= ~FLG_CLICK_TIMER; + } + } + + // Send the event to the listeners that are interested. + psl = 0; + while ((psl = geventGetSourceListener((GSourceHandle)(&MouseConfig), psl))) { + if (!(pe = (GEventMouse *)geventGetEventBuffer(psl))) { + // This listener is missing - save the meta events that have happened + psl->srcflags |= meta; + continue; + } + + // If we haven't really moved (and there are no meta events) don't bother sending the event + if (mdiff <= GINPUT_MOUSE_MAX_MOVE_JITTER * GINPUT_MOUSE_MAX_MOVE_JITTER && !psl->srcflags && !meta && !(psl->listenflags & GLISTEN_MOUSENOFILTER)) + continue; + + // Send the event if we are listening for it + if (((MouseConfig.t.buttons & GINPUT_MOUSE_BTN_LEFT) && (psl->listenflags & GLISTEN_MOUSEDOWNMOVES)) + || (!(MouseConfig.t.buttons & GINPUT_MOUSE_BTN_LEFT) && (psl->listenflags & GLISTEN_MOUSEUPMOVES)) + || (meta && (psl->listenflags & GLISTEN_MOUSEMETA))) { + pe->type = GINPUT_MOUSE_EVENT_TYPE; + pe->instance = 0; + pe->x = MouseConfig.t.x; + pe->y = MouseConfig.t.y; + pe->z = MouseConfig.t.z; + pe->current_buttons = MouseConfig.t.buttons; + pe->last_buttons = MouseConfig.last_buttons; + pe->meta = meta; + if (psl->srcflags) { + pe->current_buttons |= GINPUT_MISSED_MOUSE_EVENT; + pe->meta |= psl->srcflags; + psl->srcflags = 0; + } + geventSendEvent(psl); + } + } +} + +/* Mouse Functions */ +GSourceHandle ginputGetMouse(uint16_t instance) { + #if GINPUT_MOUSE_NEED_CALIBRATION + Calibration *pc; + #endif + + // We only support a single mouse instance currently + if (instance) + return 0; + + // Do we need to initialise the mouse subsystem? + if (!(MouseConfig.flags & FLG_INIT_DONE)) { + ginput_lld_mouse_init(); + + #if GINPUT_MOUSE_NEED_CALIBRATION + #if GINPUT_MOUSE_LLD_CALIBRATION_LOADSAVE + if (!MouseConfig.fnloadcal) { + MouseConfig.fnloadcal = ginput_lld_mouse_calibration_load; + MouseConfig.flags &= ~FLG_CAL_FREE; + } + if (!MouseConfig.fnsavecal) + MouseConfig.fnsavecal = ginput_lld_mouse_calibration_save; + #endif + if (MouseConfig.fnloadcal && (pc = (Calibration *)MouseConfig.fnloadcal(instance))) { + MouseConfig.caldata = pc[0]; + MouseConfig.flags |= (FLG_CAL_OK|FLG_CAL_SAVED); + if ((MouseConfig.flags & FLG_CAL_FREE)) + chHeapFree((void *)pc); + } else + ginputCalibrateMouse(instance); + #endif + + // Get the first reading + MouseConfig.last_buttons = 0; + get_calibrated_reading(&MouseConfig.t); + + // Mark init as done and start the Poll timer + MouseConfig.flags |= FLG_INIT_DONE; + gtimerStart(&MouseTimer, MousePoll, 0, TRUE, GINPUT_MOUSE_POLL_PERIOD); + } + + // Return our structure as the handle + return (GSourceHandle)&MouseConfig; +} + +/* Get the current mouse position and button status. + * Unlike a listener event, this status cannot record meta events such as "CLICK" + * Returns FALSE on error (eg invalid instance) + */ +bool_t ginputGetMouseStatus(uint16_t instance, GEventMouse *pe) { + if (instance || (MouseConfig.flags & (FLG_INIT_DONE|FLG_IN_CAL)) != FLG_INIT_DONE) + return FALSE; + + pe->type = GINPUT_MOUSE_EVENT_TYPE; + pe->instance = instance; + pe->x = MouseConfig.t.x; + pe->y = MouseConfig.t.y; + pe->z = MouseConfig.t.z; + pe->current_buttons = MouseConfig.t.buttons; + pe->last_buttons = MouseConfig.last_buttons; + if (pe->current_buttons & ~pe->last_buttons & GINPUT_MOUSE_BTN_LEFT) + pe->meta = GMETA_MOUSE_DOWN; + else if (~pe->current_buttons & pe->last_buttons & GINPUT_MOUSE_BTN_LEFT) + pe->meta = GMETA_MOUSE_UP; + else + pe->meta = GMETA_NONE; + return TRUE; +} + +/* Run a mouse calibration. + * Returns FALSE if the driver doesn't support it or if the handle is invalid. + */ +bool_t ginputCalibrateMouse(uint16_t instance) { + #if !GINPUT_MOUSE_NEED_CALIBRATION + (void) instance; + + return FALSE; + #else + + const coord_t height = gdispGetHeight(); + const coord_t width = gdispGetWidth(); + const MousePoint cross[] = {{(width / 4), (height / 4)}, + {(width - (width / 4)) , (height / 4)}, + {(width - (width / 4)) , (height - (height / 4))}, + {(width / 2), (height / 2)}}; /* Check point */ + MousePoint points[GINPUT_MOUSE_CALIBRATION_POINTS]; + const MousePoint *pc; + MousePoint *pt; + int32_t px, py; + unsigned i, j; + + if (instance || (MouseConfig.flags & FLG_IN_CAL)) + return FALSE; + + MouseConfig.flags |= FLG_IN_CAL; + gtimerStop(&MouseTimer); + MouseConfig.flags &= ~(FLG_CAL_OK|FLG_CAL_SAVED); + + #if GDISP_NEED_CONTROL + gdispSetOrientation(GDISP_ROTATE_0); + #endif + + gdispClear(Blue); + + gdispFillStringBox(0, 5, width, 30, GINPUT_MOUSE_CALIBRATION_TEXT, GINPUT_MOUSE_CALIBRATION_FONT, White, Blue, justifyCenter); + + #if GINPUT_MOUSE_MAX_CALIBRATION_ERROR >= 0 + do { + #endif + for(i = 0, pt = points, pc = cross; i < GINPUT_MOUSE_CALIBRATION_POINTS; i++, pt++, pc++) { + _tsDrawCross(pc); + + do { + + /* Wait for the mouse to be pressed */ + while(get_raw_reading(&MouseConfig.t), !(MouseConfig.t.buttons & GINPUT_MOUSE_BTN_LEFT)) + chThdSleepMilliseconds(20); + + /* Average all the samples while the mouse is down */ + for(px = py = 0, j = 0; + chThdSleepMilliseconds(20), /* Settling time between readings */ + get_raw_reading(&MouseConfig.t), + (MouseConfig.t.buttons & GINPUT_MOUSE_BTN_LEFT); + j++) { + px += MouseConfig.t.x; + py += MouseConfig.t.y; + } + + } while(!j); + + pt->x = px / j; + pt->y = py / j; + + _tsClearCross(pc); + } + + /* Apply 3 point calibration algorithm */ + _tsDo3PointCalibration(cross, points, &MouseConfig.caldata); + + /* Verification of correctness of calibration (optional) : + * See if the 4th point (Middle of the screen) coincides with the calibrated + * result. If point is within +/- Squareroot(ERROR) pixel margin, then successful calibration + * Else, start from the beginning. + */ + #if GINPUT_MOUSE_MAX_CALIBRATION_ERROR >= 0 + /* Transform the co-ordinates */ + MouseConfig.t.x = points[3].x; + MouseConfig.t.y = points[3].y; + _tsTransform(&MouseConfig.t, &MouseConfig.caldata); + + /* Calculate the delta */ + px = (MouseConfig.t.x - cross[3].x) * (MouseConfig.t.x - cross[3].x) + + (MouseConfig.t.y - cross[3].y) * (MouseConfig.t.y - cross[3].y); + + } while (px > GINPUT_MOUSE_MAX_CALIBRATION_ERROR * GINPUT_MOUSE_MAX_CALIBRATION_ERROR); + #endif + + // Restart everything + MouseConfig.flags |= FLG_CAL_OK; + MouseConfig.last_buttons = 0; + get_calibrated_reading(&MouseConfig.t); + MouseConfig.flags &= ~FLG_IN_CAL; + if ((MouseConfig.flags & FLG_INIT_DONE)) + gtimerStart(&MouseTimer, MousePoll, 0, TRUE, GINPUT_MOUSE_POLL_PERIOD); + + // Save the calibration data (if possible) + if (MouseConfig.fnsavecal) { + MouseConfig.fnsavecal(instance, (const uint8_t *)&MouseConfig.caldata, sizeof(MouseConfig.caldata)); + MouseConfig.flags |= FLG_CAL_SAVED; + } + return TRUE; + #endif +} + +/* Set the routines to save and fetch calibration data. + * This function should be called before first calling ginputGetMouse() for a particular instance + * as the gdispGetMouse() routine may attempt to fetch calibration data and perform a startup calibration if there is no way to get it. + * If this is called after gdispGetMouse() has been called and the driver requires calibration storage, it will immediately save the data is has already obtained. + * The 'requireFree' parameter indicates if the fetch buffer must be free()'d to deallocate the buffer provided by the Fetch routine. + */ +void ginputSetMouseCalibrationRoutines(uint16_t instance, GMouseCalibrationSaveRoutine fnsave, GMouseCalibrationLoadRoutine fnload, bool_t requireFree) { + #if GINPUT_MOUSE_NEED_CALIBRATION + if (instance) + return; + + MouseConfig.fnloadcal = fnload; + MouseConfig.fnsavecal = fnsave; + if (requireFree) + MouseConfig.flags |= FLG_CAL_FREE; + else + MouseConfig.flags &= ~FLG_CAL_FREE; + #if GINPUT_MOUSE_LLD_CALIBRATION_LOADSAVE + if (!MouseConfig.fnloadcal) { + MouseConfig.fnloadcal = ginput_lld_mouse_calibration_load; + MouseConfig.flags &= ~FLG_CAL_FREE; + } + if (!MouseConfig.fnsavecal) + MouseConfig.fnsavecal = ginput_lld_mouse_calibration_save; + #endif + if (MouseConfig.fnsavecal && (MouseConfig.flags & (FLG_CAL_OK|FLG_CAL_SAVED)) == FLG_CAL_OK) { + MouseConfig.fnsavecal(instance, (const uint8_t *)&MouseConfig.caldata, sizeof(MouseConfig.caldata)); + MouseConfig.flags |= FLG_CAL_SAVED; + } + #else + (void)instance, (void)fnsave, (void)fnload, (void)requireFree; + #endif +} + +/* Test if a particular mouse instance requires routines to save its calibration data. */ +bool_t ginputRequireMouseCalibrationStorage(uint16_t instance) { + if (instance) + return FALSE; + + #if GINPUT_MOUSE_NEED_CALIBRATION && !GINPUT_MOUSE_LLD_CALIBRATION_LOADSAVE + return TRUE; + #else + return FALSE; + #endif +} + +/* Wake up the mouse driver from an interrupt service routine (there may be new readings available) */ +void ginputMouseWakeup(void) { + gtimerJab(&MouseTimer); +} + +/* Wake up the mouse driver from an interrupt service routine (there may be new readings available) */ +void ginputMouseWakeupI(void) { + gtimerJabI(&MouseTimer); +} + +#endif /* GINPUT_NEED_MOUSE */ +/** @} */ diff --git a/src/ginput/toggle.c b/src/ginput/toggle.c new file mode 100644 index 00000000..a49ebfd3 --- /dev/null +++ b/src/ginput/toggle.c @@ -0,0 +1,161 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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 this program. If not, see . +*/ + +/** + * @file src/ginput/toggle.c + * @brief GINPUT toggle code. + * + * @addtogroup GINPUT_TOGGLE + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "gtimer.h" +#include "ginput.h" + +#if GINPUT_NEED_TOGGLE || defined(__DOXYGEN__) + +#include "lld/ginput/toggle.h" + +#ifndef GINPUT_TOGGLE_POLL_PERIOD + #define GINPUT_TOGGLE_POLL_PERIOD 250 +#endif + +#define GINPUT_TOGGLE_ISON 0x01 +#define GINPUT_TOGGLE_INVERT 0x02 + +static GTIMER_DECL(ToggleTimer); +static struct GEventToggleStatus_t { + uint8_t status; +} ToggleStatus[GINPUT_TOGGLE_NUM_PORTS]; + +// Our polling function +static void TogglePoll(void *param) { + (void) param; + + const GToggleConfig *ptc; + GSourceListener *psl; + GEventToggle *pe; + unsigned i, bits, mask; + uint8_t state; + + // Loop while there are bits to get + for(ptc = GInputToggleConfigTable, i=0; i < GINPUT_TOGGLE_NUM_PORTS; ptc++) { + + // Get the next block of bits + bits = ginput_lld_toggle_getbits(ptc) ^ ptc->invert; + + // Extract the bits of use + for(mask = ptc->mask; i < GINPUT_TOGGLE_NUM_PORTS && mask; mask >>= 1, bits >>= 1) { + // Ignore bits not in our mask + if (!(mask & 1)) + continue; + + // Calculate our new state + state = ToggleStatus[i].status & ~GINPUT_TOGGLE_ISON; + if (state & GINPUT_TOGGLE_INVERT) + bits ^= 1; + if (bits & 1) + state |= GINPUT_TOGGLE_ISON; + + // Has it changed? + if ((state ^ ToggleStatus[i].status) & GINPUT_TOGGLE_ISON) { + + // Save the new state + ToggleStatus[i].status = state; + + // Send the event to the listeners that are interested. + psl = 0; + while ((psl = geventGetSourceListener((GSourceHandle)(ToggleStatus+i), psl))) { + if (!(pe = (GEventToggle *)geventGetEventBuffer(psl))) + continue; + if ((state & GINPUT_TOGGLE_ISON)) { + if ((psl->listenflags & GLISTEN_TOGGLE_ON)) { + pe->type = GEVENT_TOGGLE; + pe->instance = i; + pe->on = TRUE; + geventSendEvent(psl); + } + } else { + if ((psl->listenflags & GLISTEN_TOGGLE_OFF)) { + pe->type = GEVENT_TOGGLE; + pe->instance = i; + pe->on = FALSE; + geventSendEvent(psl); + } + } + } + } + + // Next toggle switch + i++; + } + } +} + +/* Hardware Toggle/Switch/Button Functions */ +GSourceHandle ginputGetToggle(uint16_t instance) { + const GToggleConfig *ptc; + + if (instance >= GINPUT_TOGGLE_NUM_PORTS) + return 0; + + // Do we need to initialise the toggle subsystem? + if (!gtimerIsActive(&ToggleTimer)) { + for(ptc = GInputToggleConfigTable; ptc < GInputToggleConfigTable+sizeof(GInputToggleConfigTable)/sizeof(GInputToggleConfigTable[0]); ptc++) + ginput_lld_toggle_init(ptc); + gtimerStart(&ToggleTimer, TogglePoll, 0, TRUE, GINPUT_TOGGLE_POLL_PERIOD); + } + + // OK - return this input + return (GSourceHandle)(ToggleStatus+instance); +} + +// If invert is true, invert the on/off sense for the toggle +void ginputInvertToggle(uint16_t instance, bool_t invert) { + if (instance >= GINPUT_TOGGLE_NUM_PORTS) + return; + if (invert) { + if (!(ToggleStatus[instance].status & GINPUT_TOGGLE_INVERT)) { + ToggleStatus[instance].status |= GINPUT_TOGGLE_INVERT; + ToggleStatus[instance].status ^= GINPUT_TOGGLE_ISON; + } + } else { + if ((ToggleStatus[instance].status & GINPUT_TOGGLE_INVERT)) { + ToggleStatus[instance].status &= ~GINPUT_TOGGLE_INVERT; + ToggleStatus[instance].status ^= GINPUT_TOGGLE_ISON; + } + } +} + +/* Get the current toggle status. + * Returns FALSE on error (eg invalid instance) + */ +bool_t ginputGetToggleStatus(uint16_t instance, GEventToggle *ptoggle) { + if (instance >= GINPUT_TOGGLE_NUM_PORTS) + return FALSE; + ptoggle->type = GEVENT_TOGGLE; + ptoggle->instance = instance; + ptoggle->on = (ToggleStatus[instance].status & GINPUT_TOGGLE_ISON) ? TRUE : FALSE; + return TRUE; +} + +#endif /* GINPUT_NEED_TOGGLE */ +/** @} */ diff --git a/src/gtimer.c b/src/gtimer.c index 98556607..8956a190 100644 --- a/src/gtimer.c +++ b/src/gtimer.c @@ -36,14 +36,14 @@ #define GTIMER_FLG_JABBED 0x0004 #define GTIMER_FLG_SCHEDULED 0x0008 -#define TimeIsWithin(time, start, end) (end > start ? (time >= start && time <= end) : (time >= start || time <= end)) +/* Don't rework this macro to use a ternary operator - the gcc compiler stuffs it up */ +#define TimeIsWithin(x, start, end) ((end >= start && x >= start && x <= end) || (end < start && (x >= start || x <= end))) // This mutex protects access to our tables static MUTEX_DECL(mutex); static Thread *pThread = 0; static GTimer *pTimerHead = 0; -static systime_t lastTime = 0; -static SEMAPHORE_DECL(waitsem, 0); +static BSEMAPHORE_DECL(waitsem, TRUE); static WORKING_AREA(waTimerThread, GTIMER_THREAD_STACK_SIZE); /*===========================================================================*/ @@ -55,7 +55,7 @@ static msg_t GTimerThreadHandler(void *arg) { GTimer *pt; systime_t tm; systime_t nxtTimeout; - systime_t tmptime; + systime_t lastTime; GTimerFunction fn; void *param; @@ -64,9 +64,11 @@ static msg_t GTimerThreadHandler(void *arg) { #endif nxtTimeout = TIME_INFINITE; + lastTime = 0; while(1) { /* Wait for work to do. */ - chSemWaitTimeout(&waitsem, nxtTimeout); + chThdYield(); // Give someone else a go no matter how busy we are + chBSemWaitTimeout(&waitsem, nxtTimeout); restartTimerChecks: @@ -87,11 +89,13 @@ static msg_t GTimerThreadHandler(void *arg) { if ((pt->flags & GTIMER_FLG_PERIODIC) && pt->period != TIME_IMMEDIATE) { // Yes - Update ready for the next period if (!(pt->flags & GTIMER_FLG_INFINITE)) { - do { - pt->when += pt->period; // We may have skipped a period - } while (TimeIsWithin(pt->when, lastTime, tm)); + // We may have skipped a period. + // We use this complicated formulae rather than a loop + // because the gcc compiler stuffs up the loop so that it + // either loops forever or doesn't get executed at all. + pt->when += ((tm + pt->period - pt->when) / pt->period) * pt->period; } - + // We are definitely no longer jabbed pt->flags &= ~GTIMER_FLG_JABBED; @@ -120,10 +124,8 @@ static msg_t GTimerThreadHandler(void *arg) { } // Find when we next need to wake up - if (!(pt->flags & GTIMER_FLG_INFINITE)) { - tmptime = pt->when - tm; - if (tmptime < nxtTimeout) nxtTimeout = tmptime; - } + if (!(pt->flags & GTIMER_FLG_INFINITE) && pt->when - tm < nxtTimeout) + nxtTimeout = pt->when - tm; pt = pt->next; } while(pt != pTimerHead); } @@ -201,11 +203,13 @@ void gtimerStart(GTimer *pt, GTimerFunction fn, void *param, bool_t periodic, sy pt->flags = GTIMER_FLG_SCHEDULED; if (periodic) pt->flags |= GTIMER_FLG_PERIODIC; - if (millisec != TIME_INFINITE) { + if (millisec == TIME_INFINITE) { + pt->flags |= GTIMER_FLG_INFINITE; + pt->period = TIME_INFINITE; + } else { pt->period = MS2ST(millisec); pt->when = chTimeNow() + pt->period; - } else - pt->flags |= GTIMER_FLG_INFINITE; + } // Just pop it on the end of the queue if (pTimerHead) { @@ -217,7 +221,8 @@ void gtimerStart(GTimer *pt, GTimerFunction fn, void *param, bool_t periodic, sy pt->next = pt->prev = pTimerHead = pt; // Bump the thread - chSemSignal(&waitsem); + if (!(pt->flags & GTIMER_FLG_INFINITE)) + chBSemSignal(&waitsem); chMtxUnlock(); } @@ -248,6 +253,17 @@ void gtimerStop(GTimer *pt) { chMtxUnlock(); } +/** + * @brief Test if a timer is currently active + * + * @param[in] pt Pointer to a GTimer structure + * + * @api + */ +bool_t gtimerIsActive(GTimer *pt) { + return (pt->flags & GTIMER_FLG_SCHEDULED) ? TRUE : FALSE; +} + /** * @brief Jab a timer causing the current period to immediate expire * @details The callback function will be called as soon as possible. @@ -268,7 +284,7 @@ void gtimerJab(GTimer *pt) { pt->flags |= GTIMER_FLG_JABBED; // Bump the thread - chSemSignal(&waitsem); + chBSemSignal(&waitsem); chMtxUnlock(); } @@ -291,7 +307,7 @@ void gtimerJabI(GTimer *pt) { pt->flags |= GTIMER_FLG_JABBED; // Bump the thread - chSemSignalI(&waitsem); + chBSemSignalI(&waitsem); } #endif /* GFX_USE_GTIMER */ diff --git a/src/gwin.c b/src/gwin.c index 86765935..87288aa9 100644 --- a/src/gwin.c +++ b/src/gwin.c @@ -31,17 +31,11 @@ #if GFX_USE_GWIN || defined(__DOXYGEN__) -#include - -#define GWIN_CONSOLE_USE_CLEAR_LINES TRUE -#define GWIN_CONSOLE_USE_FILLED_CHARS FALSE - -#define GWIN_FLG_DYNAMIC 0x0001 -#define GWIN_FIRST_CONTROL_FLAG 0x0002 -#define GBTN_FLG_ALLOCTXT (GWIN_FIRST_CONTROL_FLAG<<0) +#include "gwin/gwin_internal.h" +// Internal routine for use by GWIN components only // Initialise a window creating it dynamicly if required. -static GHandle gwinInit(GWindowObject *gw, coord_t x, coord_t y, coord_t width, coord_t height, size_t size) { +GHandle _gwinInit(GWindowObject *gw, coord_t x, coord_t y, coord_t width, coord_t height, size_t size) { coord_t w, h; // Check the window size against the screen size @@ -90,7 +84,7 @@ static GHandle gwinInit(GWindowObject *gw, coord_t x, coord_t y, coord_t width, * @api */ GHandle gwinCreateWindow(GWindowObject *gw, coord_t x, coord_t y, coord_t width, coord_t height) { - if (!(gw = (GWindowObject *)gwinInit((GWindowObject *)gw, x, y, width, height, sizeof(GWindowObject)))) + if (!(gw = (GWindowObject *)_gwinInit((GWindowObject *)gw, x, y, width, height, sizeof(GWindowObject)))) return 0; gw->type = GW_WINDOW; return (GHandle)gw; @@ -536,360 +530,6 @@ void gwinFillStringBox(GHandle gh, coord_t x, coord_t y, coord_t cx, coord_t cy, } #endif -/*------------------------------------------------------------------------------------------------------------------------*/ - -#if GWIN_NEED_CONSOLE || defined(__DOXYGEN__) - -/* - * Stream interface implementation. The interface is write only - */ - -#define Stream2GWindow(ip) ((GHandle)(((char *)(ip)) - (size_t)(&(((GConsoleObject *)0)->stream)))) - -static size_t GWinStreamWrite(void *ip, const uint8_t *bp, size_t n) { gwinPutCharArray(Stream2GWindow(ip), (const char *)bp, n); return RDY_OK; } -static size_t GWinStreamRead(void *ip, uint8_t *bp, size_t n) { (void)ip; (void)bp; (void)n; return 0; } -static msg_t GWinStreamPut(void *ip, uint8_t b) { gwinPutChar(Stream2GWindow(ip), (char)b); return RDY_OK; } -static msg_t GWinStreamGet(void *ip) {(void)ip; return RDY_OK; } -static msg_t GWinStreamPutTimed(void *ip, uint8_t b, systime_t time) { (void)time; gwinPutChar(Stream2GWindow(ip), (char)b); return RDY_OK; } -static msg_t GWinStreamGetTimed(void *ip, systime_t timeout) { (void)ip; (void)timeout; return RDY_OK; } -static size_t GWinStreamWriteTimed(void *ip, const uint8_t *bp, size_t n, systime_t time) { (void)time; gwinPutCharArray(Stream2GWindow(ip), (const char *)bp, n); return RDY_OK; } -static size_t GWinStreamReadTimed(void *ip, uint8_t *bp, size_t n, systime_t time) { (void)ip; (void)bp; (void)n; (void)time; return 0; } - -struct GConsoleWindowVMT_t { - _base_asynchronous_channel_methods -}; - -static const struct GConsoleWindowVMT_t GWindowConsoleVMT = { - GWinStreamWrite, - GWinStreamRead, - GWinStreamPut, - GWinStreamGet, - GWinStreamPutTimed, - GWinStreamGetTimed, - GWinStreamWriteTimed, - GWinStreamReadTimed -}; - -/** - * @brief Create a console window. - * @details A console window allows text to be written using chprintf() (and the console functions defined here). - * @brief Text in a console window supports newlines and will wrap text as required. - * @return NULL if there is no resultant drawing area, otherwise a window handle. - * - * @param[in] gc The GConsoleObject structure to initialise. If this is NULL the structure is dynamically allocated. - * @param[in] x,y The screen co-ordinates for the bottom left corner of the window - * @param[in] width The width of the window - * @param[in] height The height of the window - * @param[in] font The font to use - * @note The console is not automatically cleared on creation. You must do that by calling gwinClear() (possibly after changing your background color) - * @note If the dispay does not support scrolling, the window will be cleared when the bottom line is reached. - * @note The default drawing color gets set to White and the background drawing color to Black. - * @note The dimensions and position may be changed to fit on the real screen. - * - * @api - */ -GHandle gwinCreateConsole(GConsoleObject *gc, coord_t x, coord_t y, coord_t width, coord_t height, font_t font) { - if (!(gc = (GConsoleObject *)gwinInit((GWindowObject *)gc, x, y, width, height, sizeof(GConsoleObject)))) - return 0; - gc->gwin.type = GW_CONSOLE; - gwinSetFont(&gc->gwin, font); - gc->stream.vmt = &GWindowConsoleVMT; - gc->cx = 0; - gc->cy = 0; - return (GHandle)gc; -} - -/** - * @brief Get a stream from a console window suitable for use with chprintf(). - * @return The stream handle or NULL if this is not a console window. - * - * @param[in] gh The window handle (must be a console window) - * - * @api - */ -BaseSequentialStream *gwinGetConsoleStream(GHandle gh) { - if (gh->type != GW_CONSOLE) - return 0; - return (BaseSequentialStream *)&(((GConsoleObject *)(gh))->stream); -} - -/** - * @brief Put a character at the cursor position in the window. - * @note Uses the current foreground color to draw the character and fills the background using the background drawing color - * - * @param[in] gh The window handle (must be a console window) - * @param[in] c The character to draw - * - * @api - */ -void gwinPutChar(GHandle gh, char c) { - uint8_t width; - #define gcw ((GConsoleObject *)gh) - - if (gh->type != GW_CONSOLE || !gh->font) return; - - #if GDISP_NEED_CLIP - gdispSetClip(gh->x, gh->y, gh->width, gh->height); - #endif - - if (c == '\n') { - gcw->cx = 0; - gcw->cy += gcw->fy; - // We use lazy scrolling here and only scroll when the next char arrives - } else if (c == '\r') { - // gcw->cx = 0; - } else { - width = gdispGetCharWidth(c, gh->font) + gcw->fp; - if (gcw->cx + width >= gh->width) { - gcw->cx = 0; - gcw->cy += gcw->fy; - } - - if (gcw->cy + gcw->fy > gh->height) { -#if GDISP_NEED_SCROLL - /* scroll the console */ - gdispVerticalScroll(gh->x, gh->y, gh->width, gh->height, gcw->fy, gh->bgcolor); - /* reset the cursor to the start of the last line */ - gcw->cx = 0; - gcw->cy = (((coord_t)(gh->height/gcw->fy))-1)*gcw->fy; -#else - /* clear the console */ - gdispFillArea(gh->x, gh->y, gh->width, gh->height, gh->bgcolor); - /* reset the cursor to the top of the window */ - gcw->cx = 0; - gcw->cy = 0; -#endif - } - -#if GWIN_CONSOLE_USE_CLEAR_LINES - /* clear to the end of the line */ - if (gcw->cx == 0) - gdispFillArea(gh->x, gh->y + gcw->cy, gh->width, gcw->fy, gh->bgcolor); -#endif -#if GWIN_CONSOLE_USE_FILLED_CHARS - gdispFillChar(gh->x + gcw->cx, gh->y + gcw->cy, c, gh->font, gh->color, gh->bgcolor); -#else - gdispDrawChar(gh->x + gcw->cx, gh->y + gcw->cy, c, gh->font, gh->color); -#endif - - /* update cursor */ - gcw->cx += width; - } - #undef gcw -} - -/** - * @brief Put a string at the cursor position in the window. It will wrap lines as required. - * @note Uses the current foreground color to draw the string and fills the background using the background drawing color - * - * @param[in] gh The window handle (must be a console window) - * @param[in] str The string to draw - * - * @api - */ -void gwinPutString(GHandle gh, const char *str) { - while(*str) - gwinPutChar(gh, *str++); -} - -/** - * @brief Put the character array at the cursor position in the window. It will wrap lines as required. - * @note Uses the current foreground color to draw the string and fills the background using the background drawing color - * - * @param[in] gh The window handle (must be a console window) - * @param[in] str The string to draw - * @param[in] n The number of characters to draw - * - * @api - */ -void gwinPutCharArray(GHandle gh, const char *str, size_t n) { - while(n--) - gwinPutChar(gh, *str++); -} -#endif - -/*------------------------------------------------------------------------------------------------------------------------*/ - -#if GWIN_NEED_BUTTON || defined(__DOXYGEN__) - -static const GButtonStyle GButtonDefaultStyle = { - GBTN_3D, - HTML2COLOR(0x404040), // color_up_edge; - HTML2COLOR(0xE0E0E0), // color_up_fill; - HTML2COLOR(0x000000), // color_up_txt; - HTML2COLOR(0x404040), // color_dn_edge; - HTML2COLOR(0x808080), // color_dn_fill; - HTML2COLOR(0x404040), // color_dn_txt; - }; - -/** - * @brief Create a button window. - * @return NULL if there is no resultant drawing area, otherwise a window handle. - * - * @param[in] gb The GConsoleWindow structure to initialise. If this is NULL the structure is dynamically allocated. - * @param[in] x,y The screen co-ordinates for the bottom left corner of the window - * @param[in] width The width of the window - * @param[in] height The height of the window - * @param[in] font The font to use - * @param[in] type The type of button - * @note The drawing color gets set to White and the background drawing color to Black. - * @note The dimensions and position may be changed to fit on the real screen. - * @note The button is not automatically drawn. Call gwinButtonDraw() after changing the button style or setting the text. - * - * @api - */ -GHandle gwinCreateButton(GButtonObject *gb, coord_t x, coord_t y, coord_t width, coord_t height, font_t font, GButtonType type) { - if (!(gb = (GButtonObject *)gwinInit((GWindowObject *)gb, x, y, width, height, sizeof(GButtonObject)))) - return 0; - gb->gwin.type = GW_BUTTON; - gwinSetFont(&gb->gwin, font); - gwinSetButtonStyle(&gb->gwin, &GButtonDefaultStyle); - gb->type = type; - gb->state = GBTN_UP; - gb->txt = ""; - gb->callback = 0; - gb->inputsrc = 0; - return (GHandle)gb; -} - -/** - * @brief Set the style of a button. - * @details The button style is defined by its shape and colours. - * - * @param[in] gh The window handle (must be a button window) - * @param[in] style The button style to set. - * @note The button is not automatically redrawn. Call gwinButtonDraw() after changing the button style - * - * @api - */ -void gwinSetButtonStyle(GHandle gh, const GButtonStyle *style) { - #define gbw ((GButtonObject *)gh) - if (gh->type != GW_BUTTON) - return; - - gbw->style.shape = style->shape; - gbw->style.color_up_edge = style->color_up_edge; - gbw->style.color_up_fill = style->color_up_fill; - gbw->style.color_dn_edge = style->color_dn_edge; - gbw->style.color_dn_fill = style->color_dn_fill; - gbw->style.color_up_txt = style->color_up_txt; - gbw->style.color_dn_txt = style->color_dn_txt; - #undef gbw -} - -/** - * @brief Set the text of a button. - * - * @param[in] gh The window handle (must be a button window) - * @param[in] txt The button text to set. This must be a constant string unless useAlloc is set. - * @param[in] useAlloc If TRUE the string specified will be copied into dynamically allocated memory. - * @note The button is not automatically redrawn. Call gwinButtonDraw() after changing the button text. - * - * @api - */ -void gwinSetButtonText(GHandle gh, const char *txt, bool_t useAlloc) { - #define gbw ((GButtonObject *)gh) - if (gh->type != GW_BUTTON) - return; - - // Dispose of the old string - if ((gh->flags & GBTN_FLG_ALLOCTXT)) { - gh->flags &= ~GBTN_FLG_ALLOCTXT; - if (gbw->txt) { - chHeapFree((void *)gbw->txt); - gbw->txt = ""; - } - } - // Alloc the new text if required - if (txt && useAlloc) { - char *str; - - if ((str = (char *)chHeapAlloc(NULL, strlen(txt)+1))) { - gh->flags |= GBTN_FLG_ALLOCTXT; - strcpy(str, txt); - } - txt = (const char *)str; - } - - gbw->txt = txt ? txt : ""; - #undef gbw -} - -/** - * @brief Redraw the button. - * - * @param[in] gh The window handle (must be a button window) - * - * @api - */ -void gwinButtonDraw(GHandle gh) { - color_t cedge; - color_t cfill; - color_t ctxt; - const char * txt; - #define gbw ((GButtonObject *)gh) - #define RND_CNR_SIZE 5 - - if (gh->type != GW_BUTTON) - return; - - #if GDISP_NEED_CLIP - gdispSetClip(gh->x, gh->y, gh->width, gh->height); - #endif - - // Get the text (safely) - txt = gh->font && gbw->txt ? gbw->txt : ""; - - // Determine the colors to use - switch(gbw->state) { - case GBTN_DOWN: - cedge = gbw->style.color_dn_edge; - cfill = gbw->style.color_dn_fill; - ctxt = gbw->style.color_dn_txt; - break; - case GBTN_UP: default: - cedge = gbw->style.color_up_edge; - cfill = gbw->style.color_up_fill; - ctxt = gbw->style.color_up_txt; - break; - } - - // Draw according to the shape specified. - switch(gbw->style.shape) { -#if GDISP_NEED_ARC - case GBTN_ROUNDED: - if (gh->width >= 2*RND_CNR_SIZE+10) { - gdispFillRoundedBox(gh->x+1, gh->y+1, gh->width-2, gh->height-2, RND_CNR_SIZE-1, cfill); - gdispDrawStringBox(gh->x+1, gh->y+RND_CNR_SIZE, gh->width-2, gh->height-(2*RND_CNR_SIZE), txt, gh->font, ctxt, justifyCenter); - gdispDrawRoundedBox(gh->x, gh->y, gh->width, gh->height, RND_CNR_SIZE, cedge); - break; - } - /* Fall Through */ -#endif - case GBTN_SQUARE: - gdispFillStringBox(gh->x+1, gh->y+1, gh->width-2, gh->height-2, txt, gh->font, ctxt, cfill, justifyCenter); - gdispDrawBox(gh->x, gh->y, gh->width, gh->height, cedge); - break; -#if GDISP_NEED_ELLIPSE - case GBTN_ELLIPSE: - gdispFillEllipse(gh->x+1, gh->y+1, gh->width/2-1, gh->height/2-1, cfill); - gdispDrawStringBox(gh->x+1, gh->y+1, gh->width-2, gh->height-2, txt, gh->font, ctxt, justifyCenter); - gdispDrawEllipse(gh->x, gh->y, gh->width/2, gh->height/2, cedge); - break; -#endif - case GBTN_3D: default: - gdispFillStringBox(gh->x, gh->y, gh->width-1, gh->height-1, txt, gh->font, ctxt, cfill, justifyCenter); - gdispDrawLine(gh->x+gh->width-1, gh->y, gh->x+gh->width-1, gh->y+gh->height-1, cedge); - gdispDrawLine(gh->x, gh->y+gh->height-1, gh->x+gh->width-2, gh->y+gh->height-1, cedge); - break; - } - #undef gbw -} - -//void gwinSetButtonCallback(GHandle gh, ????); -//void gwinSetButtonInput(GHandle gh, ????); -#endif - #endif /* GFX_USE_GWIN */ /** @} */ diff --git a/src/gwin/button.c b/src/gwin/button.c new file mode 100644 index 00000000..f62a86a4 --- /dev/null +++ b/src/gwin/button.c @@ -0,0 +1,331 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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 this program. If not, see . +*/ + +/** + * @file src/gwin/button.c + * @brief GWIN Driver code. + * + * @addtogroup GWIN_BUTTON + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "gwin.h" +#include "ginput.h" + +#if !defined(GFX_USE_GINPUT) || !GFX_USE_GINPUT + #error "GWIN Buttons require GFX_USE_GINPUT" +#endif + +#if (GFX_USE_GWIN && GWIN_NEED_BUTTON) || defined(__DOXYGEN__) + +#include +#include "gwin_internal.h" + +static const GButtonStyle GButtonDefaultStyle = { + GBTN_3D, + HTML2COLOR(0x404040), // color_up_edge; + HTML2COLOR(0xE0E0E0), // color_up_fill; + HTML2COLOR(0x000000), // color_up_txt; + HTML2COLOR(0x404040), // color_dn_edge; + HTML2COLOR(0x808080), // color_dn_fill; + HTML2COLOR(0x404040), // color_dn_txt; + }; + +// Process an event callback +static void gwinButtonCallback(void *param, GEvent *pe) { + GSourceListener *psl; + #define gh ((GHandle)param) + #define gbw ((GButtonObject *)param) + #define gsh ((GSourceHandle)param) + #define pme ((GEventMouse *)pe) + #define pte ((GEventTouch *)pe) + #define pxe ((GEventToggle *)pe) + #define pbe ((GEventGWinButton *)pe) + + switch (pe->type) { + #if defined(GINPUT_NEED_MOUSE) && GINPUT_NEED_MOUSE + case GEVENT_MOUSE: + case GEVENT_TOUCH: + // Ignore anything other than the primary mouse button going up or down + if (!((pme->current_buttons ^ pme->last_buttons) & GINPUT_MOUSE_BTN_LEFT)) + return; + + if (gbw->state == GBTN_UP) { + // Our button is UP: Test for button down over the button + if ((pme->current_buttons & GINPUT_MOUSE_BTN_LEFT) + && pme->x >= gbw->gwin.x && pme->x < gbw->gwin.x + gbw->gwin.width + && pme->y >= gbw->gwin.y && pme->y < gbw->gwin.y + gbw->gwin.height) { + gbw->state = GBTN_DOWN; + gwinButtonDraw((GHandle)param); + } + return; + } + + // Our button is DOWN + + // Skip more mouse downs + if ((pme->current_buttons & GINPUT_MOUSE_BTN_LEFT)) + return; + + // This must be a mouse up - set the button as UP + gbw->state = GBTN_UP; + gwinButtonDraw((GHandle)param); + + // If the mouse up was over the button then create the event + if (pme->x >= gbw->gwin.x && pme->x < gbw->gwin.x + gbw->gwin.width + && pme->y >= gbw->gwin.y && pme->y < gbw->gwin.y + gbw->gwin.height) + break; + + return; + #endif + + #if defined(GINPUT_NEED_TOGGLE) && GINPUT_NEED_TOGGLE + case GEVENT_TOGGLE: + // State has changed - update the button + gbw->state = pxe->on ? GBTN_DOWN : GBTN_UP; + gwinButtonDraw((GHandle)param); + + // Trigger the event on button down (different than for mouse/touch) + if (gbw->state == GBTN_DOWN) + break; + + return; + #endif + + default: + return; + } + + // Trigger a GWIN Button Event + psl = 0; + while ((psl = geventGetSourceListener(gsh, psl))) { + if (!(pe = geventGetEventBuffer(psl))) + continue; + pbe->type = GEVENT_GWIN_BUTTON; + pbe->button = gh; + geventSendEvent(psl); + } + + #undef pbe + #undef pme + #undef pte + #undef pxe + #undef gsh + #undef gbw + #undef gh +} + +/** + * @brief Create a button window. + * @return NULL if there is no resultant drawing area, otherwise a window handle. + * + * @param[in] gb The GConsoleWindow structure to initialise. If this is NULL the structure is dynamically allocated. + * @param[in] x,y The screen co-ordinates for the bottom left corner of the window + * @param[in] width The width of the window + * @param[in] height The height of the window + * @param[in] font The font to use + * @param[in] type The type of button + * @note The drawing color gets set to White and the background drawing color to Black. + * @note The dimensions and position may be changed to fit on the real screen. + * @note The button is not automatically drawn. Call gwinButtonDraw() after changing the button style or setting the text. + * + * @api + */ +GHandle gwinCreateButton(GButtonObject *gb, coord_t x, coord_t y, coord_t width, coord_t height, font_t font, GButtonType type) { + if (!(gb = (GButtonObject *)_gwinInit((GWindowObject *)gb, x, y, width, height, sizeof(GButtonObject)))) + return 0; + gb->gwin.type = GW_BUTTON; + gwinSetFont(&gb->gwin, font); + gwinSetButtonStyle(&gb->gwin, &GButtonDefaultStyle); + gb->type = type; + gb->state = GBTN_UP; + gb->txt = ""; + geventListenerInit(&gb->listener); + geventRegisterCallback(&gb->listener, gwinButtonCallback, gb); + return (GHandle)gb; +} + +/** + * @brief Set the style of a button. + * @details The button style is defined by its shape and colours. + * + * @param[in] gh The window handle (must be a button window) + * @param[in] style The button style to set. + * @note The button is not automatically redrawn. Call gwinButtonDraw() after changing the button style + * + * @api + */ +void gwinSetButtonStyle(GHandle gh, const GButtonStyle *style) { + #define gbw ((GButtonObject *)gh) + if (gh->type != GW_BUTTON) + return; + + gbw->style.shape = style->shape; + gbw->style.color_up_edge = style->color_up_edge; + gbw->style.color_up_fill = style->color_up_fill; + gbw->style.color_dn_edge = style->color_dn_edge; + gbw->style.color_dn_fill = style->color_dn_fill; + gbw->style.color_up_txt = style->color_up_txt; + gbw->style.color_dn_txt = style->color_dn_txt; + #undef gbw +} + +/** + * @brief Set the text of a button. + * + * @param[in] gh The window handle (must be a button window) + * @param[in] txt The button text to set. This must be a constant string unless useAlloc is set. + * @param[in] useAlloc If TRUE the string specified will be copied into dynamically allocated memory. + * @note The button is not automatically redrawn. Call gwinButtonDraw() after changing the button text. + * + * @api + */ +void gwinSetButtonText(GHandle gh, const char *txt, bool_t useAlloc) { + #define gbw ((GButtonObject *)gh) + if (gh->type != GW_BUTTON) + return; + + // Dispose of the old string + if ((gh->flags & GBTN_FLG_ALLOCTXT)) { + gh->flags &= ~GBTN_FLG_ALLOCTXT; + if (gbw->txt) { + chHeapFree((void *)gbw->txt); + gbw->txt = ""; + } + } + // Alloc the new text if required + if (txt && useAlloc) { + char *str; + + if ((str = (char *)chHeapAlloc(NULL, strlen(txt)+1))) { + gh->flags |= GBTN_FLG_ALLOCTXT; + strcpy(str, txt); + } + txt = (const char *)str; + } + + gbw->txt = txt ? txt : ""; + #undef gbw +} + +/** + * @brief Redraw the button. + * + * @param[in] gh The window handle (must be a button window) + * + * @api + */ +void gwinButtonDraw(GHandle gh) { + color_t cedge; + color_t cfill; + color_t ctxt; + const char * txt; + #define gbw ((GButtonObject *)gh) + #define RND_CNR_SIZE 5 + + if (gh->type != GW_BUTTON) + return; + + #if GDISP_NEED_CLIP + gdispSetClip(gh->x, gh->y, gh->width, gh->height); + #endif + + // Get the text (safely) + txt = gh->font && gbw->txt ? gbw->txt : ""; + + // Determine the colors to use + switch(gbw->state) { + case GBTN_DOWN: + cedge = gbw->style.color_dn_edge; + cfill = gbw->style.color_dn_fill; + ctxt = gbw->style.color_dn_txt; + break; + case GBTN_UP: default: + cedge = gbw->style.color_up_edge; + cfill = gbw->style.color_up_fill; + ctxt = gbw->style.color_up_txt; + break; + } + + // Draw according to the shape specified. + switch(gbw->style.shape) { +#if GDISP_NEED_ARC + case GBTN_ROUNDED: + if (gh->width >= 2*RND_CNR_SIZE+10) { + gdispFillRoundedBox(gh->x+1, gh->y+1, gh->width-2, gh->height-2, RND_CNR_SIZE-1, cfill); + gdispDrawStringBox(gh->x+1, gh->y+RND_CNR_SIZE, gh->width-2, gh->height-(2*RND_CNR_SIZE), txt, gh->font, ctxt, justifyCenter); + gdispDrawRoundedBox(gh->x, gh->y, gh->width, gh->height, RND_CNR_SIZE, cedge); + break; + } + /* Fall Through */ +#endif + case GBTN_SQUARE: + gdispFillStringBox(gh->x+1, gh->y+1, gh->width-2, gh->height-2, txt, gh->font, ctxt, cfill, justifyCenter); + gdispDrawBox(gh->x, gh->y, gh->width, gh->height, cedge); + break; +#if GDISP_NEED_ELLIPSE + case GBTN_ELLIPSE: + gdispFillEllipse(gh->x+1, gh->y+1, gh->width/2-1, gh->height/2-1, cfill); + gdispDrawStringBox(gh->x+1, gh->y+1, gh->width-2, gh->height-2, txt, gh->font, ctxt, justifyCenter); + gdispDrawEllipse(gh->x, gh->y, gh->width/2, gh->height/2, cedge); + break; +#endif + case GBTN_3D: default: + gdispFillStringBox(gh->x, gh->y, gh->width-1, gh->height-1, txt, gh->font, ctxt, cfill, justifyCenter); + gdispDrawLine(gh->x+gh->width-1, gh->y, gh->x+gh->width-1, gh->y+gh->height-1, cedge); + gdispDrawLine(gh->x, gh->y+gh->height-1, gh->x+gh->width-2, gh->y+gh->height-1, cedge); + break; + } + #undef gbw +} + +// Attach a source to this button. Sources recognised: Mouse, Touch and Toggle - others are ignored (returns false). +bool_t gwinAttachButtonSource(GHandle gh, GSourceHandle gsh, GEventType type) { + #define gbw ((GButtonObject *)gh) + unsigned flags; + + switch (type) { + #if defined(GINPUT_NEED_MOUSE) && GINPUT_NEED_MOUSE + case GEVENT_MOUSE: + flags = 0; + break; + #endif + #if defined(GINPUT_NEED_TOUCH) && GINPUT_NEED_TOUCH + case GEVENT_TOUCH: + flags = 0; + break; + #endif + #if defined(GINPUT_NEED_TOGGLE) && GINPUT_NEED_TOGGLE + case GEVENT_TOGGLE: + flags = GLISTEN_TOGGLE_OFF|GLISTEN_TOGGLE_ON; + break; + #endif + default: + return FALSE; + } + return geventAttachSource(&gbw->listener, gsh, flags); + + #undef gbw +} + +#endif /* GFX_USE_GWIN && GWIN_NEED_BUTTON */ +/** @} */ + diff --git a/src/gwin/console.c b/src/gwin/console.c new file mode 100644 index 00000000..63960c23 --- /dev/null +++ b/src/gwin/console.c @@ -0,0 +1,210 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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 this program. If not, see . +*/ + +/** + * @file src/gwin/console.c + * @brief GWIN Driver code. + * + * @addtogroup GWIN_CONSOLE + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "gwin.h" + +#if (GFX_USE_GWIN && GWIN_NEED_CONSOLE) || defined(__DOXYGEN__) + +#include +#include "gwin_internal.h" + + +#define GWIN_CONSOLE_USE_CLEAR_LINES TRUE +#define GWIN_CONSOLE_USE_FILLED_CHARS FALSE + +/* + * Stream interface implementation. The interface is write only + */ + +#define Stream2GWindow(ip) ((GHandle)(((char *)(ip)) - (size_t)(&(((GConsoleObject *)0)->stream)))) + +static size_t GWinStreamWrite(void *ip, const uint8_t *bp, size_t n) { gwinPutCharArray(Stream2GWindow(ip), (const char *)bp, n); return RDY_OK; } +static size_t GWinStreamRead(void *ip, uint8_t *bp, size_t n) { (void)ip; (void)bp; (void)n; return 0; } +static msg_t GWinStreamPut(void *ip, uint8_t b) { gwinPutChar(Stream2GWindow(ip), (char)b); return RDY_OK; } +static msg_t GWinStreamGet(void *ip) {(void)ip; return RDY_OK; } +static msg_t GWinStreamPutTimed(void *ip, uint8_t b, systime_t time) { (void)time; gwinPutChar(Stream2GWindow(ip), (char)b); return RDY_OK; } +static msg_t GWinStreamGetTimed(void *ip, systime_t timeout) { (void)ip; (void)timeout; return RDY_OK; } +static size_t GWinStreamWriteTimed(void *ip, const uint8_t *bp, size_t n, systime_t time) { (void)time; gwinPutCharArray(Stream2GWindow(ip), (const char *)bp, n); return RDY_OK; } +static size_t GWinStreamReadTimed(void *ip, uint8_t *bp, size_t n, systime_t time) { (void)ip; (void)bp; (void)n; (void)time; return 0; } + +struct GConsoleWindowVMT_t { + _base_asynchronous_channel_methods +}; + +static const struct GConsoleWindowVMT_t GWindowConsoleVMT = { + GWinStreamWrite, + GWinStreamRead, + GWinStreamPut, + GWinStreamGet, + GWinStreamPutTimed, + GWinStreamGetTimed, + GWinStreamWriteTimed, + GWinStreamReadTimed +}; + +/** + * @brief Create a console window. + * @details A console window allows text to be written using chprintf() (and the console functions defined here). + * @brief Text in a console window supports newlines and will wrap text as required. + * @return NULL if there is no resultant drawing area, otherwise a window handle. + * + * @param[in] gc The GConsoleObject structure to initialise. If this is NULL the structure is dynamically allocated. + * @param[in] x,y The screen co-ordinates for the bottom left corner of the window + * @param[in] width The width of the window + * @param[in] height The height of the window + * @param[in] font The font to use + * @note The console is not automatically cleared on creation. You must do that by calling gwinClear() (possibly after changing your background color) + * @note If the dispay does not support scrolling, the window will be cleared when the bottom line is reached. + * @note The default drawing color gets set to White and the background drawing color to Black. + * @note The dimensions and position may be changed to fit on the real screen. + * + * @api + */ +GHandle gwinCreateConsole(GConsoleObject *gc, coord_t x, coord_t y, coord_t width, coord_t height, font_t font) { + if (!(gc = (GConsoleObject *)_gwinInit((GWindowObject *)gc, x, y, width, height, sizeof(GConsoleObject)))) + return 0; + gc->gwin.type = GW_CONSOLE; + gwinSetFont(&gc->gwin, font); + gc->stream.vmt = &GWindowConsoleVMT; + gc->cx = 0; + gc->cy = 0; + return (GHandle)gc; +} + +/** + * @brief Get a stream from a console window suitable for use with chprintf(). + * @return The stream handle or NULL if this is not a console window. + * + * @param[in] gh The window handle (must be a console window) + * + * @api + */ +BaseSequentialStream *gwinGetConsoleStream(GHandle gh) { + if (gh->type != GW_CONSOLE) + return 0; + return (BaseSequentialStream *)&(((GConsoleObject *)(gh))->stream); +} + +/** + * @brief Put a character at the cursor position in the window. + * @note Uses the current foreground color to draw the character and fills the background using the background drawing color + * + * @param[in] gh The window handle (must be a console window) + * @param[in] c The character to draw + * + * @api + */ +void gwinPutChar(GHandle gh, char c) { + uint8_t width; + #define gcw ((GConsoleObject *)gh) + + if (gh->type != GW_CONSOLE || !gh->font) return; + + #if GDISP_NEED_CLIP + gdispSetClip(gh->x, gh->y, gh->width, gh->height); + #endif + + if (c == '\n') { + gcw->cx = 0; + gcw->cy += gcw->fy; + // We use lazy scrolling here and only scroll when the next char arrives + } else if (c == '\r') { + // gcw->cx = 0; + } else { + width = gdispGetCharWidth(c, gh->font) + gcw->fp; + if (gcw->cx + width >= gh->width) { + gcw->cx = 0; + gcw->cy += gcw->fy; + } + + if (gcw->cy + gcw->fy > gh->height) { +#if GDISP_NEED_SCROLL + /* scroll the console */ + gdispVerticalScroll(gh->x, gh->y, gh->width, gh->height, gcw->fy, gh->bgcolor); + /* reset the cursor to the start of the last line */ + gcw->cx = 0; + gcw->cy = (((coord_t)(gh->height/gcw->fy))-1)*gcw->fy; +#else + /* clear the console */ + gdispFillArea(gh->x, gh->y, gh->width, gh->height, gh->bgcolor); + /* reset the cursor to the top of the window */ + gcw->cx = 0; + gcw->cy = 0; +#endif + } + +#if GWIN_CONSOLE_USE_CLEAR_LINES + /* clear to the end of the line */ + if (gcw->cx == 0) + gdispFillArea(gh->x, gh->y + gcw->cy, gh->width, gcw->fy, gh->bgcolor); +#endif +#if GWIN_CONSOLE_USE_FILLED_CHARS + gdispFillChar(gh->x + gcw->cx, gh->y + gcw->cy, c, gh->font, gh->color, gh->bgcolor); +#else + gdispDrawChar(gh->x + gcw->cx, gh->y + gcw->cy, c, gh->font, gh->color); +#endif + + /* update cursor */ + gcw->cx += width; + } + #undef gcw +} + +/** + * @brief Put a string at the cursor position in the window. It will wrap lines as required. + * @note Uses the current foreground color to draw the string and fills the background using the background drawing color + * + * @param[in] gh The window handle (must be a console window) + * @param[in] str The string to draw + * + * @api + */ +void gwinPutString(GHandle gh, const char *str) { + while(*str) + gwinPutChar(gh, *str++); +} + +/** + * @brief Put the character array at the cursor position in the window. It will wrap lines as required. + * @note Uses the current foreground color to draw the string and fills the background using the background drawing color + * + * @param[in] gh The window handle (must be a console window) + * @param[in] str The string to draw + * @param[in] n The number of characters to draw + * + * @api + */ +void gwinPutCharArray(GHandle gh, const char *str, size_t n) { + while(n--) + gwinPutChar(gh, *str++); +} + +#endif /* GFX_USE_GWIN && GWIN_NEED_CONSOLE */ +/** @} */ + diff --git a/src/gwin/gwin.mk b/src/gwin/gwin.mk new file mode 100644 index 00000000..cf952580 --- /dev/null +++ b/src/gwin/gwin.mk @@ -0,0 +1,2 @@ +GFXSRC += $(GFXLIB)/src/gwin/console.c \ + $(GFXLIB)/src/gwin/button.c diff --git a/src/gwin/gwin_internal.h b/src/gwin/gwin_internal.h new file mode 100644 index 00000000..13401a6f --- /dev/null +++ b/src/gwin/gwin_internal.h @@ -0,0 +1,53 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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 this program. If not, see . +*/ +/** + * @file gwin_internal.h + * @brief GWIN Graphic window subsystem header file. + * + * @addtogroup GWIN + * @{ + */ +#ifndef _GWIN_INTERNAL_H +#define _GWIN_INTERNAL_H + +#if GFX_USE_GWIN || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Sub-system constants. */ +/*===========================================================================*/ + +#define GWIN_FLG_DYNAMIC 0x0001 +#define GWIN_FIRST_CONTROL_FLAG 0x0002 +#define GBTN_FLG_ALLOCTXT (GWIN_FIRST_CONTROL_FLAG<<0) + +#ifdef __cplusplus +extern "C" { +#endif + +GHandle _gwinInit(GWindowObject *gw, coord_t x, coord_t y, coord_t width, coord_t height, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_USE_GWIN */ + +#endif /* _GWIN_INTERNAL_H */ +/** @} */ -- cgit v1.2.3