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.
988 lines
26 KiB
988 lines
26 KiB
2 years ago
|
/*
|
||
|
* 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
|