diff --git a/go.mod b/go.mod index 7e51eee..db0c1a5 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.3 require ( fyne.io/systray v1.11.0 + gabe565.com/utils v0.0.0-20241114041836-facb2857c9b1 github.com/Masterminds/sprig/v3 v3.3.0 github.com/dmarkham/enumer v1.5.10 github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a @@ -50,7 +51,7 @@ require ( github.com/tj/assert v0.0.3 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/sys v0.26.0 // indirect golang.org/x/tools v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c72abfe..7ce023b 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= +gabe565.com/utils v0.0.0-20241114041836-facb2857c9b1 h1:Y7L3PkUDFFhEOxaN+vhqstaPNqlE8K55wdHonon2Ux4= +gabe565.com/utils v0.0.0-20241114041836-facb2857c9b1/go.mod h1:1WioSVukwGZYG4Q0LJBnRhgYyVljmW2Izl+RW36ALUc= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= @@ -107,8 +109,8 @@ golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/internal/config/color.go b/internal/config/color.go deleted file mode 100644 index c3e38fc..0000000 --- a/internal/config/color.go +++ /dev/null @@ -1,68 +0,0 @@ -package config - -import ( - "bytes" - "errors" - "fmt" - "image/color" - "strconv" -) - -var ( - ErrMissingPrefix = errors.New(`hex code missing "#" prefix`) - ErrInvalidLength = errors.New("hex code should be 4 or 7 characters") -) - -type HexColor color.NRGBA - -func (h HexColor) MarshalText() ([]byte, error) { - shorthand := h.R>>4 == h.R&0xF && h.G>>4 == h.G&0xF && h.B>>4 == h.B&0xF - if shorthand { - return []byte(fmt.Sprintf("#%x%x%x", h.R&0xF, h.G&0xF, h.B&0xF)), nil - } - return []byte(fmt.Sprintf("#%02x%02x%02x", h.R, h.G, h.B)), nil -} - -func (h *HexColor) UnmarshalText(text []byte) error { - if !bytes.HasPrefix(text, []byte("#")) { - return ErrMissingPrefix - } - switch len(text) { - case 4, 7: - default: - return ErrInvalidLength - } - - parsed, err := strconv.ParseUint(string(text[1:]), 16, 32) - if err != nil { - return err - } - - //nolint:gosec - if parsed > 0xFFF { - h.R = uint8(parsed >> 16 & 0xFF) - h.G = uint8(parsed >> 8 & 0xFF) - h.B = uint8(parsed & 0xFF) - } else { - h.R = uint8(parsed >> 8 & 0xF) - h.R |= h.R << 4 - h.G = uint8(parsed >> 4 & 0xF) - h.G |= h.G << 4 - h.B = uint8(parsed & 0xF) - h.B |= h.B << 4 - } - h.A = 0xFF - return nil -} - -func (h HexColor) RGBA() color.RGBA { - return color.RGBA(h) -} - -func White() HexColor { - return HexColor{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF} -} - -func Black() HexColor { - return HexColor{A: 0xFF} -} diff --git a/internal/config/color_test.go b/internal/config/color_test.go deleted file mode 100644 index 0e6a478..0000000 --- a/internal/config/color_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package config - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestHexColor_MarshalText(t *testing.T) { - t.Parallel() - type fields struct { - HexColor HexColor - } - tests := []struct { - name string - fields fields - want []byte - wantErr require.ErrorAssertionFunc - }{ - {"white", fields{HexColor{R: 0xFF, G: 0xFF, B: 0xFF}}, []byte("#fff"), require.NoError}, - {"black", fields{HexColor{}}, []byte("#000"), require.NoError}, - {"red", fields{HexColor{R: 0xFF}}, []byte("#f00"), require.NoError}, - {"green", fields{HexColor{G: 0xFF}}, []byte("#0f0"), require.NoError}, - {"blue", fields{HexColor{B: 0xFF}}, []byte("#00f"), require.NoError}, - {"blue-gray", fields{HexColor{R: 0x60, G: 0x7D, B: 0x8B}}, []byte("#607d8b"), require.NoError}, - {"increment", fields{HexColor{R: 1, G: 2, B: 3}}, []byte("#010203"), require.NoError}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - h := tt.fields.HexColor - got, err := h.MarshalText() - tt.wantErr(t, err) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestHexColor_UnmarshalText(t *testing.T) { - t.Parallel() - type args struct { - text []byte - } - tests := []struct { - name string - args args - want HexColor - wantErr require.ErrorAssertionFunc - }{ - {"white", args{[]byte("#fff")}, HexColor{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}, require.NoError}, - {"black", args{[]byte("#000")}, HexColor{A: 0xFF}, require.NoError}, - {"red", args{[]byte("#f00")}, HexColor{R: 0xFF, A: 0xFF}, require.NoError}, - {"green", args{[]byte("#0f0")}, HexColor{G: 0xFF, A: 0xFF}, require.NoError}, - {"blue", args{[]byte("#00f")}, HexColor{B: 0xFF, A: 0xFF}, require.NoError}, - {"blue-gray", args{[]byte("#607d8b")}, HexColor{R: 0x60, G: 0x7D, B: 0x8B, A: 0xFF}, require.NoError}, - {"missing-prefix", args{[]byte("fff")}, HexColor{}, require.Error}, - {"too-long", args{[]byte("#fffffff")}, HexColor{}, require.Error}, - {"too-short", args{[]byte("#fffff")}, HexColor{}, require.Error}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - h := HexColor{} - tt.wantErr(t, h.UnmarshalText(tt.args.text)) - assert.Equal(t, tt.want, h) - }) - } -} diff --git a/internal/config/config.go b/internal/config/config.go index 2accf45..41dab06 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,7 @@ import ( "path/filepath" "runtime" + "gabe565.com/utils/colorx" "github.com/spf13/pflag" ) @@ -26,10 +27,10 @@ type Config struct { } type DynamicIcon struct { - Enabled bool `toml:"enabled"` - FontColor HexColor `toml:"font-color" comment:"Hex code used to render text."` - FontFile string `toml:"font-file" comment:"Font path or filename of a system font. If left blank, an embedded font will be used."` - MaxFontSize float64 `toml:"max-font-size" comment:"Maximum font size in points."` + Enabled bool `toml:"enabled"` + FontColor colorx.Hex `toml:"font-color" comment:"Hex code used to render text."` + FontFile string `toml:"font-file" comment:"Font path or filename of a system font. If left blank, an embedded font will be used."` + MaxFontSize float64 `toml:"max-font-size" comment:"Maximum font size in points."` } type Arrows struct { diff --git a/internal/config/default.go b/internal/config/default.go index 89209cc..feb5b30 100644 --- a/internal/config/default.go +++ b/internal/config/default.go @@ -1,12 +1,14 @@ package config import ( + "image/color" "log/slog" "path/filepath" "runtime" "strings" "time" + "gabe565.com/utils/colorx" flag "github.com/spf13/pflag" ) @@ -18,7 +20,7 @@ func New(opts ...Option) *Config { Units: UnitMgdl, DynamicIcon: DynamicIcon{ Enabled: true, - FontColor: White(), + FontColor: colorx.Hex{Color: color.White}, MaxFontSize: 40, }, Arrows: Arrows{ @@ -50,7 +52,7 @@ func New(opts ...Option) *Config { case "darwin": conf.DynamicIcon.Enabled = false case "windows": - conf.DynamicIcon.FontColor = Black() + conf.DynamicIcon.FontColor = colorx.Hex{Color: color.Black} } conf.Flags = flag.NewFlagSet("", flag.ContinueOnError) diff --git a/internal/dynamicicon/dynamicicon.go b/internal/dynamicicon/dynamicicon.go index 3dfd705..01beba3 100644 --- a/internal/dynamicicon/dynamicicon.go +++ b/internal/dynamicicon/dynamicicon.go @@ -104,7 +104,7 @@ func (d *DynamicIcon) Generate(p *nightscout.Properties) ([]byte, error) { drawer := &font.Drawer{ Dst: d.img, - Src: image.NewUniform(d.config.DynamicIcon.FontColor.RGBA()), + Src: image.NewUniform(d.config.DynamicIcon.FontColor), } fontSize := d.config.DynamicIcon.MaxFontSize * 2 diff --git a/internal/tray/items/preferences/dynamic_icon_color.go b/internal/tray/items/preferences/dynamic_icon_color.go index 921398e..e7d1578 100644 --- a/internal/tray/items/preferences/dynamic_icon_color.go +++ b/internal/tray/items/preferences/dynamic_icon_color.go @@ -19,11 +19,10 @@ type DynamicIconColor struct { *systray.MenuItem } -//nolint:gosec func (l DynamicIconColor) Choose() error { c, err := zenity.SelectColor( zenity.Title("Dynamic Icon Color"), - zenity.Color(l.config.DynamicIcon.FontColor.RGBA()), + zenity.Color(l.config.DynamicIcon.FontColor), ) if err != nil { if errors.Is(err, zenity.ErrCanceled) { @@ -32,11 +31,7 @@ func (l DynamicIconColor) Choose() error { return err } - r, g, b, a := c.RGBA() - l.config.DynamicIcon.FontColor.R = uint8(r) - l.config.DynamicIcon.FontColor.G = uint8(g) - l.config.DynamicIcon.FontColor.B = uint8(b) - l.config.DynamicIcon.FontColor.A = uint8(a) + l.config.DynamicIcon.FontColor.Color = c if err := l.config.Write(); err != nil { return err } diff --git a/internal/tray/systray.go b/internal/tray/systray.go index 2f2a278..bd511f2 100644 --- a/internal/tray/systray.go +++ b/internal/tray/systray.go @@ -2,6 +2,7 @@ package tray import ( "context" + "image/color" "io" "log/slog" "os" @@ -154,8 +155,8 @@ func (t *Tray) onReady(ctx context.Context) func() { //nolint:gocyclo } else { if icon, err := t.dynamicIcon.Generate(msg.Properties); err == nil { systray.SetTitle("") - switch t.config.DynamicIcon.FontColor { - case config.White(): + switch t.config.DynamicIcon.FontColor.Color { + case color.White: systray.SetTemplateIcon(icon, icon) default: systray.SetIcon(icon)