diff --git a/internal/ui/render.go b/internal/ui/render.go index 9ee0f0f..f789a6a 100644 --- a/internal/ui/render.go +++ b/internal/ui/render.go @@ -2,10 +2,13 @@ package ui import ( "image" + "image/color" + "gioui.org/f32" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" + "gioui.org/op/paint" "gioui.org/text" "gioui.org/unit" "gioui.org/widget/material" @@ -107,10 +110,8 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) { } } - // Position filename at (reg.X + 8, filenameLineY) - call1 := op.Offset(image.Point{X: int(reg.X + unit.Dp(8)), Y: int(filenameLineY)}).Push(gtx.Ops) - material.Label(th, r.theme.FontSize, displayFilename).Layout(gtx) - call1.Pop() + // Position filename at (reg.X + 8, filenameLineY) - draw using shaper to avoid double-shaping + r.drawText(gtx, th.Shaper, displayFilename, r.theme.FontSize, reg.X+unit.Dp(8), filenameLineY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) // Line 2: Icons iconsLineY := filenameLineY + filenameLineH @@ -120,21 +121,15 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) { iconY := iconsLineY if sb.CutCopy { - call := op.Offset(image.Point{X: int(leftX), Y: int(iconY)}).Push(gtx.Ops) - material.Label(th, r.theme.FontSize, "✂").Layout(gtx) - call.Pop() + r.drawText(gtx, th.Shaper, "✂", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) leftX += unit.Dp(36) } if sb.Copy { - call := op.Offset(image.Point{X: int(leftX), Y: int(iconY)}).Push(gtx.Ops) - material.Label(th, r.theme.FontSize, "⎘").Layout(gtx) - call.Pop() + r.drawText(gtx, th.Shaper, "⎘", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) leftX += unit.Dp(36) } if sb.Paste { - call := op.Offset(image.Point{X: int(leftX), Y: int(iconY)}).Push(gtx.Ops) - material.Label(th, r.theme.FontSize, "⎘").Layout(gtx) - call.Pop() + r.drawText(gtx, th.Shaper, "⎘", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) leftX += unit.Dp(36) } @@ -142,15 +137,11 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) { rightX := reg.X + reg.W - unit.Dp(8) if sb.Search { rightX -= unit.Dp(36) - call := op.Offset(image.Point{X: int(rightX), Y: int(iconY)}).Push(gtx.Ops) - material.Label(th, r.theme.FontSize, "🔍").Layout(gtx) - call.Pop() + r.drawText(gtx, th.Shaper, "🔍", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) } if sb.ConflictIcon { rightX -= unit.Dp(36) - call := op.Offset(image.Point{X: int(rightX), Y: int(iconY)}).Push(gtx.Ops) - material.Label(th, r.theme.FontSize, "⚠").Layout(gtx) - call.Pop() + r.drawText(gtx, th.Shaper, "⚠", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) } c.Pop() @@ -205,6 +196,48 @@ func boolToString(b bool) string { return "Off" } +// drawText draws text using the same approach as Gio's textView: +// layout text, iterate glyphs, buffer into lines, and draw using shaper.Shape(). +func (r *Renderer) drawText(gtx layout.Context, shp *text.Shaper, str string, size unit.Sp, x, y unit.Dp, col color.NRGBA) { + // Layout text + shp.LayoutString(text.Parameters{PxPerEm: fixed.I(gtx.Sp(size))}, str) + + // Draw glyphs using the same approach as Gio's textView + var glyphs [32]text.Glyph + line := glyphs[:0] + for g, ok := shp.NextGlyph(); ok; g, ok = shp.NextGlyph() { + line = append(line, g) + if g.Flags&text.FlagLineBreak != 0 || cap(line)-len(line) == 0 { + r.drawLine(gtx, shp, line, x, y, col) + line = line[:0] + } + } + if len(line) > 0 { + r.drawLine(gtx, shp, line, x, y, col) + } +} + +// drawLine draws a line of glyphs using shaper.Shape() and shaper.Bitmaps(). +func (r *Renderer) drawLine(gtx layout.Context, shp *text.Shaper, line []text.Glyph, x, y unit.Dp, col color.NRGBA) { + // Apply offset transform + off := f32.Point{X: float32(x), Y: float32(y)} + t := op.Affine(f32.Affine2D{}.Offset(off)).Push(gtx.Ops) + + // Draw vector glyphs + path := shp.Shape(line) + outline := clip.Outline{Path: path}.Op().Push(gtx.Ops) + paint.ColorOp{Color: col}.Add(gtx.Ops) + paint.PaintOp{}.Add(gtx.Ops) + outline.Pop() + + // Draw bitmap glyphs (emoji, etc.) + if call := shp.Bitmaps(line); call != (op.CallOp{}) { + call.Add(gtx.Ops) + } + + t.Pop() +} + // charWidths returns the cumulative width after each rune in str. func charWidths(gtx layout.Context, shp *text.Shaper, str string, size unit.Sp) []int { // Match Gio's textView: PxPerEm = font size in device pixels diff --git a/pad b/pad index 004c7ae..902a495 100755 Binary files a/pad and b/pad differ