...

Source file src/code.rocketnine.space/tslocum/cview/treeview.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  // Tree navigation events.
    10  const (
    11  	treeNone int = iota
    12  	treeHome
    13  	treeEnd
    14  	treeUp
    15  	treeDown
    16  	treePageUp
    17  	treePageDown
    18  )
    19  
    20  // TreeNode represents one node in a tree view.
    21  type TreeNode struct {
    22  	// The reference object.
    23  	reference interface{}
    24  
    25  	// This node's child nodes.
    26  	children []*TreeNode
    27  
    28  	// The item's text.
    29  	text string
    30  
    31  	// The text color.
    32  	color tcell.Color
    33  
    34  	// Whether or not this node can be focused and selected.
    35  	selectable bool
    36  
    37  	// Whether or not this node's children should be displayed.
    38  	expanded bool
    39  
    40  	// The additional horizontal indent of this node's text.
    41  	indent int
    42  
    43  	// An optional function which is called when the user focuses this node.
    44  	focused func()
    45  
    46  	// An optional function which is called when the user selects this node.
    47  	selected func()
    48  
    49  	// Temporary member variables.
    50  	parent    *TreeNode // The parent node (nil for the root).
    51  	level     int       // The hierarchy level (0 for the root, 1 for its children, and so on).
    52  	graphicsX int       // The x-coordinate of the left-most graphics rune.
    53  	textX     int       // The x-coordinate of the first rune of the text.
    54  
    55  	sync.RWMutex
    56  }
    57  
    58  // NewTreeNode returns a new tree node.
    59  func NewTreeNode(text string) *TreeNode {
    60  	return &TreeNode{
    61  		text:       text,
    62  		color:      Styles.PrimaryTextColor,
    63  		indent:     2,
    64  		expanded:   true,
    65  		selectable: true,
    66  	}
    67  }
    68  
    69  // Walk traverses this node's subtree in depth-first, pre-order (NLR) order and
    70  // calls the provided callback function on each traversed node (which includes
    71  // this node) with the traversed node and its parent node (nil for this node).
    72  // The callback returns whether traversal should continue with the traversed
    73  // node's child nodes (true) or not recurse any deeper (false).
    74  func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) {
    75  	n.Lock()
    76  	defer n.Unlock()
    77  
    78  	n.walk(callback)
    79  }
    80  
    81  func (n *TreeNode) walk(callback func(node, parent *TreeNode) bool) {
    82  	n.parent = nil
    83  	nodes := []*TreeNode{n}
    84  	for len(nodes) > 0 {
    85  		// Pop the top node and process it.
    86  		node := nodes[len(nodes)-1]
    87  		nodes = nodes[:len(nodes)-1]
    88  		if !callback(node, node.parent) {
    89  			// Don't add any children.
    90  			continue
    91  		}
    92  
    93  		// Add children in reverse order.
    94  		for index := len(node.children) - 1; index >= 0; index-- {
    95  			node.children[index].parent = node
    96  			nodes = append(nodes, node.children[index])
    97  		}
    98  	}
    99  }
   100  
   101  // SetReference allows you to store a reference of any type in this node. This
   102  // will allow you to establish a mapping between the TreeView hierarchy and your
   103  // internal tree structure.
   104  func (n *TreeNode) SetReference(reference interface{}) {
   105  	n.Lock()
   106  	defer n.Unlock()
   107  
   108  	n.reference = reference
   109  }
   110  
   111  // GetReference returns this node's reference object.
   112  func (n *TreeNode) GetReference() interface{} {
   113  	n.RLock()
   114  	defer n.RUnlock()
   115  
   116  	return n.reference
   117  }
   118  
   119  // SetChildren sets this node's child nodes.
   120  func (n *TreeNode) SetChildren(childNodes []*TreeNode) {
   121  	n.Lock()
   122  	defer n.Unlock()
   123  
   124  	n.children = childNodes
   125  }
   126  
   127  // GetText returns this node's text.
   128  func (n *TreeNode) GetText() string {
   129  	n.RLock()
   130  	defer n.RUnlock()
   131  
   132  	return n.text
   133  }
   134  
   135  // GetChildren returns this node's children.
   136  func (n *TreeNode) GetChildren() []*TreeNode {
   137  	n.RLock()
   138  	defer n.RUnlock()
   139  
   140  	return n.children
   141  }
   142  
   143  // ClearChildren removes all child nodes from this node.
   144  func (n *TreeNode) ClearChildren() {
   145  	n.Lock()
   146  	defer n.Unlock()
   147  
   148  	n.children = nil
   149  }
   150  
   151  // AddChild adds a new child node to this node.
   152  func (n *TreeNode) AddChild(node *TreeNode) {
   153  	n.Lock()
   154  	defer n.Unlock()
   155  
   156  	n.children = append(n.children, node)
   157  }
   158  
   159  // SetSelectable sets a flag indicating whether this node can be focused and
   160  // selected by the user.
   161  func (n *TreeNode) SetSelectable(selectable bool) {
   162  	n.Lock()
   163  	defer n.Unlock()
   164  
   165  	n.selectable = selectable
   166  }
   167  
   168  // SetFocusedFunc sets the function which is called when the user navigates to
   169  // this node.
   170  //
   171  // This function is also called when the user selects this node.
   172  func (n *TreeNode) SetFocusedFunc(handler func()) {
   173  	n.Lock()
   174  	defer n.Unlock()
   175  
   176  	n.focused = handler
   177  }
   178  
   179  // SetSelectedFunc sets a function which is called when the user selects this
   180  // node by hitting Enter when it is focused.
   181  func (n *TreeNode) SetSelectedFunc(handler func()) {
   182  	n.Lock()
   183  	defer n.Unlock()
   184  
   185  	n.selected = handler
   186  }
   187  
   188  // SetExpanded sets whether or not this node's child nodes should be displayed.
   189  func (n *TreeNode) SetExpanded(expanded bool) {
   190  	n.Lock()
   191  	defer n.Unlock()
   192  
   193  	n.expanded = expanded
   194  }
   195  
   196  // Expand makes the child nodes of this node appear.
   197  func (n *TreeNode) Expand() {
   198  	n.Lock()
   199  	defer n.Unlock()
   200  
   201  	n.expanded = true
   202  }
   203  
   204  // Collapse makes the child nodes of this node disappear.
   205  func (n *TreeNode) Collapse() {
   206  	n.Lock()
   207  	defer n.Unlock()
   208  
   209  	n.expanded = false
   210  }
   211  
   212  // ExpandAll expands this node and all descendent nodes.
   213  func (n *TreeNode) ExpandAll() {
   214  	n.Walk(func(node, parent *TreeNode) bool {
   215  		node.expanded = true
   216  		return true
   217  	})
   218  }
   219  
   220  // CollapseAll collapses this node and all descendent nodes.
   221  func (n *TreeNode) CollapseAll() {
   222  	n.Walk(func(node, parent *TreeNode) bool {
   223  		n.expanded = false
   224  		return true
   225  	})
   226  }
   227  
   228  // IsExpanded returns whether the child nodes of this node are visible.
   229  func (n *TreeNode) IsExpanded() bool {
   230  	n.RLock()
   231  	defer n.RUnlock()
   232  
   233  	return n.expanded
   234  }
   235  
   236  // SetText sets the node's text which is displayed.
   237  func (n *TreeNode) SetText(text string) {
   238  	n.Lock()
   239  	defer n.Unlock()
   240  
   241  	n.text = text
   242  }
   243  
   244  // GetColor returns the node's color.
   245  func (n *TreeNode) GetColor() tcell.Color {
   246  	n.RLock()
   247  	defer n.RUnlock()
   248  
   249  	return n.color
   250  }
   251  
   252  // SetColor sets the node's text color.
   253  func (n *TreeNode) SetColor(color tcell.Color) {
   254  	n.Lock()
   255  	defer n.Unlock()
   256  
   257  	n.color = color
   258  }
   259  
   260  // SetIndent sets an additional indentation for this node's text. A value of 0
   261  // keeps the text as far left as possible with a minimum of line graphics. Any
   262  // value greater than that moves the text to the right.
   263  func (n *TreeNode) SetIndent(indent int) {
   264  	n.Lock()
   265  	defer n.Unlock()
   266  
   267  	n.indent = indent
   268  }
   269  
   270  // TreeView displays tree structures. A tree consists of nodes (TreeNode
   271  // objects) where each node has zero or more child nodes and exactly one parent
   272  // node (except for the root node which has no parent node).
   273  //
   274  // The SetRoot() function is used to specify the root of the tree. Other nodes
   275  // are added locally to the root node or any of its descendents. See the
   276  // TreeNode documentation for details on node attributes. (You can use
   277  // SetReference() to store a reference to nodes of your own tree structure.)
   278  //
   279  // Nodes can be focused by calling SetCurrentNode(). The user can navigate the
   280  // selection or the tree by using the following keys:
   281  //
   282  //   - j, down arrow, right arrow: Move (the selection) down by one node.
   283  //   - k, up arrow, left arrow: Move (the selection) up by one node.
   284  //   - g, home: Move (the selection) to the top.
   285  //   - G, end: Move (the selection) to the bottom.
   286  //   - Ctrl-F, page down: Move (the selection) down by one page.
   287  //   - Ctrl-B, page up: Move (the selection) up by one page.
   288  //
   289  // Selected nodes can trigger the "selected" callback when the user hits Enter.
   290  //
   291  // The root node corresponds to level 0, its children correspond to level 1,
   292  // their children to level 2, and so on. Per default, the first level that is
   293  // displayed is 0, i.e. the root node. You can call SetTopLevel() to hide
   294  // levels.
   295  //
   296  // If graphics are turned on (see SetGraphics()), lines indicate the tree's
   297  // hierarchy. Alternative (or additionally), you can set different prefixes
   298  // using SetPrefixes() for different levels, for example to display hierarchical
   299  // bullet point lists.
   300  type TreeView struct {
   301  	*Box
   302  
   303  	// The root node.
   304  	root *TreeNode
   305  
   306  	// The currently focused node or nil if no node is focused.
   307  	currentNode *TreeNode
   308  
   309  	// The movement to be performed during the call to Draw(), one of the
   310  	// constants defined above.
   311  	movement int
   312  
   313  	// The top hierarchical level shown. (0 corresponds to the root level.)
   314  	topLevel int
   315  
   316  	// Strings drawn before the nodes, based on their level.
   317  	prefixes [][]byte
   318  
   319  	// Vertical scroll offset.
   320  	offsetY int
   321  
   322  	// If set to true, all node texts will be aligned horizontally.
   323  	align bool
   324  
   325  	// If set to true, the tree structure is drawn using lines.
   326  	graphics bool
   327  
   328  	// The text color for selected items.
   329  	selectedTextColor *tcell.Color
   330  
   331  	// The background color for selected items.
   332  	selectedBackgroundColor *tcell.Color
   333  
   334  	// The color of the lines.
   335  	graphicsColor tcell.Color
   336  
   337  	// Visibility of the scroll bar.
   338  	scrollBarVisibility ScrollBarVisibility
   339  
   340  	// The scroll bar color.
   341  	scrollBarColor tcell.Color
   342  
   343  	// An optional function called when the focused tree item changes.
   344  	changed func(node *TreeNode)
   345  
   346  	// An optional function called when a tree item is selected.
   347  	selected func(node *TreeNode)
   348  
   349  	// An optional function called when the user moves away from this primitive.
   350  	done func(key tcell.Key)
   351  
   352  	// The visible nodes, top-down, as set by process().
   353  	nodes []*TreeNode
   354  
   355  	sync.RWMutex
   356  }
   357  
   358  // NewTreeView returns a new tree view.
   359  func NewTreeView() *TreeView {
   360  	return &TreeView{
   361  		Box:                 NewBox(),
   362  		scrollBarVisibility: ScrollBarAuto,
   363  		graphics:            true,
   364  		graphicsColor:       Styles.GraphicsColor,
   365  		scrollBarColor:      Styles.ScrollBarColor,
   366  	}
   367  }
   368  
   369  // SetRoot sets the root node of the tree.
   370  func (t *TreeView) SetRoot(root *TreeNode) {
   371  	t.Lock()
   372  	defer t.Unlock()
   373  
   374  	t.root = root
   375  }
   376  
   377  // GetRoot returns the root node of the tree. If no such node was previously
   378  // set, nil is returned.
   379  func (t *TreeView) GetRoot() *TreeNode {
   380  	t.RLock()
   381  	defer t.RUnlock()
   382  
   383  	return t.root
   384  }
   385  
   386  // SetCurrentNode focuses a node or, when provided with nil, clears focus.
   387  // Selected nodes must be visible and selectable, or else the selection will be
   388  // changed to the top-most selectable and visible node.
   389  //
   390  // This function does NOT trigger the "changed" callback.
   391  func (t *TreeView) SetCurrentNode(node *TreeNode) {
   392  	t.Lock()
   393  	defer t.Unlock()
   394  
   395  	t.currentNode = node
   396  	if t.currentNode.focused != nil {
   397  		t.Unlock()
   398  		t.currentNode.focused()
   399  		t.Lock()
   400  	}
   401  }
   402  
   403  // GetCurrentNode returns the currently selected node or nil of no node is
   404  // currently selected.
   405  func (t *TreeView) GetCurrentNode() *TreeNode {
   406  	t.RLock()
   407  	defer t.RUnlock()
   408  
   409  	return t.currentNode
   410  }
   411  
   412  // SetTopLevel sets the first tree level that is visible with 0 referring to the
   413  // root, 1 to the root's child nodes, and so on. Nodes above the top level are
   414  // not displayed.
   415  func (t *TreeView) SetTopLevel(topLevel int) {
   416  	t.Lock()
   417  	defer t.Unlock()
   418  
   419  	t.topLevel = topLevel
   420  }
   421  
   422  // SetPrefixes defines the strings drawn before the nodes' texts. This is a
   423  // slice of strings where each element corresponds to a node's hierarchy level,
   424  // i.e. 0 for the root, 1 for the root's children, and so on (levels will
   425  // cycle).
   426  //
   427  // For example, to display a hierarchical list with bullet points:
   428  //
   429  //   treeView.SetGraphics(false).
   430  //     SetPrefixes([]string{"* ", "- ", "x "})
   431  func (t *TreeView) SetPrefixes(prefixes []string) {
   432  	t.Lock()
   433  	defer t.Unlock()
   434  
   435  	t.prefixes = make([][]byte, len(prefixes))
   436  	for i := range prefixes {
   437  		t.prefixes[i] = []byte(prefixes[i])
   438  	}
   439  }
   440  
   441  // SetAlign controls the horizontal alignment of the node texts. If set to true,
   442  // all texts except that of top-level nodes will be placed in the same column.
   443  // If set to false, they will indent with the hierarchy.
   444  func (t *TreeView) SetAlign(align bool) {
   445  	t.Lock()
   446  	defer t.Unlock()
   447  
   448  	t.align = align
   449  }
   450  
   451  // SetGraphics sets a flag which determines whether or not line graphics are
   452  // drawn to illustrate the tree's hierarchy.
   453  func (t *TreeView) SetGraphics(showGraphics bool) {
   454  	t.Lock()
   455  	defer t.Unlock()
   456  
   457  	t.graphics = showGraphics
   458  }
   459  
   460  // SetSelectedTextColor sets the text color of selected items.
   461  func (t *TreeView) SetSelectedTextColor(color tcell.Color) {
   462  	t.Lock()
   463  	defer t.Unlock()
   464  	t.selectedTextColor = &color
   465  }
   466  
   467  // SetSelectedBackgroundColor sets the background color of selected items.
   468  func (t *TreeView) SetSelectedBackgroundColor(color tcell.Color) {
   469  	t.Lock()
   470  	defer t.Unlock()
   471  	t.selectedBackgroundColor = &color
   472  }
   473  
   474  // SetGraphicsColor sets the colors of the lines used to draw the tree structure.
   475  func (t *TreeView) SetGraphicsColor(color tcell.Color) {
   476  	t.Lock()
   477  	defer t.Unlock()
   478  
   479  	t.graphicsColor = color
   480  }
   481  
   482  // SetScrollBarVisibility specifies the display of the scroll bar.
   483  func (t *TreeView) SetScrollBarVisibility(visibility ScrollBarVisibility) {
   484  	t.Lock()
   485  	defer t.Unlock()
   486  
   487  	t.scrollBarVisibility = visibility
   488  }
   489  
   490  // SetScrollBarColor sets the color of the scroll bar.
   491  func (t *TreeView) SetScrollBarColor(color tcell.Color) {
   492  	t.Lock()
   493  	defer t.Unlock()
   494  
   495  	t.scrollBarColor = color
   496  }
   497  
   498  // SetChangedFunc sets the function which is called when the user navigates to
   499  // a new tree node.
   500  func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) {
   501  	t.Lock()
   502  	defer t.Unlock()
   503  
   504  	t.changed = handler
   505  }
   506  
   507  // SetSelectedFunc sets the function which is called when the user selects a
   508  // node by pressing Enter on the current selection.
   509  func (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) {
   510  	t.Lock()
   511  	defer t.Unlock()
   512  
   513  	t.selected = handler
   514  }
   515  
   516  // SetDoneFunc sets a handler which is called whenever the user presses the
   517  // Escape, Tab, or Backtab key.
   518  func (t *TreeView) SetDoneFunc(handler func(key tcell.Key)) {
   519  	t.Lock()
   520  	defer t.Unlock()
   521  
   522  	t.done = handler
   523  }
   524  
   525  // GetScrollOffset returns the number of node rows that were skipped at the top
   526  // of the tree view. Note that when the user navigates the tree view, this value
   527  // is only updated after the tree view has been redrawn.
   528  func (t *TreeView) GetScrollOffset() int {
   529  	t.RLock()
   530  	defer t.RUnlock()
   531  
   532  	return t.offsetY
   533  }
   534  
   535  // GetRowCount returns the number of "visible" nodes. This includes nodes which
   536  // fall outside the tree view's box but notably does not include the children
   537  // of collapsed nodes. Note that this value is only up to date after the tree
   538  // view has been drawn.
   539  func (t *TreeView) GetRowCount() int {
   540  	t.RLock()
   541  	defer t.RUnlock()
   542  
   543  	return len(t.nodes)
   544  }
   545  
   546  // Transform modifies the current selection.
   547  func (t *TreeView) Transform(tr Transformation) {
   548  	t.Lock()
   549  	defer t.Unlock()
   550  
   551  	switch tr {
   552  	case TransformFirstItem:
   553  		t.movement = treeHome
   554  	case TransformLastItem:
   555  		t.movement = treeEnd
   556  	case TransformPreviousItem:
   557  		t.movement = treeUp
   558  	case TransformNextItem:
   559  		t.movement = treeDown
   560  	case TransformPreviousPage:
   561  		t.movement = treePageUp
   562  	case TransformNextPage:
   563  		t.movement = treePageDown
   564  	}
   565  
   566  	t.process()
   567  }
   568  
   569  // process builds the visible tree, populates the "nodes" slice, and processes
   570  // pending selection actions.
   571  func (t *TreeView) process() {
   572  	_, _, _, height := t.GetInnerRect()
   573  
   574  	// Determine visible nodes and their placement.
   575  	var graphicsOffset, maxTextX int
   576  	t.nodes = nil
   577  	selectedIndex := -1
   578  	topLevelGraphicsX := -1
   579  	if t.graphics {
   580  		graphicsOffset = 1
   581  	}
   582  	t.root.walk(func(node, parent *TreeNode) bool {
   583  		// Set node attributes.
   584  		node.parent = parent
   585  		if parent == nil {
   586  			node.level = 0
   587  			node.graphicsX = 0
   588  			node.textX = 0
   589  		} else {
   590  			node.level = parent.level + 1
   591  			node.graphicsX = parent.textX
   592  			node.textX = node.graphicsX + graphicsOffset + node.indent
   593  		}
   594  		if !t.graphics && t.align {
   595  			// Without graphics, we align nodes on the first column.
   596  			node.textX = 0
   597  		}
   598  		if node.level == t.topLevel {
   599  			// No graphics for top level nodes.
   600  			node.graphicsX = 0
   601  			node.textX = 0
   602  		}
   603  
   604  		// Add the node to the list.
   605  		if node.level >= t.topLevel {
   606  			// This node will be visible.
   607  			if node.textX > maxTextX {
   608  				maxTextX = node.textX
   609  			}
   610  			if node == t.currentNode && node.selectable {
   611  				selectedIndex = len(t.nodes)
   612  			}
   613  
   614  			// Maybe we want to skip this level.
   615  			if t.topLevel == node.level && (topLevelGraphicsX < 0 || node.graphicsX < topLevelGraphicsX) {
   616  				topLevelGraphicsX = node.graphicsX
   617  			}
   618  
   619  			t.nodes = append(t.nodes, node)
   620  		}
   621  
   622  		// Recurse if desired.
   623  		return node.expanded
   624  	})
   625  
   626  	// Post-process positions.
   627  	for _, node := range t.nodes {
   628  		// If text must align, we correct the positions.
   629  		if t.align && node.level > t.topLevel {
   630  			node.textX = maxTextX
   631  		}
   632  
   633  		// If we skipped levels, shift to the left.
   634  		if topLevelGraphicsX > 0 {
   635  			node.graphicsX -= topLevelGraphicsX
   636  			node.textX -= topLevelGraphicsX
   637  		}
   638  	}
   639  
   640  	// Process selection. (Also trigger events if necessary.)
   641  	if selectedIndex >= 0 {
   642  		// Move the selection.
   643  		newSelectedIndex := selectedIndex
   644  	MovementSwitch:
   645  		switch t.movement {
   646  		case treeUp:
   647  			for newSelectedIndex > 0 {
   648  				newSelectedIndex--
   649  				if t.nodes[newSelectedIndex].selectable {
   650  					break MovementSwitch
   651  				}
   652  			}
   653  			newSelectedIndex = selectedIndex
   654  		case treeDown:
   655  			for newSelectedIndex < len(t.nodes)-1 {
   656  				newSelectedIndex++
   657  				if t.nodes[newSelectedIndex].selectable {
   658  					break MovementSwitch
   659  				}
   660  			}
   661  			newSelectedIndex = selectedIndex
   662  		case treeHome:
   663  			for newSelectedIndex = 0; newSelectedIndex < len(t.nodes); newSelectedIndex++ {
   664  				if t.nodes[newSelectedIndex].selectable {
   665  					break MovementSwitch
   666  				}
   667  			}
   668  			newSelectedIndex = selectedIndex
   669  		case treeEnd:
   670  			for newSelectedIndex = len(t.nodes) - 1; newSelectedIndex >= 0; newSelectedIndex-- {
   671  				if t.nodes[newSelectedIndex].selectable {
   672  					break MovementSwitch
   673  				}
   674  			}
   675  			newSelectedIndex = selectedIndex
   676  		case treePageDown:
   677  			if newSelectedIndex+height < len(t.nodes) {
   678  				newSelectedIndex += height
   679  			} else {
   680  				newSelectedIndex = len(t.nodes) - 1
   681  			}
   682  			for ; newSelectedIndex < len(t.nodes); newSelectedIndex++ {
   683  				if t.nodes[newSelectedIndex].selectable {
   684  					break MovementSwitch
   685  				}
   686  			}
   687  			newSelectedIndex = selectedIndex
   688  		case treePageUp:
   689  			if newSelectedIndex >= height {
   690  				newSelectedIndex -= height
   691  			} else {
   692  				newSelectedIndex = 0
   693  			}
   694  			for ; newSelectedIndex >= 0; newSelectedIndex-- {
   695  				if t.nodes[newSelectedIndex].selectable {
   696  					break MovementSwitch
   697  				}
   698  			}
   699  			newSelectedIndex = selectedIndex
   700  		}
   701  		t.currentNode = t.nodes[newSelectedIndex]
   702  		if newSelectedIndex != selectedIndex {
   703  			t.movement = treeNone
   704  			if t.changed != nil {
   705  				t.Unlock()
   706  				t.changed(t.currentNode)
   707  				t.Lock()
   708  			}
   709  			if t.currentNode.focused != nil {
   710  				t.Unlock()
   711  				t.currentNode.focused()
   712  				t.Lock()
   713  			}
   714  		}
   715  		selectedIndex = newSelectedIndex
   716  
   717  		// Move selection into viewport.
   718  		if selectedIndex-t.offsetY >= height {
   719  			t.offsetY = selectedIndex - height + 1
   720  		}
   721  		if selectedIndex < t.offsetY {
   722  			t.offsetY = selectedIndex
   723  		}
   724  	} else {
   725  		// If selection is not visible or selectable, select the first candidate.
   726  		if t.currentNode != nil {
   727  			for index, node := range t.nodes {
   728  				if node.selectable {
   729  					selectedIndex = index
   730  					t.currentNode = node
   731  					break
   732  				}
   733  			}
   734  		}
   735  		if selectedIndex < 0 {
   736  			t.currentNode = nil
   737  		}
   738  	}
   739  }
   740  
   741  // Draw draws this primitive onto the screen.
   742  func (t *TreeView) Draw(screen tcell.Screen) {
   743  	if !t.GetVisible() {
   744  		return
   745  	}
   746  
   747  	t.Box.Draw(screen)
   748  
   749  	t.Lock()
   750  	defer t.Unlock()
   751  
   752  	if t.root == nil {
   753  		return
   754  	}
   755  
   756  	t.process()
   757  
   758  	// Scroll the tree.
   759  	x, y, width, height := t.GetInnerRect()
   760  	switch t.movement {
   761  	case treeUp:
   762  		t.offsetY--
   763  	case treeDown:
   764  		t.offsetY++
   765  	case treeHome:
   766  		t.offsetY = 0
   767  	case treeEnd:
   768  		t.offsetY = len(t.nodes)
   769  	case treePageUp:
   770  		t.offsetY -= height
   771  	case treePageDown:
   772  		t.offsetY += height
   773  	}
   774  	t.movement = treeNone
   775  
   776  	// Fix invalid offsets.
   777  	if t.offsetY >= len(t.nodes)-height {
   778  		t.offsetY = len(t.nodes) - height
   779  	}
   780  	if t.offsetY < 0 {
   781  		t.offsetY = 0
   782  	}
   783  
   784  	// Calculate scroll bar position.
   785  	rows := len(t.nodes)
   786  	cursor := int(float64(rows) * (float64(t.offsetY) / float64(rows-height)))
   787  
   788  	// Draw the tree.
   789  	posY := y
   790  	lineStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.graphicsColor)
   791  	for index, node := range t.nodes {
   792  		// Skip invisible parts.
   793  		if posY >= y+height {
   794  			break
   795  		}
   796  		if index < t.offsetY {
   797  			continue
   798  		}
   799  
   800  		// Draw the graphics.
   801  		if t.graphics {
   802  			// Draw ancestor branches.
   803  			ancestor := node.parent
   804  			for ancestor != nil && ancestor.parent != nil && ancestor.parent.level >= t.topLevel {
   805  				if ancestor.graphicsX >= width {
   806  					continue
   807  				}
   808  
   809  				// Draw a branch if this ancestor is not a last child.
   810  				if ancestor.parent.children[len(ancestor.parent.children)-1] != ancestor {
   811  					if posY-1 >= y && ancestor.textX > ancestor.graphicsX {
   812  						PrintJoinedSemigraphics(screen, x+ancestor.graphicsX, posY-1, Borders.Vertical, t.graphicsColor)
   813  					}
   814  					if posY < y+height {
   815  						screen.SetContent(x+ancestor.graphicsX, posY, Borders.Vertical, nil, lineStyle)
   816  					}
   817  				}
   818  				ancestor = ancestor.parent
   819  			}
   820  
   821  			if node.textX > node.graphicsX && node.graphicsX < width {
   822  				// Connect to the node above.
   823  				if posY-1 >= y && t.nodes[index-1].graphicsX <= node.graphicsX && t.nodes[index-1].textX > node.graphicsX {
   824  					PrintJoinedSemigraphics(screen, x+node.graphicsX, posY-1, Borders.TopLeft, t.graphicsColor)
   825  				}
   826  
   827  				// Join this node.
   828  				if posY < y+height {
   829  					screen.SetContent(x+node.graphicsX, posY, Borders.BottomLeft, nil, lineStyle)
   830  					for pos := node.graphicsX + 1; pos < node.textX && pos < width; pos++ {
   831  						screen.SetContent(x+pos, posY, Borders.Horizontal, nil, lineStyle)
   832  					}
   833  				}
   834  			}
   835  		}
   836  
   837  		// Draw the prefix and the text.
   838  		if node.textX < width && posY < y+height {
   839  			// Prefix.
   840  			var prefixWidth int
   841  			if len(t.prefixes) > 0 {
   842  				_, prefixWidth = Print(screen, t.prefixes[(node.level-t.topLevel)%len(t.prefixes)], x+node.textX, posY, width-node.textX, AlignLeft, node.color)
   843  			}
   844  
   845  			// Text.
   846  			if node.textX+prefixWidth < width {
   847  				style := tcell.StyleDefault.Foreground(node.color)
   848  				if node == t.currentNode {
   849  					backgroundColor := node.color
   850  					foregroundColor := t.backgroundColor
   851  					if t.selectedTextColor != nil {
   852  						foregroundColor = *t.selectedTextColor
   853  					}
   854  					if t.selectedBackgroundColor != nil {
   855  						backgroundColor = *t.selectedBackgroundColor
   856  					}
   857  					style = tcell.StyleDefault.Background(backgroundColor).Foreground(foregroundColor)
   858  				}
   859  				PrintStyle(screen, []byte(node.text), x+node.textX+prefixWidth, posY, width-node.textX-prefixWidth, AlignLeft, style)
   860  			}
   861  		}
   862  
   863  		// Draw scroll bar.
   864  		RenderScrollBar(screen, t.scrollBarVisibility, x+(width-1), posY, height, rows, cursor, posY-y, t.hasFocus, t.scrollBarColor)
   865  
   866  		// Advance.
   867  		posY++
   868  	}
   869  }
   870  
   871  // InputHandler returns the handler for this primitive.
   872  func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
   873  	return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
   874  		selectNode := func() {
   875  			t.Lock()
   876  			currentNode := t.currentNode
   877  			t.Unlock()
   878  			if currentNode == nil {
   879  				return
   880  			}
   881  
   882  			if t.selected != nil {
   883  				t.selected(currentNode)
   884  			}
   885  			if currentNode.focused != nil {
   886  				currentNode.focused()
   887  			}
   888  			if currentNode.selected != nil {
   889  				currentNode.selected()
   890  			}
   891  		}
   892  
   893  		t.Lock()
   894  		defer t.Unlock()
   895  
   896  		// Because the tree is flattened into a list only at drawing time, we also
   897  		// postpone the (selection) movement to drawing time.
   898  		if HitShortcut(event, Keys.Cancel, Keys.MovePreviousField, Keys.MoveNextField) {
   899  			if t.done != nil {
   900  				t.Unlock()
   901  				t.done(event.Key())
   902  				t.Lock()
   903  			}
   904  		} else if HitShortcut(event, Keys.MoveFirst, Keys.MoveFirst2) {
   905  			t.movement = treeHome
   906  		} else if HitShortcut(event, Keys.MoveLast, Keys.MoveLast2) {
   907  			t.movement = treeEnd
   908  		} else if HitShortcut(event, Keys.MoveUp, Keys.MoveUp2) {
   909  			t.movement = treeUp
   910  		} else if HitShortcut(event, Keys.MoveDown, Keys.MoveDown2) {
   911  			t.movement = treeDown
   912  		} else if HitShortcut(event, Keys.MovePreviousPage) {
   913  			t.movement = treePageUp
   914  		} else if HitShortcut(event, Keys.MoveNextPage) {
   915  			t.movement = treePageDown
   916  		} else if HitShortcut(event, Keys.Select, Keys.Select2) {
   917  			t.Unlock()
   918  			selectNode()
   919  			t.Lock()
   920  		}
   921  
   922  		t.process()
   923  	})
   924  }
   925  
   926  // MouseHandler returns the mouse handler for this primitive.
   927  func (t *TreeView) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   928  	return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   929  		x, y := event.Position()
   930  		if !t.InRect(x, y) {
   931  			return false, nil
   932  		}
   933  
   934  		switch action {
   935  		case MouseLeftClick:
   936  			_, rectY, _, _ := t.GetInnerRect()
   937  			y -= rectY
   938  			if y >= 0 && y < len(t.nodes) {
   939  				node := t.nodes[y]
   940  				if node.selectable {
   941  					if t.currentNode != node && t.changed != nil {
   942  						t.changed(node)
   943  					}
   944  					if t.selected != nil {
   945  						t.selected(node)
   946  					}
   947  					t.currentNode = node
   948  				}
   949  			}
   950  			consumed = true
   951  			setFocus(t)
   952  		case MouseScrollUp:
   953  			t.movement = treeUp
   954  			consumed = true
   955  		case MouseScrollDown:
   956  			t.movement = treeDown
   957  			consumed = true
   958  		}
   959  
   960  		return
   961  	})
   962  }
   963  

View as plain text