diff --git a/cmd/pad/main.go b/cmd/pad/main.go index 71786b3..5c634bb 100644 --- a/cmd/pad/main.go +++ b/cmd/pad/main.go @@ -9,6 +9,7 @@ import ( "gioui.org/op" "gioui.org/text" "gioui.org/unit" + "gioui.org/font/gofont" "pad/internal/editor" "pad/internal/ui" @@ -30,7 +31,7 @@ func main() { func run(w *app.Window) error { var ops op.Ops - shaper := text.NewShaper() + shaper := text.NewShaper(text.WithCollection(gofont.Collection())) renderer := ui.New(ui.Theme{FontSize: 14}, shaper) // Initial screen size diff --git a/internal/editor/state.go b/internal/editor/state.go index b178636..5f0cd7d 100644 --- a/internal/editor/state.go +++ b/internal/editor/state.go @@ -26,7 +26,7 @@ func NewState(screenWidth, screenHeight unit.Dp) *State { // It takes screen dimensions and returns []Element. func EditorLayout(screenWidth, screenHeight unit.Dp) []ui.Element { // Margin to avoid clipping on macOS round corners - margin := unit.Dp(5) + margin := unit.Dp(10) // Compute StatusBar region // Line 1: filename (24 DP) diff --git a/internal/ui/render.go b/internal/ui/render.go index cc95174..b356768 100644 --- a/internal/ui/render.go +++ b/internal/ui/render.go @@ -9,6 +9,7 @@ import ( "gioui.org/text" "gioui.org/unit" "gioui.org/widget/material" + "golang.org/x/image/math/fixed" ) // Renderer consumes a slice of elements and draws them. @@ -88,17 +89,31 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) { filenameLineY := reg.Y filenameLineH := unit.Dp(24) - // Truncate filename if needed + // Truncate filename only if it doesn't fit displayFilename := sb.Filename - // Simple truncation: if filename is too long, truncate it - if len(displayFilename) > 30 { - displayFilename = displayFilename[:27] + "..." + availableWidth := int(reg.W - unit.Dp(8) - unit.Dp(40)) // left margin + right icons in DP + ellipsisWidth := textWidth(gtx, th.Shaper, "...", r.theme.FontSize) + // 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, w := range widths { + if w <= spaceForText { + if i+1 < len(displayFilename) { + displayFilename = displayFilename[:i+1] + "..." + } else { + displayFilename = displayFilename[:i] + "..." + } + break + } + } } // Position filename at (reg.X + 8, filenameLineY) - gtx.Constraints.Min.X = int(reg.X + unit.Dp(8)) - gtx.Constraints.Min.Y = int(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() // Line 2: Icons iconsLineY := filenameLineY + filenameLineH @@ -108,15 +123,21 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) { iconY := iconsLineY if sb.CutCopy { - r.drawIconButton(gtx, th, "✂", leftX, iconY) + call := op.Offset(image.Point{X: int(leftX), Y: int(iconY)}).Push(gtx.Ops) + material.Label(th, r.theme.FontSize, "✂").Layout(gtx) + call.Pop() leftX += unit.Dp(36) } if sb.Copy { - r.drawIconButton(gtx, th, "⎘", leftX, iconY) + call := op.Offset(image.Point{X: int(leftX), Y: int(iconY)}).Push(gtx.Ops) + material.Label(th, r.theme.FontSize, "⎘").Layout(gtx) + call.Pop() leftX += unit.Dp(36) } if sb.Paste { - r.drawIconButton(gtx, th, "⎘", leftX, iconY) + call := op.Offset(image.Point{X: int(leftX), Y: int(iconY)}).Push(gtx.Ops) + material.Label(th, r.theme.FontSize, "⎘").Layout(gtx) + call.Pop() leftX += unit.Dp(36) } @@ -124,11 +145,15 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) { rightX := reg.X + reg.W - unit.Dp(8) if sb.Search { rightX -= unit.Dp(36) - r.drawIconButton(gtx, th, "🔍", rightX, iconY) + call := op.Offset(image.Point{X: int(rightX), Y: int(iconY)}).Push(gtx.Ops) + material.Label(th, r.theme.FontSize, "🔍").Layout(gtx) + call.Pop() } if sb.ConflictIcon { rightX -= unit.Dp(36) - r.drawIconButton(gtx, th, "⚠", rightX, iconY) + call := op.Offset(image.Point{X: int(rightX), Y: int(iconY)}).Push(gtx.Ops) + material.Label(th, r.theme.FontSize, "⚠").Layout(gtx) + call.Pop() } c.Pop() @@ -139,12 +164,6 @@ func (r *Renderer) drawBottomBar(gtx layout.Context, bb BottomBar) { th := material.NewTheme() th.Shaper = r.shp - // Clip to bottom bar region - c := clip.Rect{ - Min: image.Point{X: int(reg.X), Y: int(reg.Y)}, - Max: image.Point{X: int(reg.X + reg.W), Y: int(reg.Y + reg.H)}, - }.Push(gtx.Ops) - // Left: Cursor position cursorX := reg.X + unit.Dp(8) call1 := op.Offset(image.Point{X: int(cursorX), Y: int(reg.Y)}).Push(gtx.Ops) @@ -158,12 +177,10 @@ func (r *Renderer) drawBottomBar(gtx layout.Context, bb BottomBar) { call2.Pop() // Right: Word wrap button - wordWrapX := reg.X + reg.W - unit.Dp(120) + wordWrapX := reg.X + reg.W - unit.Dp(80) call3 := op.Offset(image.Point{X: int(wordWrapX), Y: int(reg.Y)}).Push(gtx.Ops) - material.Label(th, r.theme.FontSize, "[Word Wrap: "+boolToString(bb.WordWrap)+"]").Layout(gtx) + material.Label(th, r.theme.FontSize, "W:"+boolToString(bb.WordWrap)).Layout(gtx) call3.Pop() - - c.Pop() } func (r *Renderer) drawButton(gtx layout.Context, btn Button) { @@ -190,3 +207,41 @@ func boolToString(b bool) string { } return "Off" } + +// textWidth measures the total width of a string using the shaper. +func textWidth(gtx layout.Context, shp *text.Shaper, str string, size unit.Sp) int { + shp.LayoutString(text.Parameters{PxPerEm: 1024}, str) + var lastX fixed.Int26_6 + for { + g, ok := shp.NextGlyph() + if !ok { + break + } + x := g.X + g.Advance + if x > lastX { + lastX = x + } + } + return int(lastX) +} + +// charWidths returns the cumulative width after each rune in str. +func charWidths(gtx layout.Context, shp *text.Shaper, str string, size unit.Sp) []int { + shp.LayoutString(text.Parameters{PxPerEm: 1024}, str) + var widths []int + var cumX fixed.Int26_6 + var runeIdx int + for { + g, ok := shp.NextGlyph() + if !ok { + break + } + cumX = g.X + g.Advance + // Each glyph corresponds to one rune in our simple ASCII case + if runeIdx < len(str) { + widths = append(widths, int(cumX)) + runeIdx++ + } + } + return widths +} diff --git a/pad b/pad index a87d5a9..538de64 100755 Binary files a/pad and b/pad differ