diff options
-rw-r--r-- | lib/ui/grid.go | 1 | ||||
-rw-r--r-- | lib/ui/list.go | 110 | ||||
-rw-r--r-- | widgets/directories.go | 71 |
3 files changed, 156 insertions, 26 deletions
diff --git a/lib/ui/grid.go b/lib/ui/grid.go index 3c375ee..5fe6ab2 100644 --- a/lib/ui/grid.go +++ b/lib/ui/grid.go @@ -5,6 +5,7 @@ import ( "math" ) +// A container which arranges its children on a grid. type Grid struct { rows []GridSpec rowLayout []gridLayout diff --git a/lib/ui/list.go b/lib/ui/list.go new file mode 100644 index 0000000..58f304f --- /dev/null +++ b/lib/ui/list.go @@ -0,0 +1,110 @@ +package ui + +import ( + "fmt" +) + +// A container which arranges its children in a list. +type List struct { + Items []*ListItem + itemHeight int + onInvalidate func(d Drawable) + selected int +} + +type ListItem struct { + Content Drawable + invalid bool +} + +type SelectableDrawable interface { + Drawable + DrawWithSelected(ctx *Context, selected bool) +} + +func NewList() *List { + return &List{itemHeight: 1, selected: -1} +} + +func (list *List) OnInvalidate(onInvalidate func(d Drawable)) { + list.onInvalidate = onInvalidate +} + +func (list *List) Invalidate() { + for _, item := range list.Items { + item.Content.Invalidate() + } + if list.onInvalidate != nil { + list.onInvalidate(list) + } +} + +func (list *List) Draw(ctx *Context) { + for i, item := range list.Items { + if !item.invalid { + continue + } + subctx := ctx.Subcontext(0, i, ctx.Width(), list.itemHeight) + if content, ok := item.Content.(SelectableDrawable); ok { + content.DrawWithSelected(subctx, i == list.selected) + } else { + item.Content.Draw(subctx) + } + } +} + +func (list *List) Add(child Drawable) { + list.Items = append(list.Items, &ListItem{Content: child, invalid: true}) + child.OnInvalidate(list.childInvalidated) + list.Invalidate() +} + +func (list *List) Remove(child Drawable) { + for i, item := range list.Items { + if item.Content == child { + list.Items = append(list.Items[:i], list.Items[i+1:]...) + child.OnInvalidate(nil) + list.Invalidate() + return + } + } + panic(fmt.Errorf("Attempted to remove unknown child")) +} + +func (list *List) Set(items []Drawable) { + for _, item := range list.Items { + item.Content.OnInvalidate(nil) + } + list.Items = make([]*ListItem, len(items)) + for i, item := range items { + list.Items[i] = &ListItem{Content: item, invalid: true} + item.OnInvalidate(list.childInvalidated) + } + list.Invalidate() +} + +func (list *List) Select(index int) { + if index >= len(list.Items) || index < 0 { + panic(fmt.Errorf("Attempted to select unknown child")) + } + list.selected = index + list.Invalidate() +} + +func (list *List) ItemHeight(height int) { + list.itemHeight = height + list.Invalidate() +} + +func (list *List) childInvalidated(child Drawable) { + for _, item := range list.Items { + if item.Content == child { + item.invalid = true + if list.onInvalidate != nil { + list.onInvalidate(list) + } + return + } + } + panic(fmt.Errorf("Attempted to invalidate unknown child")) +} diff --git a/widgets/directories.go b/widgets/directories.go index 16b0e5a..ff2f6f5 100644 --- a/widgets/directories.go +++ b/widgets/directories.go @@ -3,6 +3,7 @@ package widgets import ( "log" "sort" + "strings" "github.com/gdamore/tcell" @@ -13,7 +14,7 @@ import ( type DirectoryList struct { conf *config.AccountConfig - dirs []string + dirs *ui.List logger *log.Logger onInvalidate func(d ui.Drawable) worker *types.Worker @@ -22,50 +23,68 @@ type DirectoryList struct { func NewDirectoryList(conf *config.AccountConfig, logger *log.Logger, worker *types.Worker) *DirectoryList { - return &DirectoryList{conf: conf, logger: logger, worker: worker} + return &DirectoryList{ + conf: conf, + dirs: ui.NewList(), + logger: logger, + worker: worker, + } } func (dirlist *DirectoryList) UpdateList() { - var dirs []string + var dirs []ui.Drawable dirlist.worker.PostAction( &types.ListDirectories{}, func(msg types.WorkerMessage) { switch msg := msg.(type) { case *types.Directory: - dirs = append(dirs, msg.Name) + if len(dirlist.conf.Folders) > 1 { + idx := sort.SearchStrings(dirlist.conf.Folders, msg.Name) + if idx == len(dirlist.conf.Folders) || + dirlist.conf.Folders[idx] != msg.Name { + break + } + } + dirs = append(dirs, directoryEntry(msg.Name)) case *types.Done: - sort.Strings(dirs) - dirlist.dirs = dirs - dirlist.Invalidate() + sort.Slice(dirs, func(_a, _b int) bool { + a, _ := dirs[_a].(directoryEntry) + b, _ := dirs[_b].(directoryEntry) + return strings.Compare(string(a), string(b)) > 0 + }) + dirlist.dirs.Set(dirs) } }) } func (dirlist *DirectoryList) OnInvalidate(onInvalidate func(d ui.Drawable)) { - dirlist.onInvalidate = onInvalidate + dirlist.dirs.OnInvalidate(func(_ ui.Drawable) { + onInvalidate(dirlist) + }) } func (dirlist *DirectoryList) Invalidate() { - if dirlist.onInvalidate != nil { - dirlist.onInvalidate(dirlist) - } + dirlist.dirs.Invalidate() } func (dirlist *DirectoryList) Draw(ctx *ui.Context) { + dirlist.dirs.Draw(ctx) +} + +type directoryEntry string + +func (d directoryEntry) OnInvalidate(_ func(_ ui.Drawable)) { +} + +func (d directoryEntry) Invalidate() { +} + +func (d directoryEntry) Draw(ctx *ui.Context) { + d.DrawWithSelected(ctx, false) +} + +func (d directoryEntry) DrawWithSelected(ctx *ui.Context, selected bool) { + // TODO: distinguish the selected item ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) - row := 0 - for _, name := range dirlist.dirs { - if row >= ctx.Height() { - break - } - if len(dirlist.conf.Folders) > 1 { - idx := sort.SearchStrings(dirlist.conf.Folders, name) - if idx == len(dirlist.conf.Folders) || - dirlist.conf.Folders[idx] != name { - continue - } - } - ctx.Printf(0, row, tcell.StyleDefault, "%s", name) - row++ - } + ctx.Printf(0, 0, tcell.StyleDefault, "%s", d) } |