- New shaper.go: ShapeText() returns ShapedText with measured width + stored PathSpec + BitmapsCall, DrawShapedText() draws with zero shaper calls - drawStatusBar: ShapeText for filename + right icons, positions from widths - drawBottomBar: ShapeText for cursor/bytepos/wrap, positions by alignment - No separate measureText helper: ShapeText.Width provides measurements - StatusBar uses IconLeft/IconRow string slices for icon configuration - BottomBar uses BarAlignment enum for item positioning
112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
package ui
|
|
|
|
import (
|
|
"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"
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
// ShapedText holds layout results from a single ShapeText call.
|
|
// The PathSpec and BitmapsCall can be drawn without further shaper access.
|
|
type ShapedText struct {
|
|
Width int // total width in pixels
|
|
PathSpec clip.PathSpec // vector glyph path, ready to draw
|
|
BitmapsCall op.CallOp // bitmap glyph call (empty if none)
|
|
FirstX fixed.Int26_6 // first glyph X offset
|
|
FirstY int32 // first glyph Y offset
|
|
Text string
|
|
FontSize unit.Sp
|
|
}
|
|
|
|
// ShapeText layouts text, measures width, and returns a ShapedText
|
|
// with an immutable path ready for drawing. The shaper is called once.
|
|
func (r *Renderer) ShapeText(gtx layout.Context, str string, size unit.Sp) ShapedText {
|
|
r.shp.LayoutString(text.Parameters{
|
|
PxPerEm: fixed.I(gtx.Sp(size)),
|
|
MinWidth: 0,
|
|
MaxWidth: 1000,
|
|
MaxLines: 1,
|
|
}, str)
|
|
|
|
// Collect glyphs and measure width
|
|
var glyphs []text.Glyph
|
|
var cumWidth fixed.Int26_6
|
|
for {
|
|
g, ok := r.shp.NextGlyph()
|
|
if !ok {
|
|
break
|
|
}
|
|
cumWidth += g.Advance
|
|
glyphs = append(glyphs, g)
|
|
}
|
|
|
|
// Build lines and collect the last line's path spec
|
|
var lastSpec clip.PathSpec
|
|
var lastBitmaps op.CallOp
|
|
var firstX fixed.Int26_6
|
|
var firstY int32
|
|
var line []text.Glyph
|
|
for _, g := range glyphs {
|
|
if len(line) == 0 {
|
|
firstX = g.X
|
|
firstY = g.Y
|
|
}
|
|
line = append(line, g)
|
|
if g.Flags&text.FlagLineBreak != 0 || cap(line)-len(line) == 0 {
|
|
lastSpec = r.shp.Shape(line)
|
|
lastBitmaps = r.shp.Bitmaps(line)
|
|
line = line[:0]
|
|
}
|
|
}
|
|
if len(line) > 0 {
|
|
lastSpec = r.shp.Shape(line)
|
|
lastBitmaps = r.shp.Bitmaps(line)
|
|
}
|
|
|
|
return ShapedText{
|
|
Width: int(cumWidth >> 6),
|
|
PathSpec: lastSpec,
|
|
BitmapsCall: lastBitmaps,
|
|
FirstX: firstX,
|
|
FirstY: firstY,
|
|
Text: str,
|
|
FontSize: size,
|
|
}
|
|
}
|
|
|
|
// DrawShapedText draws a pre-shaped text at the given position.
|
|
// x, y are in Dp. Uses the stored path spec — zero shaper calls.
|
|
func (r *Renderer) DrawShapedText(gtx layout.Context, st ShapedText, x, y Dp, col color.NRGBA) {
|
|
xPx := r.toPx(x)
|
|
yPx := r.toPx(y)
|
|
|
|
m := op.Record(gtx.Ops)
|
|
|
|
// Compute offset: position + first glyph offset
|
|
offX := float32(xPx) + float32(st.FirstX)/64.0
|
|
offY := float32(yPx) + float32(st.FirstY)
|
|
t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(offX, offY))).Push(gtx.Ops)
|
|
|
|
// Draw vector path
|
|
outline := clip.Outline{Path: st.PathSpec}.Op().Push(gtx.Ops)
|
|
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
outline.Pop()
|
|
|
|
// Draw bitmap glyphs
|
|
if st.BitmapsCall != (op.CallOp{}) {
|
|
st.BitmapsCall.Add(gtx.Ops)
|
|
}
|
|
|
|
t.Pop()
|
|
call := m.Stop()
|
|
call.Add(gtx.Ops)
|
|
}
|