mirror of
https://github.com/status-im/status-console-client.git
synced 2025-02-23 16:18:23 +00:00
288 lines
6.6 KiB
Go
288 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/jroimartin/gocui"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Type of views.
|
|
const (
|
|
ViewContacts = "contacts"
|
|
ViewChat = "chat"
|
|
ViewInput = "input"
|
|
ViewNotification = "notification"
|
|
)
|
|
|
|
// View describes a single terminal view.
|
|
type View struct {
|
|
Name string
|
|
Title string
|
|
Enabled bool
|
|
TopLeft func(int, int) (int, int)
|
|
BottomRight func(int, int) (int, int)
|
|
Autoscroll bool
|
|
Cursor bool
|
|
Editable bool
|
|
Wrap bool
|
|
Highlight bool
|
|
SelBgColor, SelFgColor gocui.Attribute
|
|
|
|
Keybindings []Binding
|
|
|
|
OnActivate func(*View)
|
|
OnDeactivate func(*View)
|
|
}
|
|
|
|
// ViewManager manages a set of views.
|
|
type ViewManager struct {
|
|
g *gocui.Gui
|
|
views []*View
|
|
activeView string
|
|
previousView string
|
|
}
|
|
|
|
// NewViewManager returns a new view manager.
|
|
func NewViewManager(views []*View, g *gocui.Gui) *ViewManager {
|
|
m := ViewManager{
|
|
g: g,
|
|
views: views,
|
|
}
|
|
|
|
if len(views) > 0 {
|
|
m.previousView = views[0].Name
|
|
m.activeView = m.previousView
|
|
}
|
|
|
|
m.g.Highlight = true
|
|
m.g.Cursor = true
|
|
m.g.SelFgColor = gocui.ColorGreen
|
|
|
|
m.g.SetManagerFunc(m.Layout)
|
|
|
|
return &m
|
|
}
|
|
|
|
// SetViews replaces the existing views with the new ones.
|
|
func (m *ViewManager) SetViews(views []*View) error {
|
|
for _, v := range m.views {
|
|
if err := m.DeleteView(v.Name); err != nil {
|
|
return errors.Wrap(err, "failed to set views")
|
|
}
|
|
}
|
|
|
|
m.views = views
|
|
|
|
if len(views) > 0 {
|
|
m.previousView = views[0].Name
|
|
m.activeView = m.previousView
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *ViewManager) setKeybindings(viewName string, bindings []Binding) error {
|
|
for _, b := range bindings {
|
|
if err := m.g.SetKeybinding(viewName, b.Key, b.Mod, b.Handler); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetGlobalKeybindings sets a global key bindings which work from each view.
|
|
func (m *ViewManager) SetGlobalKeybindings(bindings []Binding) error {
|
|
globalKeybindingsViewName := ""
|
|
|
|
m.g.DeleteKeybindings(globalKeybindingsViewName)
|
|
if err := m.setKeybindings(globalKeybindingsViewName, bindings); err != nil {
|
|
return errors.Wrap(err, "failed to set global keybindings")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RawView returns a underlying low level view struct.
|
|
func (m *ViewManager) RawView(name string) (*gocui.View, error) {
|
|
return m.g.View(name)
|
|
}
|
|
|
|
// ViewByName returns a View by name.
|
|
func (m *ViewManager) ViewByName(name string) *View {
|
|
for _, v := range m.views {
|
|
if v.Name == name {
|
|
return v
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ViewIndex returns an index of a view by name.
|
|
func (m *ViewManager) ViewIndex(name string) int {
|
|
for i, v := range m.views {
|
|
if v.Name == name {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Layout puts the views in the terminal window.
|
|
func (m *ViewManager) Layout(g *gocui.Gui) error {
|
|
maxX, maxY := g.Size()
|
|
|
|
for _, config := range m.views {
|
|
if !config.Enabled {
|
|
continue
|
|
}
|
|
|
|
x0, y0 := config.TopLeft(maxX, maxY)
|
|
x1, y1 := config.BottomRight(maxX, maxY)
|
|
|
|
v, err := m.g.SetView(config.Name, x0, y0, x1, y1)
|
|
if err != nil && err != gocui.ErrUnknownView {
|
|
return errors.Wrap(err, "failed to do layout")
|
|
}
|
|
// add bindings only once
|
|
if err == gocui.ErrUnknownView {
|
|
if err := m.setKeybindings(config.Name, config.Keybindings); err != nil {
|
|
return errors.Wrap(err, "failed to do layout")
|
|
}
|
|
}
|
|
|
|
if config.Title != "" {
|
|
v.Title = config.Title
|
|
} else {
|
|
v.Title = strings.ToUpper(config.Name)
|
|
}
|
|
v.Autoscroll = config.Autoscroll
|
|
v.Editable = config.Editable
|
|
v.Wrap = config.Wrap
|
|
v.Highlight = config.Highlight
|
|
v.SelFgColor = config.SelFgColor
|
|
v.SelBgColor = config.SelBgColor
|
|
|
|
if m.activeView == config.Name {
|
|
if _, err := m.setCurrentView(config.Name); err != nil {
|
|
return errors.Wrap(err, "failed to do layout")
|
|
}
|
|
|
|
m.g.Highlight = config.Highlight
|
|
m.g.Cursor = config.Cursor
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteView removes a view and its bindings.
|
|
func (m *ViewManager) DeleteView(name string) error {
|
|
if err := m.g.DeleteView(name); err != nil {
|
|
return err
|
|
}
|
|
|
|
m.g.DeleteKeybindings(name)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *ViewManager) setCurrentView(name string) (*gocui.View, error) {
|
|
return m.g.SetCurrentView(name)
|
|
}
|
|
|
|
// EnableView makes the view enable and selects it immediately.
|
|
func (m *ViewManager) EnableView(name string) error {
|
|
log.Printf("[ViewManager::EnableView] enabling '%s'", name)
|
|
|
|
view := m.ViewByName(name)
|
|
if view == nil {
|
|
return fmt.Errorf("failed to enable non-existing view '%s'", name)
|
|
}
|
|
|
|
view.Enabled = true
|
|
|
|
if err := m.Layout(m.g); err != nil {
|
|
return errors.Wrap(err, fmt.Sprintf("failed to enable view '%s'", name))
|
|
}
|
|
|
|
_, err := m.SelectView(name)
|
|
return errors.Wrap(err, "failed to enable view")
|
|
}
|
|
|
|
// DisableView disables the view and selects the previous active one.
|
|
func (m *ViewManager) DisableView(name string) error {
|
|
log.Printf("[ViewManager::DisableView] disabling '%s'", name)
|
|
|
|
view := m.ViewByName(name)
|
|
if view == nil {
|
|
return fmt.Errorf("failed to disable non-existing view '%s'", name)
|
|
}
|
|
|
|
view.Enabled = false
|
|
|
|
if err := m.Layout(m.g); err != nil {
|
|
return errors.Wrap(err, fmt.Sprintf("failed to disable view '%s'", name))
|
|
}
|
|
|
|
_, err := m.SelectView(m.previousView)
|
|
return errors.Wrap(err, "failed to disable view")
|
|
}
|
|
|
|
// SelectView selects a view to be active by name.
|
|
func (m *ViewManager) SelectView(name string) (*gocui.View, error) {
|
|
log.Printf("[ViewManager::SelectView] selecting %s", name)
|
|
|
|
if m.activeView == name {
|
|
return m.RawView(name)
|
|
}
|
|
|
|
nextView := m.ViewByName(name)
|
|
if nextView == nil {
|
|
return nil, fmt.Errorf("failed to select non-existing '%s' view", name)
|
|
}
|
|
|
|
gocuiView, err := m.setCurrentView(nextView.Name)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, fmt.Sprintf("failed to select view '%s'", nextView.Name))
|
|
}
|
|
|
|
// deactivate callback
|
|
currentView := m.ViewByName(m.activeView)
|
|
if currentView == nil {
|
|
return nil, fmt.Errorf("failed to view a view by name '%s'", m.activeView)
|
|
} else if currentView.OnDeactivate != nil {
|
|
currentView.OnDeactivate(currentView)
|
|
}
|
|
|
|
// activate callback
|
|
if nextView.OnActivate != nil {
|
|
nextView.OnActivate(nextView)
|
|
}
|
|
|
|
m.previousView = m.activeView
|
|
m.activeView = name
|
|
|
|
m.g.Cursor = nextView.Cursor
|
|
m.g.Highlight = nextView.Highlight
|
|
|
|
return gocuiView, nil
|
|
}
|
|
|
|
// NextView selects a next view clockwise.
|
|
func (m *ViewManager) NextView() error {
|
|
nextActive := m.ViewIndex(m.activeView)
|
|
for {
|
|
nextActive = (nextActive + 1) % len(m.views)
|
|
|
|
nextView := m.views[nextActive]
|
|
if !nextView.Enabled {
|
|
continue
|
|
}
|
|
|
|
_, err := m.SelectView(nextView.Name)
|
|
return err
|
|
}
|
|
}
|