Improve filename truncation to fit exact number of characters
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
68d431408d
commit
bbd16be9f2
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
|
"gioui.org/font/gofont"
|
||||||
|
|
||||||
"pad/internal/editor"
|
"pad/internal/editor"
|
||||||
"pad/internal/ui"
|
"pad/internal/ui"
|
||||||
|
|
@ -30,7 +31,7 @@ func main() {
|
||||||
func run(w *app.Window) error {
|
func run(w *app.Window) error {
|
||||||
var ops op.Ops
|
var ops op.Ops
|
||||||
|
|
||||||
shaper := text.NewShaper()
|
shaper := text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||||
renderer := ui.New(ui.Theme{FontSize: 14}, shaper)
|
renderer := ui.New(ui.Theme{FontSize: 14}, shaper)
|
||||||
|
|
||||||
// Initial screen size
|
// Initial screen size
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func NewState(screenWidth, screenHeight unit.Dp) *State {
|
||||||
// It takes screen dimensions and returns []Element.
|
// It takes screen dimensions and returns []Element.
|
||||||
func EditorLayout(screenWidth, screenHeight unit.Dp) []ui.Element {
|
func EditorLayout(screenWidth, screenHeight unit.Dp) []ui.Element {
|
||||||
// Margin to avoid clipping on macOS round corners
|
// Margin to avoid clipping on macOS round corners
|
||||||
margin := unit.Dp(5)
|
margin := unit.Dp(10)
|
||||||
|
|
||||||
// Compute StatusBar region
|
// Compute StatusBar region
|
||||||
// Line 1: filename (24 DP)
|
// Line 1: filename (24 DP)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Renderer consumes a slice of elements and draws them.
|
// 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
|
filenameLineY := reg.Y
|
||||||
filenameLineH := unit.Dp(24)
|
filenameLineH := unit.Dp(24)
|
||||||
|
|
||||||
// Truncate filename if needed
|
// Truncate filename only if it doesn't fit
|
||||||
displayFilename := sb.Filename
|
displayFilename := sb.Filename
|
||||||
// Simple truncation: if filename is too long, truncate it
|
availableWidth := int(reg.W - unit.Dp(8) - unit.Dp(40)) // left margin + right icons in DP
|
||||||
if len(displayFilename) > 30 {
|
ellipsisWidth := textWidth(gtx, th.Shaper, "...", r.theme.FontSize)
|
||||||
displayFilename = displayFilename[:27] + "..."
|
// 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)
|
// Position filename at (reg.X + 8, filenameLineY)
|
||||||
gtx.Constraints.Min.X = int(reg.X + unit.Dp(8))
|
call1 := op.Offset(image.Point{X: int(reg.X + unit.Dp(8)), Y: int(filenameLineY)}).Push(gtx.Ops)
|
||||||
gtx.Constraints.Min.Y = int(filenameLineY)
|
|
||||||
material.Label(th, r.theme.FontSize, displayFilename).Layout(gtx)
|
material.Label(th, r.theme.FontSize, displayFilename).Layout(gtx)
|
||||||
|
call1.Pop()
|
||||||
|
|
||||||
// Line 2: Icons
|
// Line 2: Icons
|
||||||
iconsLineY := filenameLineY + filenameLineH
|
iconsLineY := filenameLineY + filenameLineH
|
||||||
|
|
@ -108,15 +123,21 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) {
|
||||||
iconY := iconsLineY
|
iconY := iconsLineY
|
||||||
|
|
||||||
if sb.CutCopy {
|
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)
|
leftX += unit.Dp(36)
|
||||||
}
|
}
|
||||||
if sb.Copy {
|
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)
|
leftX += unit.Dp(36)
|
||||||
}
|
}
|
||||||
if sb.Paste {
|
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)
|
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)
|
rightX := reg.X + reg.W - unit.Dp(8)
|
||||||
if sb.Search {
|
if sb.Search {
|
||||||
rightX -= unit.Dp(36)
|
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 {
|
if sb.ConflictIcon {
|
||||||
rightX -= unit.Dp(36)
|
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()
|
c.Pop()
|
||||||
|
|
@ -139,12 +164,6 @@ func (r *Renderer) drawBottomBar(gtx layout.Context, bb BottomBar) {
|
||||||
th := material.NewTheme()
|
th := material.NewTheme()
|
||||||
th.Shaper = r.shp
|
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
|
// Left: Cursor position
|
||||||
cursorX := reg.X + unit.Dp(8)
|
cursorX := reg.X + unit.Dp(8)
|
||||||
call1 := op.Offset(image.Point{X: int(cursorX), Y: int(reg.Y)}).Push(gtx.Ops)
|
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()
|
call2.Pop()
|
||||||
|
|
||||||
// Right: Word wrap button
|
// 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)
|
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()
|
call3.Pop()
|
||||||
|
|
||||||
c.Pop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) drawButton(gtx layout.Context, btn Button) {
|
func (r *Renderer) drawButton(gtx layout.Context, btn Button) {
|
||||||
|
|
@ -190,3 +207,41 @@ func boolToString(b bool) string {
|
||||||
}
|
}
|
||||||
return "Off"
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user