-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
3 changed files
with
306 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
package model | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/rs/zerolog/log" | ||
) | ||
|
||
const ( | ||
// StackPush denotes an add on the stack. | ||
StackPush StackAction = 1 << iota | ||
|
||
// StackPop denotes a delete on the stack. | ||
StackPop | ||
) | ||
|
||
// StackAction represents an action on the stack. | ||
type StackAction int | ||
|
||
// StackEvent represents an operation on a view stack. | ||
type StackEvent struct { | ||
// Kind represents the event condition. | ||
Action StackAction | ||
|
||
// Item represents the targeted item. | ||
Component Component | ||
} | ||
|
||
// StackListener represents a stack listener. | ||
type StackListener interface { | ||
// StackPushed indicates a new item was added. | ||
StackPushed(Component) | ||
|
||
// StackPopped indicates an item was deleted | ||
StackPopped(old, new Component) | ||
|
||
// StackTop indicates the top of the stack | ||
StackTop(Component) | ||
} | ||
|
||
// Stack represents a stacks of components. | ||
type Stack struct { | ||
components []Component | ||
listeners []StackListener | ||
mx sync.RWMutex | ||
} | ||
|
||
// NewStack returns a new initialized stack. | ||
func NewStack() *Stack { | ||
return &Stack{} | ||
} | ||
|
||
// Flatten returns a string representation of the component stack. | ||
func (s *Stack) Flatten() []string { | ||
s.mx.RLock() | ||
defer s.mx.RUnlock() | ||
|
||
ss := make([]string, len(s.components)) | ||
for i, c := range s.components { | ||
ss[i] = c.Name() | ||
} | ||
return ss | ||
} | ||
|
||
// RemoveListener removes a listener. | ||
func (s *Stack) RemoveListener(l StackListener) { | ||
victim := -1 | ||
for i, lis := range s.listeners { | ||
if lis == l { | ||
victim = i | ||
break | ||
} | ||
} | ||
if victim == -1 { | ||
return | ||
} | ||
s.listeners = append(s.listeners[:victim], s.listeners[victim+1:]...) | ||
} | ||
|
||
// AddListener registers a stack listener. | ||
func (s *Stack) AddListener(l StackListener) { | ||
s.listeners = append(s.listeners, l) | ||
if !s.Empty() { | ||
l.StackTop(s.Top()) | ||
} | ||
} | ||
|
||
// Push adds a new item. | ||
func (s *Stack) Push(c Component) { | ||
if top := s.Top(); top != nil { | ||
//top.Stop() | ||
} | ||
|
||
s.mx.Lock() | ||
{ | ||
s.components = append(s.components, c) | ||
} | ||
s.mx.Unlock() | ||
s.notify(StackPush, c) | ||
} | ||
|
||
// Pop removed the top item and returns it. | ||
func (s *Stack) Pop() (Component, bool) { | ||
if s.Empty() { | ||
return nil, false | ||
} | ||
|
||
var c Component | ||
s.mx.Lock() | ||
{ | ||
c = s.components[len(s.components)-1] | ||
s.components = s.components[:len(s.components)-1] | ||
} | ||
s.mx.Unlock() | ||
s.notify(StackPop, c) | ||
|
||
return c, true | ||
} | ||
|
||
// Peek returns stack state. | ||
func (s *Stack) Peek() []Component { | ||
s.mx.RLock() | ||
defer s.mx.RUnlock() | ||
|
||
return s.components | ||
} | ||
|
||
// Clear clear out the stack using pops. | ||
func (s *Stack) Clear() { | ||
for range s.components { | ||
s.Pop() | ||
} | ||
} | ||
|
||
// Empty returns true if the stack is empty. | ||
func (s *Stack) Empty() bool { | ||
s.mx.RLock() | ||
defer s.mx.RUnlock() | ||
|
||
return len(s.components) == 0 | ||
} | ||
|
||
// IsLast indicates if stack only has one item left. | ||
func (s *Stack) IsLast() bool { | ||
return len(s.components) == 1 | ||
} | ||
|
||
// Previous returns the previous component if any. | ||
func (s *Stack) Previous() Component { | ||
if s.Empty() { | ||
return nil | ||
} | ||
if s.IsLast() { | ||
return s.Top() | ||
} | ||
|
||
return s.components[len(s.components)-2] | ||
} | ||
|
||
// Top returns the top most item or nil if the stack is empty. | ||
func (s *Stack) Top() Component { | ||
if s.Empty() { | ||
return nil | ||
} | ||
|
||
return s.components[len(s.components)-1] | ||
} | ||
|
||
func (s *Stack) notify(a StackAction, c Component) { | ||
for _, l := range s.listeners { | ||
switch a { | ||
case StackPush: | ||
l.StackPushed(c) | ||
case StackPop: | ||
l.StackPopped(c, s.Top()) | ||
} | ||
} | ||
} | ||
|
||
// ---------------------------------------------------------------------------- | ||
// Helpers... | ||
|
||
// Dump prints out the stack. | ||
func (s *Stack) Dump() { | ||
log.Debug().Msgf("--- Stack Dump %p---", s) | ||
for i, c := range s.components { | ||
log.Debug().Msgf("%d -- %s -- %#v", i, c.Name(), c) | ||
} | ||
log.Debug().Msg("------------------") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package model | ||
|
||
import "github.com/derailed/tview" | ||
|
||
// Hinter represent a menu mnemonic provider. | ||
type Hinter interface { | ||
// Hints returns a collection of menu hints. | ||
Hints() MenuHints | ||
} | ||
|
||
// Primitive represents a UI primitive. | ||
type Primitive interface { | ||
tview.Primitive | ||
|
||
// Name returns the view name. | ||
Name() string | ||
} | ||
|
||
// Component represents a ui component. | ||
type Component interface { | ||
Primitive | ||
Hinter | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,105 @@ | ||
package ui | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/one2nc/cloud-lens/internal/model" | ||
"github.com/derailed/tview" | ||
"github.com/rs/zerolog/log" | ||
) | ||
|
||
// Pages represents a stack of view pages. | ||
type Pages struct { | ||
*tview.Pages | ||
*model.Stack | ||
} | ||
|
||
// NewPages return a new view. | ||
func NewPages() *Pages { | ||
pages := Pages{Pages: tview.NewPages()} | ||
return &pages | ||
p := Pages{ | ||
Pages: tview.NewPages(), | ||
Stack: model.NewStack(), | ||
} | ||
p.Stack.AddListener(&p) | ||
|
||
return &p | ||
} | ||
|
||
// IsTopDialog checks if front page is a dialog. | ||
func (p *Pages) IsTopDialog() bool { | ||
_, pa := p.GetFrontPage() | ||
switch pa.(type) { | ||
case *tview.ModalForm: | ||
return true | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
// Show displays a given page. | ||
func (p *Pages) Show(c model.Component) { | ||
p.SwitchToPage(componentID(c)) | ||
} | ||
|
||
// Current returns the current component. | ||
func (p *Pages) Current() model.Component { | ||
c := p.CurrentPage() | ||
if c == nil { | ||
return nil | ||
} | ||
|
||
return c.Item.(model.Component) | ||
} | ||
|
||
// AddAndShow adds a new page and bring it to front. | ||
func (p *Pages) addAndShow(c model.Component) { | ||
p.add(c) | ||
p.Show(c) | ||
} | ||
|
||
// Add adds a new page. | ||
func (p *Pages) add(c model.Component) { | ||
p.AddPage(componentID(c), c, true, true) | ||
} | ||
|
||
// Delete removes a page. | ||
func (p *Pages) delete(c model.Component) { | ||
p.RemovePage(componentID(c)) | ||
} | ||
|
||
// Dump for debug. | ||
func (p *Pages) Dump() { | ||
log.Debug().Msgf("Dumping Pages %p", p) | ||
for i, c := range p.Stack.Peek() { | ||
log.Debug().Msgf("%d -- %s -- %#v", i, componentID(c), p.GetPrimitive(componentID(c))) | ||
} | ||
} | ||
|
||
// Stack Protocol... | ||
|
||
// StackPushed notifies a new component was pushed. | ||
func (p *Pages) StackPushed(c model.Component) { | ||
p.addAndShow(c) | ||
} | ||
|
||
// StackPopped notifies a component was removed. | ||
func (p *Pages) StackPopped(o, top model.Component) { | ||
p.delete(o) | ||
} | ||
|
||
// StackTop notifies a new component is at the top of the stack. | ||
func (p *Pages) StackTop(top model.Component) { | ||
if top == nil { | ||
return | ||
} | ||
p.Show(top) | ||
} | ||
|
||
// Helpers... | ||
|
||
func componentID(c model.Component) string { | ||
if c.Name() == "" { | ||
panic("Component has no name") | ||
} | ||
return fmt.Sprintf("%s-%p", c.Name(), c) | ||
} |