diff --git a/GUI/Adafruit_GFX.c b/GUI/Adafruit_GFX.c index 99a11a2..a95c7c8 100644 --- a/GUI/Adafruit_GFX.c +++ b/GUI/Adafruit_GFX.c @@ -467,7 +467,20 @@ void GFX_drawDottedLine(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t x1, i */ /**************************************************************************/ void GFX_drawFastVLine(Adafruit_GFX* gfx, int16_t x, int16_t y, int16_t h, uint16_t color) { - GFX_drawLine(gfx, x, y, x, y + h - 1, color); + if (h <= 0) return; + + // Boundary check + if (x < 0 || x >= gfx->_width) return; + if (y < 0) { + h += y; + y = 0; + } + if (y + h > gfx->_height) h = gfx->_height - y; + if (h <= 0) return; + + for (int16_t i = 0; i < h; i++) { + GFX_drawPixel(gfx, x, y + i, color); + } } /**************************************************************************/ @@ -480,7 +493,20 @@ void GFX_drawFastVLine(Adafruit_GFX* gfx, int16_t x, int16_t y, int16_t h, uint1 */ /**************************************************************************/ void GFX_drawFastHLine(Adafruit_GFX* gfx, int16_t x, int16_t y, int16_t w, uint16_t color) { - GFX_drawLine(gfx, x, y, x + w - 1, y, color); + if (w <= 0) return; + + // Boundary check + if (y < 0 || y >= gfx->_height) return; + if (x < 0) { + w += x; + x = 0; + } + if (x + w > gfx->_width) w = gfx->_width - x; + if (w <= 0) return; + + for (int16_t i = 0; i < w; i++) { + GFX_drawPixel(gfx, x + i, y, color); + } } /**************************************************************************/ @@ -564,8 +590,8 @@ void GFX_drawCircle(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t r, uint16 @param x0 Center-point x coordinate @param y0 Center-point y coordinate @param r Radius of circle - @param cornername Mask bit #1 or bit #2 to indicate which quarters of - the circle we're doing + @param cornername Mask bit #1, #2, #4, and #8 to indicate which quarters + of the circle we're doing @param color 16-bit 5-6-5 Color to draw with */ /**************************************************************************/ @@ -620,11 +646,12 @@ void GFX_fillCircle(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t r, uint16 /**************************************************************************/ /*! - @brief Quarter-circle drawer with fill, used for circles and roundrects + @brief Half-circle drawer with fill, used for circles and roundrects @param x0 Center-point x coordinate @param y0 Center-point y coordinate @param r Radius of circle - @param corners Mask bits indicating which quarters we're doing + @param corners Mask bits indicating which sides of the circle we are + doing, left (1) and/or right (2) @param delta Offset from center-point, used for round-rects @param color 16-bit 5-6-5 Color to fill with */ @@ -665,6 +692,102 @@ void GFX_fillCircleHelper(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t r, } } +/**************************************************************************/ +/*! + @brief Draw an ellipse outline + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param rw Horizontal radius of ellipse + @param rh Vertical radius of ellipse + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GFX_drawEllipse(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t rw, int16_t rh, uint16_t color) { + // Bresenham's ellipse algorithm + int16_t x = 0, y = rh; + int32_t rw2 = rw * rw, rh2 = rh * rh; + int32_t twoRw2 = 2 * rw2, twoRh2 = 2 * rh2; + + int32_t decision = rh2 - (rw2 * rh) + (rw2 / 4); + + // region 1 + while ((twoRh2 * x) < (twoRw2 * y)) { + GFX_drawPixel(gfx, x0 + x, y0 + y, color); + GFX_drawPixel(gfx, x0 - x, y0 + y, color); + GFX_drawPixel(gfx, x0 + x, y0 - y, color); + GFX_drawPixel(gfx, x0 - x, y0 - y, color); + x++; + if (decision < 0) { + decision += rh2 + (twoRh2 * x); + } else { + decision += rh2 + (twoRh2 * x) - (twoRw2 * y); + y--; + } + } + + // region 2 + decision = ((rh2 * (2 * x + 1) * (2 * x + 1)) >> 2) + (rw2 * (y - 1) * (y - 1)) - (rw2 * rh2); + while (y >= 0) { + GFX_drawPixel(gfx, x0 + x, y0 + y, color); + GFX_drawPixel(gfx, x0 - x, y0 + y, color); + GFX_drawPixel(gfx, x0 + x, y0 - y, color); + GFX_drawPixel(gfx, x0 - x, y0 - y, color); + y--; + if (decision > 0) { + decision += rw2 - (twoRw2 * y); + } else { + decision += rw2 + (twoRh2 * x) - (twoRw2 * y); + x++; + } + } +} + +/**************************************************************************/ +/*! + @brief Draw an ellipse with filled colour + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param rw Horizontal radius of ellipse + @param rh Vertical radius of ellipse + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GFX_fillEllipse(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t rw, int16_t rh, uint16_t color) { + // Bresenham's ellipse algorithm + int16_t x = 0, y = rh; + int32_t rw2 = rw * rw, rh2 = rh * rh; + int32_t twoRw2 = 2 * rw2, twoRh2 = 2 * rh2; + + int32_t decision = rh2 - (rw2 * rh) + (rw2 / 4); + + // region 1 + while ((twoRh2 * x) < (twoRw2 * y)) { + x++; + if (decision < 0) { + decision += rh2 + (twoRh2 * x); + } else { + decision += rh2 + (twoRh2 * x) - (twoRw2 * y); + GFX_drawFastHLine(gfx, x0 - (x - 1), y0 + y, 2 * (x - 1) + 1, color); + GFX_drawFastHLine(gfx, x0 - (x - 1), y0 - y, 2 * (x - 1) + 1, color); + y--; + } + } + + // region 2 + decision = ((rh2 * (2 * x + 1) * (2 * x + 1)) >> 2) + (rw2 * (y - 1) * (y - 1)) - (rw2 * rh2); + while (y >= 0) { + GFX_drawFastHLine(gfx, x0 - x, y0 + y, 2 * x + 1, color); + GFX_drawFastHLine(gfx, x0 - x, y0 - y, 2 * x + 1, color); + y--; + if (decision > 0) { + decision += rw2 - (twoRw2 * y); + } else { + decision += rw2 + (twoRh2 * x) - (twoRw2 * y); + x++; + } + } +} + /**************************************************************************/ /*! @brief Draw a rectangle with no fill color @@ -1033,31 +1156,6 @@ int16_t GFX_getUTF8Width(Adafruit_GFX* gfx, const char* str) { return w; } -int16_t GFX_getUTF8Widthf(Adafruit_GFX* gfx, const char* format, ...) { - char buf[64] = {0}; - char* str = buf; - size_t len; - va_list va; - - va_start(va, format); - len = vsnprintf(buf, sizeof(buf), format, va); - va_end(va); - - if (len > sizeof(buf) - 1) { - str = malloc(len + 1); - if (str == NULL) return 0; - va_start(va, format); - vsnprintf(str, len + 1, format, va); - va_end(va); - } - - int16_t w = GFX_getUTF8Width(gfx, str); - - if (str != buf) free(str); - - return w; -} - size_t GFX_print(Adafruit_GFX* gfx, const char c) { int16_t delta; uint16_t e = utf8_next(gfx, (uint8_t)c); diff --git a/GUI/Adafruit_GFX.h b/GUI/Adafruit_GFX.h index 259fe16..3842535 100644 --- a/GUI/Adafruit_GFX.h +++ b/GUI/Adafruit_GFX.h @@ -72,6 +72,8 @@ void GFX_drawCircleHelper(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t r, void GFX_fillCircle(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t r, uint16_t color); void GFX_fillCircleHelper(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, uint16_t color); +void GFX_drawEllipse(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t rw, int16_t rh, uint16_t color); +void GFX_fillEllipse(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t rw, int16_t rh, uint16_t color); void GFX_drawTriangle(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color); void GFX_fillTriangle(Adafruit_GFX* gfx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, @@ -94,7 +96,6 @@ int16_t GFX_drawGlyph(Adafruit_GFX* gfx, int16_t x, int16_t y, uint16_t e); int16_t GFX_drawStr(Adafruit_GFX* gfx, int16_t x, int16_t y, const char* s); int16_t GFX_drawUTF8(Adafruit_GFX* gfx, int16_t x, int16_t y, const char* str); int16_t GFX_getUTF8Width(Adafruit_GFX* gfx, const char* str); -int16_t GFX_getUTF8Widthf(Adafruit_GFX* gfx, const char* format, ...); size_t GFX_print(Adafruit_GFX* gfx, const char c); size_t GFX_write(Adafruit_GFX* gfx, const char* buffer, size_t size); size_t GFX_printf(Adafruit_GFX* gfx, const char* format, ...); diff --git a/GUI/GUI.c b/GUI/GUI.c index b9fe13e..f439ebc 100644 --- a/GUI/GUI.c +++ b/GUI/GUI.c @@ -12,6 +12,9 @@ GFX_setFont(gfx, font); \ GFX_printf(gfx, __VA_ARGS__); +// height to use larger layout +#define large_layout(data) ((data)->height >= 400) + typedef struct { uint8_t month; uint8_t day; @@ -194,15 +197,15 @@ static void DrawDateHeader(Adafruit_GFX* gfx, int16_t x, int16_t y, tm_t* tm, st GFX_printf(gfx, " [%s]", Lunar_ZodiacString[LUNAR_GetZodiac(Lunar)]); GFX_setTextColor(gfx, GFX_BLACK, GFX_WHITE); - DrawBattery(gfx, data->width - 10 - 2, data->height > 300 ? 16 : 6, 20, data->voltage); + DrawBattery(gfx, data->width - 10 - 2, large_layout(data) ? 16 : 6, 20, data->voltage); GFX_setCursor(gfx, data->width - GFX_getUTF8Width(gfx, data->ssid) - 10, y); GFX_printf(gfx, "%s", data->ssid); } static void DrawWeekHeader(Adafruit_GFX* gfx, int16_t x, int16_t y, gui_data_t* data) { - GFX_setFont(gfx, data->height > 300 ? u8g2_font_wqy12_t_lunar : u8g2_font_wqy9_t_lunar); + GFX_setFont(gfx, large_layout(data) ? u8g2_font_wqy12_t_lunar : u8g2_font_wqy9_t_lunar); uint8_t w = (data->width - 2 * x) / 7; - uint8_t h = data->height > 300 ? 32 : 24; + uint8_t h = large_layout(data) ? 32 : 24; uint8_t r = (data->width - 2 * x) % 7; uint8_t fh = (h - GFX_getFontHeight(gfx)) / 2 + GFX_getFontAscent(gfx) + 1; int16_t cw = GFX_getUTF8Width(gfx, Lunar_DayString[0]); @@ -225,7 +228,7 @@ static void DrawMonthDays(Adafruit_GFX* gfx, int16_t x, int16_t y, tm_t* tm, str int16_t bw = (data->width - x - 10) / 7; int16_t bh = (data->height - y - 10) / monthDayRows; - bool large = data->height > 300; + bool large = large_layout(data); if (large) { for (uint8_t i = 1; i < monthDayRows; i++) @@ -245,7 +248,8 @@ static void DrawMonthDays(Adafruit_GFX* gfx, int16_t x, int16_t y, tm_t* tm, str LUNAR_SolarToLunar(Lunar, year, month, day); - int16_t cr = large ? 13 : 10; + int16_t cr = large ? 15 : 11; + if (monthDayRows > 5) cr -= 1; // reduce circle height for 6 week rows int16_t bx = x + (bw - 2 * cr) / 2 + displayWeek * bw; int16_t by = y + (bh - 2 * cr) / 2 + (i + adjustedFirstDay) / 7 * bh + 3; @@ -256,24 +260,26 @@ static void DrawMonthDays(Adafruit_GFX* gfx, int16_t x, int16_t y, tm_t* tm, str GFX_setTextColor(gfx, weekend ? GFX_RED : GFX_BLACK, GFX_WHITE); } + char buf[10] = {0}; + snprintf(buf, sizeof(buf), "%d", day); GFX_setFont(gfx, large ? u8g2_font_helvB18_tn : u8g2_font_helvB14_tn); - GFX_setCursor(gfx, bx + (2 * cr - GFX_getUTF8Widthf(gfx, "%d", day)) / 2, by - (cr - GFX_getFontHeight(gfx))); - GFX_printf(gfx, "%d", day); + GFX_setCursor(gfx, bx + (2 * cr - GFX_getUTF8Width(gfx, buf)) / 2, by - (cr - GFX_getFontHeight(gfx)) - 1); + GFX_printf(gfx, "%s", buf); - char festival[10] = {0}; GFX_setFont(gfx, large ? u8g2_font_wqy12_t_lunar : u8g2_font_wqy9_t_lunar); GFX_setFontMode(gfx, 1); // transparent - if (GetFestival(year, month, day, actualWeek, Lunar, festival)) { + if (GetFestival(year, month, day, actualWeek, Lunar, buf)) { if (day != tm->tm_mday) GFX_setTextColor(gfx, GFX_RED, GFX_WHITE); } else { if (Lunar->Date == 1) - snprintf(festival, sizeof(festival), "%s%s", Lunar_MonthLeapString[Lunar->IsLeap], + snprintf(buf, sizeof(buf), "%s%s", Lunar_MonthLeapString[Lunar->IsLeap], Lunar_MonthString[Lunar->Month]); else - snprintf(festival, sizeof(festival), "%s", Lunar_DateString[Lunar->Date]); + snprintf(buf, sizeof(buf), "%s", Lunar_DateString[Lunar->Date]); } - GFX_setCursor(gfx, bx + (2 * cr - GFX_getUTF8Width(gfx, festival)) / 2, gfx->ty + GFX_getFontHeight(gfx) + 3); - GFX_printf(gfx, "%s", festival); + GFX_setCursor(gfx, bx + (2 * cr - GFX_getUTF8Width(gfx, buf)) / 2 + 1, + gfx->ty + GFX_getFontHeight(gfx) + (large ? 5 : 3)); + GFX_printf(gfx, "%s", buf); bool work = false; if (year == HOLIDAY_YEAR && GetHoliday(month, day, &work)) { @@ -293,7 +299,7 @@ static void DrawMonthDays(Adafruit_GFX* gfx, int16_t x, int16_t y, tm_t* tm, str } static void DrawCalendar(Adafruit_GFX* gfx, tm_t* tm, struct Lunar_Date* Lunar, gui_data_t* data) { - bool large = data->height > 300; + bool large = large_layout(data); DrawDateHeader(gfx, 10, large ? 38 : 28, tm, Lunar, data); DrawWeekHeader(gfx, 10, large ? 44 : 32, data); DrawMonthDays(gfx, 10, large ? 84 : 64, tm, Lunar, data); @@ -316,10 +322,10 @@ static void DrawCalendar(Adafruit_GFX* gfx, tm_t* tm, struct Lunar_Date* Lunar, https://forum.arduino.cc/t/fast-7-segment-number-display-for-tft/296619/4 */ -static void Draw7Number(Adafruit_GFX *gfx, int n, unsigned int xLoc, unsigned int yLoc, char cS, unsigned int fC, unsigned int bC, int nD) { - unsigned int num=abs(n),i,t,w,col,h,a,b,j=1,d=0,S2=5*cS,S3=2*cS,S4=7*cS,x1=cS+1,x2=S3+S2+1,y1=yLoc+x1,y3=yLoc+S3+S4+1; - unsigned int seg[7][3]={{x1,yLoc,1},{x2,y1,0},{x2,y3+x1,0},{x1,(2*y3)-yLoc,1},{0,y3+x1,0},{0,y1,0},{x1,y3,1}}; - unsigned char nums[12]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40},c=(c=abs(cS))>10?10:(c<1)?1:c,cnt=(cnt=abs(nD))>10?10:(cnt<1)?1:cnt; +static void Draw7Number(Adafruit_GFX *gfx, int16_t n, uint16_t xLoc, uint16_t yLoc, int16_t cS, uint16_t fC, uint16_t bC, int16_t nD) { + uint16_t num=abs(n),i,t,w,col,h,a,b,j=1,d=0,S2=5*cS,S3=2*cS,S4=7*cS,x1=cS+1,x2=S3+S2+1,y1=yLoc+x1,y3=yLoc+S3+S4+1; + uint16_t seg[7][3]={{x1,yLoc,1},{x2,y1,0},{x2,y3+x1,0},{x1,(2*y3)-yLoc,1},{0,y3+x1,0},{0,y1,0},{x1,y3,1}}; + uint8_t nums[12]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40},c=(c=abs(cS))>10?10:(c<1)?1:c,cnt=(cnt=abs(nD))>10?10:(cnt<1)?1:cnt; for (xLoc+=cnt*(d=S2+(3*S3)+2);cnt>0;cnt--){ for (i=(num>9)?num%10:((!cnt)&&(n<0))?11:((nD<0)&&(!num))?10:num,xLoc-=d,num/=10,j=0;j<7;++j){ col=(nums[i]&(1<height > 300 ? 100 : 40; + uint8_t padding = large_layout(data) ? 100 : 40; GFX_setCursor(gfx, padding, 36); GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%d", tm->tm_year + YEAR0); GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "年"); diff --git a/GUI/u8g2_font.c b/GUI/u8g2_font.c index 36afa8f..8cd2c31 100644 --- a/GUI/u8g2_font.c +++ b/GUI/u8g2_font.c @@ -415,14 +415,19 @@ static int16_t u8g2_font_draw_glyph(u8g2_font_t* u8g2, int16_t x, int16_t y, uin //======================================================== +/** + * Check, whether the font contains a glyph for the requested encoding. + */ uint8_t u8g2_IsGlyph(u8g2_font_t* u8g2, uint16_t requested_encoding) { /* updated to new code */ if (u8g2_font_get_glyph_data(u8g2, requested_encoding) != NULL) return 1; return 0; } -/* side effect: updates u8g2->font_decode and u8g2->glyph_x_offset */ -/* actually u8g2_GetGlyphWidth returns the glyph delta x and glyph width itself is set as side effect */ +/** + * side effect: updates u8g2->font_decode and u8g2->glyph_x_offset. + * actually u8g2_GetGlyphWidth returns the glyph delta x and glyph width itself is set as side effect + */ int8_t u8g2_GetGlyphWidth(u8g2_font_t* u8g2, uint16_t requested_encoding) { const uint8_t* glyph_data = u8g2_font_get_glyph_data(u8g2, requested_encoding); if (glyph_data == NULL) return 0; @@ -435,16 +440,49 @@ int8_t u8g2_GetGlyphWidth(u8g2_font_t* u8g2, uint16_t requested_encoding) { return u8g2_font_decode_get_signed_bits(&(u8g2->font_decode), u8g2->font_info.bits_per_delta_x); } +/** + * Defines, whether the glyph and string drawing functions will write the background color (mode 0/solid, is_transparent + * = 0) or not (mode 1/transparent, is_transparent = 1). + * Default mode is 0 (background color of the characters is overwritten). + */ void u8g2_SetFontMode(u8g2_font_t* u8g2, uint8_t is_transparent) { u8g2->font_decode.is_transparent = is_transparent; // new font procedures } +/** + * The arguments defines the drawing direction of all strings or glyphs. + * + * Argument String Rotation Description + * 0 0 degree Left to right + * 1 90 degree Top to down + * 2 180 degree Right to left + * 3 270 degree Down to top + */ void u8g2_SetFontDirection(u8g2_font_t* u8g2, uint8_t dir) { u8g2->font_decode.dir = dir; } +/** + * Draw a single character. + * + * The character is placed at the specified pixel posion x and y. + * U8g2 supports the lower 16 bit of the unicode character range (plane 0/Basic Multilingual Plane): + * The encoding can be any value from 0 to 65535. + * The glyph can be drawn only, if the encoding exists in the active font. + */ int16_t u8g2_DrawGlyph(u8g2_font_t* u8g2, int16_t x, int16_t y, uint16_t encoding) { return u8g2_font_draw_glyph(u8g2, x, y, encoding); } +/** + * Draw a string. + * + * The first character is placed at position x and y. + * Use setFont to assign a font before drawing a string on the display. + * To draw a character with encoding 127 to 255, use the escape sequence "\xab" (hex value ab) or "\xyz" + * (octal value xyz). + * This function can not draw any glyph with encoding greater or equal to 256. Use drawUTF8 or drawGlyph to access + * glyphs with encoding greater or equal to 256. + * The X2 variant will double the size of the sting but will ignore the font direction setting. + */ int16_t u8g2_DrawStr(u8g2_font_t* u8g2, int16_t x, int16_t y, const char* s) { int16_t sum, delta; sum = 0; @@ -471,6 +509,9 @@ int16_t u8g2_DrawStr(u8g2_font_t* u8g2, int16_t x, int16_t y, const char* s) { return sum; } +/** + * Define a u8g2 font for the glyph and string drawing functions. + */ void u8g2_SetFont(u8g2_font_t* u8g2, const uint8_t* font) { if (u8g2->font != font) { u8g2->font = font; @@ -480,6 +521,12 @@ void u8g2_SetFont(u8g2_font_t* u8g2, const uint8_t* font) { } } +/** + * Set foreground color for font drawing + */ void u8g2_SetForegroundColor(u8g2_font_t* u8g2, uint16_t fg) { u8g2->font_decode.fg_color = fg; } +/** + * Set background color for font drawing + */ void u8g2_SetBackgroundColor(u8g2_font_t* u8g2, uint16_t bg) { u8g2->font_decode.bg_color = bg; }