ugfx/src/gwin/gwin_textedit.c

245 lines
6.8 KiB
C
Raw Normal View History

/*
* 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.org/license.html
*/
/**
* @file src/gwin/gwin_textedit.c
* @brief GWIN TextEdit widget header file
*/
#include "gfx.h"
#if GFX_USE_GWIN && GWIN_NEED_TEXTEDIT
#include "gwin_class.h"
#include <string.h>
// Some settings
const int TEXT_PADDING_LEFT = 4;
const int FOCUS_BORDER_THICKNESS = 3;
const int CURSOR_PADDING_LEFT = 0;
const int CURSOR_EXTRA_HEIGHT = 1;
// Macros to assist in data type conversions
#define gh2obj ((GTexteditObject *)gh)
#define gw2obj ((GTexteditObject *)gw)
// cursorPos is the position of the next character
// textBuffer[cursorPos++] = readKey();
2015-08-16 12:05:32 +00:00
static void _shiftTextLeft(char* buffer, size_t maxSize, size_t index)
{
// Find the end of the string
size_t indexTerminator = index;
2015-08-16 12:05:32 +00:00
while (buffer[indexTerminator] != '\0' && indexTerminator < maxSize-1) {
indexTerminator++;
}
// Shift
memcpy(&buffer[index-1], &buffer[index], indexTerminator-index);
// Terminate the string
buffer[indexTerminator-1] = '\0';
}
2015-08-16 12:05:32 +00:00
static void _shiftTextRight(char* buffer, size_t maxSize, size_t index, char fillChar)
{
// Find the end of the string
size_t indexTerminator = index;
2015-08-16 12:05:32 +00:00
while (buffer[indexTerminator] != '\0' && indexTerminator < maxSize-1) {
indexTerminator++;
}
// Shift
memcpy(&buffer[index+1], &buffer[index], indexTerminator-index);
// Fill the gap
buffer[index] = fillChar;
}
2015-08-16 14:18:54 +00:00
#if GINPUT_NEED_KEYBOARD || GWIN_NEED_KEYBOARD
2015-08-16 11:53:47 +00:00
static void TextEditKeyboard(GWidgetObject* gw, GEventKeyboard* pke)
{
2015-08-15 23:35:46 +00:00
// Only react on KEYDOWN events. Ignore KEYUP events.
if (pke->keystate & GKEYSTATE_KEYUP) {
return;
}
// Is it a special key?
if (pke->keystate & GKEYSTATE_SPECIAL) {
// Arrow keys to move the cursor
switch ((uint8_t)pke->c[0]) {
case GKEY_LEFT:
if (gw2obj->cursorPos > 0) {
gw2obj->cursorPos--;
}
break;
case GKEY_RIGHT:
if (gw2obj->cursorPos < strlen(gw2obj->textBuffer)) {
gw2obj->cursorPos++;
}
break;
default:
break;
}
}
// Parse the key press
else if (pke->bytecount >= 1) {
// Is it backspace?
if (pke->c[0] == GKEY_BACKSPACE) {
if (gw2obj->cursorPos == 0) {
return;
}
2015-08-16 12:05:32 +00:00
_shiftTextLeft(gw2obj->textBuffer, gw2obj->maxSize, gw2obj->cursorPos--);
}
// Is it delete?
else if (pke->c[0] == GKEY_DEL) {
// Check whether there is a character on the right side of the cursor
if (gw2obj->textBuffer[gw2obj->cursorPos] == '\0') {
return;
}
2015-08-16 12:05:32 +00:00
_shiftTextLeft(gw2obj->textBuffer, gw2obj->maxSize, gw2obj->cursorPos+1);
}
// Add a new character
else {
2015-08-14 16:55:36 +00:00
// Prevent buffer overflow
2015-08-16 11:53:47 +00:00
if (gw2obj->cursorPos >= gw2obj->maxSize)
2015-08-14 16:55:36 +00:00
return;
// Shift everything right from the cursor by one character. This includes the '\0'. Then inser the new character.
2015-08-16 12:05:32 +00:00
_shiftTextRight(gw2obj->textBuffer, gw2obj->maxSize, gw2obj->cursorPos++, pke->c[0]);
}
// Set the new text
gwinSetText((GHandle)gw, gw2obj->textBuffer, FALSE);
}
2015-08-12 23:12:34 +00:00
_gwinUpdate((GHandle)gw);
}
#endif
static void gwinTexteditDefaultDraw(GWidgetObject* gw, void* param);
static const gwidgetVMT texteditVMT = {
{
"TextEdit", // The class name
sizeof(GTexteditObject), // The object size
_gwidgetDestroy, // The destroy routine
_gwidgetRedraw, // The redraw routine
0, // The after-clear routine
},
gwinTexteditDefaultDraw, // default drawing routine
#if GINPUT_NEED_MOUSE
{
0, // Process mose down events (NOT USED)
0, // Process mouse up events (NOT USED)
0, // Process mouse move events (NOT USED)
},
#endif
2015-08-16 14:18:54 +00:00
#if GINPUT_NEED_KEYBOARD || GWIN_NEED_KEYBOARD
{
2015-08-16 11:53:47 +00:00
TextEditKeyboard // Process keyboard key down events
},
#endif
#if GINPUT_NEED_TOGGLE
{
0, // No toggle role
0, // Assign Toggles (NOT USED)
0, // Get Toggles (NOT USED)
0, // Process toggle off event (NOT USED)
0, // Process toggle on event (NOT USED)
},
#endif
#if GINPUT_NEED_DIAL
{
0, // No dial roles
0, // Assign Dials (NOT USED)
0, // Get Dials (NOT USED)
0, // Procees dial move events (NOT USED)
},
#endif
};
2015-08-16 11:53:47 +00:00
GHandle gwinGTexteditCreate(GDisplay* g, GTexteditObject* wt, GWidgetInit* pInit, size_t maxSize)
{
// Create the underlying widget
2015-08-16 11:53:47 +00:00
if (!(wt = (GTexteditObject*)_gwidgetCreate(g, &wt->w, pInit, &texteditVMT)))
return 0;
// Allocate the text buffer
2015-08-16 11:53:47 +00:00
wt->maxSize = maxSize;
2015-08-16 12:05:32 +00:00
wt->textBuffer = gfxAlloc(wt->maxSize);
2015-08-16 11:53:47 +00:00
if (wt->textBuffer == 0)
return 0;
// Initialize the text buffer
size_t i = 0;
2015-08-16 12:05:32 +00:00
for (i = 0; i < wt->maxSize; i++) {
2015-08-16 11:53:47 +00:00
wt->textBuffer[i] = '\0';
}
2015-08-14 18:48:41 +00:00
// Set text and cursor position
2015-08-16 11:53:47 +00:00
strncpy(wt->textBuffer, gwinGetText((GHandle)wt), wt->maxSize); // FixMe: pInit->text leads to a segfault
wt->cursorPos = strlen(wt->textBuffer);
2015-08-14 18:48:41 +00:00
2015-08-16 11:53:47 +00:00
gwinSetVisible(&wt->w.g, pInit->g.show);
2015-08-16 11:53:47 +00:00
return (GHandle)wt;
}
static void gwinTexteditDefaultDraw(GWidgetObject* gw, void* param)
{
(void)param;
// Is it a valid handle?
if (gw->g.vmt != (gwinVMT*)&texteditVMT) {
return;
}
// Retrieve colors
color_t textColor = (gw->g.flags & GWIN_FLG_SYSENABLED) ? gw->pstyle->enabled.text : gw->pstyle->disabled.text;
color_t cursorColor = (gw->g.flags & GWIN_FLG_SYSENABLED) ? gw->pstyle->enabled.edge : gw->pstyle->disabled.edge;
// Render background and string
gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, gw->pstyle->background);
gdispGFillStringBox(gw->g.display, gw->g.x + TEXT_PADDING_LEFT, gw->g.y, gw->g.width, gw->g.height, gw->text, gw->g.font, textColor, gw->pstyle->background, justifyLeft);
2015-08-16 11:53:47 +00:00
// Render border (always)
gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, (gw->g.flags & GWIN_FLG_SYSENABLED) ? gw->pstyle->enabled.edge : gw->pstyle->disabled.edge);
// Render highlighted border of focused
2015-08-16 12:20:53 +00:00
_gwidgetDrawFocusRect(gw, 1, 1, gw->g.width-2, gw->g.height-2);
// Render cursor (if focused)
2015-08-14 18:48:41 +00:00
if (gwinGetFocus() == (GHandle)gw) {
// Calculate cursor stuff
2015-08-14 18:48:41 +00:00
coord_t textWidth = gdispGetStringWidthCount(gw2obj->textBuffer, gw->g.font, gw2obj->cursorPos);
coord_t cursorHeight = gdispGetFontMetric(gw->g.font, fontHeight);
coord_t cursorSpacingTop = (gw->g.height - cursorHeight)/2 - CURSOR_EXTRA_HEIGHT;
coord_t cursorSpacingBottom = (gw->g.height - cursorHeight)/2 - CURSOR_EXTRA_HEIGHT;
// Draw cursor
coord_t lineX0 = gw->g.x + textWidth + CURSOR_PADDING_LEFT + TEXT_PADDING_LEFT + gdispGetFontMetric(gw->g.font, fontBaselineX)/2;
2015-08-14 18:48:41 +00:00
coord_t lineX1 = lineX0;
coord_t lineY0 = gw->g.y + cursorSpacingTop;
coord_t lineY1 = gw->g.y + gw->g.height - cursorSpacingBottom;
gdispGDrawLine(gw->g.display, lineX0, lineY0, lineX1, lineY1, cursorColor);
}
}
#undef gh2obj
#undef gw2obj
#endif // GFX_USE_GWIN && GWIN_NEED_TEXTEDIT