icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

commit fc3feb07bc0deee31daa2f59bd8e2b68297fe0e5
parent 2c1ad131ca2cea8e662275d20e43169bdf3ce743
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date:   Thu,  6 May 2021 21:30:34 +0800

feat: save as icns with ctrl-s hotkey

Signed-off-by: Jack Mordaunt <jackmordaunt@gmail.com>

Diffstat:
Mcmd/preview/main.go | 91++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 76 insertions(+), 15 deletions(-)

diff --git a/cmd/preview/main.go b/cmd/preview/main.go @@ -5,13 +5,16 @@ import ( "fmt" "image" "image/color" + "image/png" "log" "os" "path/filepath" "strconv" + "strings" "gioui.org/app" "gioui.org/font/gofont" + "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" "gioui.org/layout" @@ -41,7 +44,7 @@ func main() { if len(os.Args) > 1 { if file := os.Args[1]; filepath.Ext(file) == ".icns" { go func() { - imgs, err := LoadICNS(file) + imgs, err := LoadImage(file) ui.ProcessedIcon <- ProcessedIconResult{ Imgs: imgs, File: filepath.Base(file), @@ -75,6 +78,8 @@ type UI struct { Icons []widget.Image // FileName is the name of the source icon file on disk. FileName string + // Source is the original image data. + Source image.Image OpenBtn widget.Clickable SideBar layout.List @@ -115,6 +120,33 @@ func (ui *UI) Update(gtx C) { if ui.Processing { op.InvalidateOp{}.Add(gtx.Ops) } + for _, event := range gtx.Events(ui) { + if k, ok := event.(key.Event); ok { + if k.Name == "S" && k.Modifiers.Contain(key.ModShortcut) && ui.Source != nil { + if err := func() error { + file, err := zenity.SelectFileSave( + zenity.Title("Save as icns"), + zenity.Filename(UseExt(ui.FileName, ".icns"))) + if err != nil { + return fmt.Errorf("selecting file: %w", err) + } + if err := ui.SaveAs(file); err != nil { + return fmt.Errorf("saving to icns: %w", err) + } + return nil + }(); err != nil { + log.Printf("saving png as icns: %v", err) + } + } + } + } + for ii := range ui.Icons { + for _, event := range gtx.Events(ui.Icons[ii]) { + if c, ok := event.(pointer.Event); ok && c.Type == pointer.Release { + ui.Preview = &ui.Icons[ii] + } + } + } if ui.OpenBtn.Clicked() { ui.Processing = true go func() { @@ -123,7 +155,7 @@ func (ui *UI) Update(gtx C) { if err != nil { return nil, "", fmt.Errorf("selecting file: %w", err) } - imgs, err := LoadICNS(file) + imgs, err := LoadImage(file) if err != nil { return nil, "", err } @@ -136,13 +168,6 @@ func (ui *UI) Update(gtx C) { } }() } - for ii := range ui.Icons { - for _, event := range gtx.Events(ui.Icons[ii]) { - if c, ok := event.(pointer.Event); ok && c.Type == pointer.Release { - ui.Preview = &ui.Icons[ii] - } - } - } select { case r := <-ui.ProcessedIcon: if r.Err != nil { @@ -157,6 +182,9 @@ func (ui *UI) Update(gtx C) { Position: l.Center, }) } + if len(r.Imgs) > 0 { + ui.Source = r.Imgs[0] + } if len(ui.Icons) > 0 { ui.Preview = &ui.Icons[0] } @@ -170,6 +198,8 @@ func (ui *UI) Update(gtx C) { // Layout the UI. func (ui *UI) Layout(gtx C) D { ui.SideBar.Axis = l.Vertical + key.InputOp{Tag: ui}.Add(gtx.Ops) + key.FocusOp{Tag: ui}.Add(gtx.Ops) return l.Flex{ Axis: l.Horizontal, }.Layout( @@ -263,6 +293,22 @@ func (ui *UI) LayoutThumbnail(gtx C, ii int) D { ) } +// SaveAs saves the previewed image as an icns icon at the path specified. +func (ui *UI) SaveAs(path string) error { + if ui.Source == nil { + return nil + } + f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644) + if err != nil { + return fmt.Errorf("creating file: %w", err) + } + defer f.Close() + if err := icns.Encode(f, ui.Source); err != nil { + return fmt.Errorf("encoding icns: %w", err) + } + return nil +} + // LoadImages loads the specified images to preview. // Safe for concurrent use. func (ui *UI) LoadImages(name string, imgs []image.Image) { @@ -273,8 +319,8 @@ func (ui *UI) LoadImages(name string, imgs []image.Image) { } } -// LoadICNS decodes all images from the specified icns file. -func LoadICNS(path string) ([]image.Image, error) { +// LoadImage will load all icons from an icns file, or generate them from a png file. +func LoadImage(path string) ([]image.Image, error) { path, err := filepath.Abs(path) if err != nil { return nil, fmt.Errorf("resolving file path: %w", err) @@ -284,9 +330,24 @@ func LoadICNS(path string) ([]image.Image, error) { return nil, err } defer f.Close() - imgs, err := icns.DecodeAll(f) - if err != nil { - return nil, fmt.Errorf("decoding: %w", err) + switch filepath.Ext(path) { + case ".icns": + imgs, err := icns.DecodeAll(f) + if err != nil { + return nil, fmt.Errorf("decoding icns: %w", err) + } + return imgs, nil + case ".png": + img, err := png.Decode(f) + if err != nil { + return nil, fmt.Errorf("decoding png: %w", err) + } + return []image.Image{img}, nil } - return imgs, nil + return nil, nil +} + +// UseExt replaces any existing file extension with the provided one. +func UseExt(s, ext string) string { + return strings.Replace(s, filepath.Ext(s), ".icns", 1) }