Files
why/main.go
2025-03-24 23:35:47 -04:00

188 lines
4.9 KiB
Go

package main
import (
_ "embed"
"fmt"
"log"
"os"
"path/filepath"
"slices"
"github.com/disintegration/imaging"
tk "modernc.org/tk9.0"
)
// this is a default image
//
//go:embed noise.png
var noise []byte
var validFileTypes = []tk.FileType{
{TypeName: "BMP", Extensions: []string{".bmp"}},
{TypeName: "JPEG", Extensions: []string{".jpg", ".jpeg"}},
{TypeName: "GIF", Extensions: []string{".gif"}},
{TypeName: "PNG", Extensions: []string{".png"}},
{TypeName: "SVG", Extensions: []string{".svg"}},
{TypeName: "TGA", Extensions: []string{".tga"}},
{TypeName: "TIFF", Extensions: []string{".tiff"}},
}
var metaActive bool
var fileList *tk.ToplevelWidget
var directoryState struct {
currentDirectory string
currentFile string
images []string
}
func isImage(entry os.DirEntry) bool {
if entry.IsDir() {
return false
}
ext := filepath.Ext(entry.Name())
for _, ft := range validFileTypes {
if slices.Contains(ft.Extensions, ext) {
return true
}
}
return false
}
func newFileInDirectory(img *tk.LabelWidget) func() {
return func() {
files := tk.GetOpenFile(tk.Filetypes(validFileTypes))
if len(files) < 1 || files[0] == "" {
log.Println("no file chosen")
return
}
directoryState.currentFile = filepath.Base(files[0])
directoryState.currentDirectory = filepath.Dir(files[0])
dirfiles, err := os.ReadDir(directoryState.currentDirectory)
if err != nil {
log.Println(err)
return
}
directoryState.images = []string{}
for _, v := range dirfiles {
if isImage(v) {
directoryState.images = append(directoryState.images, v.Name())
}
}
updateImage(files[0], img)
}
}
func newDirectory(img *tk.LabelWidget) func() {
return func() {
dir := tk.ChooseDirectory()
if dir == "" {
log.Println("no directory chosen")
return
}
directoryState.currentDirectory = dir
dirfiles, err := os.ReadDir(directoryState.currentDirectory)
if err != nil {
log.Println(err)
return
}
directoryState.images = []string{}
for _, v := range dirfiles {
if isImage(v) {
directoryState.images = append(directoryState.images, v.Name())
}
}
updateImage(filepath.Join(directoryState.currentDirectory, directoryState.images[0]), img)
}
}
func updateImage(file string, img *tk.LabelWidget) {
i, err := imaging.Open(file, imaging.AutoOrientation(true))
if err != nil {
log.Println(err.Error())
return
}
// todo: make these bounds max out at display size
if i.Bounds().Dy() > i.Bounds().Dx() {
i = imaging.Resize(i, 0, 1000, imaging.CatmullRom)
} else {
i = imaging.Resize(i, 1000, 0, imaging.CatmullRom)
}
repaint(img, filepath.Base(file), tk.Data(i))
directoryState.currentFile = filepath.Base(file)
}
func repaint(img *tk.LabelWidget, name string, opt tk.Opt) {
photo := tk.NewPhoto(opt)
log.Printf("height: %v, width: %v", photo.Height(), photo.Width())
img.Configure(tk.Image(tk.NewPhoto(opt)))
if name != "" {
tk.App.WmTitle(fmt.Sprintf("why | %s", name))
} else {
tk.App.WmTitle("why")
}
}
func main() {
img := tk.Label()
repaint(img, "", tk.Data(noise))
menubar := tk.Menu()
fileMenu := menubar.Menu()
fileMenu.AddCommand(tk.Lbl("Open File"), tk.Underline(0), tk.Accelerator("Meta+O"), tk.Command(newFileInDirectory(img)))
fileMenu.AddCommand(tk.Lbl("Open Directory"), tk.Underline(0), tk.Command(newDirectory(img)))
menubar.AddCascade(tk.Lbl("File"), tk.Underline(0), tk.Mnu(fileMenu))
tk.App.Configure(tk.Mnu(menubar))
// TODO: if someone presses the Meta key again after the openfile dialog box closes,
// it triggers a _release_ event instead of a press event. The second time afterward, it works correctly.
tk.Bind(tk.App, "<KeyPress>", tk.Command(func(e *tk.Event) {
curr := slices.Index(directoryState.images, directoryState.currentFile)
switch e.Keysym {
case ".":
log.Printf("state: %+v", directoryState)
case "Meta_L", "Meta_R":
metaActive = true
case "o":
if metaActive {
newFileInDirectory(img)()
}
case "a":
if metaActive {
if fileList == nil {
fileList = tk.Toplevel()
fileList.WmTitle("files")
tk.Bind(fileList, "<Destroy>", tk.Command(func(e *tk.Event) {
// list closed by user click on <x>
fileList = nil
}))
} else {
// list closed by Meta-a
tk.Destroy(fileList)
}
}
case "Up":
if curr > 0 {
updateImage(filepath.Join(directoryState.currentDirectory, directoryState.images[curr-1]), img)
}
case "Down":
if curr < len(directoryState.images)-1 && curr != -1 {
updateImage(filepath.Join(directoryState.currentDirectory, directoryState.images[curr+1]), img)
}
}
}))
tk.Bind(tk.App, "<KeyRelease>", tk.Command(func(e *tk.Event) {
switch e.Keysym {
case "Meta_L", "Meta_R":
metaActive = false
}
}))
// todo: resize image based on scroll events
// tk.Bind(tk.App, "<TouchpadScroll>", tk.Command(func(e *tk.Event) {
// log.Printf("%v, %v", int16(e.Delta>>16), int16(e.Delta&0xFFFF))
// }))
tk.Pack(img)
tk.App.Wait()
}