...

Source file src/code.rocketnine.space/tslocum/cview/panels.go

Documentation: code.rocketnine.space/tslocum/cview

     1  package cview
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/gdamore/tcell/v2"
     7  )
     8  
     9  // panel represents a single panel of a Panels object.
    10  type panel struct {
    11  	Name    string    // The panel's name.
    12  	Item    Primitive // The panel's primitive.
    13  	Resize  bool      // Whether or not to resize the panel when it is drawn.
    14  	Visible bool      // Whether or not this panel is visible.
    15  }
    16  
    17  // Panels is a container for other primitives often used as the application's
    18  // root primitive. It allows to easily switch the visibility of the contained
    19  // primitives.
    20  type Panels struct {
    21  	*Box
    22  
    23  	// The contained panels. (Visible) panels are drawn from back to front.
    24  	panels []*panel
    25  
    26  	// We keep a reference to the function which allows us to set the focus to
    27  	// a newly visible panel.
    28  	setFocus func(p Primitive)
    29  
    30  	// An optional handler which is called whenever the visibility or the order of
    31  	// panels changes.
    32  	changed func()
    33  
    34  	sync.RWMutex
    35  }
    36  
    37  // NewPanels returns a new Panels object.
    38  func NewPanels() *Panels {
    39  	p := &Panels{
    40  		Box: NewBox(),
    41  	}
    42  	p.focus = p
    43  	return p
    44  }
    45  
    46  // SetChangedFunc sets a handler which is called whenever the visibility or the
    47  // order of any visible panels changes. This can be used to redraw the panels.
    48  func (p *Panels) SetChangedFunc(handler func()) {
    49  	p.Lock()
    50  	defer p.Unlock()
    51  
    52  	p.changed = handler
    53  }
    54  
    55  // GetPanelCount returns the number of panels currently stored in this object.
    56  func (p *Panels) GetPanelCount() int {
    57  	p.RLock()
    58  	defer p.RUnlock()
    59  
    60  	return len(p.panels)
    61  }
    62  
    63  // AddPanel adds a new panel with the given name and primitive. If there was
    64  // previously a panel with the same name, it is overwritten. Leaving the name
    65  // empty may cause conflicts in other functions so always specify a non-empty
    66  // name.
    67  //
    68  // Visible panels will be drawn in the order they were added (unless that order
    69  // was changed in one of the other functions). If "resize" is set to true, the
    70  // primitive will be set to the size available to the Panels primitive whenever
    71  // the panels are drawn.
    72  func (p *Panels) AddPanel(name string, item Primitive, resize, visible bool) {
    73  	hasFocus := p.HasFocus()
    74  
    75  	p.Lock()
    76  	defer p.Unlock()
    77  
    78  	var added bool
    79  	for i, pg := range p.panels {
    80  		if pg.Name == name {
    81  			p.panels[i] = &panel{Item: item, Name: name, Resize: resize, Visible: visible}
    82  			added = true
    83  			break
    84  		}
    85  	}
    86  	if !added {
    87  		p.panels = append(p.panels, &panel{Item: item, Name: name, Resize: resize, Visible: visible})
    88  	}
    89  	if p.changed != nil {
    90  		p.Unlock()
    91  		p.changed()
    92  		p.Lock()
    93  	}
    94  	if hasFocus {
    95  		p.Unlock()
    96  		p.Focus(p.setFocus)
    97  		p.Lock()
    98  	}
    99  }
   100  
   101  // RemovePanel removes the panel with the given name. If that panel was the only
   102  // visible panel, visibility is assigned to the last panel.
   103  func (p *Panels) RemovePanel(name string) {
   104  	hasFocus := p.HasFocus()
   105  
   106  	p.Lock()
   107  	defer p.Unlock()
   108  
   109  	var isVisible bool
   110  	for index, panel := range p.panels {
   111  		if panel.Name == name {
   112  			isVisible = panel.Visible
   113  			p.panels = append(p.panels[:index], p.panels[index+1:]...)
   114  			if panel.Visible && p.changed != nil {
   115  				p.Unlock()
   116  				p.changed()
   117  				p.Lock()
   118  			}
   119  			break
   120  		}
   121  	}
   122  	if isVisible {
   123  		for index, panel := range p.panels {
   124  			if index < len(p.panels)-1 {
   125  				if panel.Visible {
   126  					break // There is a remaining visible panel.
   127  				}
   128  			} else {
   129  				panel.Visible = true // We need at least one visible panel.
   130  			}
   131  		}
   132  	}
   133  	if hasFocus {
   134  		p.Unlock()
   135  		p.Focus(p.setFocus)
   136  		p.Lock()
   137  	}
   138  }
   139  
   140  // HasPanel returns true if a panel with the given name exists in this object.
   141  func (p *Panels) HasPanel(name string) bool {
   142  	p.RLock()
   143  	defer p.RUnlock()
   144  
   145  	for _, panel := range p.panels {
   146  		if panel.Name == name {
   147  			return true
   148  		}
   149  	}
   150  	return false
   151  }
   152  
   153  // ShowPanel sets a panel's visibility to "true" (in addition to any other panels
   154  // which are already visible).
   155  func (p *Panels) ShowPanel(name string) {
   156  	hasFocus := p.HasFocus()
   157  
   158  	p.Lock()
   159  	defer p.Unlock()
   160  
   161  	for _, panel := range p.panels {
   162  		if panel.Name == name {
   163  			panel.Visible = true
   164  			if p.changed != nil {
   165  				p.Unlock()
   166  				p.changed()
   167  				p.Lock()
   168  			}
   169  			break
   170  		}
   171  	}
   172  	if hasFocus {
   173  		p.Unlock()
   174  		p.Focus(p.setFocus)
   175  		p.Lock()
   176  	}
   177  }
   178  
   179  // HidePanel sets a panel's visibility to "false".
   180  func (p *Panels) HidePanel(name string) {
   181  	hasFocus := p.HasFocus()
   182  
   183  	p.Lock()
   184  	defer p.Unlock()
   185  
   186  	for _, panel := range p.panels {
   187  		if panel.Name == name {
   188  			panel.Visible = false
   189  			if p.changed != nil {
   190  				p.Unlock()
   191  				p.changed()
   192  				p.Lock()
   193  			}
   194  			break
   195  		}
   196  	}
   197  	if hasFocus {
   198  		p.Unlock()
   199  		p.Focus(p.setFocus)
   200  		p.Lock()
   201  	}
   202  }
   203  
   204  // SetCurrentPanel sets a panel's visibility to "true" and all other panels'
   205  // visibility to "false".
   206  func (p *Panels) SetCurrentPanel(name string) {
   207  	hasFocus := p.HasFocus()
   208  
   209  	p.Lock()
   210  	defer p.Unlock()
   211  
   212  	for _, panel := range p.panels {
   213  		if panel.Name == name {
   214  			panel.Visible = true
   215  		} else {
   216  			panel.Visible = false
   217  		}
   218  	}
   219  	if p.changed != nil {
   220  		p.Unlock()
   221  		p.changed()
   222  		p.Lock()
   223  	}
   224  	if hasFocus {
   225  		p.Unlock()
   226  		p.Focus(p.setFocus)
   227  		p.Lock()
   228  	}
   229  }
   230  
   231  // SendToFront changes the order of the panels such that the panel with the given
   232  // name comes last, causing it to be drawn last with the next update (if
   233  // visible).
   234  func (p *Panels) SendToFront(name string) {
   235  	hasFocus := p.HasFocus()
   236  
   237  	p.Lock()
   238  	defer p.Unlock()
   239  
   240  	for index, panel := range p.panels {
   241  		if panel.Name == name {
   242  			if index < len(p.panels)-1 {
   243  				p.panels = append(append(p.panels[:index], p.panels[index+1:]...), panel)
   244  			}
   245  			if panel.Visible && p.changed != nil {
   246  				p.Unlock()
   247  				p.changed()
   248  				p.Lock()
   249  			}
   250  			break
   251  		}
   252  	}
   253  	if hasFocus {
   254  		p.Unlock()
   255  		p.Focus(p.setFocus)
   256  		p.Lock()
   257  	}
   258  }
   259  
   260  // SendToBack changes the order of the panels such that the panel with the given
   261  // name comes first, causing it to be drawn first with the next update (if
   262  // visible).
   263  func (p *Panels) SendToBack(name string) {
   264  	hasFocus := p.HasFocus()
   265  
   266  	p.Lock()
   267  	defer p.Unlock()
   268  
   269  	for index, pg := range p.panels {
   270  		if pg.Name == name {
   271  			if index > 0 {
   272  				p.panels = append(append([]*panel{pg}, p.panels[:index]...), p.panels[index+1:]...)
   273  			}
   274  			if pg.Visible && p.changed != nil {
   275  				p.Unlock()
   276  				p.changed()
   277  				p.Lock()
   278  			}
   279  			break
   280  		}
   281  	}
   282  	if hasFocus {
   283  		p.Unlock()
   284  		p.Focus(p.setFocus)
   285  		p.Lock()
   286  	}
   287  }
   288  
   289  // GetFrontPanel returns the front-most visible panel. If there are no visible
   290  // panels, ("", nil) is returned.
   291  func (p *Panels) GetFrontPanel() (name string, item Primitive) {
   292  	p.RLock()
   293  	defer p.RUnlock()
   294  
   295  	for index := len(p.panels) - 1; index >= 0; index-- {
   296  		if p.panels[index].Visible {
   297  			return p.panels[index].Name, p.panels[index].Item
   298  		}
   299  	}
   300  	return
   301  }
   302  
   303  // HasFocus returns whether or not this primitive has focus.
   304  func (p *Panels) HasFocus() bool {
   305  	p.RLock()
   306  	defer p.RUnlock()
   307  
   308  	for _, panel := range p.panels {
   309  		if panel.Item.GetFocusable().HasFocus() {
   310  			return true
   311  		}
   312  	}
   313  	return false
   314  }
   315  
   316  // Focus is called by the application when the primitive receives focus.
   317  func (p *Panels) Focus(delegate func(p Primitive)) {
   318  	p.Lock()
   319  	defer p.Unlock()
   320  
   321  	if delegate == nil {
   322  		return // We cannot delegate so we cannot focus.
   323  	}
   324  	p.setFocus = delegate
   325  	var topItem Primitive
   326  	for _, panel := range p.panels {
   327  		if panel.Visible {
   328  			topItem = panel.Item
   329  		}
   330  	}
   331  	if topItem != nil {
   332  		p.Unlock()
   333  		delegate(topItem)
   334  		p.Lock()
   335  	}
   336  }
   337  
   338  // Draw draws this primitive onto the screen.
   339  func (p *Panels) Draw(screen tcell.Screen) {
   340  	if !p.GetVisible() {
   341  		return
   342  	}
   343  
   344  	p.Box.Draw(screen)
   345  
   346  	p.Lock()
   347  	defer p.Unlock()
   348  
   349  	x, y, width, height := p.GetInnerRect()
   350  
   351  	for _, panel := range p.panels {
   352  		if !panel.Visible {
   353  			continue
   354  		}
   355  		if panel.Resize {
   356  			panel.Item.SetRect(x, y, width, height)
   357  		}
   358  		panel.Item.Draw(screen)
   359  	}
   360  }
   361  
   362  // MouseHandler returns the mouse handler for this primitive.
   363  func (p *Panels) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   364  	return p.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   365  		if !p.InRect(event.Position()) {
   366  			return false, nil
   367  		}
   368  
   369  		// Pass mouse events along to the last visible panel item that takes it.
   370  		for index := len(p.panels) - 1; index >= 0; index-- {
   371  			panel := p.panels[index]
   372  			if panel.Visible {
   373  				consumed, capture = panel.Item.MouseHandler()(action, event, setFocus)
   374  				if consumed {
   375  					return
   376  				}
   377  			}
   378  		}
   379  
   380  		return
   381  	})
   382  }
   383  
   384  // Support backwards compatibility with Pages.
   385  type page = panel
   386  
   387  // Pages is a wrapper around Panels.
   388  //
   389  // Deprecated: This type is provided for backwards compatibility.
   390  // Developers should use Panels instead.
   391  type Pages struct {
   392  	*Panels
   393  }
   394  
   395  // NewPages returns a new Panels object.
   396  //
   397  // Deprecated: This function is provided for backwards compatibility.
   398  // Developers should use NewPanels instead.
   399  func NewPages() *Pages {
   400  	return &Pages{NewPanels()}
   401  }
   402  
   403  // GetPageCount returns the number of panels currently stored in this object.
   404  func (p *Pages) GetPageCount() int {
   405  	return p.GetPanelCount()
   406  }
   407  
   408  // AddPage adds a new panel with the given name and primitive.
   409  func (p *Pages) AddPage(name string, item Primitive, resize, visible bool) {
   410  	p.AddPanel(name, item, resize, visible)
   411  }
   412  
   413  // AddAndSwitchToPage calls Add(), then SwitchTo() on that newly added panel.
   414  func (p *Pages) AddAndSwitchToPage(name string, item Primitive, resize bool) {
   415  	p.AddPanel(name, item, resize, true)
   416  	p.SetCurrentPanel(name)
   417  }
   418  
   419  // RemovePage removes the panel with the given name.
   420  func (p *Pages) RemovePage(name string) {
   421  	p.RemovePanel(name)
   422  }
   423  
   424  // HasPage returns true if a panel with the given name exists in this object.
   425  func (p *Pages) HasPage(name string) bool {
   426  	return p.HasPanel(name)
   427  }
   428  
   429  // ShowPage sets a panel's visibility to "true".
   430  func (p *Pages) ShowPage(name string) {
   431  	p.ShowPanel(name)
   432  }
   433  
   434  // HidePage sets a panel's visibility to "false".
   435  func (p *Pages) HidePage(name string) {
   436  	p.HidePanel(name)
   437  }
   438  
   439  // SwitchToPage sets a panel's visibility to "true" and all other panels'
   440  // visibility to "false".
   441  func (p *Pages) SwitchToPage(name string) {
   442  	p.SetCurrentPanel(name)
   443  }
   444  
   445  // GetFrontPage returns the front-most visible panel.
   446  func (p *Pages) GetFrontPage() (name string, item Primitive) {
   447  	return p.GetFrontPanel()
   448  }
   449  

View as plain text