package main import ( _ "embed" "io" "log" "os" "path/filepath" "slices" 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 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) { f, err := os.Open(file) if err != nil { log.Println(err.Error()) return } i, err := io.ReadAll(f) if err != nil { log.Println(err.Error()) return } img.Configure(tk.Image(tk.NewPhoto(tk.Data(i)))) directoryState.currentFile = filepath.Base(file) } func main() { img := tk.Label(tk.Image(tk.NewPhoto(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)) // 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, "", 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 { tk.Toplevel() } 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, "", 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, "", tk.Command(func(e *tk.Event) { // log.Printf("%v, %v", int16(e.Delta>>16), int16(e.Delta&0xFFFF)) // })) tk.Pack(img) tk.App.Configure(tk.Mnu(menubar)).Center().Wait() }