You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
856 lines
26 KiB
856 lines
26 KiB
/* |
|
* EEZ Generic Firmware |
|
* Copyright (C) 2015-present, Envox d.o.o. |
|
* |
|
* This program 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. |
|
|
|
* This program 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 <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
#include <string.h> |
|
#define _USE_MATH_DEFINES |
|
#include <math.h> |
|
|
|
#include <eez/core/util.h> |
|
|
|
#include <eez/gui/gui.h> |
|
|
|
namespace eez { |
|
namespace gui { |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
bool styleIsHorzAlignLeft(const Style *style) { |
|
return (style->flags & STYLE_FLAGS_HORZ_ALIGN_MASK) == STYLE_FLAGS_HORZ_ALIGN_LEFT; |
|
} |
|
|
|
bool styleIsHorzAlignRight(const Style *style) { |
|
return (style->flags & STYLE_FLAGS_HORZ_ALIGN_MASK) == STYLE_FLAGS_HORZ_ALIGN_RIGHT; |
|
} |
|
|
|
bool styleIsVertAlignTop(const Style *style) { |
|
return (style->flags & STYLE_FLAGS_VERT_ALIGN_MASK) == STYLE_FLAGS_VERT_ALIGN_TOP; |
|
} |
|
|
|
bool styleIsVertAlignBottom(const Style *style) { |
|
return (style->flags & STYLE_FLAGS_VERT_ALIGN_MASK) == STYLE_FLAGS_VERT_ALIGN_BOTTOM; |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void drawBorderAndBackground(int &x1, int &y1, int &x2, int &y2, const Style *style, uint16_t color, bool ignoreLuminocity) { |
|
const WidgetCursor& widgetCursor = g_widgetCursor; |
|
|
|
bool isTransparent = true; |
|
bool hasBorder = false; |
|
bool hasBorderRadius = false; |
|
|
|
if (style) { |
|
hasBorder = style->borderSizeTop > 0 || style->borderSizeRight > 0 || style->borderSizeBottom > 0 || style->borderSizeLeft > 0; |
|
|
|
hasBorderRadius = |
|
style->borderRadiusTLX > 0 || style->borderRadiusTLY > 0 || style->borderRadiusTRX > 0 || style->borderRadiusTRY > 0 || |
|
style->borderRadiusBRX > 0 || style->borderRadiusBRY > 0 || style->borderRadiusBLX > 0 || style->borderRadiusBLY > 0; |
|
|
|
if (color != TRANSPARENT_COLOR_INDEX && style && style->opacity == 255 && !(hasBorder && hasBorderRadius)) { |
|
// non-transparent color |
|
isTransparent = false; |
|
} else if (style->backgroundImage) { |
|
auto bitmap = getBitmap(style->backgroundImage); |
|
if (bitmap->bpp != 32) { |
|
// non-transparent bitmap |
|
isTransparent = false; |
|
} |
|
} |
|
} |
|
|
|
if (isTransparent && !widgetCursor.refreshed) { |
|
size_t startStackPointer; |
|
if (widgetCursor.backgroundStyleStackPointer > 0) { |
|
for (startStackPointer = widgetCursor.backgroundStyleStackPointer - 1; startStackPointer > 0; startStackPointer--) { |
|
auto &backgroundStyle = widgetCursor.backgroundStyleStack[startStackPointer]; |
|
|
|
auto color = backgroundStyle.active ? backgroundStyle.style->activeBackgroundColor : backgroundStyle.style->backgroundColor; |
|
if (color != TRANSPARENT_COLOR_INDEX && backgroundStyle.style->opacity == 255) { |
|
// non-transparent color |
|
break; |
|
} else if (backgroundStyle.style->backgroundImage) { |
|
auto bitmap = getBitmap(backgroundStyle.style->backgroundImage); |
|
if (bitmap) { |
|
if (bitmap->bpp != 32) { |
|
// non-transparent bitmap |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} else { |
|
startStackPointer = 0; |
|
} |
|
|
|
for (size_t i = startStackPointer; i < widgetCursor.backgroundStyleStackPointer; i++) { |
|
auto &backgroundStyle = widgetCursor.backgroundStyleStack[i]; |
|
|
|
auto color = backgroundStyle.active ? backgroundStyle.style->activeBackgroundColor : backgroundStyle.style->backgroundColor; |
|
if (color != TRANSPARENT_COLOR_INDEX) { |
|
display::setColor(color, ignoreLuminocity); |
|
auto savedOpacity = display::setOpacity(backgroundStyle.style->opacity); |
|
display::fillRect(x1, y1, x2, y2); |
|
display::setOpacity(savedOpacity); |
|
} |
|
|
|
if (backgroundStyle.style->backgroundImage) { |
|
auto bitmap = getBitmap(backgroundStyle.style->backgroundImage); |
|
if (bitmap) { |
|
int x = backgroundStyle.x; |
|
int y = backgroundStyle.y; |
|
int w = MIN(x2 - x1 + 1, x + bitmap->w - x1); |
|
int h = MIN(y2 - y1 + 1, y + bitmap->h - y1); |
|
|
|
if (w >= 0 && h > 0) { |
|
auto bytesPerPixel = bitmap->bpp / 8; |
|
|
|
uint32_t offset = 0; |
|
|
|
if (x < x1) { |
|
offset += (x1 - x) * bytesPerPixel; |
|
} |
|
|
|
if (y < y1) { |
|
offset += (y1 - y) * bitmap->w * bytesPerPixel; |
|
} |
|
|
|
Image image; |
|
|
|
image.width = w; |
|
image.height = h; |
|
image.bpp = bitmap->bpp; |
|
image.lineOffset = bitmap->w - w; |
|
image.pixels = (uint8_t *)bitmap->pixels + offset; |
|
|
|
display::drawBitmap(&image, x1, y1); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (!style) { |
|
return; |
|
} |
|
|
|
if (hasBorder) { |
|
if (style->borderColor != TRANSPARENT_COLOR_INDEX) { |
|
display::setColor(style->borderColor, ignoreLuminocity); |
|
} |
|
|
|
if (hasBorderRadius) { |
|
if (color != TRANSPARENT_COLOR_INDEX) { |
|
display::setBackColor(color, ignoreLuminocity); |
|
} |
|
|
|
int lineWidth = style->borderSizeTop; |
|
if (lineWidth < style->borderSizeRight) { |
|
lineWidth = style->borderSizeRight; |
|
} |
|
if (lineWidth < style->borderSizeBottom) { |
|
lineWidth = style->borderSizeBottom; |
|
} |
|
if (lineWidth < style->borderSizeLeft) { |
|
lineWidth = style->borderSizeLeft; |
|
} |
|
|
|
auto savedOpacity = display::setOpacity(style->opacity); |
|
display::AggDrawing aggDrawing; |
|
display::aggInit(aggDrawing); |
|
display::fillRoundedRect( |
|
aggDrawing, |
|
x1, y1, x2, y2, |
|
lineWidth, |
|
style->borderRadiusTLX, style->borderRadiusTLY, style->borderRadiusTRX, style->borderRadiusTRY, |
|
style->borderRadiusBRX, style->borderRadiusBRY, style->borderRadiusBLX, style->borderRadiusBLY, |
|
style->borderColor != TRANSPARENT_COLOR_INDEX, color != TRANSPARENT_COLOR_INDEX |
|
); |
|
display::setOpacity(savedOpacity); |
|
|
|
lineWidth++; |
|
|
|
x1 += lineWidth; |
|
y1 += lineWidth; |
|
x2 -= lineWidth; |
|
y2 -= lineWidth; |
|
|
|
return; |
|
} |
|
|
|
if (style->borderColor != TRANSPARENT_COLOR_INDEX) { |
|
if (style->borderSizeLeft > 0) { |
|
display::fillRect(x1, y1, x1 + style->borderSizeLeft - 1, y2); |
|
} |
|
if (style->borderSizeTop > 0) { |
|
display::fillRect(x1, y1, x2, y1 + style->borderSizeTop - 1); |
|
} |
|
if (style->borderSizeRight > 0) { |
|
display::fillRect(x2 - (style->borderSizeRight - 1), y1, x2, y2); |
|
} |
|
if (style->borderSizeBottom > 0) { |
|
display::fillRect(x1, y2 - (style->borderSizeBottom - 1), x2, y2); |
|
} |
|
} |
|
|
|
x1 += style->borderSizeLeft; |
|
y1 += style->borderSizeTop; |
|
x2 -= style->borderSizeRight; |
|
y2 -= style->borderSizeBottom; |
|
} else { |
|
if (hasBorderRadius) { |
|
if (color != TRANSPARENT_COLOR_INDEX) { |
|
display::setBackColor(color, ignoreLuminocity); |
|
auto savedOpacity = display::setOpacity(style->opacity); |
|
display::AggDrawing aggDrawing; |
|
display::aggInit(aggDrawing); |
|
display::fillRoundedRect( |
|
aggDrawing, |
|
x1, y1, x2, y2, |
|
0, |
|
style->borderRadiusTLX, style->borderRadiusTLY, style->borderRadiusTRX, style->borderRadiusTRY, |
|
style->borderRadiusBRX, style->borderRadiusBRY, style->borderRadiusBLX, style->borderRadiusBLY, |
|
style->borderColor != TRANSPARENT_COLOR_INDEX, color != TRANSPARENT_COLOR_INDEX |
|
); |
|
display::setOpacity(savedOpacity); |
|
} |
|
return; |
|
} |
|
} |
|
|
|
if (color != TRANSPARENT_COLOR_INDEX) { |
|
display::setColor(color, ignoreLuminocity); |
|
auto savedOpacity = display::setOpacity(style->opacity); |
|
display::fillRect(x1, y1, x2, y2); |
|
display::setOpacity(savedOpacity); |
|
} |
|
|
|
if (style->backgroundImage) { |
|
auto bitmap = getBitmap(style->backgroundImage); |
|
if (bitmap) { |
|
Image image; |
|
|
|
int w = MIN(x2 - x1 + 1, bitmap->w); |
|
int h = MIN(y2 - y1 + 1, bitmap->h); |
|
|
|
image.width = w; |
|
image.height = h; |
|
image.bpp = bitmap->bpp; |
|
image.lineOffset = bitmap->w - w; |
|
image.pixels = (uint8_t *)bitmap->pixels; |
|
|
|
display::drawBitmap(&image, x1, y1); |
|
} |
|
} |
|
} |
|
|
|
void drawText( |
|
const char *text, int textLength, |
|
int x, int y, int w, int h, |
|
const Style *style, |
|
bool active, bool blink, bool ignoreLuminocity, |
|
uint16_t *overrideColor, uint16_t *overrideBackgroundColor, |
|
uint16_t *overrideActiveColor, uint16_t *overrideActiveBackgroundColor, |
|
bool useSmallerFontIfDoesNotFit, int cursorPosition, int xScroll, |
|
bool boolSkipBackground |
|
) { |
|
int x1 = x; |
|
int y1 = y; |
|
int x2 = x + w - 1; |
|
int y2 = y + h - 1; |
|
|
|
if (!boolSkipBackground) { |
|
uint16_t backgroundColor; |
|
if (active || blink) { |
|
if (overrideActiveBackgroundColor) { |
|
backgroundColor = *overrideActiveBackgroundColor; |
|
} else { |
|
backgroundColor = style->activeBackgroundColor; |
|
} |
|
} else { |
|
if (overrideBackgroundColor) { |
|
backgroundColor = *overrideBackgroundColor; |
|
} else { |
|
backgroundColor = style->backgroundColor; |
|
} |
|
} |
|
drawBorderAndBackground(x1, y1, x2, y2, style, backgroundColor, ignoreLuminocity); |
|
} |
|
|
|
font::Font font = styleGetFont(style); |
|
if (!font) { |
|
return; |
|
} |
|
|
|
int width = display::measureStr(text, textLength, font, 0); |
|
while (useSmallerFontIfDoesNotFit && width > x2 - x1 + 1 && g_hooks.styleGetSmallerFont(font)) { |
|
width = display::measureStr(text, textLength, font, 0); |
|
} |
|
int height = font.getHeight(); |
|
|
|
int x_offset; |
|
if (styleIsHorzAlignLeft(style)) { |
|
x_offset = x1 + style->paddingLeft; |
|
} else if (styleIsHorzAlignRight(style)) { |
|
x_offset = x2 - style->paddingRight - width; |
|
} else { |
|
x_offset = x1 + ((x2 - x1 + 1) - width) / 2; |
|
if (x_offset < x1) { |
|
x_offset = x1; |
|
} |
|
} |
|
|
|
int y_offset; |
|
if (styleIsVertAlignTop(style)) { |
|
y_offset = y1 + style->paddingTop; |
|
} else if (styleIsVertAlignBottom(style)) { |
|
y_offset = y2 - style->paddingBottom - height; |
|
} else { |
|
y_offset = y1 + ((y2 - y1 + 1) - height) / 2; |
|
} |
|
if (y_offset < 0) { |
|
y_offset = y1; |
|
} |
|
|
|
// draw text |
|
if (active || blink) { |
|
if (overrideActiveColor) { |
|
display::setColor(*overrideActiveColor, ignoreLuminocity); |
|
} else { |
|
display::setColor(style->activeColor, ignoreLuminocity); |
|
} |
|
} else { |
|
if (overrideColor) { |
|
display::setColor(*overrideColor, ignoreLuminocity); |
|
} else { |
|
display::setColor(style->color, ignoreLuminocity); |
|
} |
|
} |
|
display::drawStr(text, textLength, x_offset - xScroll, y_offset, x1, y1, x2, y2, font, cursorPosition); |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
int getCharIndexAtPosition(int xPos, const char *text, int textLength, int x, int y, int w, int h, const Style *style) { |
|
int x1 = x; |
|
int y1 = y; |
|
int x2 = x + w - 1; |
|
int y2 = y + h - 1; |
|
|
|
x1 += style->borderSizeLeft; |
|
y1 += style->borderSizeTop; |
|
x2 -= style->borderSizeRight; |
|
y2 -= style->borderSizeBottom; |
|
|
|
font::Font font = styleGetFont(style); |
|
|
|
int width = display::measureStr(text, textLength, font, 0); |
|
int height = font.getHeight(); |
|
|
|
int x_offset; |
|
if (styleIsHorzAlignLeft(style)) { |
|
x_offset = x1 + style->paddingLeft; |
|
} else if (styleIsHorzAlignRight(style)) { |
|
x_offset = x2 - style->paddingRight - width; |
|
} else { |
|
x_offset = x1 + ((x2 - x1 + 1) - width) / 2; |
|
if (x_offset < x1) { |
|
x_offset = x1; |
|
} |
|
} |
|
|
|
int y_offset; |
|
if (styleIsVertAlignTop(style)) { |
|
y_offset = y1 + style->paddingTop; |
|
} else if (styleIsVertAlignBottom(style)) { |
|
y_offset = y2 - style->paddingBottom - height; |
|
} else { |
|
y_offset = y1 + ((y2 - y1 + 1) - height) / 2; |
|
} |
|
if (y_offset < 0) { |
|
y_offset = y1; |
|
} |
|
|
|
return display::getCharIndexAtPosition(xPos, text, textLength, x_offset, y_offset, x1, y1, x2, y2, font); |
|
} |
|
|
|
int getCursorXPosition(int cursorPosition, const char *text, int textLength, int x, int y, int w, int h, const Style *style) { |
|
int x1 = x; |
|
int y1 = y; |
|
int x2 = x + w - 1; |
|
int y2 = y + h - 1; |
|
|
|
x1 += style->borderSizeLeft; |
|
y1 += style->borderSizeTop; |
|
x2 -= style->borderSizeRight; |
|
y2 -= style->borderSizeBottom; |
|
|
|
font::Font font = styleGetFont(style); |
|
|
|
int width = display::measureStr(text, textLength, font, 0); |
|
int height = font.getHeight(); |
|
|
|
int x_offset; |
|
if (styleIsHorzAlignLeft(style)) { |
|
x_offset = x1 + style->paddingLeft; |
|
} else if (styleIsHorzAlignRight(style)) { |
|
x_offset = x2 - style->paddingRight - width; |
|
} else { |
|
x_offset = x1 + ((x2 - x1 + 1) - width) / 2; |
|
if (x_offset < x1) { |
|
x_offset = x1; |
|
} |
|
} |
|
|
|
int y_offset; |
|
if (styleIsVertAlignTop(style)) { |
|
y_offset = y1 + style->paddingTop; |
|
} else if (styleIsVertAlignBottom(style)) { |
|
y_offset = y2 - style->paddingBottom - height; |
|
} else { |
|
y_offset = y1 + ((y2 - y1 + 1) - height) / 2; |
|
} |
|
if (y_offset < 0) { |
|
y_offset = y1; |
|
} |
|
|
|
return display::getCursorXPosition(cursorPosition, text, textLength, x_offset, y_offset, x1, y1, x2, y2, font); |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
static const unsigned int CONF_MULTILINE_TEXT_MAX_LINE_LENGTH = 1000; |
|
|
|
enum MultilineTextRenderStep { |
|
MEASURE, |
|
RENDER |
|
}; |
|
|
|
struct MultilineTextRender { |
|
const char *text; |
|
int x1; |
|
int y1; |
|
int x2; |
|
int y2; |
|
const Style *style; |
|
bool active; |
|
int firstLineIndent; |
|
int hangingIndent; |
|
|
|
font::Font font; |
|
int spaceWidth; |
|
|
|
int lineHeight; |
|
int textHeight; |
|
|
|
char line[CONF_MULTILINE_TEXT_MAX_LINE_LENGTH + 1]; |
|
int lineIndent; |
|
int lineWidth; |
|
|
|
void appendToLine(const char *str, size_t n) { |
|
size_t j = strlen(line); |
|
for (size_t i = 0; i < n && j < CONF_MULTILINE_TEXT_MAX_LINE_LENGTH; i++, j++) { |
|
line[j] = str[i]; |
|
} |
|
line[j] = 0; |
|
} |
|
|
|
void flushLine(int y, MultilineTextRenderStep step) { |
|
if (line[0] && lineWidth) { |
|
if (step == RENDER) { |
|
int x; |
|
|
|
if (styleIsHorzAlignLeft(style)) { |
|
x = x1; |
|
} else if (styleIsHorzAlignRight(style)) { |
|
x = x2 + 1 - lineWidth; |
|
} else { |
|
x = x1 + int((x2 - x1 + 1 - lineWidth) / 2); |
|
} |
|
|
|
display::drawStr(line, -1, x + lineIndent, y, x, y, x + lineWidth - 1, y + font.getHeight() - 1, font, -1); |
|
} else { |
|
textHeight = MAX(textHeight, y + lineHeight - y1); |
|
} |
|
|
|
line[0] = 0; |
|
lineWidth = lineIndent = hangingIndent; |
|
} |
|
} |
|
|
|
int executeStep(MultilineTextRenderStep step) { |
|
textHeight = 0; |
|
|
|
int y = y1; |
|
|
|
line[0] = 0; |
|
lineWidth = lineIndent = firstLineIndent; |
|
|
|
int i = 0; |
|
while (true) { |
|
int j = i; |
|
while (text[i] != 0 && text[i] != ' ' && text[i] != '\n') |
|
++i; |
|
|
|
int width = display::measureStr(text + j, i - j, font); |
|
|
|
while (lineWidth + (line[0] ? spaceWidth : 0) + width > x2 - x1 + 1) { |
|
if (!line[0]) { |
|
i--; |
|
width = display::measureStr(text + j, i - j, font); |
|
continue; |
|
} |
|
|
|
flushLine(y, step); |
|
|
|
y += lineHeight; |
|
if (y + lineHeight - 1 > y2) { |
|
break; |
|
} |
|
} |
|
|
|
if (y + lineHeight - 1 > y2) { |
|
break; |
|
} |
|
|
|
if (line[0]) { |
|
appendToLine(" ", 1); |
|
lineWidth += spaceWidth; |
|
} |
|
appendToLine(text + j, i - j); |
|
lineWidth += width; |
|
|
|
while (text[i] == ' ') { |
|
++i; |
|
} |
|
|
|
if (text[i] == 0 || text[i] == '\n') { |
|
flushLine(y, step); |
|
|
|
y += lineHeight; |
|
|
|
if (text[i] == 0) { |
|
break; |
|
} |
|
|
|
++i; |
|
|
|
int extraHeightBetweenParagraphs = (int)(0.2 * lineHeight); |
|
y += extraHeightBetweenParagraphs; |
|
|
|
if (y + lineHeight - 1 > y2) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
flushLine(y, step); |
|
|
|
return textHeight + font.getHeight() - lineHeight; |
|
} |
|
|
|
int measure() { |
|
x1 += style->borderSizeLeft; |
|
y1 += style->borderSizeTop; |
|
x2 -= style->borderSizeRight; |
|
y2 -= style->borderSizeBottom; |
|
|
|
font = styleGetFont(style); |
|
|
|
lineHeight = (int)(0.9 * font.getHeight()); |
|
if (lineHeight <= 0) { |
|
return 0; |
|
} |
|
|
|
auto spaceGlyph = font.getGlyph(' '); |
|
spaceWidth = spaceGlyph->dx; |
|
|
|
x1 += style->paddingLeft; |
|
x2 -= style->paddingRight; |
|
y1 += style->paddingTop; |
|
y2 -= style->paddingBottom; |
|
|
|
return executeStep(MEASURE); |
|
} |
|
|
|
void render() { |
|
drawBorderAndBackground(x1, y1, x2, y2, style, active ? style->activeBackgroundColor : style->backgroundColor); |
|
|
|
// |
|
font = styleGetFont(style); |
|
|
|
lineHeight = (int)(0.9 * font.getHeight()); |
|
if (lineHeight <= 0) { |
|
return; |
|
} |
|
|
|
auto spaceGlyph = font.getGlyph(' '); |
|
spaceWidth = spaceGlyph->dx; |
|
|
|
// draw text |
|
display::setColor(active ? style->activeColor : style->color); |
|
|
|
x1 += style->paddingLeft; |
|
x2 -= style->paddingRight; |
|
y1 += style->paddingTop; |
|
y2 -= style->paddingBottom; |
|
|
|
int textHeight = executeStep(MEASURE); |
|
|
|
if (styleIsVertAlignTop(style)) { |
|
} else if (styleIsVertAlignBottom(style)) { |
|
y1 = y2 + 1 - textHeight; |
|
} else { |
|
y1 += (int)((y2 - y1 + 1 - textHeight) / 2); |
|
} |
|
y2 = y1 + textHeight - 1; |
|
|
|
executeStep(RENDER); |
|
} |
|
}; |
|
|
|
void drawMultilineText(const char *text, int x, int y, int w, int h, const Style *style, bool active, bool blinking, int firstLineIndent, int hangingIndent) { |
|
MultilineTextRender multilineTextRender; |
|
|
|
multilineTextRender.text = text; |
|
multilineTextRender.x1 = x; |
|
multilineTextRender.y1 = y; |
|
multilineTextRender.x2 = x + w - 1; |
|
multilineTextRender.y2 = y + h - 1; |
|
multilineTextRender.style = style; |
|
multilineTextRender.active = active || blinking; |
|
multilineTextRender.firstLineIndent = firstLineIndent; |
|
multilineTextRender.hangingIndent = hangingIndent; |
|
|
|
multilineTextRender.render(); |
|
} |
|
|
|
int measureMultilineText(const char *text, int x, int y, int w, int h, const Style *style, int firstLineIndent, int hangingIndent) { |
|
MultilineTextRender multilineTextRender; |
|
|
|
multilineTextRender.text = text; |
|
multilineTextRender.x1 = x; |
|
multilineTextRender.y1 = y; |
|
multilineTextRender.x2 = x + w - 1; |
|
multilineTextRender.y2 = y + h - 1; |
|
multilineTextRender.style = style; |
|
multilineTextRender.active = false; |
|
multilineTextRender.firstLineIndent = firstLineIndent; |
|
multilineTextRender.hangingIndent = hangingIndent; |
|
|
|
return multilineTextRender.measure(); |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void drawBitmap(Image *image, int x, int y, int w, int h, const Style *style, bool active) { |
|
int x1 = x; |
|
int y1 = y; |
|
int x2 = x + w - 1; |
|
int y2 = y + h - 1; |
|
|
|
int width = image->width; |
|
int height = image->height; |
|
|
|
int x_offset; |
|
if (styleIsHorzAlignLeft(style)) |
|
x_offset = x1 + style->paddingLeft; |
|
else if (styleIsHorzAlignRight(style)) |
|
x_offset = x2 - style->paddingRight - width; |
|
else |
|
x_offset = x1 + ((x2 - x1) - width) / 2; |
|
if (x_offset < 0) |
|
x_offset = x1; |
|
|
|
int y_offset; |
|
if (styleIsVertAlignTop(style)) |
|
y_offset = y1 + style->paddingTop; |
|
else if (styleIsVertAlignBottom(style)) |
|
y_offset = y2 - style->paddingBottom - height; |
|
else |
|
y_offset = y1 + ((y2 - y1) - height) / 2; |
|
if (y_offset < 0) |
|
y_offset = y1; |
|
|
|
|
|
// draw bitmap |
|
uint8_t savedOpacity = display::getOpacity(); |
|
|
|
if (active) { |
|
display::setBackColor(style->activeBackgroundColor); |
|
display::setColor(style->activeColor); |
|
display::setOpacity(style->opacity); |
|
} else { |
|
display::setBackColor(style->backgroundColor); |
|
display::setColor(style->color); |
|
display::setOpacity(style->opacity); |
|
} |
|
|
|
display::drawBitmap(image, x_offset, y_offset); |
|
|
|
display::setOpacity(savedOpacity); |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void drawRectangle(int x, int y, int w, int h, const Style *style, bool active, bool ignoreLuminocity, bool invertColors) { |
|
if (w > 0 && h > 0) { |
|
int x1 = x; |
|
int y1 = y; |
|
int x2 = x + w - 1; |
|
int y2 = y + h - 1; |
|
|
|
uint16_t color; |
|
if (style) { |
|
if (invertColors) { |
|
color = active ? style->activeBackgroundColor : style->backgroundColor; |
|
} else { |
|
color = active ? style->activeColor : style->color; |
|
} |
|
} else { |
|
color = TRANSPARENT_COLOR_INDEX; |
|
} |
|
|
|
drawBorderAndBackground(x1, y1, x2, y2, style, color, ignoreLuminocity); |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
enum ShadowGlpyh { |
|
SHADOW_GLYPH_TOP_LEFT, |
|
SHADOW_GLYPH_TOP, |
|
SHADOW_GLYPH_TOP_RIGHT, |
|
SHADOW_GLYPH_LEFT, |
|
SHADOW_GLYPH_RIGHT, |
|
SHADOW_GLYPH_BOTTOM_LEFT, |
|
SHADOW_GLYPH_BOTTOM, |
|
SHADOW_GLYPH_BOTTOM_RIGHT, |
|
}; |
|
|
|
static const int T = 4; |
|
static const int R = 7; |
|
static const int B = 10; |
|
static const int L = 7; |
|
|
|
static const int W = 20; |
|
static const int H = 20; |
|
|
|
void drawShadowGlyph(ShadowGlpyh shadowGlyph, int x, int y, int xClip = -1, int yClip = -1) { |
|
font::Font font(getFontData(FONT_ID_SHADOW)); |
|
|
|
if (xClip == -1) { |
|
xClip = x + W - 1; |
|
} |
|
if (yClip == -1) { |
|
yClip = y + H - 1; |
|
} |
|
char glyph = 32 + shadowGlyph; |
|
display::drawStr(&glyph, 1, x, y, x, y, xClip, yClip, font, -1); |
|
} |
|
|
|
void drawShadow(int x1, int y1, int x2, int y2) { |
|
display::setColor(64, 64, 64); |
|
|
|
int left = x1 - L; |
|
int top = y1 - T; |
|
|
|
int right = x2 + R - (W - 1); |
|
int bottom = y2 + B - (H - 1); |
|
|
|
drawShadowGlyph(SHADOW_GLYPH_TOP_LEFT, left, top); |
|
for (int x = left + W; x < right; x += W) { |
|
drawShadowGlyph(SHADOW_GLYPH_TOP, x, top, right - 1); |
|
} |
|
drawShadowGlyph(SHADOW_GLYPH_TOP_RIGHT, right, top); |
|
for (int y = top + H; y < bottom; y += H) { |
|
drawShadowGlyph(SHADOW_GLYPH_LEFT, left, y, -1, bottom - 1); |
|
} |
|
for (int y = top + H; y < bottom; y += H) { |
|
drawShadowGlyph(SHADOW_GLYPH_RIGHT, right, y, -1, bottom - 1); |
|
} |
|
drawShadowGlyph(SHADOW_GLYPH_BOTTOM_LEFT, left, bottom); |
|
for (int x = left + W; x < right; x += W) { |
|
drawShadowGlyph(SHADOW_GLYPH_BOTTOM, x, bottom, right - 1); |
|
} |
|
drawShadowGlyph(SHADOW_GLYPH_BOTTOM_RIGHT, right, bottom); |
|
} |
|
|
|
void expandRectWithShadow(int &x1, int &y1, int &x2, int &y2) { |
|
x1 -= L; |
|
y1 -= T; |
|
x2 += R; |
|
y2 += B; |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
void drawLine(int x1, int y1, int x2, int y2) { |
|
display::startPixelsDraw(); |
|
|
|
int dx = x2 - x1; |
|
int dy = y2 - y1; |
|
|
|
int length; |
|
if (abs(dx) > abs(dy)) { |
|
length = abs(dx); |
|
} else { |
|
length = abs(dy); |
|
} |
|
|
|
float xinc = (float)dx / length; |
|
float yinc = (float)dy / length; |
|
float x = (float)x1; |
|
float y = (float)y1; |
|
for (int i = 0; i < length; i++) { |
|
display::drawPixel((int)roundf(x), (int)roundf(y)); |
|
x += xinc; |
|
y += yinc; |
|
} |
|
|
|
display::endPixelsDraw(); |
|
} |
|
|
|
// http://members.chello.at/~easyfilter/bresenham.html |
|
void drawAntialiasedLine(int x0, int y0, int x1, int y1) { |
|
display::startPixelsDraw(); |
|
|
|
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; |
|
int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1; |
|
int err = dx - dy, e2, x2; /* error value e_xy */ |
|
int ed = dx + dy == 0 ? 1 : (int)sqrt((float)dx*dx + (float)dy*dy); |
|
|
|
for (; ; ) { /* pixel loop */ |
|
display::drawPixel(x0, y0, 255 - 255 * abs(err - dx + dy) / ed); |
|
e2 = err; x2 = x0; |
|
if (2 * e2 >= -dx) { /* x step */ |
|
if (x0 == x1) break; |
|
if (e2 + dy < ed) display::drawPixel(x0, y0 + sy, 255 - 255 * (e2 + dy) / ed); |
|
err -= dy; x0 += sx; |
|
} |
|
if (2 * e2 <= dy) { /* y step */ |
|
if (y0 == y1) break; |
|
if (dx - e2 < ed) display::drawPixel(x2 + sx, y0, 255 - 255 * (dx - e2) / ed); |
|
err += dx; y0 += sy; |
|
} |
|
} |
|
|
|
display::endPixelsDraw(); |
|
} |
|
|
|
} // namespace gui |
|
} // namespace eez
|
|
|