func drawParagraph(ctx *nanovgo.Context, x, y, width, height, mx, my float32) { 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." ctx.Save() defer ctx.Restore() ctx.SetFontSize(18.0) ctx.SetFontFace("sans") ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignTop) _, _, lineh := ctx.TextMetrics() // 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. runes := []rune(text) var gx, gy float32 var gutter int lnum := 0 for _, row := range ctx.TextBreakLinesRune(runes, width) { hit := mx > x && mx < (x+width) && my >= y && my < (y+lineh) ctx.BeginPath() var alpha uint8 if hit { alpha = 64 } else { alpha = 16 } ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, alpha)) ctx.Rect(x, y, row.Width, lineh) ctx.Fill() ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 255)) ctx.TextRune(x, y, runes[row.StartIndex:row.EndIndex]) if hit { var caretX float32 if mx < x+row.Width/2 { caretX = x } else { caretX = x + row.Width } px := x lineRune := runes[row.StartIndex:row.EndIndex] glyphs := ctx.TextGlyphPositionsRune(x, y, lineRune) for j, glyph := range glyphs { x0 := glyph.X var x1 float32 if j+1 < len(glyphs) { x1 = glyphs[j+1].X } else { x1 = x + row.Width } gx = x0*0.3 + x1*0.7 if mx >= px && mx < gx { caretX = glyph.X } px = gx } ctx.BeginPath() ctx.SetFillColor(nanovgo.RGBA(255, 192, 0, 255)) ctx.Rect(caretX, y, 1, lineh) ctx.Fill() gutter = lnum + 1 gx = x - 10 gy = y + lineh/2 } lnum++ y += lineh } if gutter > 0 { txt := strconv.Itoa(gutter) ctx.SetFontSize(13.0) ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignMiddle) _, bounds := ctx.TextBounds(gx, gy, txt) ctx.BeginPath() ctx.SetFillColor(nanovgo.RGBA(255, 192, 0, 255)) ctx.RoundedRect( float32(int(bounds[0]-4)), float32(int(bounds[1]-2)), float32(int(bounds[2]-bounds[0])+8), float32(int(bounds[3]-bounds[1])+4), float32(int(bounds[3]-bounds[1])+4)/2.0-1.0) ctx.Fill() ctx.SetFillColor(nanovgo.RGBA(32, 32, 32, 255)) ctx.Text(gx, gy, txt) } y += 20.0 ctx.SetFontSize(13.0) ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignTop) ctx.SetTextLineHeight(1.2) bounds := ctx.TextBoxBounds(x, y, 150, "Hover your mouse over the text to see calculated caret position.") // Fade the tooltip out when close to it. gx = absF((mx - (bounds[0]+bounds[2])*0.5) / (bounds[0] - bounds[2])) gy = absF((my - (bounds[1]+bounds[3])*0.5) / (bounds[1] - bounds[3])) a := maxF(gx, gy) - 0.5 a = clampF(a, 0, 1) ctx.SetGlobalAlpha(a) ctx.BeginPath() ctx.SetFillColor(nanovgo.RGBA(220, 220, 220, 255)) ctx.RoundedRect(bounds[0]-2, bounds[1]-2, float32(int(bounds[2]-bounds[0])+4), float32(int(bounds[3]-bounds[1])+4), 3) px := float32(int((bounds[2] + bounds[0]) / 2)) ctx.MoveTo(px, bounds[1]-10) ctx.LineTo(px+7, bounds[1]+1) ctx.LineTo(px-7, bounds[1]+1) ctx.Fill() ctx.SetFillColor(nanovgo.RGBA(0, 0, 0, 220)) ctx.TextBox(x, y, 150, "Hover your mouse over the text to see calculated caret position.") }
func (t *TextBox) Draw(self Widget, ctx *nanovgo.Context) { t.WidgetImplement.Draw(self, ctx) x := float32(t.x) y := float32(t.y) w := float32(t.w) h := float32(t.h) bg := nanovgo.BoxGradient(x+1, y+2, w-2, h-2, 3, 4, nanovgo.MONO(255, 32), nanovgo.MONO(32, 32)) fg1 := nanovgo.BoxGradient(x+1, y+2, w-2, h-2, 3, 4, nanovgo.MONO(150, 32), nanovgo.MONO(32, 32)) fg2 := nanovgo.BoxGradient(x+1, y+2, w-2, h-2, 3, 4, nanovgo.RGBA(255, 0, 0, 100), nanovgo.RGBA(255, 0, 0, 50)) ctx.BeginPath() ctx.RoundedRect(x+1, y+2, w-2, h-2, 3) if t.editable && t.Focused() { if t.validFormat { ctx.SetFillPaint(fg1) } else { ctx.SetFillPaint(fg2) } } else { ctx.SetFillPaint(bg) } ctx.Fill() ctx.BeginPath() ctx.RoundedRect(x+0.5, y+0.5, w-1, h-1, 2.5) ctx.SetStrokeColor(nanovgo.MONO(0, 48)) ctx.Stroke() ctx.SetFontSize(float32(t.FontSize())) ctx.SetFontFace(t.Font()) drawPosX := x drawPosY := y + h*0.5 + 1 xSpacing := h * 0.3 var unitWidth float32 if t.unitImage > 0 { iw, ih, _ := ctx.ImageSize(t.unitImage) unitHeight := float32(ih) * 0.4 unitWidth = float32(iw) * unitHeight / float32(h) imgPaint := nanovgo.ImagePattern(x+w-xSpacing-unitWidth, drawPosY-unitHeight*0.5, unitWidth, unitHeight, 0, t.unitImage, toF(t.enabled, 0.7, 0.35)) ctx.BeginPath() ctx.Rect(x+w-xSpacing-unitWidth, drawPosY-unitHeight*0.5, unitWidth, unitHeight) ctx.SetFillPaint(imgPaint) ctx.Fill() unitWidth += 2 } else if t.units != "" { unitWidth, _ = ctx.TextBounds(0, 0, t.units) ctx.SetFillColor(nanovgo.MONO(255, toB(t.enabled, 64, 32))) ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignMiddle) ctx.Text(x+w-xSpacing, drawPosY, t.units) } switch t.alignment { case TextLeft: ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) drawPosX += xSpacing case TextRight: ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignMiddle) drawPosX += w - unitWidth - xSpacing case TextCenter: ctx.SetTextAlign(nanovgo.AlignCenter | nanovgo.AlignMiddle) drawPosX += w * 0.5 } if t.enabled { ctx.SetFillColor(t.theme.TextColor) } else { ctx.SetFillColor(t.theme.DisabledTextColor) } // clip visible text area clipX := x + xSpacing - 1 clipY := y + 1.0 clipWidth := w - unitWidth - 2.0*xSpacing + 2.0 clipHeight := h - 3.0 ctx.Scissor(clipX, clipY, clipWidth, clipHeight) oldDrawPosX := drawPosX drawPosX += t.textOffset if t.committed { ctx.Text(drawPosX, drawPosY, t.value) } else { text := t.editingText() textString := string(text) _, bounds := ctx.TextBounds(drawPosX, drawPosY, textString) lineH := bounds[3] - bounds[1] // find cursor positions glyphs := ctx.TextGlyphPositionsRune(drawPosX, drawPosY, text) t.updateCursor(ctx, bounds[2], glyphs) // compute text offset prevCPos := toI(t.cursorPos > 0, t.cursorPos-1, 0) nextCPos := toI(t.cursorPos < len(glyphs), t.cursorPos+1, len(glyphs)) prevCX := t.textIndex2Position(prevCPos, bounds[2], glyphs) nextCX := t.textIndex2Position(nextCPos, bounds[2], glyphs) if nextCX > clipX+clipWidth { t.textOffset -= nextCX - (clipX + clipWidth) + 1.0 } if prevCX < clipX { t.textOffset += clipX - prevCX + 1.0 } drawPosX = oldDrawPosX + t.textOffset // draw text with offset ctx.TextRune(drawPosX, drawPosY, text) _, bounds = ctx.TextBounds(drawPosX, drawPosY, textString) // recompute cursor position glyphs = ctx.TextGlyphPositionsRune(drawPosX, drawPosY, text) var caretX float32 = -1 if len(t.preeditText) != 0 { // draw preedit text caretX = t.textIndex2Position(t.cursorPos+len(t.preeditText), bounds[2], glyphs) offsetIndex := t.cursorPos offsetX := t.textIndex2Position(t.cursorPos, bounds[2], glyphs) ctx.SetStrokeColor(nanovgo.MONO(255, 160)) ctx.SetFillColor(nanovgo.MONO(255, 80)) ctx.SetStrokeWidth(2.0) for i, blockLength := range t.preeditBlocks { nextOffsetIndex := offsetIndex + blockLength nextOffsetX := t.textIndex2Position(nextOffsetIndex, bounds[2], glyphs) if i != t.preeditFocusedBlock { ctx.BeginPath() ctx.MoveTo(offsetX+2, drawPosY+lineH*0.5-1) ctx.LineTo(nextOffsetX-2, drawPosY+lineH*0.5-1) ctx.Stroke() } else { ctx.BeginPath() ctx.Rect(offsetX, drawPosY-lineH*0.5, nextOffsetX-offsetX, lineH) ctx.Fill() } offsetIndex = nextOffsetIndex offsetX = nextOffsetX } screen := t.FindWindow().Parent().(*Screen) oldCurX, oldCurY, oldCurH := screen.PreeditCursorPos() absX, absY := t.Parent().AbsolutePosition() newCurX := int(caretX) + absX newCurY := int(drawPosY+lineH*0.5) + absY newCurH := int(lineH) if oldCurX != newCurX || oldCurY != newCurY || oldCurH != newCurH { screen.SetPreeditCursorPos(newCurX, newCurY, newCurH) } } else if t.cursorPos > -1 { // regular cursor and selection area caretX = t.textIndex2Position(t.cursorPos, bounds[2], glyphs) if t.selectionPos > -1 { caretX2 := caretX selX := t.textIndex2Position(t.selectionPos, bounds[2], glyphs) if caretX2 > selX { selX, caretX2 = caretX2, selX } // draw selection ctx.BeginPath() ctx.SetFillColor(nanovgo.MONO(255, 80)) ctx.Rect(caretX2, drawPosY-lineH*0.5, selX-caretX2, lineH) ctx.Fill() } } if caretX > 0 { // draw cursor ctx.BeginPath() ctx.MoveTo(caretX, drawPosY-lineH*0.5) ctx.LineTo(caretX, drawPosY+lineH*0.5) ctx.SetStrokeColor(nanovgo.RGBA(255, 192, 0, 255)) ctx.SetStrokeWidth(1.0) ctx.Stroke() } } ctx.ResetScissor() }