guibackendnanovg.h File Reference

Go to the documentation of this file. Source: include/ffw/gui/backend/guibackendnanovg.h

/* This file is part of FineFramework project */
#ifndef FFW_GUI_BACKEND_NANOVG
#define FFW_GUI_BACKEND_NANOVG

#include <fstream>
#include <nanovg.h>
#include <nanovg_gl_utils.h>
#include "../guiwindow.h"

#define COLOR_TO_NVGCOLOR(c) (nvgRGBAf(c.r, c.g, c.b, c.a))

#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 GuiImageNanoVG: public GuiImage {
    public:
        GuiImageNanoVG(NVGcontext* ctx, const int w, const int h, const int flags, const unsigned char* data):
        ctx(ctx), width(0), height(0) {
            image = nvgCreateImageRGBA(ctx, w, h, flags, data);
            if (image >= 0) {
                nvgImageSize(ctx, image, &width, &height);
            }
        }
        GuiImageNanoVG(NVGcontext* ctx, const std::string& path, const int flags):
        ctx(ctx), width(0), height(0) {
            image = nvgCreateImage(ctx, path.c_str(), flags);
            if (image >= 0) {
                nvgImageSize(ctx, image, &width, &height);
            }
        }
        virtual ~GuiImageNanoVG() {
            nvgDeleteImage(ctx, image);
        }
        void destroy() override {
            nvgDeleteImage(ctx, image);
        }
        bool isCreated() const override {
            return image >= 0;
        }
        int getWidth() const override {
            return width;
        }
        int getHeight() const override {
            return height;
        }
        int getImageId() const {
            return image;
        }
    private:
        NVGcontext * ctx;
        int image;
        int width;
        int height;
    };
    class GuiFontNanoVG: public GuiFont {
    public:
        GuiFontNanoVG(NVGcontext* ctx, const std::string& path, const float size):
        ctx(ctx),size(size) {
            font = nvgCreateFont(ctx, "", path.c_str());
        }
        virtual ~GuiFontNanoVG() = default;
        bool isCreated() const override {
            return font >= 0;
        }
        void destroy() override {
            // void
        }
        ffw::Pointf getStringSize(const std::string& str, const float maxWidth, 
            const float lineHeight) const override {

            nvgFontFaceId(ctx, font);
            nvgTextLineHeight(ctx, lineHeight);
            nvgTextLetterSpacing(ctx, 0);
            nvgFontSize(ctx, size);
            float bounds[4];
            nvgTextBoxBounds(
                ctx, 0.0f, 0.0f, maxWidth >= 0.0f ? maxWidth : std::numeric_limits<float>::max(),
                str.c_str(), str.c_str() + str.size(), bounds
            );
            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.

            /*nvgFontFace(ctx, font);
            // nvgTextLineHeight(ctx, lineHeight);
            nvgFontSize(ctx, size);
            nvgTextLetterSpacing(ctx, 0);

            char buff[5];
            *utf8::append(c, buff) = '\0';

            NVGglyphPosition position = { nullptr, 0, 0, 0 };
            (void)nvgTextGlyphPositions(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 {

            nvgFontFaceId(ctx, font);
            nvgTextLineHeight(ctx, lineHeight);
            nvgTextLetterSpacing(ctx, 0);
            nvgFontSize(ctx, size);
            std::vector<Chunk> ret;

            auto start = str.c_str();
            const auto end = str.c_str() + str.size();
            while (true) {
                const auto initial = ret.size();

                NVGtextRow rows[4];
                const auto total = nvgTextBreakLines(ctx, 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';
            float bounds[4];
            nvgTextBoxBounds(ctx, 0.0f, 0.0f, std::numeric_limits<float>::max(), &w, &w + 1, bounds);
            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 {

            nvgFontFaceId(ctx, font);
            //nvgTextLineHeight(ctx, lineHeight);
            nvgTextLetterSpacing(ctx, 0);
            nvgFontSize(ctx, size);

            const auto begin = str;
            const auto end = str + len;

            std::unique_ptr<NVGglyphPosition[]> positions(new NVGglyphPosition[len]);
            const size_t total = nvgTextGlyphPositions(ctx, 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;

            nvgFontFaceId(ctx, font);
            //nvgTextLineHeight(ctx, lineHeight);
            nvgTextLetterSpacing(ctx, 0);
            nvgFontSize(ctx, size);

            const auto begin = str;
            const auto end = str + len;

            std::unique_ptr<NVGglyphPosition[]> positions(new NVGglyphPosition[len]);
            const auto total = nvgTextGlyphPositions(ctx, 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;
        }
        int getFontFaceId() const {
            return font;
        }
    private:
        NVGcontext * ctx;
        int font;
        float size;
    };
    class GuiWindowNanoVG: public GuiWindow {
    public:
        GuiWindowNanoVG(NVGcontext* ctx):ctx(ctx) {
            
        }
        virtual ~GuiWindowNanoVG() {
            destroy();
        }
        void destroy() {
            if (fbo) {
                nvgluDeleteFramebuffer(fbo);
            }
        }
        void resize(const int width, const int height) override {
            if (fbo) {
                nvgluDeleteFramebuffer(fbo);
            }
            fbo = nvgluCreateFramebuffer(ctx, width, height, NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
        }
        void startRender() override {
            nvgluBindFramebuffer(fbo);
            const auto width = getSize().x;
            const auto height = getSize().y;

            glViewport(0, 0, int(width), int(height));

            nvgBeginFrame(ctx, width, height, 1.0f);
        }
        void endRender() override {
            nvgResetScissor(ctx);
            nvgEndFrame(ctx);
            nvgluBindFramebuffer(nullptr);
        }
        void setScissors(const ffw::Pointf& pos, const ffw::Pointf& size) const override {
            nvgScissor(ctx, pos.x, pos.y, size.x, size.y);
        }
        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 {
            nvgBeginPath(ctx);
            nvgRect(ctx, pos.x, pos.y, size.x, size.y);
            nvgFillColor(ctx, COLOR_TO_NVGCOLOR(color));
            nvgFill(ctx);
        }
        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 {

            nvgBeginPath(ctx);
            nvgRoundedRectVarying(ctx, pos.x, pos.y, size.x, size.y, tl, tr, br, bl);
            nvgFillColor(ctx, COLOR_TO_NVGCOLOR(color));
            nvgFill(ctx);
        }
        void drawLine(const ffw::Pointf& start, const ffw::Pointf& end,
            const ffw::Color& color, float width) const override {
            nvgBeginPath(ctx);
            nvgMoveTo(ctx, start.x, start.y);
            nvgLineTo(ctx, end.x, end.y);
            nvgStrokeColor(ctx, COLOR_TO_NVGCOLOR(color));
            nvgStroke(ctx);
        }
        void drawCircle(const ffw::Pointf& pos, const float radius,
            const ffw::Color& color) const override {
            nvgBeginPath(ctx);
            nvgCircle(ctx, pos.x, pos.y, radius);
            nvgFillColor(ctx, COLOR_TO_NVGCOLOR(color));
            nvgFill(ctx);
        }
        void drawArc(const ffw::Pointf& pos, const float inner, const float outer,
            const float start, const float end, const ffw::Color& color) const override {
            nvgBeginPath(ctx);
            const auto p0 = pos + ffw::Pointf(outer, 0).rotate(start);
            const auto p1 = pos + ffw::Pointf(inner, 0).rotate(end);
            nvgMoveTo(ctx, p0.x, p0.y);
            nvgArc(ctx, pos.x, pos.y, outer, float(start * DEG_TO_RAD), float(end * DEG_TO_RAD), NVG_CW);
            nvgLineTo(ctx, p1.x, p1.y);
            nvgArc(ctx, pos.x, pos.y, inner, float(end * DEG_TO_RAD), float(start * DEG_TO_RAD), NVG_CCW);
            nvgClosePath(ctx);
            nvgFillColor(ctx, COLOR_TO_NVGCOLOR(color));
            nvgFill(ctx);
        }
        void drawQuad(const ffw::Pointf& p0, const ffw::Pointf& p1, const ffw::Pointf& p2, 
            const ffw::Pointf& p3, const ffw::Color& color) const override {
            nvgBeginPath(ctx);
            nvgMoveTo(ctx, p0.x, p0.y);
            nvgLineTo(ctx, p1.x, p1.y);
            nvgLineTo(ctx, p2.x, p2.y);
            nvgLineTo(ctx, p3.x, p3.y);
            nvgClosePath(ctx);
            nvgFillColor(ctx, COLOR_TO_NVGCOLOR(color));
            nvgFill(ctx);
        }
        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 nvgFont = dynamic_cast<const ffw::GuiFontNanoVG*>(font);
            if (nvgFont) {
                nvgFontFaceId(ctx, nvgFont->getFontFaceId());
                nvgTextLineHeight(ctx, lineHeight);
                nvgTextLetterSpacing(ctx, 0);
                nvgFontSize(ctx, nvgFont->getSize());
                nvgFillColor(ctx, COLOR_TO_NVGCOLOR(color));
                float ascender, descender, lineh = 0.0f;
                nvgTextMetrics(ctx, &ascender, &descender, &lineh);
                nvgTextBox(ctx, pos.x, pos.y + 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 {
            nvgBeginPath(ctx);
            nvgMoveTo(ctx, p0.x, p0.y);
            nvgLineTo(ctx, p1.x, p1.y);
            nvgLineTo(ctx, p2.x, p2.y);
            nvgClosePath(ctx);
            nvgFillColor(ctx, COLOR_TO_NVGCOLOR(color));
            nvgFill(ctx);
        }
        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 nvgImage = dynamic_cast<const ffw::GuiImageNanoVG*>(image);
            if (nvgImage) {
                int w, h = 0;
                nvgImageSize(ctx, nvgImage->getImageId(), &w, &h);
                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 = nvgImagePattern(
                    ctx, 
                    pos.x - size.x * (subX / subZ), 
                    pos.y - size.y * (subY / subW), 
                    size.x / subZ,
                    size.y / subW, 
                    0.0f, 
                    nvgImage->getImageId(), 
                    color.a
                );
                nvgBeginPath(ctx);
                nvgRect(ctx, pos.x, pos.y, size.x, size.y);
                nvgFillPaint(ctx, pattern);
                nvgFill(ctx);
            }
        }
        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) {
                    nvgBeginPath(ctx);
                    nvgRoundedRectVarying(ctx, pos.x, pos.y, size.x, size.y, 
                        background.radius[0], background.radius[1], 
                        background.radius[2], background.radius[3]);
                    nvgFillColor(ctx, COLOR_TO_NVGCOLOR(background.color));
                    nvgFill(ctx);
                } else {
                    nvgBeginPath(ctx);
                    nvgRect(ctx, pos.x, pos.y, size.x, size.y);
                    nvgFillColor(ctx, COLOR_TO_NVGCOLOR(background.color));
                    nvgFill(ctx);
                }
            }
        }
        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]) {
                    nvgBeginPath(ctx);
                    const auto borderHalf = border.size[0] / 2.0f;
                    nvgRoundedRectVarying(ctx, pos.x + borderHalf, pos.y + borderHalf, size.x - border.size[0], size.y - border.size[0], border.radius[0], border.radius[1], border.radius[2], border.radius[3]);
                    nvgStrokeColor(ctx, COLOR_TO_NVGCOLOR(border.color[0]));
                    nvgStrokeWidth(ctx, border.size[0]);
                    nvgStroke(ctx);
                    return;
                }
            }

            // Top
            nvgStrokeColor(ctx, COLOR_TO_NVGCOLOR(border.color[0]));
            nvgBeginPath(ctx);
            nvgMoveTo(ctx, 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;

            nvgStrokeWidth(ctx, border.size[0]);
            nvgLineTo(ctx, 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])) {
                    nvgBezierTo(ctx, cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y);
                }
                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;

                    nvgBezierTo(ctx, cp0A.x, cp0A.y, cp0B.x, cp0B.y, mid.x, mid.y);

                    nvgStroke(ctx);
                    nvgBeginPath(ctx);
                    nvgMoveTo(ctx, mid.x, mid.y);
                    nvgStrokeColor(ctx, COLOR_TO_NVGCOLOR(border.color[1]));

                    nvgBezierTo(ctx, cp1A.x, cp1A.y, cp1B.x, cp1B.y, end.x, end.y);
                }
            }

            endX = pos.x + size.x - border.size[1] / 2.0f;
            endY = pos.y + size.y - border.radius[2];

            nvgStrokeWidth(ctx, border.size[1]);
            nvgLineTo(ctx, 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])) {
                    nvgBezierTo(ctx, cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y);
                }
                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;

                    nvgBezierTo(ctx, cp0A.x, cp0A.y, cp0B.x, cp0B.y, mid.x, mid.y);

                    nvgStroke(ctx);
                    nvgBeginPath(ctx);
                    nvgMoveTo(ctx, mid.x, mid.y);
                    nvgStrokeColor(ctx, COLOR_TO_NVGCOLOR(border.color[2]));

                    nvgBezierTo(ctx, cp1A.x, cp1A.y, cp1B.x, cp1B.y, end.x, end.y);
                }
            }

            endX = pos.x + border.radius[3];
            endY = pos.y + size.y - border.size[2] / 2.0f;

            nvgStrokeWidth(ctx, border.size[2]);
            nvgLineTo(ctx, 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])) {
                    nvgBezierTo(ctx, cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y);
                }
                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;

                    nvgBezierTo(ctx, cp0A.x, cp0A.y, cp0B.x, cp0B.y, mid.x, mid.y);

                    nvgStroke(ctx);
                    nvgBeginPath(ctx);
                    nvgMoveTo(ctx, mid.x, mid.y);
                    nvgStrokeColor(ctx, COLOR_TO_NVGCOLOR(border.color[3]));

                    nvgBezierTo(ctx, cp1A.x, cp1A.y, cp1B.x, cp1B.y, end.x, end.y);
                }
            }

            endX = pos.x + border.size[3] / 2.0f;
            endY = pos.y + border.radius[0];

            nvgStrokeWidth(ctx, border.size[3]);
            nvgLineTo(ctx, 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])) {
                    nvgBezierTo(ctx, cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y);
                }
                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;

                    nvgBezierTo(ctx, cp0A.x, cp0A.y, cp0B.x, cp0B.y, mid.x, mid.y);

                    nvgStroke(ctx);
                    nvgBeginPath(ctx);
                    nvgMoveTo(ctx, mid.x, mid.y);
                    nvgStrokeWidth(ctx, border.size[0]);
                    nvgStrokeColor(ctx, COLOR_TO_NVGCOLOR(border.color[0]));

                    nvgBezierTo(ctx, cp1A.x, cp1A.y, cp1B.x, cp1B.y, end.x, end.y);
                }
            }

            nvgStroke(ctx);
        }

        const NVGLUframebuffer* getFbo() const {
            return fbo;
        }
    private:
        NVGcontext* ctx = nullptr;
        NVGLUframebuffer* fbo = nullptr;
    };
}
#endif