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)
|
Before Width: | Height: | Size: 444 B After Width: | Height: | Size: 444 B |
|
Before Width: | Height: | Size: 301 B After Width: | Height: | Size: 301 B |
|
Before Width: | Height: | Size: 778 B After Width: | Height: | Size: 778 B |
|
Before Width: | Height: | Size: 347 B After Width: | Height: | Size: 347 B |
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 552 B After Width: | Height: | Size: 552 B |
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||