org.lwjgl.demo.nanovg.Demo.java Source code

Java tutorial

Introduction

Here is the source code for org.lwjgl.demo.nanovg.Demo.java

Source

/*
 * Copyright LWJGL. All rights reserved.
 * License terms: https://www.lwjgl.org/license
 */
package org.lwjgl.demo.nanovg;

/*
 * Copyright (c) 2013 Mikko Mononen memon@inside.org
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

import org.lwjgl.*;
import org.lwjgl.nanovg.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;

import java.io.*;
import java.nio.*;
import java.util.*;

import static java.lang.Math.*;
import static org.lwjgl.demo.util.IOUtil.*;
import static org.lwjgl.nanovg.NanoVG.*;
import static org.lwjgl.opengl.ARBTimerQuery.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.stb.STBImageWrite.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;

/**
 * Base NanoVG demo class.
 *
 * <p>This is a Java port of
 * <a href="https://github.com/memononen/nanovg/blob/master/example/demo.c">https://github.com/memononen/nanovg/blob/master/example/demo.c</a>.</p>
 */
class Demo {

    private static final ByteBuffer ICON_SEARCH = cpToUTF8(0x1F50D);
    private static final ByteBuffer ICON_CIRCLED_CROSS = cpToUTF8(0x2716);
    private static final ByteBuffer ICON_CHEVRON_RIGHT = cpToUTF8(0xE75E);
    private static final ByteBuffer ICON_CHECK = cpToUTF8(0x2713);
    private static final ByteBuffer ICON_LOGIN = cpToUTF8(0xE740);
    private static final ByteBuffer ICON_TRASH = cpToUTF8(0xE729);

    static final NVGColor colorA = NVGColor.create(), colorB = NVGColor.create(), colorC = NVGColor.create();

    static final NVGPaint paintA = NVGPaint.create(), paintB = NVGPaint.create(), paintC = NVGPaint.create();

    private static final NVGTextRow.Buffer rows = NVGTextRow.create(3);
    private static final NVGGlyphPosition.Buffer glyphs = NVGGlyphPosition.create(100);
    private static final ByteBuffer paragraph = memUTF8(
            "This is longer chunk of text.\n  \n  Would have used lorem ipsum but she    was busy jumping over the lazy dog with the fox and all the men "
                    + "who came to the aid of the party.",
            false);

    private static final FloatBuffer lineh = BufferUtils.createFloatBuffer(1);
    private static final FloatBuffer bounds = BufferUtils.createFloatBuffer(4);

    private static final ByteBuffer hoverText = memASCII(
            "Hover your mouse over the text to see calculated caret position.", false);

    static class DemoData {

        final ByteBuffer entypo = loadResource("demo/nanovg/entypo.ttf", 40 * 1024);
        final ByteBuffer RobotoRegular = loadResource("demo/nanovg/Roboto-Regular.ttf", 150 * 1024);
        final ByteBuffer RobotoBold = loadResource("demo/nanovg/Roboto-Bold.ttf", 150 * 1024);
        final ByteBuffer NotoEmojiRegular = loadResource("demo/nanovg/NotoEmoji-Regular.ttf", 450 * 1024);

        int fontNormal, fontBold, fontIcons, fontEmoji;

        int[] images = new int[12];
    }

    static final DoubleBuffer mx = BufferUtils.createDoubleBuffer(1), my = BufferUtils.createDoubleBuffer(1);

    static final IntBuffer winWidth = BufferUtils.createIntBuffer(1), winHeight = BufferUtils.createIntBuffer(1);

    static final IntBuffer fbWidth = BufferUtils.createIntBuffer(1), fbHeight = BufferUtils.createIntBuffer(1);

    static final FloatBuffer gpuTimes = BufferUtils.createFloatBuffer(3);

    protected Demo() {
    }

    private static float maxf(float a, float b) {
        return a > b ? a : b;
    }

    private static float clampf(float a, float mn, float mx) {
        return a < mn ? mn : (a > mx ? mx : a);
    }

    private static boolean isBlack(NVGColor col) {
        return col.r() == 0.0f && col.g() == 0.0f && col.b() == 0.0f && col.a() == 0.0f;
    }

    private static ByteBuffer cpToUTF8(int cp) {
        return memUTF8(new String(Character.toChars(cp)), false);
    }

    static NVGColor rgba(int r, int g, int b, int a, NVGColor color) {
        color.r(r / 255.0f);
        color.g(g / 255.0f);
        color.b(b / 255.0f);
        color.a(a / 255.0f);

        return color;
    }

    private static void drawWindow(long vg, String title, float x, float y, float w, float h) {
        float cornerRadius = 3.0f;
        NVGPaint shadowPaint = paintA;
        NVGPaint headerPaint = paintB;

        nvgSave(vg);
        //nvgClearState(vg);

        // Window
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x, y, w, h, cornerRadius);
        nvgFillColor(vg, rgba(28, 30, 34, 192, colorA));
        //nvgFillColor(vg, rgba(0,0,0,128, color));
        nvgFill(vg);

        // Drop shadow
        nvgBoxGradient(vg, x, y + 2, w, h, cornerRadius * 2, 10, rgba(0, 0, 0, 128, colorA),
                rgba(0, 0, 0, 0, colorB), shadowPaint);
        nvgBeginPath(vg);
        nvgRect(vg, x - 10, y - 10, w + 20, h + 30);
        nvgRoundedRect(vg, x, y, w, h, cornerRadius);
        nvgPathWinding(vg, NVG_HOLE);
        nvgFillPaint(vg, shadowPaint);
        nvgFill(vg);

        // Header
        nvgLinearGradient(vg, x, y, x, y + 15, rgba(255, 255, 255, 8, colorA), rgba(0, 0, 0, 16, colorB),
                headerPaint);
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + 1, y + 1, w - 2, 30, cornerRadius - 1);
        nvgFillPaint(vg, headerPaint);
        nvgFill(vg);
        nvgBeginPath(vg);
        nvgMoveTo(vg, x + 0.5f, y + 0.5f + 30);
        nvgLineTo(vg, x + 0.5f + w - 1, y + 0.5f + 30);
        nvgStrokeColor(vg, rgba(0, 0, 0, 32, colorA));
        nvgStroke(vg);

        nvgFontSize(vg, 18.0f);
        nvgFontFace(vg, "sans-bold");
        nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);

        try (MemoryStack stack = stackPush()) {
            ByteBuffer titleText = stack.ASCII(title, false);

            nvgFontBlur(vg, 2);
            nvgFillColor(vg, rgba(0, 0, 0, 128, colorA));
            nvgText(vg, x + w / 2, y + 16 + 1, titleText);

            nvgFontBlur(vg, 0);
            nvgFillColor(vg, rgba(220, 220, 220, 160, colorA));
            nvgText(vg, x + w / 2, y + 16, titleText);
        }

        nvgRestore(vg);
    }

    private static void drawSearchBox(long vg, String text, float x, float y, float w, float h) {
        NVGPaint bg = paintA;
        float cornerRadius = h / 2 - 1;

        // Edit
        nvgBoxGradient(vg, x, y + 1.5f, w, h, h / 2, 5, rgba(0, 0, 0, 16, colorA), rgba(0, 0, 0, 92, colorB), bg);
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x, y, w, h, cornerRadius);
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        /*nvgBeginPath(vg);
          nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f);
        nvgStrokeColor(vg, rgba(0,0,0,48, colorA));
        nvgStroke(vg);*/

        nvgFontSize(vg, h * 1.3f);
        nvgFontFace(vg, "icons");
        nvgFillColor(vg, rgba(255, 255, 255, 64, colorA));
        nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + h * 0.55f, y + h * 0.55f, ICON_SEARCH);

        nvgFontSize(vg, 20.0f);
        nvgFontFace(vg, "sans");
        nvgFillColor(vg, rgba(255, 255, 255, 32, colorA));

        nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + h * 1.05f, y + h * 0.5f, text);

        nvgFontSize(vg, h * 1.3f);
        nvgFontFace(vg, "icons");
        nvgFillColor(vg, rgba(255, 255, 255, 32, colorA));
        nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + w - h * 0.55f, y + h * 0.55f, ICON_CIRCLED_CROSS);
    }

    private static void drawDropDown(long vg, String text, float x, float y, float w, float h) {
        NVGPaint bg = paintA;
        float cornerRadius = 4.0f;

        nvgLinearGradient(vg, x, y, x, y + h, rgba(255, 255, 255, 16, colorA), rgba(0, 0, 0, 16, colorB), bg);
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + 1, y + 1, w - 2, h - 2, cornerRadius - 1);
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + 0.5f, y + 0.5f, w - 1, h - 1, cornerRadius - 0.5f);
        nvgStrokeColor(vg, rgba(0, 0, 0, 48, colorA));
        nvgStroke(vg);

        nvgFontSize(vg, 20.0f);
        nvgFontFace(vg, "sans");
        nvgFillColor(vg, rgba(255, 255, 255, 160, colorA));
        nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + h * 0.3f, y + h * 0.5f, text);

        nvgFontSize(vg, h * 1.3f);
        nvgFontFace(vg, "icons");
        nvgFillColor(vg, rgba(255, 255, 255, 64, colorA));
        nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + w - h * 0.5f, y + h * 0.5f, ICON_CHEVRON_RIGHT);
    }

    private static void drawLabel(long vg, String text, float x, float y, float w, float h) {
        nvgFontSize(vg, 18.0f);
        nvgFontFace(vg, "sans");
        nvgFillColor(vg, rgba(255, 255, 255, 128, colorA));

        nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
        nvgText(vg, x, y + h * 0.5f, text);
    }

    private static void drawEditBoxBase(long vg, float x, float y, float w, float h) {
        NVGPaint bg = paintA;

        // Edit
        nvgBoxGradient(vg, x + 1, y + 1 + 1.5f, w - 2, h - 2, 3, 4, rgba(255, 255, 255, 32, colorA),
                rgba(32, 32, 32, 32, colorB), bg);
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + 1, y + 1, w - 2, h - 2, 4 - 1);
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + 0.5f, y + 0.5f, w - 1, h - 1, 4 - 0.5f);
        nvgStrokeColor(vg, rgba(0, 0, 0, 48, colorA));
        nvgStroke(vg);
    }

    private static void drawEditBox(long vg, String text, float x, float y, float w, float h) {
        drawEditBoxBase(vg, x, y, w, h);

        nvgFontSize(vg, 20.0f);
        nvgFontFace(vg, "sans");
        nvgFillColor(vg, rgba(255, 255, 255, 64, colorA));
        nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + h * 0.3f, y + h * 0.5f, text);
    }

    private static void drawEditBoxNum(long vg, String text, String units, float x, float y, float w, float h) {

        float uw;

        drawEditBoxBase(vg, x, y, w, h);

        uw = nvgTextBounds(vg, 0, 0, units, (FloatBuffer) null);

        nvgFontSize(vg, 18.0f);
        nvgFontFace(vg, "sans");
        nvgFillColor(vg, rgba(255, 255, 255, 64, colorA));
        nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + w - h * 0.3f, y + h * 0.5f, units);

        nvgFontSize(vg, 20.0f);
        nvgFontFace(vg, "sans");
        nvgFillColor(vg, rgba(255, 255, 255, 128, colorA));
        nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + w - uw - h * 0.5f, y + h * 0.5f, text);
    }

    private static void drawCheckBox(long vg, String text, float x, float y, float w, float h) {
        NVGPaint bg = paintA;

        nvgFontSize(vg, 18.0f);
        nvgFontFace(vg, "sans");
        nvgFillColor(vg, rgba(255, 255, 255, 160, colorA));

        nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + 28, y + h * 0.5f, text);

        nvgBoxGradient(vg, x + 1, y + (int) (h * 0.5f) - 9 + 1, 18, 18, 3, 3, rgba(0, 0, 0, 32, colorA),
                rgba(0, 0, 0, 92, colorB), bg);
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + 1, y + (int) (h * 0.5f) - 9, 18, 18, 3);
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        nvgFontSize(vg, 40);
        nvgFontFace(vg, "icons");
        nvgFillColor(vg, rgba(255, 255, 255, 128, colorA));
        nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
        nvgText(vg, x + 9 + 2, y + h * 0.5f, ICON_CHECK);
    }

    private static void drawButton(long vg, ByteBuffer preicon, String text, float x, float y, float w, float h,
            NVGColor col) {
        NVGPaint bg = paintA;
        float cornerRadius = 4.0f;
        float tw, iw = 0;

        nvgLinearGradient(vg, x, y, x, y + h, rgba(255, 255, 255, isBlack(col) ? 16 : 32, colorB),
                rgba(0, 0, 0, isBlack(col) ? 16 : 32, colorC), bg);
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + 1, y + 1, w - 2, h - 2, cornerRadius - 1);
        if (!isBlack(col)) {
            nvgFillColor(vg, col);
            nvgFill(vg);
        }
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + 0.5f, y + 0.5f, w - 1, h - 1, cornerRadius - 0.5f);
        nvgStrokeColor(vg, rgba(0, 0, 0, 48, colorA));
        nvgStroke(vg);

        try (MemoryStack stack = stackPush()) {
            ByteBuffer textEncoded = stack.ASCII(text, false);

            nvgFontSize(vg, 20.0f);
            nvgFontFace(vg, "sans-bold");
            tw = nvgTextBounds(vg, 0, 0, textEncoded, (FloatBuffer) null);
            if (preicon != null) {
                nvgFontSize(vg, h * 1.3f);
                nvgFontFace(vg, "icons");
                iw = nvgTextBounds(vg, 0, 0, preicon, (FloatBuffer) null);
                iw += h * 0.15f;
            }

            if (preicon != null) {
                nvgFontSize(vg, h * 1.3f);
                nvgFontFace(vg, "icons");
                nvgFillColor(vg, rgba(255, 255, 255, 96, colorA));
                nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
                nvgText(vg, x + w * 0.5f - tw * 0.5f - iw * 0.75f, y + h * 0.5f, preicon);
            }

            nvgFontSize(vg, 20.0f);
            nvgFontFace(vg, "sans-bold");
            nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
            nvgFillColor(vg, rgba(0, 0, 0, 160, colorA));
            nvgText(vg, x + w * 0.5f - tw * 0.5f + iw * 0.25f, y + h * 0.5f - 1, textEncoded);
            nvgFillColor(vg, rgba(255, 255, 255, 160, colorA));
            nvgText(vg, x + w * 0.5f - tw * 0.5f + iw * 0.25f, y + h * 0.5f, textEncoded);
        }
    }

    private static void drawSlider(long vg, float pos, float x, float y, float w, float h) {
        NVGPaint bg = paintA, knob = paintB;
        float cy = y + (int) (h * 0.5f);
        float kr = (int) (h * 0.25f);

        nvgSave(vg);
        //nvgClearState(vg);

        // Slot
        nvgBoxGradient(vg, x, cy - 2 + 1, w, 4, 2, 2, rgba(0, 0, 0, 32, colorA), rgba(0, 0, 0, 128, colorB), bg);
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x, cy - 2, w, 4, 2);
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        // Knob Shadow
        nvgRadialGradient(vg, x + (int) (pos * w), cy + 1, kr - 3, kr + 3, rgba(0, 0, 0, 64, colorA),
                rgba(0, 0, 0, 0, colorB), bg);
        nvgBeginPath(vg);
        nvgRect(vg, x + (int) (pos * w) - kr - 5, cy - kr - 5, kr * 2 + 5 + 5, kr * 2 + 5 + 5 + 3);
        nvgCircle(vg, x + (int) (pos * w), cy, kr);
        nvgPathWinding(vg, NVG_HOLE);
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        // Knob
        nvgLinearGradient(vg, x, cy - kr, x, cy + kr, rgba(255, 255, 255, 16, colorA), rgba(0, 0, 0, 16, colorB),
                knob);
        nvgBeginPath(vg);
        nvgCircle(vg, x + (int) (pos * w), cy, kr - 1);
        nvgFillColor(vg, rgba(40, 43, 48, 255, colorA));
        nvgFill(vg);
        nvgFillPaint(vg, knob);
        nvgFill(vg);

        nvgBeginPath(vg);
        nvgCircle(vg, x + (int) (pos * w), cy, kr - 0.5f);
        nvgStrokeColor(vg, rgba(0, 0, 0, 92, colorA));
        nvgStroke(vg);

        nvgRestore(vg);
    }

    private static void drawEyes(long vg, float x, float y, float w, float h, float mx, float my, float t) {
        NVGPaint gloss = paintA, bg = paintB;
        float ex = w * 0.23f;
        float ey = h * 0.5f;
        float lx = x + ex;
        float ly = y + ey;
        float rx = x + w - ex;
        float ry = y + ey;
        float dx, dy, d;
        float br = (ex < ey ? ex : ey) * 0.5f;
        float blink = 1 - (float) pow((float) sin(t * 0.5f), 200) * 0.8f;

        nvgLinearGradient(vg, x, y + h * 0.5f, x + w * 0.1f, y + h, rgba(0, 0, 0, 32, colorA),
                rgba(0, 0, 0, 16, colorB), bg);
        nvgBeginPath(vg);
        nvgEllipse(vg, lx + 3.0f, ly + 16.0f, ex, ey);
        nvgEllipse(vg, rx + 3.0f, ry + 16.0f, ex, ey);
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        nvgLinearGradient(vg, x, y + h * 0.25f, x + w * 0.1f, y + h, rgba(220, 220, 220, 255, colorA),
                rgba(128, 128, 128, 255, colorB), bg);
        nvgBeginPath(vg);
        nvgEllipse(vg, lx, ly, ex, ey);
        nvgEllipse(vg, rx, ry, ex, ey);
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        dx = (mx - rx) / (ex * 10);
        dy = (my - ry) / (ey * 10);
        d = (float) Math.sqrt(dx * dx + dy * dy);
        if (d > 1.0f) {
            dx /= d;
            dy /= d;
        }
        dx *= ex * 0.4f;
        dy *= ey * 0.5f;
        nvgBeginPath(vg);
        nvgEllipse(vg, lx + dx, ly + dy + ey * 0.25f * (1 - blink), br, br * blink);
        nvgFillColor(vg, rgba(32, 32, 32, 255, colorA));
        nvgFill(vg);

        dx = (mx - rx) / (ex * 10);
        dy = (my - ry) / (ey * 10);
        d = (float) Math.sqrt(dx * dx + dy * dy);
        if (d > 1.0f) {
            dx /= d;
            dy /= d;
        }
        dx *= ex * 0.4f;
        dy *= ey * 0.5f;
        nvgBeginPath(vg);
        nvgEllipse(vg, rx + dx, ry + dy + ey * 0.25f * (1 - blink), br, br * blink);
        nvgFillColor(vg, rgba(32, 32, 32, 255, colorA));
        nvgFill(vg);

        nvgRadialGradient(vg, lx - ex * 0.25f, ly - ey * 0.5f, ex * 0.1f, ex * 0.75f,
                rgba(255, 255, 255, 128, colorA), rgba(255, 255, 255, 0, colorB), gloss);
        nvgBeginPath(vg);
        nvgEllipse(vg, lx, ly, ex, ey);
        nvgFillPaint(vg, gloss);
        nvgFill(vg);

        nvgRadialGradient(vg, rx - ex * 0.25f, ry - ey * 0.5f, ex * 0.1f, ex * 0.75f,
                rgba(255, 255, 255, 128, colorA), rgba(255, 255, 255, 0, colorB), gloss);
        nvgBeginPath(vg);
        nvgEllipse(vg, rx, ry, ex, ey);
        nvgFillPaint(vg, gloss);
        nvgFill(vg);
    }

    private static final float[] samples = new float[6], sx = new float[6], sy = new float[6];

    private static void drawGraph(long vg, float x, float y, float w, float h, float t) {
        NVGPaint bg = paintA;

        float dx = w / 5.0f;
        int i;

        samples[0] = (1 + (float) sin(t * 1.2345f + (float) cos(t * 0.33457f) * 0.44f)) * 0.5f;
        samples[1] = (1 + (float) sin(t * 0.68363f + (float) cos(t * 1.3f) * 1.55f)) * 0.5f;
        samples[2] = (1 + (float) sin(t * 1.1642f + (float) cos(t * 0.33457) * 1.24f)) * 0.5f;
        samples[3] = (1 + (float) sin(t * 0.56345f + (float) cos(t * 1.63f) * 0.14f)) * 0.5f;
        samples[4] = (1 + (float) sin(t * 1.6245f + (float) cos(t * 0.254f) * 0.3f)) * 0.5f;
        samples[5] = (1 + (float) sin(t * 0.345f + (float) cos(t * 0.03f) * 0.6f)) * 0.5f;

        for (i = 0; i < 6; i++) {
            sx[i] = x + i * dx;
            sy[i] = y + h * samples[i] * 0.8f;
        }

        // Graph background
        nvgLinearGradient(vg, x, y, x, y + h, rgba(0, 160, 192, 0, colorA), rgba(0, 160, 192, 64, colorB), bg);
        nvgBeginPath(vg);
        nvgMoveTo(vg, sx[0], sy[0]);
        for (i = 1; i < 6; i++) {
            nvgBezierTo(vg, sx[i - 1] + dx * 0.5f, sy[i - 1], sx[i] - dx * 0.5f, sy[i], sx[i], sy[i]);
        }
        nvgLineTo(vg, x + w, y + h);
        nvgLineTo(vg, x, y + h);
        nvgFillPaint(vg, bg);
        nvgFill(vg);

        // Graph line
        nvgBeginPath(vg);
        nvgMoveTo(vg, sx[0], sy[0] + 2);
        for (i = 1; i < 6; i++) {
            nvgBezierTo(vg, sx[i - 1] + dx * 0.5f, sy[i - 1] + 2, sx[i] - dx * 0.5f, sy[i] + 2, sx[i], sy[i] + 2);
        }
        nvgStrokeColor(vg, rgba(0, 0, 0, 32, colorA));
        nvgStrokeWidth(vg, 3.0f);
        nvgStroke(vg);

        nvgBeginPath(vg);
        nvgMoveTo(vg, sx[0], sy[0]);
        for (i = 1; i < 6; i++) {
            nvgBezierTo(vg, sx[i - 1] + dx * 0.5f, sy[i - 1], sx[i] - dx * 0.5f, sy[i], sx[i], sy[i]);
        }
        nvgStrokeColor(vg, rgba(0, 160, 192, 255, colorA));
        nvgStrokeWidth(vg, 3.0f);
        nvgStroke(vg);

        // Graph sample pos
        for (i = 0; i < 6; i++) {
            nvgRadialGradient(vg, sx[i], sy[i] + 2, 3.0f, 8.0f, rgba(0, 0, 0, 32, colorA), rgba(0, 0, 0, 0, colorB),
                    bg);
            nvgBeginPath(vg);
            nvgRect(vg, sx[i] - 10, sy[i] - 10 + 2, 20, 20);
            nvgFillPaint(vg, bg);
            nvgFill(vg);
        }

        nvgBeginPath(vg);
        for (i = 0; i < 6; i++) {
            nvgCircle(vg, sx[i], sy[i], 4.0f);
        }
        nvgFillColor(vg, rgba(0, 160, 192, 255, colorA));
        nvgFill(vg);
        nvgBeginPath(vg);
        for (i = 0; i < 6; i++) {
            nvgCircle(vg, sx[i], sy[i], 2.0f);
        }
        nvgFillColor(vg, rgba(220, 220, 220, 255, colorA));
        nvgFill(vg);

        nvgStrokeWidth(vg, 1.0f);
    }

    private static void drawSpinner(long vg, float cx, float cy, float r, float t) {
        float a0 = 0.0f + t * 6;
        float a1 = NVG_PI + t * 6;
        float r0 = r;
        float r1 = r * 0.75f;
        float ax, ay, bx, by;
        NVGPaint paint = paintA;

        nvgSave(vg);

        nvgBeginPath(vg);
        nvgArc(vg, cx, cy, r0, a0, a1, NVG_CW);
        nvgArc(vg, cx, cy, r1, a1, a0, NVG_CCW);
        nvgClosePath(vg);
        ax = cx + (float) cos(a0) * (r0 + r1) * 0.5f;
        ay = cy + (float) sin(a0) * (r0 + r1) * 0.5f;
        bx = cx + (float) cos(a1) * (r0 + r1) * 0.5f;
        by = cy + (float) sin(a1) * (r0 + r1) * 0.5f;
        nvgLinearGradient(vg, ax, ay, bx, by, rgba(0, 0, 0, 0, colorA), rgba(0, 0, 0, 128, colorB), paint);
        nvgFillPaint(vg, paint);
        nvgFill(vg);

        nvgRestore(vg);
    }

    private static void drawThumbnails(long vg, float x, float y, float w, float h, int[] images, int nimages,
            float t) {
        NVGPaint shadowPaint = paintA, imgPaint = paintB, fadePaint = paintC;

        float cornerRadius = 3.0f;
        float thumb = 60.0f;
        float arry = 30.5f;

        float stackh = (nimages / 2) * (thumb + 10) + 10;

        float u = (1 + (float) cos(t * 0.5f)) * 0.5f;
        float u2 = (1 - (float) cos(t * 0.2f)) * 0.5f;

        nvgSave(vg);
        //nvgClearState(vg);

        // Drop shadow
        nvgBoxGradient(vg, x, y + 4, w, h, cornerRadius * 2, 20, rgba(0, 0, 0, 128, colorA),
                rgba(0, 0, 0, 0, colorB), shadowPaint);
        nvgBeginPath(vg);
        nvgRect(vg, x - 10, y - 10, w + 20, h + 30);
        nvgRoundedRect(vg, x, y, w, h, cornerRadius);
        nvgPathWinding(vg, NVG_HOLE);
        nvgFillPaint(vg, shadowPaint);
        nvgFill(vg);

        // Window
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x, y, w, h, cornerRadius);
        nvgMoveTo(vg, x - 10, y + arry);
        nvgLineTo(vg, x + 1, y + arry - 11);
        nvgLineTo(vg, x + 1, y + arry + 11);
        nvgFillColor(vg, rgba(200, 200, 200, 255, colorA));
        nvgFill(vg);

        nvgSave(vg);
        nvgScissor(vg, x, y, w, h);
        nvgTranslate(vg, 0, -(stackh - h) * u);

        float dv = 1.0f / (float) (nimages - 1);

        try (MemoryStack stack = stackPush()) {
            IntBuffer imgw = stack.mallocInt(1), imgh = stack.mallocInt(1);

            for (int i = 0; i < nimages; i++) {
                float tx, ty, v, a;
                tx = x + 10;
                ty = y + 10;
                tx += (i % 2) * (thumb + 10);
                ty += (i / 2) * (thumb + 10);
                nvgImageSize(vg, images[i], imgw, imgh);

                float ix, iy, iw, ih;

                if (imgw.get(0) < imgh.get(0)) {
                    iw = thumb;
                    ih = iw * (float) imgh.get(0) / (float) imgw.get(0);
                    ix = 0;
                    iy = -(ih - thumb) * 0.5f;
                } else {
                    ih = thumb;
                    iw = ih * (float) imgw.get(0) / (float) imgh.get(0);
                    ix = -(iw - thumb) * 0.5f;
                    iy = 0;
                }

                v = i * dv;
                a = clampf((u2 - v) / dv, 0, 1);

                if (a < 1.0f) {
                    drawSpinner(vg, tx + thumb / 2, ty + thumb / 2, thumb * 0.25f, t);
                }

                nvgImagePattern(vg, tx + ix, ty + iy, iw, ih, 0.0f / 180.0f * NVG_PI, images[i], a, imgPaint);
                nvgBeginPath(vg);
                nvgRoundedRect(vg, tx, ty, thumb, thumb, 5);
                nvgFillPaint(vg, imgPaint);
                nvgFill(vg);

                nvgBoxGradient(vg, tx - 1, ty, thumb + 2, thumb + 2, 5, 3, rgba(0, 0, 0, 128, colorA),
                        rgba(0, 0, 0, 0, colorB), shadowPaint);
                nvgBeginPath(vg);
                nvgRect(vg, tx - 5, ty - 5, thumb + 10, thumb + 10);
                nvgRoundedRect(vg, tx, ty, thumb, thumb, 6);
                nvgPathWinding(vg, NVG_HOLE);
                nvgFillPaint(vg, shadowPaint);
                nvgFill(vg);

                nvgBeginPath(vg);
                nvgRoundedRect(vg, tx + 0.5f, ty + 0.5f, thumb - 1, thumb - 1, 4 - 0.5f);
                nvgStrokeWidth(vg, 1.0f);
                nvgStrokeColor(vg, rgba(255, 255, 255, 192, colorA));
                nvgStroke(vg);
            }
        }
        nvgRestore(vg);

        // Hide fades
        nvgLinearGradient(vg, x, y, x, y + 6, rgba(200, 200, 200, 255, colorA), rgba(200, 200, 200, 0, colorB),
                fadePaint);
        nvgBeginPath(vg);
        nvgRect(vg, x + 4, y, w - 8, 6);
        nvgFillPaint(vg, fadePaint);
        nvgFill(vg);

        nvgLinearGradient(vg, x, y + h, x, y + h - 6, rgba(200, 200, 200, 255, colorA),
                rgba(200, 200, 200, 0, colorB), fadePaint);
        nvgBeginPath(vg);
        nvgRect(vg, x + 4, y + h - 6, w - 8, 6);
        nvgFillPaint(vg, fadePaint);
        nvgFill(vg);

        // Scroll bar
        nvgBoxGradient(vg, x + w - 12 + 1, y + 4 + 1, 8, h - 8, 3, 4, rgba(0, 0, 0, 32, colorA),
                rgba(0, 0, 0, 92, colorB), shadowPaint);
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + w - 12, y + 4, 8, h - 8, 3);
        nvgFillPaint(vg, shadowPaint);
        //nvgFillColor(vg, rgba(255,0,0,128, color));
        nvgFill(vg);

        float scrollh = (h / stackh) * (h - 8);
        nvgBoxGradient(vg, x + w - 12 - 1, y + 4 + (h - 8 - scrollh) * u - 1, 8, scrollh, 3, 4,
                rgba(220, 220, 220, 255, colorA), rgba(128, 128, 128, 255, colorB), shadowPaint);
        nvgBeginPath(vg);
        nvgRoundedRect(vg, x + w - 12 + 1, y + 4 + 1 + (h - 8 - scrollh) * u, 8 - 2, scrollh - 2, 2);
        nvgFillPaint(vg, shadowPaint);
        //nvgFillColor(vg, rgba(0,0,0,128, color));
        nvgFill(vg);

        nvgRestore(vg);
    }

    private static void drawColorwheel(long vg, float x, float y, float w, float h, float t) {
        float hue = (float) sin(t * 0.12f);

        NVGPaint paint = paintA;

        nvgSave(vg);

        /*nvgBeginPath(vg);
          nvgRect(vg, x,y,w,h);
        nvgFillColor(vg, rgba(255,0,0,128, colorA));
        nvgFill(vg);*/

        float cx = x + w * 0.5f;
        float cy = y + h * 0.5f;
        float r1 = (w < h ? w : h) * 0.5f - 5.0f;
        float r0 = r1 - 20.0f;
        float aeps = 0.5f / r1; // half a pixel arc length in radians (2pi cancels out).

        for (int i = 0; i < 6; i++) {
            float a0 = (float) i / 6.0f * NVG_PI * 2.0f - aeps;
            float a1 = (i + 1.0f) / 6.0f * NVG_PI * 2.0f + aeps;
            nvgBeginPath(vg);
            nvgArc(vg, cx, cy, r0, a0, a1, NVG_CW);
            nvgArc(vg, cx, cy, r1, a1, a0, NVG_CCW);
            nvgClosePath(vg);
            float ax = cx + (float) cos(a0) * (r0 + r1) * 0.5f;
            float ay = cy + (float) sin(a0) * (r0 + r1) * 0.5f;
            float bx = cx + (float) cos(a1) * (r0 + r1) * 0.5f;
            float by = cy + (float) sin(a1) * (r0 + r1) * 0.5f;
            nvgHSLA(a0 / (NVG_PI * 2), 1.0f, 0.55f, (byte) 255, colorA);
            nvgHSLA(a1 / (NVG_PI * 2), 1.0f, 0.55f, (byte) 255, colorB);
            nvgLinearGradient(vg, ax, ay, bx, by, colorA, colorB, paint);
            nvgFillPaint(vg, paint);
            nvgFill(vg);
        }

        nvgBeginPath(vg);
        nvgCircle(vg, cx, cy, r0 - 0.5f);
        nvgCircle(vg, cx, cy, r1 + 0.5f);
        nvgStrokeColor(vg, rgba(0, 0, 0, 64, colorA));
        nvgStrokeWidth(vg, 1.0f);
        nvgStroke(vg);

        // Selector
        nvgSave(vg);
        nvgTranslate(vg, cx, cy);
        nvgRotate(vg, hue * NVG_PI * 2);

        // Marker on
        nvgStrokeWidth(vg, 2.0f);
        nvgBeginPath(vg);
        nvgRect(vg, r0 - 1, -3, r1 - r0 + 2, 6);
        nvgStrokeColor(vg, rgba(255, 255, 255, 192, colorA));
        nvgStroke(vg);

        nvgBoxGradient(vg, r0 - 3, -5, r1 - r0 + 6, 10, 2, 4, rgba(0, 0, 0, 128, colorA), rgba(0, 0, 0, 0, colorB),
                paint);
        nvgBeginPath(vg);
        nvgRect(vg, r0 - 2 - 10, -4 - 10, r1 - r0 + 4 + 20, 8 + 20);
        nvgRect(vg, r0 - 2, -4, r1 - r0 + 4, 8);
        nvgPathWinding(vg, NVG_HOLE);
        nvgFillPaint(vg, paint);
        nvgFill(vg);

        // Center triangle
        float r = r0 - 6;
        float ax = (float) cos(120.0f / 180.0f * NVG_PI) * r;
        float ay = (float) sin(120.0f / 180.0f * NVG_PI) * r;
        float bx = (float) cos(-120.0f / 180.0f * NVG_PI) * r;
        float by = (float) sin(-120.0f / 180.0f * NVG_PI) * r;
        nvgBeginPath(vg);
        nvgMoveTo(vg, r, 0);
        nvgLineTo(vg, ax, ay);
        nvgLineTo(vg, bx, by);
        nvgClosePath(vg);
        nvgHSLA(hue, 1.0f, 0.5f, (byte) 255, colorA);
        nvgLinearGradient(vg, r, 0, ax, ay, colorA, rgba(255, 255, 255, 255, colorB), paint);
        nvgFillPaint(vg, paint);
        nvgFill(vg);
        nvgLinearGradient(vg, (r + ax) * 0.5f, (0 + ay) * 0.5f, bx, by, rgba(0, 0, 0, 0, colorA),
                rgba(0, 0, 0, 255, colorB), paint);
        nvgFillPaint(vg, paint);
        nvgFill(vg);
        nvgStrokeColor(vg, rgba(0, 0, 0, 64, colorA));
        nvgStroke(vg);

        // Select circle on triangle
        ax = (float) cos(120.0f / 180.0f * NVG_PI) * r * 0.3f;
        ay = (float) sin(120.0f / 180.0f * NVG_PI) * r * 0.4f;
        nvgStrokeWidth(vg, 2.0f);
        nvgBeginPath(vg);
        nvgCircle(vg, ax, ay, 5);
        nvgStrokeColor(vg, rgba(255, 255, 255, 192, colorA));
        nvgStroke(vg);

        nvgRadialGradient(vg, ax, ay, 7, 9, rgba(0, 0, 0, 64, colorA), rgba(0, 0, 0, 0, colorB), paint);
        nvgBeginPath(vg);
        nvgRect(vg, ax - 20, ay - 20, 40, 40);
        nvgCircle(vg, ax, ay, 7);
        nvgPathWinding(vg, NVG_HOLE);
        nvgFillPaint(vg, paint);
        nvgFill(vg);

        nvgRestore(vg);

        nvgRestore(vg);
    }

    private static final float[] pts = new float[4 * 2];

    private static final int[] joins = { NVG_MITER, NVG_ROUND, NVG_BEVEL };
    private static final int[] caps = { NVG_BUTT, NVG_ROUND, NVG_SQUARE };

    private static void drawLines(long vg, float x, float y, float w, float h, float t) {
        float pad = 5.0f, s = w / 9.0f - pad * 2;

        nvgSave(vg);
        drawLinesPoints(pts, s, t);

        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                float fx = x + s * 0.5f + (i * 3 + j) / 9.0f * w + pad;
                float fy = y - s * 0.5f + pad;

                nvgLineCap(vg, caps[i]);
                nvgLineJoin(vg, joins[j]);

                nvgStrokeWidth(vg, s * 0.3f);
                drawLinesLine(vg, fx, fy);
            }
        }

        nvgRestore(vg);
    }

    private static void drawLinesPoints(float[] pts, float s, float t) {
        pts[0] = -s * 0.25f + (float) cos(t * 0.3f) * s * 0.5f;
        pts[1] = (float) sin(t * 0.3f) * s * 0.5f;
        pts[2] = -s * 0.25f;
        pts[3] = 0;
        pts[4] = s * 0.25f;
        pts[5] = 0;
        pts[6] = s * 0.25f + (float) cos(-t * 0.3f) * s * 0.5f;
        pts[7] = (float) sin(-t * 0.3f) * s * 0.5f;
    }

    private static void drawLinesLine(long vg, float fx, float fy) {
        float[] pts = Demo.pts;

        nvgStrokeColor(vg, rgba(0, 0, 0, 160, colorA));
        nvgBeginPath(vg);
        nvgMoveTo(vg, fx + pts[0], fy + pts[1]);
        nvgLineTo(vg, fx + pts[2], fy + pts[3]);
        nvgLineTo(vg, fx + pts[4], fy + pts[5]);
        nvgLineTo(vg, fx + pts[6], fy + pts[7]);
        nvgStroke(vg);

        nvgLineCap(vg, NVG_BUTT);
        nvgLineJoin(vg, NVG_BEVEL);

        nvgStrokeWidth(vg, 1.0f);
        nvgStrokeColor(vg, rgba(0, 192, 255, 255, colorA));
        nvgBeginPath(vg);
        nvgMoveTo(vg, fx + pts[0], fy + pts[1]);
        nvgLineTo(vg, fx + pts[2], fy + pts[3]);
        nvgLineTo(vg, fx + pts[4], fy + pts[5]);
        nvgLineTo(vg, fx + pts[6], fy + pts[7]);
        nvgStroke(vg);
    }

    static int loadDemoData(long vg, DemoData data) {
        if (vg == NULL) {
            return -1;
        }

        for (int i = 0; i < 12; i++) {
            String file = "demo/nanovg/images/image" + (i + 1) + ".jpg";
            ByteBuffer img = loadResource(file, 32 * 1024);
            data.images[i] = nvgCreateImageMem(vg, 0, img);
            if (data.images[i] == 0) {
                System.err.format("Could not load %s.\n", file);
                return -1;
            }
        }

        data.fontIcons = nvgCreateFontMem(vg, "icons", data.entypo, 0);
        if (data.fontIcons == -1) {
            System.err.format("Could not add font icons.\n");
            return -1;
        }
        data.fontNormal = nvgCreateFontMem(vg, "sans", data.RobotoRegular, 0);
        if (data.fontNormal == -1) {
            System.err.format("Could not add font italic.\n");
            return -1;
        }
        data.fontBold = nvgCreateFontMem(vg, "sans-bold", data.RobotoBold, 0);
        if (data.fontBold == -1) {
            System.err.format("Could not add font bold.\n");
            return -1;
        }

        data.fontEmoji = nvgCreateFontMem(vg, "emoji", data.NotoEmojiRegular, 0);
        if (data.fontBold == -1) {
            System.err.format("Could not add font emoji.\n");
            return -1;
        }

        nvgAddFallbackFontId(vg, data.fontNormal, data.fontEmoji);
        nvgAddFallbackFontId(vg, data.fontBold, data.fontEmoji);

        return 0;
    }

    static ByteBuffer loadResource(String resource, int bufferSize) {
        try {
            return ioResourceToByteBuffer(resource, bufferSize);
        } catch (IOException e) {
            throw new RuntimeException("Failed to load resource: " + resource, e);
        }
    }

    static void freeDemoData(long vg, DemoData data) {
        if (vg == NULL) {
            return;
        }

        for (int i = 0; i < 12; i++) {
            nvgDeleteImage(vg, data.images[i]);
        }

        memFree(hoverText);
        memFree(paragraph);

        memFree(ICON_TRASH);
        memFree(ICON_LOGIN);
        memFree(ICON_CHECK);
        memFree(ICON_CHEVRON_RIGHT);
        memFree(ICON_CIRCLED_CROSS);
        memFree(ICON_SEARCH);
    }

    private static void drawParagraph(long vg, float x, float y, float width, float height, float mx, float my) {
        float gx = 0.0f, gy = 0.0f;

        int gutter = 0;

        nvgSave(vg);

        nvgFontSize(vg, 18.0f);
        nvgFontFace(vg, "sans");
        nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
        nvgTextMetrics(vg, null, null, lineh);

        // The text break API can be used to fill a large buffer of rows,
        // or to iterate over the text just few lines (or just one) at a time.
        // The "next" variable of the last returned item tells where to continue.
        long start = memAddress(paragraph);
        long end = start + paragraph.remaining();
        int nrows, lnum = 0;
        while ((nrows = nnvgTextBreakLines(vg, start, end, width, memAddress(rows), 3)) != 0) {
            for (int i = 0; i < nrows; i++) {
                NVGTextRow row = rows.get(i);
                boolean hit = mx > x && mx < (x + width) && my >= y && my < (y + lineh.get(0));

                nvgBeginPath(vg);
                nvgFillColor(vg, rgba(255, 255, 255, hit ? 64 : 16, colorA));
                nvgRect(vg, x, y, row.width(), lineh.get(0));
                nvgFill(vg);

                nvgFillColor(vg, rgba(255, 255, 255, 255, colorA));
                nnvgText(vg, x, y, row.start(), row.end());

                if (hit) {
                    drawCaret(vg, row, lineh.get(0), x, y, mx);

                    gutter = lnum + 1;
                    gx = x - 10;
                    gy = y + lineh.get(0) / 2;
                }
                lnum++;
                y += lineh.get(0);
            }
            // Keep going...
            start = rows.get(nrows - 1).next();
        }

        if (gutter != 0) {
            drawGutter(vg, gutter, gx, gy, bounds);
        }

        y += 20.0f;

        nvgFontSize(vg, 13.0f);
        nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
        nvgTextLineHeight(vg, 1.2f);

        nvgTextBoxBounds(vg, x, y, 150, hoverText, bounds);

        // Fade the tooltip out when close to it.
        gx = abs((mx - (bounds.get(0) + bounds.get(2)) * 0.5f) / (bounds.get(0) - bounds.get(2)));
        gy = abs((my - (bounds.get(1) + bounds.get(3)) * 0.5f) / (bounds.get(1) - bounds.get(3)));
        float a = maxf(gx, gy) - 0.5f;
        a = clampf(a, 0, 1);
        nvgGlobalAlpha(vg, a);

        nvgBeginPath(vg);
        nvgFillColor(vg, rgba(220, 220, 220, 255, colorA));
        nvgRoundedRect(vg, bounds.get(0) - 2, bounds.get(1) - 2, (int) (bounds.get(2) - bounds.get(0)) + 4,
                (int) (bounds.get(3) - bounds.get(1)) + 4, 3);
        int px = (int) ((bounds.get(2) + bounds.get(0)) / 2);
        nvgMoveTo(vg, px, bounds.get(1) - 10);
        nvgLineTo(vg, px + 7, bounds.get(1) + 1);
        nvgLineTo(vg, px - 7, bounds.get(1) + 1);
        nvgFill(vg);

        nvgFillColor(vg, rgba(0, 0, 0, 220, colorA));
        nvgTextBox(vg, x, y, 150, hoverText);

        nvgRestore(vg);
    }

    private static void drawCaret(long vg, NVGTextRow row, float lineh, float x, float y, float mx) {
        float caretx = (mx < x + row.width() / 2) ? x : x + row.width();

        float px = x;

        int nglyphs = nnvgTextGlyphPositions(vg, x, y, row.start(), row.end(), memAddress(glyphs), 100);
        for (int j = 0; j < nglyphs; j++) {
            NVGGlyphPosition glyphPosition = glyphs.get(j);

            float x0 = glyphPosition.x();
            float x1 = (j + 1 < nglyphs) ? glyphs.get(j + 1).x() : x + row.width();
            float gx2 = x0 * 0.3f + x1 * 0.7f;

            if (mx >= px && mx < gx2) {
                caretx = glyphPosition.x();
            }
            px = gx2;
        }
        nvgBeginPath(vg);
        nvgFillColor(vg, rgba(255, 192, 0, 255, colorA));
        nvgRect(vg, caretx, y, 1, lineh);
        nvgFill(vg);
    }

    private static void drawGutter(long vg, int gutter, float gx, float gy, FloatBuffer bounds) {
        String txt = Integer.toString(gutter);

        nvgFontSize(vg, 13.0f);
        nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);

        nvgTextBounds(vg, gx, gy, txt, bounds);

        nvgBeginPath(vg);
        nvgFillColor(vg, rgba(255, 192, 0, 255, colorA));
        nvgRoundedRect(vg, (int) bounds.get(0) - 4, (int) bounds.get(1) - 2,
                (int) (bounds.get(2) - bounds.get(0)) + 8, (int) (bounds.get(3) - bounds.get(1)) + 4,
                ((int) (bounds.get(3) - bounds.get(1)) + 4) / 2 - 1);
        nvgFill(vg);

        nvgFillColor(vg, rgba(32, 32, 32, 255, colorA));
        nvgText(vg, gx, gy, txt);
    }

    private static void drawWidths(long vg, float x, float y, float width) {
        nvgSave(vg);

        nvgStrokeColor(vg, rgba(0, 0, 0, 255, colorA));

        for (int i = 0; i < 20; i++) {
            float w = (i + 0.5f) * 0.1f;
            nvgStrokeWidth(vg, w);
            nvgBeginPath(vg);
            nvgMoveTo(vg, x, y);
            nvgLineTo(vg, x + width, y + width * 0.3f);
            nvgStroke(vg);
            y += 10;
        }

        nvgRestore(vg);
    }

    private static void drawCaps(long vg, float x, float y, float width) {
        int[] caps = { NVG_BUTT, NVG_ROUND, NVG_SQUARE };

        float lineWidth = 8.0f;

        nvgSave(vg);

        nvgBeginPath(vg);
        nvgRect(vg, x - lineWidth / 2, y, width + lineWidth, 40);
        nvgFillColor(vg, rgba(255, 255, 255, 32, colorA));
        nvgFill(vg);

        nvgBeginPath(vg);
        nvgRect(vg, x, y, width, 40);
        nvgFillColor(vg, rgba(255, 255, 255, 32, colorA));
        nvgFill(vg);

        nvgStrokeWidth(vg, lineWidth);
        for (int i = 0; i < 3; i++) {
            nvgLineCap(vg, caps[i]);
            nvgStrokeColor(vg, rgba(0, 0, 0, 255, colorA));
            nvgBeginPath(vg);
            nvgMoveTo(vg, x, y + i * 10 + 5);
            nvgLineTo(vg, x + width, y + i * 10 + 5);
            nvgStroke(vg);
        }

        nvgRestore(vg);
    }

    private static void drawScissor(long vg, float x, float y, float t) {
        nvgSave(vg);

        // Draw first rect and set scissor to it's area.
        nvgTranslate(vg, x, y);
        nvgRotate(vg, nvgDegToRad(5));
        nvgBeginPath(vg);
        nvgRect(vg, -20, -20, 60, 40);
        nvgFillColor(vg, rgba(255, 0, 0, 255, colorA));
        nvgFill(vg);
        nvgScissor(vg, -20, -20, 60, 40);

        // Draw second rectangle with offset and rotation.
        nvgTranslate(vg, 40, 0);
        nvgRotate(vg, t);

        // Draw the intended second rectangle without any scissoring.
        nvgSave(vg);
        nvgResetScissor(vg);
        nvgBeginPath(vg);
        nvgRect(vg, -20, -10, 60, 30);
        nvgFillColor(vg, rgba(255, 128, 0, 64, colorA));
        nvgFill(vg);
        nvgRestore(vg);

        // Draw second rectangle with combined scissoring.
        nvgIntersectScissor(vg, -20, -10, 60, 30);
        nvgBeginPath(vg);
        nvgRect(vg, -20, -10, 60, 30);
        nvgFillColor(vg, rgba(255, 128, 0, 255, colorA));
        nvgFill(vg);

        nvgRestore(vg);
    }

    static void renderDemo(long vg, float mx, float my, float width, float height, float t, boolean blowup,
            DemoData data) {
        float x, y, popy;

        drawEyes(vg, width - 250, 50, 150, 100, mx, my, t);
        drawParagraph(vg, width - 450, 50, 150, 100, mx, my);
        drawGraph(vg, 0, height / 2, width, height / 2, t);
        drawColorwheel(vg, width - 300, height - 300, 250.0f, 250.0f, t);

        // Line joints
        drawLines(vg, 120, height - 50, 600, 50, t);

        // Line caps
        drawWidths(vg, 10, 50, 30);

        // Line caps
        drawCaps(vg, 10, 300, 30);

        drawScissor(vg, 50, height - 80, t);

        nvgSave(vg);
        if (blowup) {
            nvgRotate(vg, (float) sin(t * 0.3f) * 5.0f / 180.0f * NVG_PI);
            nvgScale(vg, 2.0f, 2.0f);
        }

        // Widgets
        drawWindow(vg, "Widgets `n Stuff", 50, 50, 300, 400);
        x = 60;
        y = 95;
        drawSearchBox(vg, "Search", x, y, 280, 25);
        y += 40;
        drawDropDown(vg, "Effects", x, y, 280, 28);
        popy = y + 14;
        y += 45;

        // Form
        drawLabel(vg, "Login", x, y, 280, 20);
        y += 25;
        drawEditBox(vg, "Email", x, y, 280, 28);
        y += 35;
        drawEditBox(vg, "Password", x, y, 280, 28);
        y += 38;
        drawCheckBox(vg, "Remember me", x, y, 140, 28);
        drawButton(vg, ICON_LOGIN, "Sign in", x + 138, y, 140, 28, rgba(0, 96, 128, 255, colorA));
        y += 45;

        // Slider
        drawLabel(vg, "Diameter", x, y, 280, 20);
        y += 25;
        drawEditBoxNum(vg, "123.00", "px", x + 180, y, 100, 28);
        drawSlider(vg, 0.4f, x, y, 170, 28);
        y += 55;

        drawButton(vg, ICON_TRASH, "Delete", x, y, 160, 28, rgba(128, 16, 8, 255, colorA));
        drawButton(vg, null, "Cancel", x + 170, y, 110, 28, rgba(0, 0, 0, 0, colorA));

        // Thumbnails box
        drawThumbnails(vg, 365, popy - 30, 160, 300, data.images, 12, t);

        nvgRestore(vg);
    }

    private static int mini(int a, int b) {
        return a < b ? a : b;
    }

    private static void unpremultiplyAlpha(ByteBuffer image, int w, int h, int stride) {
        int x, y;

        // Unpremultiply
        for (y = 0; y < h; y++) {
            int row = y * stride;
            for (x = 0; x < w; x++) {
                int r = image.get(row + 0), g = image.get(row + 1), b = image.get(row + 2), a = image.get(row + 3);
                if (a != 0) {
                    image.put(row + 0, (byte) mini(r * 255 / a, 255));
                    image.put(row + 1, (byte) mini(g * 255 / a, 255));
                    image.put(row + 2, (byte) mini(b * 255 / a, 255));
                }
                row += 4;
            }
        }

        // Defringe
        for (y = 0; y < h; y++) {
            int row = y * stride;
            for (x = 0; x < w; x++) {
                int r = 0, g = 0, b = 0, a = image.get(row + 3), n = 0;
                if (a == 0) {
                    if (x - 1 > 0 && image.get(row - 1) != 0) {
                        r += image.get(row - 4);
                        g += image.get(row - 3);
                        b += image.get(row - 2);
                        n++;
                    }
                    if (x + 1 < w && image.get(row + 7) != 0) {
                        r += image.get(row + 4);
                        g += image.get(row + 5);
                        b += image.get(row + 6);
                        n++;
                    }
                    if (y - 1 > 0 && image.get(row - stride + 3) != 0) {
                        r += image.get(row - stride);
                        g += image.get(row - stride + 1);
                        b += image.get(row - stride + 2);
                        n++;
                    }
                    if (y + 1 < h && image.get(row + stride + 3) != 0) {
                        r += image.get(row + stride);
                        g += image.get(row + stride + 1);
                        b += image.get(row + stride + 2);
                        n++;
                    }
                    if (n > 0) {
                        image.put(row + 0, (byte) (r / n));
                        image.put(row + 1, (byte) (g / n));
                        image.put(row + 2, (byte) (b / n));
                    }
                }
                row += 4;
            }
        }
    }

    private static void setAlpha(ByteBuffer image, int w, int h, int stride, byte a) {
        int x, y;
        for (y = 0; y < h; y++) {
            int row = y * stride;
            for (x = 0; x < w; x++) {
                image.put(row + x * 4 + 3, a);
            }
        }
    }

    private static void flipHorizontal(ByteBuffer image, int w, int h, int stride) {
        int i = 0, j = h - 1, k;
        while (i < j) {
            int ri = i * stride;
            int rj = j * stride;
            for (k = 0; k < w * 4; k++) {
                byte t = image.get(ri + k);
                image.put(ri + k, image.get(rj + k));
                image.put(rj + k, t);
            }
            i++;
            j--;
        }
    }

    static void saveScreenShot(int w, int h, boolean premult, String name) {
        ByteBuffer image = memAlloc(w * h * 4);

        // TODO: Make this work for GLES
        glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image);
        if (premult) {
            unpremultiplyAlpha(image, w, h, w * 4);
        } else {
            setAlpha(image, w, h, w * 4, (byte) 255);
        }
        flipHorizontal(image, w, h, w * 4);
        stbi_write_png(name, w, h, 4, image, w * 4);
        memFree(image);
    }

    // PERF

    static final int GRAPH_RENDER_FPS = 0, GRAPH_RENDER_MS = 1, GRAPH_RENDER_PERCENT = 2;

    private static final int GRAPH_HISTORY_COUNT = 100;

    private static final int GPU_QUERY_COUNT = 5;

    static class PerfGraph {
        int style;
        ByteBuffer name = BufferUtils.createByteBuffer(32);
        float[] values = new float[GRAPH_HISTORY_COUNT];
        int head;
    }

    static class GPUtimer {
        boolean supported;
        int cur, ret;
        IntBuffer queries = BufferUtils.createIntBuffer(GPU_QUERY_COUNT);
    }

    // TODO: move to implementation
    static void initGPUTimer(GPUtimer timer) {
        //memset(timer, 0, sizeof(*timer));
        timer.supported = GL.getCapabilities().GL_ARB_timer_query;
        timer.cur = 0;
        timer.ret = 0;
        BufferUtils.zeroBuffer(timer.queries);

        if (timer.supported) {
            glGenQueries(timer.queries);
        }
    }

    static void startGPUTimer(GPUtimer timer) {
        if (!timer.supported) {
            return;
        }
        glBeginQuery(GL_TIME_ELAPSED, timer.queries.get(timer.cur % GPU_QUERY_COUNT));
        timer.cur++;
    }

    static int stopGPUTimer(GPUtimer timer, FloatBuffer times, int maxTimes) {
        int n = 0;
        if (!timer.supported) {
            return 0;
        }

        glEndQuery(GL_TIME_ELAPSED);

        try (MemoryStack stack = stackPush()) {
            IntBuffer available = stack.ints(1);
            while (available.get(0) != 0 && timer.ret <= timer.cur) {
                // check for results if there are any
                glGetQueryObjectiv(timer.queries.get(timer.ret % GPU_QUERY_COUNT), GL_QUERY_RESULT_AVAILABLE,
                        available);
                if (available.get(0) != 0) {
                    LongBuffer timeElapsed = stack.mallocLong(1);
                    glGetQueryObjectui64v(timer.queries.get(timer.ret % GPU_QUERY_COUNT), GL_QUERY_RESULT,
                            timeElapsed);
                    timer.ret++;
                    if (n < maxTimes) {
                        times.put(n, (float) ((double) timeElapsed.get(0) * 1e-9));
                        n++;
                    }
                }
            }
        }
        return n;
    }

    static void initGraph(PerfGraph fps, int style, String name) {
        fps.style = style;
        memUTF8(name, false, fps.name);
        Arrays.fill(fps.values, 0);
        fps.head = 0;
    }

    static void updateGraph(PerfGraph fps, float frameTime) {
        fps.head = (fps.head + 1) % GRAPH_HISTORY_COUNT;
        fps.values[fps.head] = frameTime;
    }

    static float getGraphAverage(PerfGraph fps) {
        float avg = 0;
        for (int i = 0; i < GRAPH_HISTORY_COUNT; i++) {
            avg += fps.values[i];
        }
        return avg / (float) GRAPH_HISTORY_COUNT;
    }

    static void renderGraph(long vg, float x, float y, PerfGraph fps) {
        float avg = getGraphAverage(fps);

        int w = 200;
        int h = 35;

        nvgBeginPath(vg);
        nvgRect(vg, x, y, w, h);
        nvgFillColor(vg, rgba(0, 0, 0, 128, colorA));
        nvgFill(vg);

        nvgBeginPath(vg);
        nvgMoveTo(vg, x, y + h);
        if (fps.style == GRAPH_RENDER_FPS) {
            for (int i = 0; i < GRAPH_HISTORY_COUNT; i++) {
                float v = 1.0f / (0.00001f + fps.values[(fps.head + i) % GRAPH_HISTORY_COUNT]);
                float vx, vy;
                if (v > 1000.0f) {
                    v = 1000.0f;
                }
                vx = x + ((float) i / (GRAPH_HISTORY_COUNT - 1)) * w;
                vy = y + h - ((v / 1000.0f) * h);
                nvgLineTo(vg, vx, vy);
            }
        } else if (fps.style == GRAPH_RENDER_PERCENT) {
            for (int i = 0; i < GRAPH_HISTORY_COUNT; i++) {
                float v = fps.values[(fps.head + i) % GRAPH_HISTORY_COUNT] * 1.0f;
                float vx, vy;
                if (v > 100.0f) {
                    v = 100.0f;
                }
                vx = x + ((float) i / (GRAPH_HISTORY_COUNT - 1)) * w;
                vy = y + h - ((v / 100.0f) * h);
                nvgLineTo(vg, vx, vy);
            }
        } else {
            for (int i = 0; i < GRAPH_HISTORY_COUNT; i++) {
                float v = fps.values[(fps.head + i) % GRAPH_HISTORY_COUNT] * 1000.0f;
                float vx, vy;
                if (v > 4.0f) {
                    v = 4.0f;
                }
                vx = x + ((float) i / (GRAPH_HISTORY_COUNT - 1)) * w;
                vy = y + h - ((v / 4.0f) * h);
                nvgLineTo(vg, vx, vy);
            }
        }
        nvgLineTo(vg, x + w, y + h);
        nvgFillColor(vg, rgba(255, 192, 0, 128, colorA));
        nvgFill(vg);

        nvgFontFace(vg, "sans");

        if (fps.name.get(0) != '\0') {
            nvgFontSize(vg, 14.0f);
            nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
            nvgFillColor(vg, rgba(240, 240, 240, 192, colorA));
            nvgText(vg, x + 3, y + 1, fps.name);
        }

        if (fps.style == GRAPH_RENDER_FPS) {
            nvgFontSize(vg, 18.0f);
            nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
            nvgFillColor(vg, rgba(240, 240, 240, 255, colorA));
            nvgText(vg, x + w - 3, y + 1, String.format("%.2f FPS", 1.0f / avg));

            nvgFontSize(vg, 15.0f);
            nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM);
            nvgFillColor(vg, rgba(240, 240, 240, 160, colorA));
            nvgText(vg, x + w - 3, y + h - 1, String.format("%.2f ms", avg * 1000.0f));
        } else if (fps.style == GRAPH_RENDER_PERCENT) {
            nvgFontSize(vg, 18.0f);
            nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
            nvgFillColor(vg, rgba(240, 240, 240, 255, colorA));
            nvgText(vg, x + w - 3, y + 1, String.format("%.1f %%", avg * 1.0f));
        } else {
            nvgFontSize(vg, 18.0f);
            nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
            nvgFillColor(vg, rgba(240, 240, 240, 255, colorA));
            nvgText(vg, x + w - 3, y + 1, String.format("%.2f ms", avg * 1000.0f));
        }
    }

}