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.
987 lines
26 KiB
987 lines
26 KiB
/* |
|
* EEZ Modular 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 <stdio.h> |
|
#include <string.h> |
|
#include <memory.h> |
|
|
|
#include <eez/core/utf8.h> |
|
|
|
#include <eez/conf.h> |
|
#include <eez/core/util.h> |
|
|
|
#if OPTION_KEYBOARD |
|
#include <eez/core/keyboard.h> |
|
#endif |
|
|
|
#if OPTION_MOUSE |
|
#include <eez/core/mouse.h> |
|
#endif |
|
|
|
#include <eez/gui/gui.h> |
|
#include <eez/gui/thread.h> |
|
|
|
#include <eez/gui/display-private.h> |
|
|
|
#define CONF_BACKDROP_OPACITY 128 |
|
|
|
using namespace eez::gui; |
|
|
|
namespace eez { |
|
namespace gui { |
|
namespace display { |
|
|
|
DisplayState g_displayState; |
|
|
|
VideoBuffer g_renderBuffer1; |
|
VideoBuffer g_renderBuffer2; |
|
|
|
VideoBuffer g_animationBuffer1; |
|
VideoBuffer g_animationBuffer2; |
|
|
|
VideoBuffer g_syncedBuffer; |
|
VideoBuffer g_renderBuffer; |
|
VideoBuffer g_animationBuffer; |
|
|
|
bool g_takeScreenshot; |
|
|
|
uint16_t g_fc, g_bc; |
|
uint8_t g_opacity = 255; |
|
|
|
gui::font::Font g_font; |
|
|
|
static uint8_t g_colorCache[256][4]; |
|
|
|
#define FLOAT_TO_COLOR_COMPONENT(F) ((F) < 0 ? 0 : (F) > 255 ? 255 : (uint8_t)(F)) |
|
#define RGB_TO_HIGH_BYTE(R, G, B) (((R) & 248) | (G) >> 5) |
|
#define RGB_TO_LOW_BYTE(R, G, B) (((G) & 28) << 3 | (B) >> 3) |
|
|
|
static const uint16_t *g_themeColors; |
|
static uint32_t g_themeColorsCount; |
|
static const uint16_t *g_colors; |
|
|
|
bool g_dirty; |
|
|
|
RenderBuffer g_renderBuffers[NUM_BUFFERS]; |
|
static VideoBuffer g_mainBufferPointer; |
|
static int g_numBuffersToDraw; |
|
|
|
bool g_screenshotAllocated; |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void init() { |
|
onLuminocityChanged(); |
|
onThemeChanged(); |
|
|
|
g_renderBuffer1 = (VideoBuffer)VRAM_BUFFER1_START_ADDRESS; |
|
g_renderBuffer2 = (VideoBuffer)VRAM_BUFFER2_START_ADDRESS; |
|
|
|
g_animationBuffer1 = (VideoBuffer)VRAM_ANIMATION_BUFFER1_START_ADDRESS; |
|
g_animationBuffer2 = (VideoBuffer)VRAM_ANIMATION_BUFFER2_START_ADDRESS; |
|
|
|
g_renderBuffers[0].bufferPointer = (VideoBuffer)(VRAM_AUX_BUFFER1_START_ADDRESS); |
|
g_renderBuffers[1].bufferPointer = (VideoBuffer)(VRAM_AUX_BUFFER2_START_ADDRESS); |
|
g_renderBuffers[2].bufferPointer = (VideoBuffer)(VRAM_AUX_BUFFER3_START_ADDRESS); |
|
g_renderBuffers[3].bufferPointer = (VideoBuffer)(VRAM_AUX_BUFFER4_START_ADDRESS); |
|
g_renderBuffers[4].bufferPointer = (VideoBuffer)(VRAM_AUX_BUFFER5_START_ADDRESS); |
|
g_renderBuffers[5].bufferPointer = (VideoBuffer)(VRAM_AUX_BUFFER6_START_ADDRESS); |
|
|
|
initDriver(); |
|
|
|
// start with the black screen |
|
setColor(0, 0, 0); |
|
g_renderBuffer = g_renderBuffer1; |
|
fillRect(0, 0, getDisplayWidth() - 1, getDisplayHeight() - 1); |
|
g_renderBuffer = g_renderBuffer2; |
|
fillRect(0, 0, getDisplayWidth() - 1, getDisplayHeight() - 1); |
|
|
|
g_animationBuffer = g_animationBuffer1; |
|
|
|
g_syncedBuffer = g_renderBuffer1; |
|
syncBuffer(); |
|
} |
|
|
|
void turnOn() { |
|
if (g_displayState != ON && g_displayState != TURNING_ON) { |
|
g_hooks.turnOnDisplayStart(); |
|
} |
|
} |
|
|
|
bool isOn() { |
|
return g_displayState == ON || g_displayState == TURNING_ON; |
|
} |
|
|
|
void turnOff() { |
|
if (g_displayState != OFF && g_displayState != TURNING_OFF) { |
|
g_hooks.turnOffDisplayStart(); |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
#ifdef GUI_CALC_FPS |
|
bool g_calcFpsEnabled; |
|
#if defined(STYLE_ID_FPS_GRAPH) |
|
bool g_drawFpsGraphEnabled; |
|
#endif |
|
uint32_t g_fpsValues[NUM_FPS_VALUES]; |
|
uint32_t g_fpsAvg; |
|
static uint32_t g_fpsTotal; |
|
static uint32_t g_lastTimeFPS; |
|
|
|
void calcFPS() { |
|
// calculate last FPS value |
|
g_fpsTotal -= g_fpsValues[0]; |
|
|
|
for (size_t i = 1; i < NUM_FPS_VALUES; i++) { |
|
g_fpsValues[i - 1] = g_fpsValues[i]; |
|
} |
|
|
|
uint32_t time = millis(); |
|
auto diff = time - g_lastTimeFPS; |
|
|
|
auto fps = 1000 / diff; |
|
if (fps > 60) { |
|
fps = 60; |
|
} |
|
g_fpsValues[NUM_FPS_VALUES - 1] = fps; |
|
|
|
g_fpsTotal += g_fpsValues[NUM_FPS_VALUES - 1]; |
|
g_fpsAvg = g_fpsTotal / NUM_FPS_VALUES; |
|
} |
|
|
|
void drawFpsGraph(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; |
|
drawBorderAndBackground(x1, y1, x2, y2, style, style->backgroundColor); |
|
|
|
x1++; |
|
y1++; |
|
x2--; |
|
y2--; |
|
|
|
bool isRed = false; |
|
display::setColor(style->color); |
|
|
|
x = x1; |
|
for (size_t i = 0; i < NUM_FPS_VALUES && x <= x2; i++, x++) { |
|
int y = y2 - g_fpsValues[i] * (y2 - y1) / 60; |
|
if (y < y1) { |
|
y = y1; |
|
} |
|
|
|
if (g_fpsValues[i] < 40) { |
|
if (!isRed) { |
|
display::setColor16(COLOR_RED); |
|
isRed = true; |
|
} |
|
} else { |
|
if (isRed) { |
|
display::setColor(style->color); |
|
isRed = false; |
|
} |
|
} |
|
|
|
display::drawVLine(x, y, y2 - y); |
|
} |
|
} |
|
#endif |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
static void finishAnimation() { |
|
g_animationState.enabled = false; |
|
|
|
if (g_renderBuffer == g_renderBuffer1) { |
|
g_renderBuffer = g_renderBuffer2; |
|
bitBlt(g_renderBuffer1, 0, 0, getDisplayWidth() - 1, getDisplayHeight() - 1); |
|
} else { |
|
g_renderBuffer = g_renderBuffer1; |
|
bitBlt(g_renderBuffer2, 0, 0, getDisplayWidth() - 1, getDisplayHeight() - 1); |
|
} |
|
|
|
g_syncedBuffer = g_renderBuffer1; |
|
syncBuffer(); |
|
} |
|
|
|
void animate(Buffer startBuffer, void (*callback)(float t, VideoBuffer bufferOld, VideoBuffer bufferNew, VideoBuffer bufferDst), float duration) { |
|
if (g_animationState.enabled) { |
|
display::finishAnimation(); |
|
} |
|
|
|
g_animationState.enabled = true; |
|
g_animationState.startTime = 0; |
|
g_animationState.duration = duration != -1 ? duration : g_hooks.getDefaultAnimationDuration(); |
|
g_animationState.startBuffer = startBuffer; |
|
g_animationState.callback = callback; |
|
g_animationState.easingRects = remapOutQuad; |
|
g_animationState.easingOpacity = remapOutCubic; |
|
} |
|
|
|
static void animateStep() { |
|
uint32_t time = millis(); |
|
if (time == 0) { |
|
time = 1; |
|
} |
|
if (g_animationState.startTime == 0) { |
|
g_animationState.startTime = time; |
|
} |
|
float t = (time - g_animationState.startTime) / (1000.0f * g_animationState.duration); |
|
if (t < 1.0f) { |
|
if (g_syncedBuffer == g_animationBuffer1) { |
|
g_animationBuffer = g_animationBuffer2; |
|
} else { |
|
g_animationBuffer = g_animationBuffer1; |
|
} |
|
|
|
if (g_renderBuffer == g_renderBuffer1) { |
|
g_animationState.callback(t, g_renderBuffer2, g_renderBuffer1, g_animationBuffer); |
|
} else { |
|
g_animationState.callback(t, g_renderBuffer1, g_renderBuffer2, g_animationBuffer); |
|
} |
|
|
|
g_syncedBuffer = g_animationBuffer; |
|
syncBuffer(); |
|
} else { |
|
finishAnimation(); |
|
} |
|
} |
|
|
|
void update() { |
|
if (g_displayState == TURNING_ON) { |
|
g_hooks.turnOnDisplayTick(); |
|
} else if (g_displayState == TURNING_OFF) { |
|
g_hooks.turnOffDisplayTick(); |
|
} else if (g_displayState == OFF) { |
|
if (g_animationState.enabled) { |
|
display::finishAnimation(); |
|
} |
|
osDelay(16); |
|
sendMessageToGuiThread(GUI_QUEUE_MESSAGE_TYPE_DISPLAY_VSYNC, 0, 0); |
|
return; |
|
} |
|
|
|
#ifdef GUI_CALC_FPS |
|
g_lastTimeFPS = millis(); |
|
#endif |
|
|
|
display::beginRendering(); |
|
updateScreen(); |
|
display::endRendering(); |
|
|
|
#ifdef GUI_CALC_FPS |
|
if (g_calcFpsEnabled) { |
|
calcFPS(); |
|
} |
|
#endif |
|
|
|
if (!g_screenshotAllocated && g_animationState.enabled) { |
|
animateStep(); |
|
} else { |
|
g_syncedBuffer = g_renderBuffer; |
|
syncBuffer(); |
|
|
|
if (g_takeScreenshot) { |
|
copySyncedBufferToScreenshotBuffer(); |
|
|
|
g_takeScreenshot = false; |
|
g_screenshotAllocated = true; |
|
} |
|
} |
|
} |
|
|
|
const uint8_t *takeScreenshot() { |
|
#ifdef __EMSCRIPTEN__ |
|
copySyncedBufferToScreenshotBuffer(); |
|
#else |
|
while (g_screenshotAllocated) { |
|
} |
|
|
|
g_takeScreenshot = true; |
|
|
|
do { |
|
osDelay(0); |
|
} while (g_takeScreenshot); |
|
|
|
#endif |
|
|
|
return SCREENSHOOT_BUFFER_START_ADDRESS; |
|
} |
|
|
|
void releaseScreenshot() { |
|
g_screenshotAllocated = false; |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
VideoBuffer getBufferPointer() { |
|
return g_renderBuffer; |
|
} |
|
|
|
void setBufferPointer(VideoBuffer buffer) { |
|
g_renderBuffer = buffer; |
|
} |
|
|
|
void beginRendering() { |
|
if (g_syncedBuffer == g_renderBuffer1) { |
|
g_renderBuffer = g_renderBuffer2; |
|
} else if (g_syncedBuffer == g_renderBuffer2) { |
|
g_renderBuffer = g_renderBuffer1; |
|
} |
|
|
|
clearDirty(); |
|
|
|
g_mainBufferPointer = getBufferPointer(); |
|
g_numBuffersToDraw = 0; |
|
} |
|
|
|
int beginBufferRendering() { |
|
int bufferIndex = g_numBuffersToDraw++; |
|
g_renderBuffers[bufferIndex].previousBuffer = getBufferPointer(); |
|
setBufferPointer(g_renderBuffers[bufferIndex].bufferPointer); |
|
return bufferIndex; |
|
} |
|
|
|
void endBufferRendering(int bufferIndex, int x, int y, int width, int height, bool withShadow, uint8_t opacity, int xOffset, int yOffset, Rect *backdrop) { |
|
RenderBuffer &renderBuffer = g_renderBuffers[bufferIndex]; |
|
|
|
renderBuffer.x = x; |
|
renderBuffer.y = y; |
|
renderBuffer.width = width; |
|
renderBuffer.height = height; |
|
renderBuffer.withShadow = withShadow; |
|
renderBuffer.opacity = opacity; |
|
renderBuffer.xOffset = xOffset; |
|
renderBuffer.yOffset = yOffset; |
|
renderBuffer.backdrop = backdrop; |
|
|
|
setBufferPointer(renderBuffer.previousBuffer); |
|
} |
|
|
|
void endRendering() { |
|
setBufferPointer(g_mainBufferPointer); |
|
|
|
#if OPTION_KEYBOARD |
|
if (keyboard::isDisplayDirty()) { |
|
setDirty(); |
|
} |
|
#endif |
|
|
|
#if OPTION_MOUSE |
|
if (mouse::isDisplayDirty()) { |
|
setDirty(); |
|
} |
|
#endif |
|
|
|
#if defined(GUI_CALC_FPS) && defined(STYLE_ID_FPS_GRAPH) |
|
if (g_drawFpsGraphEnabled) { |
|
setDirty(); |
|
} |
|
#endif |
|
|
|
if (isDirty()) { |
|
for (int bufferIndex = 0; bufferIndex < g_numBuffersToDraw; bufferIndex++) { |
|
RenderBuffer &renderBuffer = g_renderBuffers[bufferIndex]; |
|
|
|
int sx = renderBuffer.x; |
|
int sy = renderBuffer.y; |
|
|
|
int x1 = renderBuffer.x + renderBuffer.xOffset; |
|
int y1 = renderBuffer.y + renderBuffer.yOffset; |
|
int x2 = x1 + renderBuffer.width - 1; |
|
int y2 = y1 + renderBuffer.height - 1; |
|
|
|
if (renderBuffer.backdrop) { |
|
// opacity backdrop |
|
auto savedOpacity = setOpacity(CONF_BACKDROP_OPACITY); |
|
setColor(COLOR_ID_BACKDROP); |
|
fillRect(renderBuffer.backdrop->x, renderBuffer.backdrop->y, renderBuffer.backdrop->x + renderBuffer.backdrop->w - 1, renderBuffer.backdrop->y + renderBuffer.backdrop->h - 1); |
|
setOpacity(savedOpacity); |
|
} |
|
|
|
if (renderBuffer.withShadow) { |
|
drawShadow(x1, y1, x2, y2); |
|
} |
|
|
|
bitBlt(g_renderBuffers[bufferIndex].bufferPointer, nullptr, sx, sy, x2 - x1 + 1, y2 - y1 + 1, x1, y1, renderBuffer.opacity); |
|
} |
|
|
|
#if defined(GUI_CALC_FPS) && defined(STYLE_ID_FPS_GRAPH) |
|
if (g_drawFpsGraphEnabled) { |
|
drawFpsGraph(getDisplayWidth() - 64 - 4, 4, 64, 32, getStyle(STYLE_ID_FPS_GRAPH)); |
|
} |
|
#endif |
|
|
|
#if OPTION_KEYBOARD |
|
keyboard::updateDisplay(); |
|
#endif |
|
|
|
#if OPTION_MOUSE |
|
mouse::updateDisplay(); |
|
#endif |
|
} else { |
|
if (g_syncedBuffer == g_renderBuffer1) { |
|
bitBlt(g_renderBuffer1, 0, 0, getDisplayWidth() - 1, getDisplayHeight() - 1); |
|
} else if (g_syncedBuffer == g_renderBuffer2) { |
|
bitBlt(g_renderBuffer2, 0, 0, getDisplayWidth() - 1, getDisplayHeight() - 1); |
|
} |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
uint32_t color16to32(uint16_t color, uint8_t opacity) { |
|
uint32_t color32; |
|
((uint8_t *)&color32)[0] = COLOR_TO_R(color); |
|
((uint8_t *)&color32)[1] = COLOR_TO_G(color); |
|
((uint8_t *)&color32)[2] = COLOR_TO_B(color); |
|
((uint8_t *)&color32)[3] = opacity; |
|
return color32; |
|
} |
|
|
|
uint16_t color32to16(uint32_t color) { |
|
auto pcolor = (uint8_t *)&color; |
|
return RGB_TO_COLOR(pcolor[0], pcolor[1], pcolor[1]); |
|
} |
|
|
|
uint32_t blendColor(uint32_t fgColor, uint32_t bgColor) { |
|
uint8_t *fg = (uint8_t *)&fgColor; |
|
uint8_t *bg = (uint8_t *)&bgColor; |
|
|
|
float alphaMult = fg[3] * bg[3] / 255.0f; |
|
float alphaOut = fg[3] + bg[3] - alphaMult; |
|
|
|
float r = (fg[0] * fg[3] + bg[0] * bg[3] - bg[0] * alphaMult) / alphaOut; |
|
float g = (fg[1] * fg[3] + bg[1] * bg[3] - bg[1] * alphaMult) / alphaOut; |
|
float b = (fg[2] * fg[3] + bg[2] * bg[3] - bg[2] * alphaMult) / alphaOut; |
|
|
|
r = clamp(r, 0.0f, 255.0f); |
|
g = clamp(g, 0.0f, 255.0f); |
|
b = clamp(b, 0.0f, 255.0f); |
|
|
|
uint32_t result; |
|
uint8_t *presult = (uint8_t *)&result; |
|
presult[0] = (uint8_t)r; |
|
presult[1] = (uint8_t)g; |
|
presult[2] = (uint8_t)b; |
|
presult[3] = (uint8_t)alphaOut; |
|
|
|
return result; |
|
} |
|
|
|
void onThemeChanged() { |
|
auto selectedThemeIndex = g_hooks.getSelectedThemeIndex(); |
|
g_themeColors = getThemeColors(selectedThemeIndex); |
|
g_themeColorsCount = getThemeColorsCount(selectedThemeIndex); |
|
g_colors = getColors(); |
|
} |
|
|
|
void onLuminocityChanged() { |
|
// invalidate cache |
|
for (int i = 0; i < 256; ++i) { |
|
g_colorCache[i][0] = 0; |
|
g_colorCache[i][1] = 0; |
|
g_colorCache[i][2] = 0; |
|
g_colorCache[i][3] = 0; |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
#define swap(type, i, j) {type t = i; i = j; j = t;} |
|
|
|
void rgbToHsl(float r, float g, float b, float &h, float &s, float &l) { |
|
r /= 255; |
|
g /= 255; |
|
b /= 255; |
|
|
|
float min = r; |
|
float mid = g; |
|
float max = b; |
|
|
|
if (min > mid) { |
|
swap(float, min, mid); |
|
} |
|
if (mid > max) { |
|
swap(float, mid, max); |
|
} |
|
if (min > mid) { |
|
swap(float, min, mid); |
|
} |
|
|
|
l = (max + min) / 2; |
|
|
|
if (max == min) { |
|
h = s = 0; // achromatic |
|
} else { |
|
float d = max - min; |
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); |
|
|
|
if (max == r) { |
|
h = (g - b) / d + (g < b ? 6 : 0); |
|
} else if (max == g) { |
|
h = (b - r) / d + 2; |
|
} else if (max == b) { |
|
h = (r - g) / d + 4; |
|
} |
|
|
|
h /= 6; |
|
} |
|
} |
|
|
|
float hue2rgb(float p, float q, float t) { |
|
if (t < 0) t += 1; |
|
if (t > 1) t -= 1; |
|
if (t < 1.0f/6) return p + (q - p) * 6 * t; |
|
if (t < 1.0f/2) return q; |
|
if (t < 2.0f/3) return p + (q - p) * (2.0f/3 - t) * 6; |
|
return p; |
|
} |
|
|
|
void hslToRgb(float h, float s, float l, float &r, float &g, float &b) { |
|
if (s == 0) { |
|
r = g = b = l; // achromatic |
|
} else { |
|
float q = l < 0.5 ? l * (1 + s) : l + s - l * s; |
|
float p = 2 * l - q; |
|
|
|
r = hue2rgb(p, q, h + 1.0f/3); |
|
g = hue2rgb(p, q, h); |
|
b = hue2rgb(p, q, h - 1.0f/3); |
|
} |
|
|
|
r *= 255; |
|
g *= 255; |
|
b *= 255; |
|
} |
|
|
|
void adjustColor(uint16_t &c) { |
|
if (g_hooks.getDisplayBackgroundLuminosityStep() == DISPLAY_BACKGROUND_LUMINOSITY_STEP_DEFAULT) { |
|
return; |
|
} |
|
|
|
uint8_t ch = c >> 8; |
|
uint8_t cl = c & 0xFF; |
|
|
|
int i = (ch & 0xF0) | (cl & 0x0F); |
|
if (ch == g_colorCache[i][0] && cl == g_colorCache[i][1]) { |
|
// cache hit! |
|
c = (g_colorCache[i][2] << 8) | g_colorCache[i][3]; |
|
return; |
|
} |
|
|
|
uint8_t r, g, b; |
|
r = ch & 248; |
|
g = ((ch << 5) | (cl >> 3)) & 252; |
|
b = cl << 3; |
|
|
|
float h, s, l; |
|
rgbToHsl(r, g, b, h, s, l); |
|
|
|
float a = l < 0.5 ? l : 1 - l; |
|
if (a > 0.3f) { |
|
a = 0.3f; |
|
} |
|
float lmin = l - a; |
|
float lmax = l + a; |
|
|
|
float lNew = remap((float)g_hooks.getDisplayBackgroundLuminosityStep(), |
|
(float)DISPLAY_BACKGROUND_LUMINOSITY_STEP_MIN, |
|
lmin, |
|
(float)DISPLAY_BACKGROUND_LUMINOSITY_STEP_MAX, |
|
lmax); |
|
|
|
float floatR, floatG, floatB; |
|
hslToRgb(h, s, lNew, floatR, floatG, floatB); |
|
|
|
r = FLOAT_TO_COLOR_COMPONENT(floatR); |
|
g = FLOAT_TO_COLOR_COMPONENT(floatG); |
|
b = FLOAT_TO_COLOR_COMPONENT(floatB); |
|
|
|
uint8_t chNew = RGB_TO_HIGH_BYTE(r, g, b); |
|
uint8_t clNew = RGB_TO_LOW_BYTE(r, g, b); |
|
|
|
// store new color in the cache |
|
g_colorCache[i][0] = ch; |
|
g_colorCache[i][1] = cl; |
|
g_colorCache[i][2] = chNew; |
|
g_colorCache[i][3] = clNew; |
|
|
|
c = (chNew << 8) | clNew; |
|
} |
|
|
|
uint16_t getColor16FromIndex(uint16_t color) { |
|
color = g_hooks.transformColor(color); |
|
return color < g_themeColorsCount ? g_themeColors[color] : g_colors[color - g_themeColorsCount]; |
|
} |
|
|
|
void setColor(uint8_t r, uint8_t g, uint8_t b) { |
|
g_fc = RGB_TO_COLOR(r, g, b); |
|
adjustColor(g_fc); |
|
} |
|
|
|
void setColor16(uint16_t color) { |
|
g_fc = color; |
|
adjustColor(g_fc); |
|
} |
|
|
|
void setColor(uint16_t color, bool ignoreLuminocity) { |
|
g_fc = getColor16FromIndex(color); |
|
if (!ignoreLuminocity) { |
|
adjustColor(g_fc); |
|
} |
|
} |
|
|
|
uint16_t getColor() { |
|
return g_fc; |
|
} |
|
|
|
void setBackColor(uint8_t r, uint8_t g, uint8_t b) { |
|
g_bc = RGB_TO_COLOR(r, g, b); |
|
adjustColor(g_bc); |
|
} |
|
|
|
void setBackColor(uint16_t color, bool ignoreLuminocity) { |
|
g_bc = getColor16FromIndex(color); |
|
if (!ignoreLuminocity) { |
|
adjustColor(g_bc); |
|
} |
|
} |
|
|
|
uint16_t getBackColor() { |
|
return g_bc; |
|
} |
|
|
|
uint8_t setOpacity(uint8_t opacity) { |
|
uint8_t savedOpacity = g_opacity; |
|
g_opacity = opacity; |
|
return savedOpacity; |
|
} |
|
|
|
uint8_t getOpacity() { |
|
return g_opacity; |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void drawHLine(int x, int y, int l) { |
|
fillRect(x, y, x + l, y); |
|
} |
|
|
|
void drawVLine(int x, int y, int l) { |
|
fillRect(x, y, x, y + l); |
|
} |
|
|
|
void drawRect(int x1, int y1, int x2, int y2) { |
|
drawHLine(x1, y1, x2 - x1); |
|
drawHLine(x1, y2, x2 - x1); |
|
drawVLine(x1, y1, y2 - y1); |
|
drawVLine(x2, y1, y2 - y1); |
|
} |
|
|
|
void drawFocusFrame(int x, int y, int w, int h) { |
|
int lineWidth = MIN(MIN(3, w), h); |
|
|
|
setColor16(RGB_TO_COLOR(255, 0, 255)); |
|
|
|
// top |
|
fillRect(x, y, x + w - 1, y + lineWidth - 1); |
|
|
|
// left |
|
fillRect(x, y + lineWidth, x + lineWidth - 1, y + h - lineWidth - 1); |
|
|
|
// right |
|
fillRect(x + w - lineWidth, y + lineWidth, x + w - 1, y + h - lineWidth - 1); |
|
|
|
// bottom |
|
fillRect(x, y + h - lineWidth, x + w - 1, y + h - 1); |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
void aggInit(AggDrawing& aggDrawing) { |
|
aggDrawing.rbuf.attach((uint8_t *)getBufferPointer(), getDisplayWidth(), getDisplayHeight(), getDisplayWidth() * DISPLAY_BPP / 8); |
|
aggDrawing.graphics.attach(aggDrawing.rbuf.buf(), aggDrawing.rbuf.width(), aggDrawing.rbuf.height(), aggDrawing.rbuf.stride()); |
|
} |
|
|
|
void drawRoundedRect( |
|
AggDrawing& aggDrawing, |
|
double x1, double y1, double x2, double y2, |
|
double lineWidth, |
|
double rtlx, double rtly, double rtrx, double rtry, |
|
double rbrx, double rbry, double rblx, double rbly |
|
) { |
|
fillRoundedRect( |
|
aggDrawing, |
|
x1, y1, x2, y2, |
|
lineWidth, |
|
rtlx, rtly, rtrx, rtry, |
|
rbrx, rbry, rblx, rbly, |
|
true, false |
|
); |
|
} |
|
|
|
void fillRoundedRect( |
|
AggDrawing& aggDrawing, |
|
double x1, double y1, double x2, double y2, |
|
double lineWidth, |
|
double rtlx, double rtly, double rtrx, double rtry, |
|
double rbrx, double rbry, double rblx, double rbly, |
|
bool drawLine, bool fill, |
|
double clip_x1, double clip_y1, double clip_x2, double clip_y2 |
|
) { |
|
auto &graphics = aggDrawing.graphics; |
|
|
|
if (clip_x1 != -1) { |
|
graphics.clipBox(clip_x1, clip_y1, clip_x2 + 1, clip_y2 + 1); |
|
} else { |
|
graphics.clipBox(x1, y1, x2 + 1, y2 + 1); |
|
} |
|
graphics.masterAlpha(g_opacity / 255.0); |
|
graphics.translate(x1, y1); |
|
graphics.lineWidth(lineWidth); |
|
if (lineWidth > 0 && drawLine) { |
|
graphics.lineColor(COLOR_TO_R(g_fc), COLOR_TO_G(g_fc), COLOR_TO_B(g_fc)); |
|
} else { |
|
graphics.noLine(); |
|
} |
|
if (fill) { |
|
graphics.fillColor(COLOR_TO_R(g_bc), COLOR_TO_G(g_bc), COLOR_TO_B(g_bc)); |
|
} else { |
|
graphics.noFill(); |
|
} |
|
auto w = x2 - x1 + 1; |
|
auto h = y2 - y1 + 1; |
|
graphics.roundedRect( |
|
lineWidth / 2.0, lineWidth / 2.0, w - lineWidth, h - lineWidth, |
|
rtlx, rtly, rtrx, rtry, rbrx, rbry, rblx, rbly |
|
); |
|
|
|
graphics.translate(-x1, -y1); |
|
graphics.clipBox(0, 0, aggDrawing.rbuf.width(), aggDrawing.rbuf.height()); |
|
} |
|
|
|
void fillRoundedRect( |
|
AggDrawing& aggDrawing, |
|
double x1, double y1, double x2, double y2, |
|
double lineWidth, |
|
double r, |
|
bool drawLine, bool fill, |
|
double clip_x1, double clip_y1, double clip_x2, double clip_y2 |
|
) { |
|
fillRoundedRect(aggDrawing, x1, y1, x2, y2, lineWidth, r, r, r, r, r, r, r, r, drawLine, fill, clip_x1, clip_y1, clip_x2, clip_y2); |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
static int8_t measureGlyph(int32_t encoding) { |
|
auto glyph = g_font.getGlyph(encoding); |
|
if (!glyph) |
|
return 0; |
|
|
|
return glyph->dx; |
|
} |
|
|
|
int8_t measureGlyph(int32_t encoding, gui::font::Font &font) { |
|
auto glyph = font.getGlyph(encoding); |
|
if (!glyph) |
|
return 0; |
|
|
|
return glyph->dx; |
|
} |
|
|
|
int measureStr(const char *text, int textLength, gui::font::Font &font, int max_width) { |
|
g_font = font; |
|
|
|
int width = 0; |
|
|
|
if (textLength == -1) { |
|
while (true) { |
|
utf8_int32_t encoding; |
|
text = utf8codepoint(text, &encoding); |
|
if (!encoding) { |
|
break; |
|
} |
|
int glyph_width = measureGlyph(encoding); |
|
if (max_width > 0 && width + glyph_width > max_width) { |
|
return max_width; |
|
} |
|
width += glyph_width; |
|
} |
|
} else { |
|
for (int i = 0; i < textLength; ++i) { |
|
utf8_int32_t encoding; |
|
text = utf8codepoint(text, &encoding); |
|
if (!encoding) { |
|
break; |
|
} |
|
int glyph_width = measureGlyph(encoding); |
|
if (max_width > 0 && width + glyph_width > max_width) { |
|
return max_width; |
|
} |
|
width += glyph_width; |
|
} |
|
} |
|
|
|
return width; |
|
} |
|
|
|
void drawStr(const char *text, int textLength, int x, int y, int clip_x1, int clip_y1, int clip_x2, int clip_y2, gui::font::Font &font, int cursorPosition) { |
|
g_font = font; |
|
|
|
drawStrInit(); |
|
|
|
if (textLength == -1) { |
|
textLength = utf8len(text); |
|
} |
|
|
|
int xCursor = x; |
|
|
|
int i; |
|
|
|
for (i = 0; i < textLength; ++i) { |
|
utf8_int32_t encoding; |
|
text = utf8codepoint(text, &encoding); |
|
if (!encoding) { |
|
break; |
|
} |
|
|
|
if (i == cursorPosition) { |
|
xCursor = x; |
|
} |
|
|
|
auto x1 = x; |
|
auto y1 = y; |
|
|
|
auto glyph = g_font.getGlyph(encoding); |
|
if (glyph) { |
|
int x_glyph = x1 + glyph->x; |
|
int y_glyph = y1 + g_font.getAscent() - (glyph->y + glyph->height); |
|
|
|
// draw glyph pixels |
|
int iStartByte = 0; |
|
if (x_glyph < clip_x1) { |
|
int dx_off = clip_x1 - x_glyph; |
|
iStartByte = dx_off; |
|
x_glyph = clip_x1; |
|
} |
|
|
|
if (iStartByte < glyph->width) { |
|
int offset = 0; |
|
int glyphHeight = glyph->height; |
|
if (y_glyph < clip_y1) { |
|
int dy_off = clip_y1 - y_glyph; |
|
offset += dy_off * glyph->width; |
|
glyphHeight -= dy_off; |
|
y_glyph = clip_y1; |
|
} |
|
|
|
int width; |
|
if (x_glyph + (glyph->width - iStartByte) - 1 > clip_x2) { |
|
width = clip_x2 - x_glyph + 1; |
|
} else { |
|
width = (glyph->width - iStartByte); |
|
} |
|
|
|
int height; |
|
if (y_glyph + glyphHeight - 1 > clip_y2) { |
|
height = clip_y2 - y_glyph + 1; |
|
} else { |
|
height = glyphHeight; |
|
} |
|
|
|
if (width > 0 && height > 0) { |
|
drawGlyph(glyph->pixels + offset + iStartByte, glyph->width - width, x_glyph, y_glyph, width, height); |
|
} |
|
} |
|
|
|
x += glyph->dx; |
|
} |
|
} |
|
|
|
if (i == cursorPosition) { |
|
xCursor = x; |
|
} |
|
|
|
if (cursorPosition != -1 && xCursor - CURSOR_WIDTH / 2 >= clip_x1 && xCursor + CURSOR_WIDTH / 2 - 1 <= clip_x2) { |
|
auto d = MAX(((clip_y2 - clip_y1) - font.getHeight()) / 2, 0); |
|
fillRect(xCursor - CURSOR_WIDTH / 2, clip_y1 + d, xCursor + CURSOR_WIDTH / 2 - 1, clip_y2 - d); |
|
} |
|
|
|
setDirty(); |
|
} |
|
|
|
int getCharIndexAtPosition(int xPos, const char *text, int textLength, int x, int y, int clip_x1, int clip_y1, int clip_x2,int clip_y2, gui::font::Font &font) { |
|
if (textLength == -1) { |
|
textLength = utf8len(text); |
|
} |
|
|
|
int i; |
|
|
|
for (i = 0; i < textLength; ++i) { |
|
utf8_int32_t encoding; |
|
text = utf8codepoint(text, &encoding); |
|
if (!encoding) { |
|
break; |
|
} |
|
|
|
auto glyph = font.getGlyph(encoding); |
|
auto dx = 0; |
|
if (glyph) { |
|
dx = glyph->dx; |
|
} |
|
if (xPos < x + dx / 2) { |
|
return i; |
|
} |
|
x += dx; |
|
} |
|
|
|
return i; |
|
} |
|
|
|
int getCursorXPosition(int cursorPosition, const char *text, int textLength, int x, int y, int clip_x1, int clip_y1, int clip_x2,int clip_y2, gui::font::Font &font) { |
|
if (textLength == -1) { |
|
textLength = utf8len(text); |
|
} |
|
|
|
for (int i = 0; i < textLength; ++i) { |
|
utf8_int32_t encoding; |
|
text = utf8codepoint(text, &encoding); |
|
if (!encoding) { |
|
break; |
|
} |
|
|
|
if (i == cursorPosition) { |
|
return x; |
|
} |
|
|
|
auto glyph = font.getGlyph(encoding); |
|
if (glyph) { |
|
x += glyph->dx; |
|
} |
|
} |
|
|
|
return x; |
|
} |
|
|
|
} // namespace display |
|
} // namespace gui |
|
} // namespace eez
|
|
|