mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-02-02 18:32:46 -05:00
Detect parts of the screen changed since last update and merge them into bigger regions. These regions defines parts of the context sent to the display. Refresh the region covering all of the parts since this is the most time consuming part and the size of the refreshed region doesn't change the time much. Refresh the whole screen if deep refresh is requested and previously fast refresh was used. This is needed to prevent unwanted artifacts in some cases. Refactor some parts of the gui and display code.
289 lines
9.9 KiB
C++
289 lines
9.9 KiB
C++
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
|
|
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
|
|
|
|
#include "DrawCommand.hpp"
|
|
#include "Common.hpp"
|
|
|
|
// gui components
|
|
#include "Color.hpp"
|
|
#include "Context.hpp"
|
|
#include "ImageManager.hpp"
|
|
// renderers
|
|
#include "renderers/LineRenderer.hpp"
|
|
#include "renderers/ArcRenderer.hpp"
|
|
#include "renderers/CircleRenderer.hpp"
|
|
#include "renderers/RectangleRenderer.hpp"
|
|
#include <renderers/PixelRenderer.hpp>
|
|
// text rendering
|
|
#include "FontManager.hpp"
|
|
#include "RawFont.hpp"
|
|
// utils
|
|
#include <log/log.hpp>
|
|
// module-utils
|
|
#include <cmath>
|
|
#include <cassert>
|
|
|
|
#if DEBUG_FONT == 1
|
|
#define log_warn_glyph(...) LOG_WARN(__VA_ARGS__)
|
|
#else
|
|
#define log_warn_glyph(...)
|
|
#endif
|
|
|
|
namespace gui
|
|
{
|
|
void Clear::draw(Context *ctx) const
|
|
{
|
|
ctx->fill(renderer::PixelRenderer::getColor(gui::ColorFullWhite.intensity));
|
|
}
|
|
|
|
void DrawLine::draw(Context *ctx) const
|
|
{
|
|
renderer::LineRenderer::draw(ctx, start, end, color);
|
|
}
|
|
|
|
void DrawRectangle::draw(Context *ctx) const
|
|
{
|
|
using renderer::RectangleRenderer;
|
|
|
|
// First, check if there is anything to draw
|
|
if (width == 0 || height == 0) {
|
|
return;
|
|
}
|
|
if (fillColor.alpha == Color::FullTransparent && borderColor.alpha == Color::FullTransparent) {
|
|
return;
|
|
}
|
|
if (!filled && borderColor.alpha == Color::FullTransparent) {
|
|
return;
|
|
}
|
|
|
|
Context tempContext;
|
|
Context *drawingContext = ctx;
|
|
Point position(0, 0);
|
|
|
|
if (yaps & (RectangleYap::BottomLeft | RectangleYap::TopLeft)) {
|
|
position.x += yapSize;
|
|
}
|
|
|
|
Length adjustedWidth = areaW;
|
|
Length adjustedHeight = areaH;
|
|
if (yaps != RectangleYap::None) {
|
|
adjustedWidth -= yapSize;
|
|
}
|
|
|
|
if (areaW == width && areaH == height) {
|
|
position.x += origin.x;
|
|
position.y += origin.y;
|
|
}
|
|
else {
|
|
const auto xCtx = areaX < 0 ? origin.x + areaX : origin.x;
|
|
const auto yCtx = areaY < 0 ? origin.y + areaY : origin.y;
|
|
tempContext = ctx->get(xCtx, yCtx, areaW, areaH);
|
|
drawingContext = &tempContext;
|
|
}
|
|
|
|
if (radius == 0) {
|
|
RectangleRenderer::drawFlat(
|
|
drawingContext, position, adjustedWidth, adjustedHeight, RectangleRenderer::DrawableStyle::from(*this));
|
|
}
|
|
else {
|
|
RectangleRenderer::draw(
|
|
drawingContext, position, adjustedWidth, adjustedHeight, RectangleRenderer::DrawableStyle::from(*this));
|
|
}
|
|
|
|
if (drawingContext != ctx) {
|
|
ctx->insertArea(origin.x, origin.y, areaX, areaY, width, height, tempContext);
|
|
}
|
|
}
|
|
|
|
void DrawArc::draw(Context *ctx) const
|
|
{
|
|
renderer::ArcRenderer::draw(
|
|
ctx, center, radius, start, sweep, renderer::ArcRenderer::DrawableStyle::from(*this));
|
|
}
|
|
|
|
void DrawCircle::draw(Context *ctx) const
|
|
{
|
|
renderer::CircleRenderer::draw(ctx, center, radius, renderer::CircleRenderer::DrawableStyle::from(*this));
|
|
}
|
|
|
|
void DrawText::drawChar(Context *ctx, const Point glyphOrigin, FontGlyph *glyph) const
|
|
{
|
|
auto *glyphPtr = glyph->data - glyphOrigin.x;
|
|
assert(glyph->data);
|
|
|
|
Point position = glyphOrigin;
|
|
const Position glyphMaxY = glyphOrigin.y - glyph->yoffset + glyph->height;
|
|
const Position glyphMaxX = glyphOrigin.x + glyph->width;
|
|
|
|
for (position.y = glyphOrigin.y - glyph->yoffset; position.y < glyphMaxY; ++position.y) {
|
|
for (position.x = glyphOrigin.x; position.x < glyphMaxX; ++position.x) {
|
|
if (!ctx->hasPixel(position)) {
|
|
log_warn_glyph(
|
|
"drawing out of: {x=%d,y=%d} vs {w=%d,h=%d}", position.x, position.y, ctx->getW(), ctx->getH());
|
|
return;
|
|
}
|
|
|
|
if (*(glyphPtr + position.x) == ColorFullBlack.intensity) {
|
|
renderer::PixelRenderer::draw(ctx, position, color);
|
|
}
|
|
}
|
|
glyphPtr += glyph->width;
|
|
}
|
|
}
|
|
|
|
void DrawText::draw(Context *ctx) const
|
|
{
|
|
// check if there are any characters to draw in the string provided with message.
|
|
if (str.length() == 0) {
|
|
return;
|
|
}
|
|
|
|
// get copy of original context using x,y of draw coordinates and original size of the widget
|
|
Context ctxCopy;
|
|
bool copyContext = false;
|
|
Point widgetOrigin = {0, 0};
|
|
|
|
// check if there is a need of making copy of context to use it as background
|
|
if ((areaW == width) && (areaH == height)) {
|
|
widgetOrigin = origin;
|
|
}
|
|
else {
|
|
copyContext = true;
|
|
ctxCopy = ctx->get(origin.x, origin.y, areaW, areaH);
|
|
}
|
|
|
|
// retrieve font used to draw text
|
|
RawFont *font = FontManager::getInstance().getFont(fontID);
|
|
|
|
// draw every sign
|
|
uint32_t idLast = 0, idCurrent = 0;
|
|
Point position = textOrigin;
|
|
|
|
for (uint32_t i = 0; i < str.length(); ++i) {
|
|
idCurrent = str[i]; // id stands for glued together utf-16 with no order bytes (0xFF 0xFE)
|
|
FontGlyph *glyph = font->getGlyph(idCurrent);
|
|
|
|
// do not start drawing outside of draw context.
|
|
Context *drawCtx = copyContext ? &ctxCopy : ctx;
|
|
if ((widgetOrigin.x + position.x + glyph->xoffset >= drawCtx->getW()) ||
|
|
(widgetOrigin.x + position.x + glyph->xoffset < 0)) {
|
|
LOG_FATAL("Drawing outside context's X boundary for glyph: %" PRIu32, glyph->id);
|
|
return;
|
|
}
|
|
if ((widgetOrigin.y + position.y >= drawCtx->getH()) || (widgetOrigin.y + position.y < 0)) {
|
|
LOG_FATAL("Drawing outside context's Y boundary for glyph: %" PRIu32, glyph->id);
|
|
return;
|
|
}
|
|
|
|
int32_t kernValue = 0;
|
|
if (i > 0) {
|
|
kernValue = font->getKerning(idLast, idCurrent);
|
|
}
|
|
|
|
drawChar(drawCtx,
|
|
{widgetOrigin.x + position.x + glyph->xoffset + kernValue, widgetOrigin.y + position.y},
|
|
glyph);
|
|
position.x += glyph->xadvance + kernValue;
|
|
|
|
idLast = idCurrent;
|
|
}
|
|
|
|
// if drawing was performed in temporary context
|
|
// reinsert drawCtx into base context
|
|
if (copyContext) {
|
|
ctx->insert(origin.x, origin.y, ctxCopy);
|
|
}
|
|
}
|
|
|
|
inline void DrawImage::checkImageSize(Context *ctx, ImageMap *image) const
|
|
{
|
|
if (image->getHeight() > ctx->getH() || image->getWidth() > ctx->getW()) {
|
|
LOG_WARN("image %s {w: %d,h %d} > context {w %d,h %d}",
|
|
image->getName().c_str(),
|
|
image->getWidth(),
|
|
ctx->getW(),
|
|
image->getHeight(),
|
|
ctx->getH());
|
|
}
|
|
}
|
|
|
|
void DrawImage::drawPixMap(Context *ctx, PixMap *pixMap) const
|
|
{
|
|
uint32_t offsetImage = 0;
|
|
uint32_t offsetContext = 0;
|
|
uint8_t *pixData = pixMap->getData();
|
|
const auto ctxData = ctx->getData();
|
|
checkImageSize(ctx, pixMap);
|
|
|
|
for (uint32_t row = 0; row < std::min(ctx->getH(), pixMap->getHeight()); row++) {
|
|
std::memcpy(ctxData + offsetContext, pixData + offsetImage, std::min(ctx->getW(), pixMap->getWidth()));
|
|
offsetImage += pixMap->getWidth();
|
|
offsetContext += ctx->getW();
|
|
}
|
|
}
|
|
|
|
void DrawImage::drawVecMap(Context *ctx, VecMap *vecMap) const
|
|
{
|
|
uint32_t offsetContext = 0;
|
|
uint32_t offsetRowContext = 0;
|
|
uint32_t imageOffset = 0;
|
|
uint8_t alphaColor = vecMap->getAlphaColor();
|
|
|
|
for (uint32_t row = 0; row < std::min(vecMap->getHeight(), ctx->getH()); row++) {
|
|
checkImageSize(ctx, vecMap);
|
|
uint16_t vecCount = *(vecMap->getData() + imageOffset);
|
|
imageOffset += sizeof(uint16_t);
|
|
|
|
const auto ctxData = ctx->getData();
|
|
offsetRowContext = offsetContext;
|
|
|
|
for (uint32_t vec = 0; vec < vecCount; ++vec) {
|
|
|
|
uint16_t vecOffset = *(vecMap->getData() + imageOffset);
|
|
imageOffset += sizeof(uint16_t);
|
|
uint16_t vecLength = *(vecMap->getData() + imageOffset);
|
|
imageOffset += sizeof(uint8_t);
|
|
uint8_t vecColor = *(vecMap->getData() + imageOffset);
|
|
imageOffset += sizeof(uint8_t);
|
|
|
|
offsetRowContext += vecOffset;
|
|
if (vecColor != alphaColor) {
|
|
std::memset(ctxData + offsetRowContext,
|
|
renderer::PixelRenderer::getColor(vecColor),
|
|
std::min(ctx->getW(), vecLength));
|
|
}
|
|
offsetRowContext += vecLength;
|
|
}
|
|
offsetContext += ctx->getW();
|
|
}
|
|
}
|
|
|
|
void DrawImage::draw(Context *ctx) const
|
|
{
|
|
// retrieve pixmap from the pixmap manager
|
|
ImageMap *imageMap = ImageManager::getInstance().getImageMap(imageID);
|
|
|
|
// if image is not found return;
|
|
if (imageMap == nullptr) {
|
|
return;
|
|
}
|
|
|
|
// get copy of original context using x,y of draw coordinates and original size of the widget
|
|
Context drawCtx = ctx->get(origin.x, origin.y, areaW, areaH);
|
|
|
|
if (imageMap->getType() == gui::ImageMap::Type::PIXMAP) {
|
|
auto pixMap = dynamic_cast<PixMap *>(imageMap);
|
|
assert(pixMap);
|
|
drawPixMap(&drawCtx, pixMap);
|
|
}
|
|
else if (imageMap->getType() == gui::ImageMap::Type::VECMAP) {
|
|
auto vecMap = dynamic_cast<VecMap *>(imageMap);
|
|
assert(vecMap);
|
|
drawVecMap(&drawCtx, vecMap);
|
|
}
|
|
|
|
// reinsert drawCtx into bast context
|
|
ctx->insert(origin.x, origin.y, drawCtx);
|
|
}
|
|
} /* namespace gui */
|