Exemple #1
0
	void NanoInk::caretCoords(size_t index, float& caretX, float& caretY, float& caretHeight)
	{
		float lineh = 0.f;
		nvgTextMetrics(mCtx, NULL, NULL, &lineh);

		const char* start = mFrame.widget().label().c_str();

		float x = skin().padding()[DIM_X];
		float y = skin().padding()[DIM_Y];
		float pwidth = mFrame.pwidth();

		for(NVGtextRow& row : mTextRows)
		{
			if(index <= row.end - start)
			{
				NVGglyphPosition position;
				nvgTextGlyphPosition(mCtx, 0.f, 0.f, row.start, row.end, index - (row.start - start), &position);
				caretX = x + position.x;
				caretY = y;
				nvgTextMetrics(mCtx, nullptr, nullptr, &caretHeight);
				return;
			}
			y += lineh;
		}

		caretX = 0.f;
		caretY = y;
		return;

		NVGglyphPosition position;
		nvgTextGlyphPosition(mCtx, 0.f, 0.f, mFrame.widget().label().c_str(), nullptr, index, &position);
		caretX = skin().padding()[DIM_X] + position.x;
		caretY = skin().padding()[DIM_Y] + 0.f;
		nvgTextMetrics(mCtx, nullptr, nullptr, &caretHeight);
	}
Exemple #2
0
	size_t NanoInk::caretIndex(float posX, float posY)
	{
		const char* start = mFrame.widget().label().c_str();
		const char* end = start + mFrame.widget().label().size();

		float pwidth = mFrame.pwidth();
		float x = skin().padding()[DIM_X];
		float y = skin().padding()[DIM_Y];

		float lineh = 0.f;
		nvgTextMetrics(mCtx, NULL, NULL, &lineh);

		for(NVGtextRow& row : mTextRows)
		{
			if(posY < y + lineh)
			{
				NVGalign halign = NVG_ALIGN_LEFT;
				if(halign & NVG_ALIGN_LEFT)
					x = x;
				else if(halign & NVG_ALIGN_CENTER)
					x = x + pwidth*0.5f - row.width*0.5f;
				else if(halign & NVG_ALIGN_RIGHT)
					x = x + pwidth - row.width;

				return nvgTextGlyphIndex(mCtx, x, y, row.start, row.end, posX) + row.start - start;
			}
			y += lineh;
		}

		return end - start;

		//return nvgTextGlyphIndex(mCtx, skin().padding()[DIM_X], skin().padding()[DIM_Y], mFrame.widget().label().c_str(), nullptr, posX);
	}
Exemple #3
0
	float NanoInk::contentSize(Dimension dim)
	{
		if(mImage)
		{
			return dim == DIM_X ? float(mImage->d_width) : float(mImage->d_height);
		}
		else if(skin().textColour().a() != 0.f)
		{
			float bounds[4];
			float height;

			this->setupText();
			nvgTextBounds(mCtx, 0.f, 0.f, mFrame.widget().label().c_str(), nullptr, bounds);
			nvgTextMetrics(mCtx, nullptr, nullptr, &height);

			return dim == DIM_X ? bounds[2] - bounds[0] : height;
		}
		else if(!skin().imageSkin().null())
		{
			if(skin().imageSkin().d_stretch == DIM_X)
				return skin().imageSkin().d_height;
			else if(skin().imageSkin().d_stretch == DIM_Y)
				return skin().imageSkin().d_width;
		}
		
		return 0.f;
	}
    float getTextHeight(Skin::SkinClass::Attributes &skinClass, const std::string& text) {
      nvgFontSize(m_vg, skinClass.textSize);
      nvgFontFace(m_vg, skinClass.font.c_str());

      float ascender, descender, lineh;
      nvgTextMetrics(m_vg, &ascender, &descender, &lineh);
      return lineh;
    }
Exemple #5
0
void Console::draw(NVGcontext* ctx){
    Widget::draw(ctx);  

    //Screen *sc = dynamic_cast<Screen *>(this->window()->parent());

    // Draw rounded rectangel around text area ////////////////////
    NVGpaint paint = nvgBoxGradient(
        ctx, mPos.x() + 1, mPos.y() + 1,
        mSize.x()-2, mSize.y(), 3, 4, Color(0, 32), Color(0, 92));
    nvgBeginPath(ctx);
    nvgRoundedRect(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y(), 3);
    nvgFillPaint(ctx, paint);
    nvgFill(ctx);
    ///////////////////////////////////////////////////////////////
    


    nvgFontSize(ctx, 18.0f);
    nvgFontFace(ctx, "sans");
    nvgTextAlign(ctx, NVG_ALIGN_LEFT|NVG_ALIGN_TOP);
    float x, y, linew, lineh;
    nvgTextMetrics(ctx, nullptr, nullptr, &lineh);
    x = mPos.x();
    y = mPos.y();
    linew = mSize.x();
    
    mNumRows = mSize.y()/lineh ; // make sure that the size of console is updated
    //setSize(Vector2i(mSize.x(), nrows*lineh)); // this code does not work. It
    //changes size each frame. Move from draw into one time function.
    
    // init console output 
    if(mInit) {
        initBuffer(ctx, linew);
        mInit = false;
    }
    //typedef std::deque<std::string> Buffer_t;

    //temp solution: when buffer is not full. Move this to updateFunction.
    for(auto it = mBuffer.begin();it!=mBuffer.end();++it) {

        nvgBeginPath(ctx);
        nvgFillColor(ctx, nvgRGBA(255,255,255,16));
        nvgRect(ctx, x, y, linew, lineh);
        nvgFill(ctx);

        nvgFillColor(ctx, nvgRGBA(255,255,255,255));
        nvgText(ctx, x, y, it->c_str(), nullptr);
        
        y += lineh;
    }
   
    updateCursor(ctx, lineh);
    //update cursor and draw
    //mCaret.onClick(ctx, lineh, mMouseDownPos - mPos);
    mCaret.draw(ctx, lineh, mPos);
}
Exemple #6
0
static void
draw_text(NVGcontext *ctx, float x, float y, const gui_byte *c,
    const gui_char *string, gui_size len)
{
    gui_float height = 0;
    nvgBeginPath(ctx);
    nvgTextMetrics(ctx, NULL, NULL, &height);
    nvgFillColor(ctx, nvgRGBA(c[0], c[1], c[2], c[3]));
    nvgText(ctx, x, y + height, string, &string[len]);
    nvgFill(ctx);
}
Exemple #7
0
void TextNode::getTextMetrics(float& ascender, float& descender, float& lineHeight) const
{
    NVGcontext& nanoVgContext = Application::getNanoVgContext();
    nvgSave(&nanoVgContext);

    setupContext(nanoVgContext);

    nvgTextMetrics(&nanoVgContext, &ascender, &descender, &lineHeight);

    nvgRestore(&nanoVgContext);
}
Exemple #8
0
void Caret::updatePosFromIdx(NVGcontext *ctx) {
    
    float lineh;
    nvgTextMetrics(ctx, nullptr, nullptr, &lineh);
    const int maxGlyphs = 1024;
    NVGglyphPosition glyphs[maxGlyphs];
    const std::string& textLine = mConsole->mBuffer[mIdx.y()];
    float textBound[4];

    nvgTextBounds(ctx, 0, 0, textLine.c_str(), nullptr, textBound);
    int nglyphs = nvgTextGlyphPositions(ctx, 0, 0, textLine.c_str(),
            nullptr, glyphs, maxGlyphs);

    //update carete position now
    mPos.y() = mIdx.y() * lineh;
    mPos.x() = cursorIndex2Position(mIdx.x(), textBound[2], glyphs, nglyphs);
}
Exemple #9
0
int
main(int argc, char *argv[])
{
    /* Platform */
    int width, height;
    const char *font_path;
    gui_size font_height;
    SDL_Window *win;
    SDL_GLContext glContext;
    NVGcontext *vg = NULL;
    unsigned int started;
    unsigned int dt;

    /* GUI */
    struct gui_input in;
    struct gui_font font;
    struct demo_gui gui;

    if (argc < 3) {
        fprintf(stdout,"Missing TTF Font file/height argument: nanovg <path> <height>\n");
        exit(EXIT_FAILURE);
    }
    font_path = argv[1];
    font_height = (gui_size)MAX(0, atoi(argv[2]));

    /* SDL */
    SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_EVENTS);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    win = SDL_CreateWindow("Demo",
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN);
    glContext = SDL_GL_CreateContext(win);
    SDL_GetWindowSize(win, &width, &height);

    /* OpenGL */
    glewExperimental = 1;
    if (glewInit() != GLEW_OK)
        die("[GLEW] failed setup\n");
    glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

    /* nanovg */
    vg = nvgCreateGLES2(NVG_ANTIALIAS|NVG_DEBUG);
    if (!vg) die("[NVG]: failed to init\n");
    nvgCreateFont(vg, "fixed", font_path);
    nvgFontFace(vg, "fixed");
    nvgFontSize(vg, font_height);
    nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);

    /* GUI */
    memset(&in, 0, sizeof in);
    memset(&gui, 0, sizeof gui);
    gui.memory = malloc(MAX_MEMORY);
    font.userdata.ptr = vg;
    nvgTextMetrics(vg, NULL, NULL, &font.height);
    font.width = font_get_width;
    init_demo(&gui, &font);

    while (gui.running) {
        /* Input */
        SDL_Event evt;
        started = SDL_GetTicks();
        gui_input_begin(&in);
        while (SDL_PollEvent(&evt)) {
            if (evt.type == SDL_WINDOWEVENT) resize(&evt);
            else if (evt.type == SDL_QUIT) goto cleanup;
            else if (evt.type == SDL_KEYUP) key(&in, &evt, gui_false);
            else if (evt.type == SDL_KEYDOWN) key(&in, &evt, gui_true);
            else if (evt.type == SDL_MOUSEBUTTONDOWN) btn(&in, &evt, gui_true);
            else if (evt.type == SDL_MOUSEBUTTONUP) btn(&in, &evt, gui_false);
            else if (evt.type == SDL_MOUSEMOTION) motion(&in, &evt);
            else if (evt.type == SDL_TEXTINPUT) text(&in, &evt);
            else if (evt.type == SDL_MOUSEWHEEL) gui_input_scroll(&in, evt.wheel.y);
        }
        gui_input_end(&in);

        /* GUI */
        SDL_GetWindowSize(win, &width, &height);
        run_demo(&gui, &in);

        /* Draw */
        glClearColor(0.4f, 0.4f, 0.4f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        draw(vg, &gui.stack, width, height);
        gui.ms = SDL_GetTicks() - started;
        SDL_GL_SwapWindow(win);

        /* Timing */
        dt = SDL_GetTicks() - started;
        gui.ms = dt;
        if (dt < DTIME)
            SDL_Delay(DTIME - dt);
    }

cleanup:
    /* Cleanup */
    free(gui.memory);
    nvgDeleteGLES2(vg);
    SDL_GL_DeleteContext(glContext);
    SDL_DestroyWindow(win);
    SDL_Quit();
    return 0;
}
		void NVGRenderer::textMetrics(float* ascender, float* descender, float* lineh)
		{
			nvgTextMetrics(m_context, ascender, descender, lineh);
		}
Exemple #11
0
void drawParagraph(struct NVGcontext* vg, float x, float y, float width, float height, float mx, float my)
{
	struct NVGtextRow rows[3];
	struct NVGglyphPosition glyphs[100];
	const char* text = "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.";
	const char* start;
	const char* end;
	int nrows, i, nglyphs, j, lnum = 0;
	float lineh;
	float caretx, px;
	float bounds[4];
	float gx = 0.0f, gy = 0.0f;
	int gutter = 0;
	NVG_NOTUSED(height);

	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.
	start = text;
	end = text + strlen(text);
	for (nrows = nvgTextBreakLines(vg, start, end, width, rows, 3); 0 != nrows; nrows = nvgTextBreakLines(vg, start, end, width, rows, 3) )
	{
		for (i = 0; i < nrows; i++) {
			struct NVGtextRow* row = &rows[i];
			int hit = mx > x && mx < (x+width) && my >= y && my < (y+lineh);

			nvgBeginPath(vg);
			nvgFillColor(vg, nvgRGBA(255,255,255,hit?64:8) );
			nvgRect(vg, x, y, row->width, lineh);
			nvgFill(vg);

			nvgFillColor(vg, nvgRGBA(255,255,255,255) );
			nvgText(vg, x, y, row->start, row->end);

			if (hit) {
				caretx = (mx < x+row->width/2) ? x : x+row->width;
				px = x;
				nglyphs = nvgTextGlyphPositions(vg, x, y, row->start, row->end, glyphs, 100);
				for (j = 0; j < nglyphs; j++) {
					float x0 = glyphs[j].x;
					float x1 = (j+1 < nglyphs) ? glyphs[j+1].x : x+row->width;
					float tgx = x0 * 0.3f + x1 * 0.7f;
					if (mx >= px && mx < tgx)
						caretx = glyphs[j].x;
					px = tgx;
				}
				nvgBeginPath(vg);
				nvgFillColor(vg, nvgRGBA(255,192,0,255) );
				nvgRect(vg, caretx, y, 1, lineh);
				nvgFill(vg);

				gutter = lnum+1;
				gx = x - 10;
				gy = y + lineh/2;
			}
			lnum++;
			y += lineh;
		}
		// Keep going...
		start = rows[nrows-1].next;
	}

	if (gutter)
	{
		char txt[16];
		bx::snprintf(txt, sizeof(txt), "%d", gutter);
		nvgFontSize(vg, 13.0f);
		nvgTextAlign(vg, NVG_ALIGN_RIGHT|NVG_ALIGN_MIDDLE);

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

		nvgBeginPath(vg);
		nvgFillColor(vg, nvgRGBA(255,192,0,255) );
		nvgRoundedRect(vg
			, bx::fround(bounds[0])-4.0f
			, bx::fround(bounds[1])-2.0f
			, bx::fround(bounds[2]-bounds[0])+8.0f
			, bx::fround(bounds[3]-bounds[1])+4.0f
			, (bx::fround(bounds[3]-bounds[1])+4.0f)/2.0f-1.0f
			);
		nvgFill(vg);

		nvgFillColor(vg, nvgRGBA(32,32,32,255) );
		nvgText(vg, gx,gy, txt, NULL);
	}

	y += 20.0f;

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

	nvgTextBoxBounds(vg, x,y, 150, "Hover your mouse over the text to see calculated caret position.", NULL, bounds);
	nvgBeginPath(vg);
	nvgFillColor(vg, nvgRGBA(220,220,220,255) );
	nvgRoundedRect(vg
		, bx::fround(bounds[0]-2.0f)
		, bx::fround(bounds[1]-2.0f)
		, bx::fround(bounds[2]-bounds[0])+4.0f
		, bx::fround(bounds[3]-bounds[1])+4.0f
		, 3.0f
		);
	px = float( (int)( (bounds[2]+bounds[0])/2) );
	nvgMoveTo(vg, px,bounds[1] - 10);
	nvgLineTo(vg, px+7,bounds[1]+1);
	nvgLineTo(vg, px-7,bounds[1]+1);
	nvgFill(vg);

	nvgFillColor(vg, nvgRGBA(0,0,0,220) );
	nvgTextBox(vg, x,y, 150, "Hover your mouse over the text to see calculated caret position.", NULL);

	nvgRestore(vg);
}
Exemple #12
0
int
main(int argc, char *argv[])
{
    /* Platform */
    int width, height;
    const char *font_path;
    zr_size font_height;
    SDL_Window *win;
    SDL_GLContext glContext;
    NVGcontext *vg = NULL;

    /* GUI */
    struct demo_gui gui;
    if (argc < 2) {
        fprintf(stdout,"Missing TTF Font file argument: gui <path>\n");
        exit(EXIT_FAILURE);
    }
    font_path = argv[1];
    font_height = 10;

    /* SDL */
    SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_EVENTS);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    win = SDL_CreateWindow("Demo",
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN);
    glContext = SDL_GL_CreateContext(win);
    SDL_GetWindowSize(win, &width, &height);

    /* OpenGL */
    glewExperimental = 1;
    if (glewInit() != GLEW_OK)
        die("[GLEW] failed setup\n");
    glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

    /* nanovg */
    vg = nvgCreateGLES2(NVG_ANTIALIAS|NVG_DEBUG);
    if (!vg) die("[NVG]: failed to init\n");
    nvgCreateFont(vg, "fixed", font_path);
    nvgFontFace(vg, "fixed");
    nvgFontSize(vg, font_height);
    nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);

    /* GUI */
    memset(&gui, 0, sizeof gui);
    zr_command_queue_init_fixed(&gui.queue, calloc(MAX_MEMORY, 1), MAX_MEMORY);
    gui.font.userdata = zr_handle_ptr(vg);
    gui.font.width = font_get_width;
    nvgTextMetrics(vg, NULL, NULL, &gui.font.height);
    init_demo(&gui);

    while (gui.running) {
        /* Input */
        SDL_Event evt;
        zr_input_begin(&gui.input);
        while (SDL_PollEvent(&evt)) {
            if (evt.type == SDL_WINDOWEVENT) resize(&evt);
            else if (evt.type == SDL_QUIT) goto cleanup;
            else if (evt.type == SDL_KEYUP) key(&gui.input, &evt, zr_false);
            else if (evt.type == SDL_KEYDOWN) key(&gui.input, &evt, zr_true);
            else if (evt.type == SDL_MOUSEBUTTONDOWN) btn(&gui.input, &evt, zr_true);
            else if (evt.type == SDL_MOUSEBUTTONUP) btn(&gui.input, &evt, zr_false);
            else if (evt.type == SDL_MOUSEMOTION) motion(&gui.input, &evt);
            else if (evt.type == SDL_TEXTINPUT) text(&gui.input, &evt);
            else if (evt.type == SDL_MOUSEWHEEL)
                zr_input_scroll(&gui.input,(float)evt.wheel.y);
        }
        zr_input_end(&gui.input);

        /* GUI */
        SDL_GetWindowSize(win, &width, &height);
        run_demo(&gui);

        /* Draw */
        glClearColor(0.4f, 0.4f, 0.4f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        draw(vg, &gui.queue, width, height);
        SDL_GL_SwapWindow(win);
    }

cleanup:
    /* Cleanup */
    free(zr_buffer_memory(&gui.queue.buffer));
    nvgDeleteGLES2(vg);
    SDL_GL_DeleteContext(glContext);
    SDL_DestroyWindow(win);
    SDL_Quit();
    return 0;
}
Exemple #13
0
int
main(int argc, char *argv[])
{
    int x,y,width, height;
    SDL_Window *win;
    SDL_GLContext glContext;
    NVGcontext *vg = NULL;

    int running = 1;
    unsigned int started;
    unsigned int dt;
    struct zr_user_font font;
    struct file_browser browser;
    const char *font_path;
    int icon_sheet;

    font_path = argv[1];
    if (argc < 2)
        die("missing argument!: <font> <icons>");

    /* SDL */
    SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_EVENTS);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    win = SDL_CreateWindow("File Explorer",
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN);
    glContext = SDL_GL_CreateContext(win);
    SDL_GetWindowSize(win, &width, &height);
    SDL_GetWindowPosition(win, &x, &y);

    /* OpenGL */
    glewExperimental = 1;
    if (glewInit() != GLEW_OK)
        die("[GLEW] failed setup\n");
    glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

    /* nanovg */
    vg = nvgCreateGLES2(NVG_ANTIALIAS|NVG_DEBUG);
    if (!vg) die("[NVG]: failed to init\n");
    nvgCreateFont(vg, "fixed", font_path);
    nvgFontFace(vg, "fixed");
    nvgFontSize(vg, 14);
    nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);

    /* GUI */
    memset(&browser, 0, sizeof browser);
    font.userdata.ptr = vg;
    nvgTextMetrics(vg, NULL, NULL, &font.height);
    font.width = font_get_width;
    file_browser_init(&browser, vg, &font, width, height);

    while (running) {
        /* Input */
        SDL_Event evt;
        started = SDL_GetTicks();
        zr_input_begin(&browser.input);
        while (SDL_PollEvent(&evt)) {
            if (evt.type == SDL_WINDOWEVENT) resize(&evt);
            else if (evt.type == SDL_QUIT) goto cleanup;
            else if (evt.type == SDL_KEYUP) key(&browser.input, &evt, zr_false);
            else if (evt.type == SDL_KEYDOWN) key(&browser.input, &evt, zr_true);
            else if (evt.type == SDL_MOUSEBUTTONDOWN) btn(&browser.input, &evt, zr_true);
            else if (evt.type == SDL_MOUSEBUTTONUP) btn(&browser.input, &evt, zr_false);
            else if (evt.type == SDL_MOUSEMOTION) motion(&browser.input, &evt);
            else if (evt.type == SDL_TEXTINPUT) text(&browser.input, &evt);
            else if (evt.type == SDL_MOUSEWHEEL) zr_input_scroll(&browser.input, evt.wheel.y);
        }
        zr_input_end(&browser.input);

        SDL_GetWindowSize(win, &width, &height);
        running = file_browser_run(&browser, width, height);

        /* Draw */
        glClearColor(0.4f, 0.4f, 0.4f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        draw(vg, &browser.queue, width, height);
        SDL_GL_SwapWindow(win);
    }

cleanup:
    /* Cleanup */
    free(browser.memory);
    nvgDeleteGLES2(vg);
    SDL_GL_DeleteContext(glContext);
    SDL_DestroyWindow(win);
    SDL_Quit();
    return 0;
}
Exemple #14
0
	void NanoInk::redrawText()
	{
		InkStyle& skin = this->skin();

		nvgResetDisplayList(mTextCache);
		nvgBindDisplayList(mCtx, mTextCache);

		float left = mFrame.cleft();
		float top = mFrame.ctop();
		float width = mFrame.cwidth();
		float height = mFrame.cheight();

		float pleft = mFrame.pleft();
		float ptop = mFrame.ptop();
		float pwidth = mFrame.pwidth();
		float pheight = mFrame.pheight();

		float contentWidth = contentSize(DIM_X) - skin.padding()[DIM_X] - skin.padding()[DIM_X + 2];
		float contentHeight = contentSize(DIM_Y) - skin.padding()[DIM_Y] - skin.padding()[DIM_Y + 2];

		float cleft = pleft;
		NVGalign halign = NVG_ALIGN_LEFT;
		if(skin.align()[DIM_X] == CENTER)
		{
			halign = NVG_ALIGN_CENTER;
			cleft = pleft + pwidth / 2.f - contentWidth / 2.f;
		}
		else if(skin.align()[DIM_X] == RIGHT)
		{
			halign = NVG_ALIGN_RIGHT;
			cleft = pleft + pwidth - contentWidth;
		}

		float ctop = ptop;
		if(skin.align()[DIM_Y] == CENTER)
			ctop = ptop + pheight / 2.f - contentHeight / 2.f;
		else if(skin.align()[DIM_Y] == RIGHT)
			ctop = ptop + pheight - contentHeight;

		// Caption
		if(!mFrame.widget().label().empty() && !(pwidth <= 0.f || pheight <= 0.f))
		{
			//if(mFrame.dclip(DIM_X) || mFrame.dclip(DIM_Y)) 
			// ^ @note this doesn't work because a frame is set to clipped only by its parent, and not when the label is larger than the frame itself
			nvgScissor(mCtx, left, top, width, height);

			this->setupText();

			float lineh = 0.f;
			nvgTextMetrics(mCtx, NULL, NULL, &lineh);

			const char* start = mFrame.widget().label().c_str();
			float x = pleft;
			float y = ptop;

			for(NVGtextRow& row : mTextRows)
			{
				if(halign & NVG_ALIGN_LEFT)
					x = pleft;
				else if(halign & NVG_ALIGN_CENTER)
					x = pleft + pwidth*0.5f - row.width*0.5f;
				else if(halign & NVG_ALIGN_RIGHT)
					x = pleft + pwidth - row.width;

				if(mSelectFirst != mSelectSecond)
				{
					size_t indexStart = row.start - start;
					size_t indexEnd = row.end - start;

					if(indexEnd > selectStart() && indexStart < selectEnd())
					{
						size_t selectStart = std::max(indexStart, this->selectStart());
						size_t selectEnd = std::min(indexEnd, this->selectEnd());

						NVGglyphPosition startPosition;
						nvgTextGlyphPosition(mCtx, 0.f, 0.f, row.start, row.end, selectStart - (row.start - start), &startPosition);

						NVGglyphPosition endPosition;
						nvgTextGlyphPosition(mCtx, 0.f, 0.f, row.start, row.end, selectEnd - (row.start - start), &endPosition);

						nvgBeginPath(mCtx);
						nvgFillColor(mCtx, nvgRGBA(0, 55, 255, 124));
						nvgRect(mCtx, x + startPosition.x, y, endPosition.x - startPosition.x, lineh);
						nvgFill(mCtx);
					}
				}

				nvgFillColor(mCtx, nvgColour(skin.mTextColour));
				nvgText(mCtx, x, y, row.start, row.end);

				y += lineh;
			}

			/*
			nvgText(mCtx, cleft, ctop, mFrame.widget().label().c_str(), nullptr);
			*/

			nvgResetScissor(mCtx);
		}

		if(mFrame.dclip(DIM_X) || mFrame.dclip(DIM_Y))
			nvgResetScissor(mCtx);

		nvgBindDisplayList(mCtx, nullptr);
	}