From d2509123b0b45b49c587829df75a53dc1ce0883e Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Sun, 10 May 2026 06:29:19 -0400 Subject: [PATCH] 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) --- {icons => internal/ui/icons}/copy.png | Bin {icons => internal/ui/icons}/copy.svg | 0 {icons => internal/ui/icons}/cut.png | Bin {icons => internal/ui/icons}/cut.svg | 0 {icons => internal/ui/icons}/gen_icons.go | 0 {icons => internal/ui/icons}/icons.go | 0 {icons => internal/ui/icons}/paste.png | Bin {icons => internal/ui/icons}/paste.svg | 0 internal/ui/render.go | 48 +++++++++++++++++----- 9 files changed, 38 insertions(+), 10 deletions(-) rename {icons => internal/ui/icons}/copy.png (100%) rename {icons => internal/ui/icons}/copy.svg (100%) rename {icons => internal/ui/icons}/cut.png (100%) rename {icons => internal/ui/icons}/cut.svg (100%) rename {icons => internal/ui/icons}/gen_icons.go (100%) rename {icons => internal/ui/icons}/icons.go (100%) rename {icons => internal/ui/icons}/paste.png (100%) rename {icons => internal/ui/icons}/paste.svg (100%) diff --git a/icons/copy.png b/internal/ui/icons/copy.png similarity index 100% rename from icons/copy.png rename to internal/ui/icons/copy.png diff --git a/icons/copy.svg b/internal/ui/icons/copy.svg similarity index 100% rename from icons/copy.svg rename to internal/ui/icons/copy.svg diff --git a/icons/cut.png b/internal/ui/icons/cut.png similarity index 100% rename from icons/cut.png rename to internal/ui/icons/cut.png diff --git a/icons/cut.svg b/internal/ui/icons/cut.svg similarity index 100% rename from icons/cut.svg rename to internal/ui/icons/cut.svg diff --git a/icons/gen_icons.go b/internal/ui/icons/gen_icons.go similarity index 100% rename from icons/gen_icons.go rename to internal/ui/icons/gen_icons.go diff --git a/icons/icons.go b/internal/ui/icons/icons.go similarity index 100% rename from icons/icons.go rename to internal/ui/icons/icons.go diff --git a/icons/paste.png b/internal/ui/icons/paste.png similarity index 100% rename from icons/paste.png rename to internal/ui/icons/paste.png diff --git a/icons/paste.svg b/internal/ui/icons/paste.svg similarity index 100% rename from icons/paste.svg rename to internal/ui/icons/paste.svg diff --git a/internal/ui/render.go b/internal/ui/render.go index 9961678..c0847e2 100644 --- a/internal/ui/render.go +++ b/internal/ui/render.go @@ -1,8 +1,12 @@ package ui import ( + "bytes" + "embed" "image" "image/color" + _ "image/png" + "strings" "gioui.org/f32" "gioui.org/layout" @@ -15,6 +19,9 @@ import ( "golang.org/x/image/math/fixed" ) +//go:embed icons/*.png +var iconFS embed.FS + // StateReader provides access to scale and window dimensions from State. // Implemented by editor.State to avoid import cycles. type StateReader interface { @@ -28,11 +35,31 @@ type Renderer struct { theme Theme shp *text.Shaper state StateReader // read-only access to State for scale/window size + icons map[string]image.Image // loaded PNG icons } // New creates a new 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. @@ -167,22 +194,23 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar, _ WindowConst // Left side: Cut, Copy, Paste icons (always visible) leftX := reg.X + Dp(8) 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}) - leftX += Dp(36) - r.drawText(gtx, th.Shaper, "⎘", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) - leftX += Dp(36) - 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(28) + r.drawPng(gtx, r.icons["copy"], leftX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) + leftX += Dp(28) + r.drawPng(gtx, r.icons["paste"], leftX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) leftX += Dp(12) // Right side: Conflict (conditional), Search (always visible) rightX := reg.X + reg.W - Dp(8) if sb.ConflictIcon { - rightX -= Dp(36) - r.drawText(gtx, th.Shaper, "⚠", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) + rightX -= Dp(28) + r.drawPng(gtx, r.icons["conflict"], rightX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) } - rightX -= Dp(36) - r.drawText(gtx, th.Shaper, "🔍", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) + rightX -= Dp(28) + r.drawPng(gtx, r.icons["search"], rightX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) c.Pop() }