diff --git a/internal/ui/render.go b/internal/ui/render.go index f789a6a..bcf3e56 100644 --- a/internal/ui/render.go +++ b/internal/ui/render.go @@ -95,23 +95,27 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) { // Truncate filename only if it doesn't fit displayFilename := sb.Filename availableWidth := int(reg.W - unit.Dp(8) - unit.Dp(40)) // left margin + right icons in DP - ellipsisWidths := charWidths(gtx, th.Shaper, "...", r.theme.FontSize) + + // Layout ellipsis once and get its width + th.Shaper.LayoutString(text.Parameters{PxPerEm: fixed.I(gtx.Sp(r.theme.FontSize))}, "...") + var ellipsisWidths []int + for { + g, ok := th.Shaper.NextGlyph() + if !ok { + break + } + if len(ellipsisWidths) == 0 { + ellipsisWidths = append(ellipsisWidths, int(g.Advance>>6)) + } else { + ellipsisWidths = append(ellipsisWidths, ellipsisWidths[len(ellipsisWidths)-1]+int(g.Advance>>6)) + } + } ellipsisWidth := ellipsisWidths[len(ellipsisWidths)-1] // Measure how wide "..." is, then find how many chars fit in availableWidth - ellipsisWidth spaceForText := availableWidth - ellipsisWidth - widths := charWidths(gtx, th.Shaper, displayFilename, r.theme.FontSize) - if len(widths) > 0 && widths[len(widths)-1] > availableWidth { - // Find the largest index where the width fits - for i := len(widths)-1; i >= 0; i-- { - if widths[i] <= spaceForText { - displayFilename = displayFilename[:i] + "..." - break - } - } - } - // 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}) + // Layout filename, measure widths, and draw in a single pass + r.drawTruncatedText(gtx, th.Shaper, displayFilename, r.theme.FontSize, reg.X+unit.Dp(8), filenameLineY, availableWidth, spaceForText, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) // Line 2: Icons iconsLineY := filenameLineY + filenameLineH @@ -196,6 +200,41 @@ func boolToString(b bool) string { return "Off" } +// drawTruncatedText layouts text, measures widths, truncates if needed, and draws in a single pass. +func (r *Renderer) drawTruncatedText(gtx layout.Context, shp *text.Shaper, str string, size unit.Sp, x, y unit.Dp, availableWidth, spaceForText int, col color.NRGBA) { + // Layout text + shp.LayoutString(text.Parameters{PxPerEm: fixed.I(gtx.Sp(size))}, str) + + // Measure widths and find truncation point + var widths []int + var cumWidth fixed.Int26_6 + fullStr := str + for { + g, ok := shp.NextGlyph() + if !ok { + break + } + cumWidth += g.Advance + w := int(cumWidth >> 6) + widths = append(widths, w) + } + + // Truncate if needed + if len(widths) > 0 && widths[len(widths)-1] > availableWidth { + // Find truncation point (from longest to shortest) + for i := len(widths)-1; i >= 0; i-- { + if widths[i] <= spaceForText { + fullStr = str[:i] + "..." + break + } + } + } + + // Re-layout truncated text and draw + shp.LayoutString(text.Parameters{PxPerEm: fixed.I(gtx.Sp(size))}, fullStr) + r.drawText(gtx, shp, fullStr, size, x, y, col) +} + // 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) { diff --git a/pad b/pad index 902a495..ec4535e 100755 Binary files a/pad and b/pad differ