diff --git a/elements/file/directory.go b/elements/file/directory.go new file mode 100644 index 0000000..9dbe8a9 --- /dev/null +++ b/elements/file/directory.go @@ -0,0 +1,73 @@ +package fileElements + +import "io/fs" +import "path/filepath" +import "git.tebibyte.media/sashakoshka/tomo/elements/basic" + +type ReadDirStatFS interface { + fs.ReadDirFS + fs.StatFS +} + +type DirectoryView struct { + *basicElements.List + + filesystem ReadDirStatFS + location string + onChoose func (file string) +} + +func NewDirectoryView ( + location string, + within ReadDirStatFS, +) ( + element *DirectoryView, + err error, +) { + element = &DirectoryView { + List: basicElements.NewList(), + } + err = element.SetLocation(location, within) + return +} + +func (element *DirectoryView) Location () (string, fs.ReadDirFS) { + return element.location, element.filesystem +} + +func (element *DirectoryView) SetLocation ( + location string, + within ReadDirStatFS, +) error { + if within == nil { + within = defaultFS { } + } + element.location = location + element.filesystem = within + return element.Update() +} + +func (element *DirectoryView) Update () error { + entries, err := element.filesystem.ReadDir(element.location) + + listEntries := make([]basicElements.ListEntry, len(entries)) + for index, entry := range entries { + filePath := filepath.Join(element.location, entry.Name()) + listEntries[index] = basicElements.NewListEntry ( + entry.Name(), + func () { + filePath := filePath + if element.onChoose != nil { + element.onChoose(filePath) + } + }) + } + element.Clear() + element.Append(listEntries...) + + return err +} + +func (element *DirectoryView) OnChoose (callback func (file string)) { + element.onChoose = callback +} diff --git a/elements/file/file.go b/elements/file/file.go new file mode 100644 index 0000000..03a4e80 --- /dev/null +++ b/elements/file/file.go @@ -0,0 +1,67 @@ +package fileElements + +import "io/fs" +import "git.tebibyte.media/sashakoshka/tomo/theme" +import "git.tebibyte.media/sashakoshka/tomo/elements/basic" + +// File is a +type File struct { + *basicElements.Icon + + // we inherit from Icon directly because it is not our responsibility + // to draw text. this will be the responsibility of the directory that + // contains the file. we don't handle mouse events on the file label + // text either because when the user clicks on that we want to rename + // the file. + + filesystem fs.StatFS + location string + onChoose func () +} + +func NewFile ( + location string, + within fs.StatFS, +) ( + element *File, + err error, +) { + element = &File { + Icon: basicElements.NewIcon(theme.IconFile, theme.IconSizeLarge), + } + err = element.SetLocation(location, within) + return +} + +func (element *File) Location () (string, fs.StatFS) { + return element.location, element.filesystem +} + +func (element *File) SetLocation ( + location string, + within fs.StatFS, +) error { + if within == nil { + within = defaultFS { } + } + element.location = location + element.filesystem = within + return element.Update() +} + +func (element *File) Update () error { + info, err := element.filesystem.Stat(element.location) + if err != nil { return err } + + if info.IsDir() { + element.SetIcon(theme.IconDirectory, theme.IconSizeLarge) + } else { + element.SetIcon(theme.IconFile, theme.IconSizeLarge) + } + + return err +} + +func (element *File) OnChoose (callback func ()) { + element.onChoose = callback +} diff --git a/elements/file/fs.go b/elements/file/fs.go new file mode 100644 index 0000000..812333b --- /dev/null +++ b/elements/file/fs.go @@ -0,0 +1,18 @@ +package fileElements + +import "os" +import "io/fs" + +type defaultFS struct { } + +func (defaultFS) Open (name string) (fs.File, error) { + return os.Open(name) +} + +func (defaultFS) ReadDir (name string) ([]DirEntry, error) { + return os.ReadDir(name) +} + +func (defaultFS) Stat (name string) (FileInfo, error) { + return os.Stat(name) +} diff --git a/examples/fileBrowser/main.go b/examples/fileBrowser/main.go new file mode 100644 index 0000000..34da5b0 --- /dev/null +++ b/examples/fileBrowser/main.go @@ -0,0 +1,62 @@ +package main + +import "os" +import "git.tebibyte.media/sashakoshka/tomo" +import "git.tebibyte.media/sashakoshka/tomo/theme" +import "git.tebibyte.media/sashakoshka/tomo/layouts/basic" +import "git.tebibyte.media/sashakoshka/tomo/elements/file" +import "git.tebibyte.media/sashakoshka/tomo/elements/basic" +import "git.tebibyte.media/sashakoshka/tomo/elements/containers" +import _ "git.tebibyte.media/sashakoshka/tomo/backends/all" + +func main () { + tomo.Run(run) +} + +func run () { + window, _ := tomo.NewWindow(384, 384) + window.SetTitle("File browser") + container := containers.NewContainer(basicLayouts.Vertical { true, true }) + window.Adopt(container) + + controlBar := containers.NewContainer(basicLayouts.Horizontal { }) + backButton := basicElements.NewButton("Back") + backButton.SetIcon(theme.IconBackward) + backButton.ShowText(false) + forwardButton := basicElements.NewButton("Forward") + forwardButton.SetIcon(theme.IconForward) + forwardButton.ShowText(false) + refreshButton := basicElements.NewButton("Refresh") + refreshButton.SetIcon(theme.IconRefresh) + refreshButton.ShowText(false) + upwardButton := basicElements.NewButton("Go Up") + upwardButton.SetIcon(theme.IconUpward) + upwardButton.ShowText(false) + locationInput := basicElements.NewTextBox("Location", "") + + scrollContainer := containers.NewScrollContainer(false, true) + homeDir,_ := os.UserHomeDir() + directoryView, _ := fileElements.NewDirectoryView(homeDir) + directoryView.Collapse(0, 8) + choose := func (filePath string) { + directoryView.SetLocation(filePath) + locationInput.SetValue(directoryView.Location()) + } + directoryView.OnChoose(choose) + locationInput.OnEnter (func () { + choose(locationInput.Value()) + }) + choose(homeDir) + + scrollContainer.Adopt(directoryView) + controlBar.Adopt(backButton, false) + controlBar.Adopt(forwardButton, false) + controlBar.Adopt(refreshButton, false) + controlBar.Adopt(upwardButton, false) + controlBar.Adopt(locationInput, true) + container.Adopt(controlBar, false) + container.Adopt(scrollContainer, true) + + window.OnClose(tomo.Stop) + window.Show() +}