guibackendfinegraphics.h File Reference
Go to the documentation of this file.
Source: include/ffw/gui/backend/guibackendfinegraphics.h
/* This file is part of FineFramework project */
#ifndef FFW_GUI_BACKEND_NANOVG
#define FFW_GUI_BACKEND_NANOVG
#include <ffw/graphics.h>
#include "../guiwindow.h"
#ifdef FFW_CLAMP
#define FFW_CLAMP(n, lower, upper) (std::max(lower, std::min(n, upper)))
#endif
#ifndef FFW_FLOAT_EQUAL
#define FFW_FLOAT_EQUAL(a, b) (std::abs(a - b) < 0.001f)
#endif
namespace ffw {
class GuiImageFineGraphics: public GuiImage {
public:
GuiImageFineGraphics() = default;
GuiImageFineGraphics(GLCanvas& canvas, const GLTexture2D& texture, const GLCanvas::ImageFlags::Flag flags = 0):texture(texture) {
image = canvas.createImage(texture, flags);
}
virtual ~GuiImageFineGraphics() = default;
void destroy() override {
// void
}
bool isCreated() const override {
return texture.isCreated();
}
int getWidth() const override {
return texture.getWidth();
}
int getHeight() const override {
return texture.getHeight();
}
const GLCanvas::Image& getImage() const {
return image;
}
private:
GLCanvas::Image image;
const GLTexture2D& texture;
};
class GuiFontFineGraphics: public GuiFont {
public:
GuiFontFineGraphics(GLCanvas& canvas, const std::string& path, const float size):
canvas(canvas),size(size) {
font = canvas.createFont(path);
}
virtual ~GuiFontFineGraphics() = default;
bool isCreated() const override {
return font.isCreated();
}
void destroy() override {
// void
}
ffw::Pointf getStringSize(const std::string& str, const float maxWidth,
const float lineHeight) const override {
canvas.fontFace(font);
canvas.textLineHeight(lineHeight);
canvas.fontSize(size);
canvas.textLetterSpacing(0);
const auto bounds = canvas.textBoxBounds(Vec2f(0.0f, 0.0f), maxWidth >= 0.0f ? maxWidth : std::numeric_limits<float>::max(), str);
return Pointf(bounds[2], bounds[3] - bounds[1]);
}
float getEmptyLineHeight(const float lineHeight) const override {
return getStringSize("W", -1.0f, lineHeight).y;
}
float getCharAdvance(unsigned int c) const override {
// See the comment on the base GuiFont::getCharAdvance() method to understand!
// In case you use this method, be aware that canvas.textGlyphPositions uses
// NanoVG nvgTextGlyphPositions which for some reason are not exactly precise
// and during the rendering of the text the char advance is slightly smaller.
/*canvas.fontFace(font);
// canvas->textLineHeight(lineHeight);
canvas.fontSize(size);
canvas.textLetterSpacing(0);
char buff[5];
*utf8::append(c, buff) = '\0';
GLCanvas::GlyphPosition position = { nullptr, 0, 0, 0 };
(void)canvas.textGlyphPositions(Vec2f(0.0f, 0.0f), buff, nullptr, &position, 1);
return position.maxx;*/
return 0.0f;
}
std::vector<Chunk> splitString(const std::string& str, const float maxWidth,
const float lineHeight) const override {
canvas.fontFace(font);
canvas.textLineHeight(lineHeight);
canvas.fontSize(size);
canvas.textLetterSpacing(0);
std::vector<Chunk> ret;
auto start = str.c_str();
const auto end = str.c_str() + str.size();
while (true) {
const auto initial = ret.size();
GLCanvas::TextRow rows[4];
const auto total = canvas.textBreakLines(start, end, maxWidth, rows, 4);
ret.resize(initial + total);
for (auto i = 0; i < total; i++) {
ret[initial + i].str = rows[i].start;
ret[initial + i].len = rows[i].end - rows[i].start;
ret[initial + i].width = rows[i].width;
}
if (rows[total - 1].next < end) {
start = rows[total - 1].next;
} else {
break;
}
}
const auto w = 'W';
const auto bounds = canvas.textBoxBounds(Vec2f(0.0f, 0.0f), std::numeric_limits<float>::max(), str);
const auto realLineHeight = bounds[3] - bounds[1];
for (auto& r : ret) {
r.height = realLineHeight;
}
return ret;
}
size_t getCharIndex(const std::string::value_type* str, const size_t len,
const Pointf& pos, float* x) const override {
canvas.fontFace(font);
// canvas->textLineHeight(lineHeight);
canvas.fontSize(size);
canvas.textLetterSpacing(0);
const auto begin = str;
const auto end = str + len;
std::unique_ptr<GLCanvas::GlyphPosition[]> positions(new GLCanvas::GlyphPosition[len]);
const size_t total = canvas.textGlyphPositions(Vec2f(0.0f, 0.0f), begin, end, positions.get(), len);
for (size_t i = 0; i < total; i++) {
const auto& p = positions[i];
// Check if the character is after the position
if (p.x <= pos.x && p.maxx >= pos.x) {
if (x) *x = p.x;
return p.str - begin;
}
}
if (x) *x = positions[total - 1].maxx;
return end - begin;
}
float getCharPos(const std::string::value_type* str, const size_t len,
const size_t index) const override {
if (index == 0) return 0.0f;
canvas.fontFace(font);
// canvas->textLineHeight(lineHeight);
canvas.fontSize(size);
canvas.textLetterSpacing(0);
const auto begin = str;
const auto end = str + len;
std::unique_ptr<GLCanvas::GlyphPosition[]> positions(new GLCanvas::GlyphPosition[len]);
const size_t total = canvas.textGlyphPositions(Vec2f(0.0f, 0.0f), begin, end, positions.get(), len);
// Check if we are looking for an index within the string
if (index < len) {
// Then return the position of the character (not the next position)
for (size_t i = 0; i < len; i++) {
if (size_t(positions[i].str - str) >= index) {
return positions[i].x;
}
}
return positions[total - 1].x;
} else {
// If we are looking for the index after the string (len == index)
// then return the position after the last character (the next position)
return positions[total - 1].maxx;
}
}
float getSize() const {
return size;
}
const GLCanvas::Font& getCanvasFont() const {
return font;
}
private:
GLCanvas& canvas;
GLCanvas::Font font;
float size;
};
class GuiWindowFineGraphics: public GuiWindow {
public:
GuiWindowFineGraphics(GLCanvas& canvas):canvas(canvas) {
}
virtual ~GuiWindowFineGraphics() {
destroy();
}
void destroy() {
fbo.destroy();
fboTexture.destroy();
}
void resize(const int width, const int height) override {
if (!fbo.isCreated()) {
fboTexture = GLTexture2D(width, height, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
fboStencil = GLRenderbuffer2D(width, height, GL_STENCIL_INDEX8);
fbo = GLFramebuffer({
GLFramebuffer::Attachment(&fboTexture, GL_COLOR_ATTACHMENT0),
GLFramebuffer::Attachment(&fboStencil, GL_STENCIL_ATTACHMENT)
});
fbo.checkStatus();
fboTextureImage = canvas.createImage(fboTexture, GLCanvas::ImageFlags::IMAGE_FLIPY);
}
fboTexture.resize(width, height, nullptr);
fboStencil.resize(width, height);
}
void startRender() override {
fbo.bind();
const auto width = getSize().x;
const auto height = getSize().y;
glViewport(0, 0, int(width), int(height));
canvas.beginFrame(getSize());
}
void endRender() override {
canvas.resetScissor();
canvas.endFrame();
fbo.unbind();
}
void setScissors(const ffw::Pointf& pos, const ffw::Pointf& size) const override {
canvas.scissor(pos, size);
}
void clearWithColor(const ffw::Color& color, const ffw::Pointf& pos, const ffw::Pointf& size) const override {
glEnable(GL_SCISSOR_TEST);
glScissor(pos.x, getSize().y - pos.y - size.y, size.x, size.y);
glClearColor(color.r, color.g, color.b, color.a);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_SCISSOR_TEST);
}
void drawRectangle(const ffw::Pointf& pos, const ffw::Pointf& size,
const ffw::Color& color) const override {
canvas.beginPath();
canvas.rect(pos, size);
canvas.fillColor(color);
canvas.fill();
}
void drawRectangleRounded(const ffw::Pointf& pos, const ffw::Pointf& size,
const ffw::Color& color, const float tl, const float tr,
const float br, const float bl) const override {
canvas.beginPath();
canvas.roundedRect(pos, size, tl, tr, br, bl);
canvas.fillColor(color);
canvas.fill();
}
void drawLine(const ffw::Pointf& start, const ffw::Pointf& end,
const ffw::Color& color, float width) const override {
canvas.beginPath();
canvas.moveTo(start);
canvas.lineTo(end);
canvas.strokeColor(color);
canvas.stroke();
}
void drawCircle(const ffw::Pointf& pos, const float radius,
const ffw::Color& color) const override {
canvas.beginPath();
canvas.circle(pos, radius);
canvas.fillColor(color);
canvas.fill();
}
void drawArc(const ffw::Pointf& pos, const float inner, const float outer,
const float start, const float end, const ffw::Color& color) const override {
canvas.beginPath();
const auto p0 = pos + ffw::Pointf(outer, 0).rotate(start);
const auto p1 = pos + ffw::Pointf(inner, 0).rotate(end);
canvas.moveTo(p0);
canvas.arc(pos, outer, start, end, GLCanvas::Winding::CW);
canvas.lineTo(p1);
canvas.arc(pos, inner, end, start, GLCanvas::Winding::CCW);
canvas.closePath();
canvas.fillColor(color);
canvas.fill();
}
void drawQuad(const ffw::Pointf& p0, const ffw::Pointf& p1, const ffw::Pointf& p2,
const ffw::Pointf& p3, const ffw::Color& color) const override {
canvas.beginPath();
canvas.moveTo(p0);
canvas.lineTo(p1);
canvas.lineTo(p2);
canvas.lineTo(p3);
canvas.closePath();
canvas.fillColor(color);
canvas.fill();
}
void drawString(const ffw::Pointf& pos, const ffw::GuiFont* font,
const std::string::value_type* str, const size_t length, const ffw::Color& color,
const float lineHeight) const override {
const auto fineFont = dynamic_cast<const ffw::GuiFontFineGraphics*>(font);
if (fineFont) {
canvas.fontFace(fineFont->getCanvasFont());
canvas.textLineHeight(lineHeight);
canvas.textLetterSpacing(0);
canvas.fontSize(fineFont->getSize());
canvas.fillColor(color);
float ascender, descender, lineh = 0.0f;
canvas.textMetrics(ascender, descender, lineh);
canvas.textBox(pos + Vec2f(0.0f, ascender), std::numeric_limits<float>::max(), str, str + length);
}
}
void drawTriangle(const ffw::Pointf& p0, const ffw::Pointf& p1, const ffw::Pointf& p2,
const ffw::Color& color) const override {
canvas.beginPath();
canvas.moveTo(p0);
canvas.lineTo(p1);
canvas.lineTo(p2);
canvas.closePath();
canvas.fillColor(color);
canvas.fill();
}
void drawImage(const ffw::Pointf& pos, const ffw::Pointf& size,
const GuiImage* image, const ffw::Recti& sub, bool mirrorX,
bool mirrorY, const ffw::Color& color) const override {
const auto fineImage = dynamic_cast<const ffw::GuiImageFineGraphics*>(image);
if (fineImage) {
const auto w = fineImage->getWidth();
const auto h = fineImage->getHeight();
const auto subX = sub.x / float(w);
const auto subY = sub.y / float(h);
const auto subZ = sub.z / float(w);
const auto subW = sub.w / float(h);
const auto pattern = canvas.imagePattern(
Vec2f(pos.x - size.x * (subX / subZ), pos.y - size.y * (subY / subW)),
Vec2f(size.x / subZ, size.y / subW),
0.0f,
fineImage->getImage(),
color.a
);
canvas.beginPath();
canvas.rect(pos, size);
canvas.fillPaint(pattern);
canvas.fill();
}
}
void drawBackground(const ffw::Pointf& pos, const ffw::Pointf& size,
const ffw::GuiStyle::Background& background, bool ignore) const override {
if (background.type == ffw::GuiStyle::Background::Type::SIMPLE) {
if (background.radius[0] > 0.0f || background.radius[1] > 0.0f ||
background.radius[2] > 0.0f || background.radius[3] > 0.0f) {
canvas.beginPath();
canvas.roundedRect(pos, size,
background.radius[0], background.radius[1],
background.radius[2], background.radius[3]);
canvas.fillColor(background.color);
canvas.fill();
} else {
canvas.beginPath();
canvas.rect(pos, size);
canvas.fillColor(background.color);
canvas.fill();
}
}
}
void drawBorder(const ffw::Pointf& pos, const ffw::Pointf& size,
const ffw::GuiStyle::Border& border) const override {
if (border.size[0] == border.size[1] && border.size[0] == border.size[2] && border.size[0] == border.size[3]) {
if (border.color[0] == border.color[1] && border.color[0] == border.color[2] && border.color[0] == border.color[3]) {
canvas.beginPath();
const auto borderHalf = border.size[0] / 2.0f;
canvas.roundedRect(Vec2f(pos.x + borderHalf, pos.y + borderHalf), Vec2f(size.x - border.size[0], size.y - border.size[0]), border.radius[0], border.radius[1], border.radius[2], border.radius[3]);
canvas.strokeColor(border.color[0]);
canvas.strokeWidth(border.size[0]);
canvas.stroke();
return;
}
}
// Top
canvas.strokeColor(border.color[0]);
canvas.beginPath();
canvas.moveTo(Vec2f(pos.x + border.radius[0], pos.y + border.size[0] / 2.0f));
auto endX = pos.x + size.x - border.radius[1];
auto endY = pos.y + border.size[0] / 2.0f;
canvas.strokeWidth(border.size[0]);
canvas.lineTo(Vec2f(endX, endY));
if (border.radius[1] > 0.1f) {
const auto cp0 = ffw::Pointf(endX + border.radius[1] / 2.0f, endY);
const auto cp1 = ffw::Pointf(pos.x + size.x - border.size[1] / 2.0f, pos.y + border.radius[1] / 2.0f);
const auto end = ffw::Pointf(pos.x + size.x - border.size[1] / 2.0f, pos.y + border.radius[1]);
if (border.color[0] == border.color[1] && FFW_FLOAT_EQUAL(border.size[0], border.size[1])) {
canvas.bezierTo(cp0, cp1, end);
}
else {
const auto start = ffw::Pointf(endX, endY);
const auto mid = (cp0 + cp1) / 2.0f;
const auto cp0A = (start + cp0) / 2.0f;
const auto cp0B = (cp0 + mid) / 2.0f;
const auto cp1A = (mid + cp1) / 2.0f;
const auto cp1B = (cp1 + end) / 2.0f;
canvas.bezierTo(cp0A, cp0B, mid);
canvas.stroke();
canvas.beginPath();
canvas.moveTo(mid);
canvas.strokeColor(border.color[1]);
canvas.bezierTo(cp1A, cp1B, end);
}
}
endX = pos.x + size.x - border.size[1] / 2.0f;
endY = pos.y + size.y - border.radius[2];
canvas.strokeWidth(border.size[1]);
canvas.lineTo(Vec2f(endX, endY));
if (border.radius[2] > 0.1f) {
const auto cp0 = ffw::Pointf(endX, endY + border.radius[2] / 2.0f);
const auto cp1 = ffw::Pointf(pos.x + size.x - border.radius[2] / 2.0f, pos.y + size.y - border.size[2] / 2.0f);
const auto end = ffw::Pointf(pos.x + size.x - border.radius[2], pos.y + size.y - border.size[2] / 2.0f);
if (border.color[1] == border.color[2] && FFW_FLOAT_EQUAL(border.size[1], border.size[2])) {
canvas.bezierTo(cp0, cp1, end);
}
else {
const auto start = ffw::Pointf(endX, endY);
const auto mid = (cp0 + cp1) / 2.0f;
const auto cp0A = (start + cp0) / 2.0f;
const auto cp0B = (cp0 + mid) / 2.0f;
const auto cp1A = (mid + cp1) / 2.0f;
const auto cp1B = (cp1 + end) / 2.0f;
canvas.bezierTo(cp0A, cp0B, mid);
canvas.stroke();
canvas.beginPath();
canvas.moveTo(mid.x);
canvas.strokeColor(border.color[2]);
canvas.bezierTo(cp1A, cp1B, end);
}
}
endX = pos.x + border.radius[3];
endY = pos.y + size.y - border.size[2] / 2.0f;
canvas.strokeWidth(border.size[2]);
canvas.lineTo(Vec2f(endX, endY));
if (border.radius[3] > 0.1f) {
const auto cp0 = ffw::Pointf(pos.x + border.radius[3] / 2.0f, endY);
const auto cp1 = ffw::Pointf(pos.x + border.size[3] / 2.0f, pos.y + size.y - border.radius[3] / 2.0f);
const auto end = ffw::Pointf(pos.x + border.size[3] / 2.0f, pos.y + size.y - border.radius[3]);
if (border.color[2] == border.color[3] && FFW_FLOAT_EQUAL(border.size[2], border.size[3])) {
canvas.bezierTo(cp0, cp1, end);
}
else {
const auto start = ffw::Pointf(endX, endY);
const auto mid = (cp0 + cp1) / 2.0f;
const auto cp0A = (start + cp0) / 2.0f;
const auto cp0B = (cp0 + mid) / 2.0f;
const auto cp1A = (mid + cp1) / 2.0f;
const auto cp1B = (cp1 + end) / 2.0f;
canvas.bezierTo(cp0A, cp0B, mid);
canvas.stroke();
canvas.beginPath();
canvas.moveTo(mid);
canvas.strokeColor(border.color[3]);
canvas.bezierTo(cp1A, cp1B, end);
}
}
endX = pos.x + border.size[3] / 2.0f;
endY = pos.y + border.radius[0];
canvas.strokeWidth(border.size[3]);
canvas.lineTo(Vec2f(endX, endY));
if (border.radius[0] > 0.1f) {
const auto cp0 = ffw::Pointf(pos.x + border.size[3] / 2.0f, pos.y + border.radius[0] / 2.0f);
const auto cp1 = ffw::Pointf(pos.x + border.radius[0] / 2.0f, pos.y + border.size[0] / 2.0f);
const auto end = ffw::Pointf(pos.x + border.radius[0], pos.y + border.size[0] / 2.0f);
if (border.color[3] == border.color[0] && FFW_FLOAT_EQUAL(border.size[3], border.size[0])) {
canvas.bezierTo(cp0, cp1, end);
}
else {
const auto start = ffw::Pointf(endX, endY);
const auto mid = (cp0 + cp1) / 2.0f;
const auto cp0A = (start + cp0) / 2.0f;
const auto cp0B = (cp0 + mid) / 2.0f;
const auto cp1A = (mid + cp1) / 2.0f;
const auto cp1B = (cp1 + end) / 2.0f;
canvas.bezierTo(cp0A, cp0B, mid);
canvas.stroke();
canvas.beginPath();
canvas.moveTo(mid);
canvas.strokeWidth(border.size[0]);
canvas.strokeColor(border.color[0]);
canvas.bezierTo(cp1A, cp1B, end);
}
}
canvas.stroke();
}
const GLFramebuffer& getFbo() const {
return fbo;
}
const GLTexture2D& getFboTexture() const {
return fboTexture;
}
const GLCanvas::Image& getFboTextureImage() const {
return fboTextureImage;
}
private:
GLCanvas& canvas;
GLFramebuffer fbo;
GLTexture2D fboTexture;
GLRenderbuffer2D fboStencil;
GLCanvas::Image fboTextureImage;
};
}
#endif