Replace Unicode icons with PNG icons in StatusBar

- Move icons/ to internal/ui/icons/ for go:embed
- Renderer.loadIcons() loads PNGs from embedded filesystem
- drawStatusBar() uses drawPng() for cut/copy/paste/search/conflict icons
- Icons are 16dp, rendered from 48x48 PNGs (2x density)
This commit is contained in:
Greg Pomerantz 2026-05-10 06:29:19 -04:00
parent 14284afa73
commit d2509123b0
9 changed files with 38 additions and 10 deletions

View File

Before

Width:  |  Height:  |  Size: 444 B

After

Width:  |  Height:  |  Size: 444 B

View File

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 301 B

View File

Before

Width:  |  Height:  |  Size: 778 B

After

Width:  |  Height:  |  Size: 778 B

View File

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

View File

Before

Width:  |  Height:  |  Size: 485 B

After

Width:  |  Height:  |  Size: 485 B

View File

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View File

@ -1,8 +1,12 @@
package ui package ui
import ( import (
"bytes"
"embed"
"image" "image"
"image/color" "image/color"
_ "image/png"
"strings"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/layout" "gioui.org/layout"
@ -15,6 +19,9 @@ import (
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
) )
//go:embed icons/*.png
var iconFS embed.FS
// StateReader provides access to scale and window dimensions from State. // StateReader provides access to scale and window dimensions from State.
// Implemented by editor.State to avoid import cycles. // Implemented by editor.State to avoid import cycles.
type StateReader interface { type StateReader interface {
@ -28,11 +35,31 @@ type Renderer struct {
theme Theme theme Theme
shp *text.Shaper shp *text.Shaper
state StateReader // read-only access to State for scale/window size state StateReader // read-only access to State for scale/window size
icons map[string]image.Image // loaded PNG icons
} }
// New creates a new Renderer. // New creates a new Renderer.
func New(th Theme, shp *text.Shaper) *Renderer { func New(th Theme, shp *text.Shaper) *Renderer {
return &Renderer{theme: th, shp: shp} r := &Renderer{theme: th, shp: shp, icons: make(map[string]image.Image)}
r.loadIcons()
return r
}
// loadIcons loads all PNG icons from the embedded filesystem.
func (r *Renderer) loadIcons() {
files := []string{"cut.png", "copy.png", "paste.png", "search.png", "conflict.png", "wrapOn.png", "wrapOff.png"}
for _, filename := range files {
data, err := iconFS.ReadFile(filename)
if err != nil {
continue // skip missing icons
}
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
continue
}
name := strings.TrimSuffix(filename, ".png")
r.icons[name] = img
}
} }
// SetState sets the State reference for the Renderer. // SetState sets the State reference for the Renderer.
@ -167,22 +194,23 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar, _ WindowConst
// Left side: Cut, Copy, Paste icons (always visible) // Left side: Cut, Copy, Paste icons (always visible)
leftX := reg.X + Dp(8) leftX := reg.X + Dp(8)
iconY := iconsLineY iconY := iconsLineY
iconSize := Dp(16) // 16dp icons
r.drawText(gtx, th.Shaper, "✂", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) r.drawPng(gtx, r.icons["cut"], leftX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
leftX += Dp(36) leftX += Dp(28)
r.drawText(gtx, th.Shaper, "⎘", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) r.drawPng(gtx, r.icons["copy"], leftX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
leftX += Dp(36) leftX += Dp(28)
r.drawText(gtx, th.Shaper, "⎘", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) r.drawPng(gtx, r.icons["paste"], leftX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
leftX += Dp(12) leftX += Dp(12)
// Right side: Conflict (conditional), Search (always visible) // Right side: Conflict (conditional), Search (always visible)
rightX := reg.X + reg.W - Dp(8) rightX := reg.X + reg.W - Dp(8)
if sb.ConflictIcon { if sb.ConflictIcon {
rightX -= Dp(36) rightX -= Dp(28)
r.drawText(gtx, th.Shaper, "⚠", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) r.drawPng(gtx, r.icons["conflict"], rightX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
} }
rightX -= Dp(36) rightX -= Dp(28)
r.drawText(gtx, th.Shaper, "🔍", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) r.drawPng(gtx, r.icons["search"], rightX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
c.Pop() c.Pop()
} }