mirror of
https://github.com/rivo/tview.git
synced 2024-11-07 03:20:39 +00:00
Added the tree view.
This commit is contained in:
parent
e643d10b36
commit
4631cd7337
@ -12,6 +12,7 @@ Among these components are:
|
||||
- __Input forms__ (include __input/password fields__, __drop-down selections__, __checkboxes__, and __buttons__)
|
||||
- Navigable multi-color __text views__
|
||||
- Sophisticated navigable __table views__
|
||||
- Flexible __tree views__
|
||||
- Selectable __lists__
|
||||
- __Grid__, __Flexbox__ and __page layouts__
|
||||
- Modal __message windows__
|
||||
@ -64,6 +65,8 @@ Add your issue here on GitHub. Feel free to get in touch if you have any questio
|
||||
|
||||
(There are no corresponding tags in the project. I only keep such a history in this README.)
|
||||
|
||||
- v0.17 (2018-06-20)
|
||||
- Added `TreeView`.
|
||||
- v0.15 (2018-05-02)
|
||||
- `Flex` and `Grid` don't clear their background per default, thus allowing for custom modals. See the [Wiki](https://github.com/rivo/tview/wiki/Modal) for an example.
|
||||
- v0.14 (2018-04-13)
|
||||
|
@ -40,6 +40,7 @@ func main() {
|
||||
TextView1,
|
||||
TextView2,
|
||||
Table,
|
||||
TreeView,
|
||||
Flex,
|
||||
Grid,
|
||||
Colors,
|
||||
@ -58,12 +59,14 @@ func main() {
|
||||
pages := tview.NewPages()
|
||||
previousSlide := func() {
|
||||
currentSlide = (currentSlide - 1 + len(slides)) % len(slides)
|
||||
info.Highlight(strconv.Itoa(currentSlide))
|
||||
info.Highlight(strconv.Itoa(currentSlide)).
|
||||
ScrollToHighlight()
|
||||
pages.SwitchToPage(strconv.Itoa(currentSlide))
|
||||
}
|
||||
nextSlide := func() {
|
||||
currentSlide = (currentSlide + 1) % len(slides)
|
||||
info.Highlight(strconv.Itoa(currentSlide))
|
||||
info.Highlight(strconv.Itoa(currentSlide)).
|
||||
ScrollToHighlight()
|
||||
pages.SwitchToPage(strconv.Itoa(currentSlide))
|
||||
}
|
||||
for index, slide := range slides {
|
||||
|
@ -68,7 +68,7 @@ const textView2 = `[green]package[white] main
|
||||
[yellow]SetDoneFunc[white]([yellow]func[white](key tcell.Key) {
|
||||
highlights := ["2"]textView[""].[yellow]GetHighlights[white]()
|
||||
hasHighlights := [yellow]len[white](highlights) > [red]0
|
||||
[white] [yellow]switch[white] key {
|
||||
[yellow]switch[white] key {
|
||||
[yellow]case[white] tcell.KeyEnter:
|
||||
[yellow]if[white] hasHighlights {
|
||||
["3"]textView[""].[yellow]Highlight[white]()
|
||||
@ -80,14 +80,14 @@ const textView2 = `[green]package[white] main
|
||||
[yellow]if[white] hasHighlights {
|
||||
current, _ := strconv.[yellow]Atoi[white](highlights[[red]0[white]])
|
||||
next := (current + [red]1[white]) % [red]9
|
||||
[white] ["5"]textView[""].[yellow]Highlight[white](strconv.[yellow]Itoa[white](next)).
|
||||
["5"]textView[""].[yellow]Highlight[white](strconv.[yellow]Itoa[white](next)).
|
||||
[yellow]ScrollToHighlight[white]()
|
||||
}
|
||||
[yellow]case[white] tcell.KeyBacktab:
|
||||
[yellow]if[white] hasHighlights {
|
||||
current, _ := strconv.[yellow]Atoi[white](highlights[[red]0[white]])
|
||||
next := (current - [red]1[white] + [red]9[white]) % [red]9
|
||||
[white] ["6"]textView[""].[yellow]Highlight[white](strconv.[yellow]Itoa[white](next)).
|
||||
["6"]textView[""].[yellow]Highlight[white](strconv.[yellow]Itoa[white](next)).
|
||||
[yellow]ScrollToHighlight[white]()
|
||||
}
|
||||
}
|
||||
|
149
demos/presentation/treeview.go
Normal file
149
demos/presentation/treeview.go
Normal file
@ -0,0 +1,149 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const treeAllCode = `[green]package[white] main
|
||||
|
||||
[green]import[white] [red]"github.com/rivo/tview"[white]
|
||||
|
||||
[green]func[white] [yellow]main[white]() {
|
||||
$$$
|
||||
|
||||
root := tview.[yellow]NewTreeNode[white]([red]"Root"[white]).
|
||||
[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]"First child"[white]).
|
||||
[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]"Grandchild A"[white])).
|
||||
[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]"Grandchild B"[white]))).
|
||||
[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]"Second child"[white]).
|
||||
[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]"Grandchild C"[white])).
|
||||
[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]"Grandchild D"[white]))).
|
||||
[yellow]AddChild[white](tview.[yellow]NewTreeNode[white]([red]"Third child"[white]))
|
||||
|
||||
tree.[yellow]SetRoot[white](root).
|
||||
[yellow]SetCurrentNode[white](root)
|
||||
|
||||
tview.[yellow]NewApplication[white]().
|
||||
[yellow]SetRoot[white](tree, true).
|
||||
[yellow]Run[white]()
|
||||
}`
|
||||
|
||||
const treeBasicCode = `tree := tview.[yellow]NewTreeView[white]()`
|
||||
|
||||
const treeTopLevelCode = `tree := tview.[yellow]NewTreeView[white]().
|
||||
[yellow]SetTopLevel[white]([red]1[white])`
|
||||
|
||||
const treeAlignCode = `tree := tview.[yellow]NewTreeView[white]().
|
||||
[yellow]SetAlign[white](true)`
|
||||
|
||||
const treePrefixCode = `tree := tview.[yellow]NewTreeView[white]().
|
||||
[yellow]SetGraphics[white](false).
|
||||
[yellow]SetTopLevel[white]([red]1[white]).
|
||||
[yellow]SetPrefixes[white]([][green]string[white]{
|
||||
[red]"[red[]* "[white],
|
||||
[red]"[darkcyan[]- "[white],
|
||||
[red]"[darkmagenta[]- "[white],
|
||||
})`
|
||||
|
||||
type node struct {
|
||||
text string
|
||||
expand bool
|
||||
selected func()
|
||||
children []*node
|
||||
}
|
||||
|
||||
var (
|
||||
tree = tview.NewTreeView()
|
||||
treeNextSlide func()
|
||||
treeCode = tview.NewTextView().SetWrap(false).SetDynamicColors(true)
|
||||
)
|
||||
|
||||
var rootNode = &node{
|
||||
text: "Root",
|
||||
children: []*node{
|
||||
{text: "Expand all", selected: func() { tree.GetRoot().ExpandAll() }},
|
||||
{text: "Collapse all", selected: func() {
|
||||
for _, child := range tree.GetRoot().GetChildren() {
|
||||
child.CollapseAll()
|
||||
}
|
||||
}},
|
||||
{text: "Hide root node", expand: true, children: []*node{
|
||||
{text: "Tree list starts one level down"},
|
||||
{text: "Works better for lists where no top node is needed"},
|
||||
{text: "Switch to this layout", selected: func() {
|
||||
tree.SetAlign(false).SetTopLevel(1).SetGraphics(true).SetPrefixes(nil)
|
||||
treeCode.SetText(strings.Replace(treeAllCode, "$$$", treeTopLevelCode, -1))
|
||||
}},
|
||||
}},
|
||||
{text: "Align node text", expand: true, children: []*node{
|
||||
{text: "For trees that are similar to lists"},
|
||||
{text: "Hierarchy shown only in line drawings"},
|
||||
{text: "Switch to this layout", selected: func() {
|
||||
tree.SetAlign(true).SetTopLevel(0).SetGraphics(true).SetPrefixes(nil)
|
||||
treeCode.SetText(strings.Replace(treeAllCode, "$$$", treeAlignCode, -1))
|
||||
}},
|
||||
}},
|
||||
{text: "Prefixes", expand: true, children: []*node{
|
||||
{text: "Best for hierarchical bullet point lists"},
|
||||
{text: "You can define your own prefixes per level"},
|
||||
{text: "Switch to this layout", selected: func() {
|
||||
tree.SetAlign(false).SetTopLevel(1).SetGraphics(false).SetPrefixes([]string{"[red]* ", "[darkcyan]- ", "[darkmagenta]- "})
|
||||
treeCode.SetText(strings.Replace(treeAllCode, "$$$", treePrefixCode, -1))
|
||||
}},
|
||||
}},
|
||||
{text: "Basic tree with graphics", expand: true, children: []*node{
|
||||
{text: "Lines illustrate hierarchy"},
|
||||
{text: "Basic indentation"},
|
||||
{text: "Switch to this layout", selected: func() {
|
||||
tree.SetAlign(false).SetTopLevel(0).SetGraphics(true).SetPrefixes(nil)
|
||||
treeCode.SetText(strings.Replace(treeAllCode, "$$$", treeBasicCode, -1))
|
||||
}},
|
||||
}},
|
||||
{text: "Next slide", selected: func() { treeNextSlide() }},
|
||||
}}
|
||||
|
||||
// TreeView demonstrates the tree view.
|
||||
func TreeView(nextSlide func()) (title string, content tview.Primitive) {
|
||||
treeNextSlide = nextSlide
|
||||
tree.SetBorder(true).
|
||||
SetTitle("TreeView")
|
||||
|
||||
// Add nodes.
|
||||
var add func(target *node) *tview.TreeNode
|
||||
add = func(target *node) *tview.TreeNode {
|
||||
node := tview.NewTreeNode(target.text).
|
||||
SetSelectable(target.expand || target.selected != nil).
|
||||
SetExpanded(target == rootNode).
|
||||
SetReference(target)
|
||||
if target.expand {
|
||||
node.SetColor(tcell.ColorGreen)
|
||||
} else if target.selected != nil {
|
||||
node.SetColor(tcell.ColorRed)
|
||||
}
|
||||
for _, child := range target.children {
|
||||
node.AddChild(add(child))
|
||||
}
|
||||
return node
|
||||
}
|
||||
root := add(rootNode)
|
||||
tree.SetRoot(root).
|
||||
SetCurrentNode(root).
|
||||
SetSelectedFunc(func(n *tview.TreeNode) {
|
||||
original := n.GetReference().(*node)
|
||||
if original.expand {
|
||||
n.SetExpanded(!n.IsExpanded())
|
||||
} else if original.selected != nil {
|
||||
original.selected()
|
||||
}
|
||||
})
|
||||
|
||||
treeCode.SetText(strings.Replace(treeAllCode, "$$$", treeBasicCode, -1)).
|
||||
SetBorderPadding(1, 1, 2, 0)
|
||||
|
||||
return "Tree", tview.NewFlex().
|
||||
AddItem(tree, 0, 1, true).
|
||||
AddItem(treeCode, codeWidth, 1, false)
|
||||
}
|
61
demos/treeview/main.go
Normal file
61
demos/treeview/main.go
Normal file
@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
// Show a navigable tree view of the current directory.
|
||||
func main() {
|
||||
rootDir := "."
|
||||
root := tview.NewTreeNode(rootDir).
|
||||
SetColor(tcell.ColorRed)
|
||||
tree := tview.NewTreeView().
|
||||
SetRoot(root).
|
||||
SetCurrentNode(root)
|
||||
|
||||
// A helper function which adds the files and directories of the given path
|
||||
// to the given target node.
|
||||
add := func(target *tview.TreeNode, path string) {
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
node := tview.NewTreeNode(file.Name()).
|
||||
SetReference(filepath.Join(path, file.Name())).
|
||||
SetSelectable(file.IsDir())
|
||||
if file.IsDir() {
|
||||
node.SetColor(tcell.ColorGreen)
|
||||
}
|
||||
target.AddChild(node)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the current directory to the root node.
|
||||
add(root, rootDir)
|
||||
|
||||
// If a directory was selected, open it.
|
||||
tree.SetSelectedFunc(func(node *tview.TreeNode) {
|
||||
reference := node.GetReference()
|
||||
if reference == nil {
|
||||
return // Selecting the root node does nothing.
|
||||
}
|
||||
children := node.GetChildren()
|
||||
if len(children) == 0 {
|
||||
// Load and show files in this directory.
|
||||
path := reference.(string)
|
||||
add(node, path)
|
||||
} else {
|
||||
// Collapse if visible, expand if collapsed.
|
||||
node.SetExpanded(!node.IsExpanded())
|
||||
}
|
||||
})
|
||||
|
||||
if err := tview.NewApplication().SetRoot(tree, true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
BIN
demos/treeview/screenshot.png
Normal file
BIN
demos/treeview/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
10
doc.go
10
doc.go
@ -7,10 +7,12 @@ Widgets
|
||||
|
||||
The package implements the following widgets:
|
||||
|
||||
- TextView: Scrollable windows that display multi-colored text. Text may also
|
||||
- TextView: A scrollable window that display multi-colored text. Text may also
|
||||
be highlighted.
|
||||
- Table: Scrollable display of tabular data. Table cells, rows, or columns may
|
||||
also be highlighted.
|
||||
- Table: A scrollable display of tabular data. Table cells, rows, or columns
|
||||
may also be highlighted.
|
||||
- TreeView: A scrollable display for hierarchical data. Tree nodes can be
|
||||
highlighted, collapsed, expanded, and more.
|
||||
- List: A navigable text list with optional keyboard shortcuts.
|
||||
- InputField: One-line input fields to enter text.
|
||||
- DropDown: Drop-down selection fields.
|
||||
@ -83,7 +85,7 @@ tag is as follows:
|
||||
|
||||
[<foreground>:<background>:<flags>]
|
||||
|
||||
Each of the three fields can be left blank and trailing fields can be ommitted.
|
||||
Each of the three fields can be left blank and trailing fields can be omitted.
|
||||
(Empty square brackets "[]", however, are not considered color tags.) Colors
|
||||
that are not specified will be left unchanged. A field with just a dash ("-")
|
||||
means "reset to default".
|
||||
|
1
table.go
1
table.go
@ -644,7 +644,6 @@ ColumnLoop:
|
||||
}
|
||||
expWidth := toDistribute * expansion / expansionTotal
|
||||
widths[index] += expWidth
|
||||
tableWidth += expWidth
|
||||
toDistribute -= expWidth
|
||||
expansionTotal -= expansion
|
||||
}
|
||||
|
90
textview.go
90
textview.go
@ -104,6 +104,10 @@ type TextView struct {
|
||||
// during re-indexing. Set to -1 if there is no current highlight.
|
||||
fromHighlight, toHighlight int
|
||||
|
||||
// The screen space column of the highlight in its first line. Set to -1 if
|
||||
// there is no current highlight.
|
||||
posHighlight int
|
||||
|
||||
// A set of region IDs that are currently highlighted.
|
||||
highlights map[string]struct{}
|
||||
|
||||
@ -171,6 +175,7 @@ func NewTextView() *TextView {
|
||||
align: AlignLeft,
|
||||
wrap: true,
|
||||
textColor: Styles.PrimaryTextColor,
|
||||
regions: false,
|
||||
dynamicColors: false,
|
||||
}
|
||||
}
|
||||
@ -503,7 +508,7 @@ func (t *TextView) reindexBuffer(width int) {
|
||||
return // Nothing has changed. We can still use the current index.
|
||||
}
|
||||
t.index = nil
|
||||
t.fromHighlight, t.toHighlight = -1, -1
|
||||
t.fromHighlight, t.toHighlight, t.posHighlight = -1, -1, -1
|
||||
|
||||
// If there's no space, there's no index.
|
||||
if width < 1 {
|
||||
@ -522,8 +527,9 @@ func (t *TextView) reindexBuffer(width int) {
|
||||
colorTags [][]string
|
||||
escapeIndices [][]int
|
||||
)
|
||||
strippedStr := str
|
||||
if t.dynamicColors {
|
||||
colorTagIndices, colorTags, escapeIndices, str, _ = decomposeString(str)
|
||||
colorTagIndices, colorTags, escapeIndices, strippedStr, _ = decomposeString(str)
|
||||
}
|
||||
|
||||
// Find all regions in this line. Then remove them.
|
||||
@ -534,14 +540,18 @@ func (t *TextView) reindexBuffer(width int) {
|
||||
if t.regions {
|
||||
regionIndices = regionPattern.FindAllStringIndex(str, -1)
|
||||
regions = regionPattern.FindAllStringSubmatch(str, -1)
|
||||
str = regionPattern.ReplaceAllString(str, "")
|
||||
if !t.dynamicColors {
|
||||
// We haven't detected escape tags yet. Do it now.
|
||||
escapeIndices = escapePattern.FindAllStringIndex(str, -1)
|
||||
str = escapePattern.ReplaceAllString(str, "[$1$2]")
|
||||
}
|
||||
strippedStr = regionPattern.ReplaceAllString(strippedStr, "")
|
||||
}
|
||||
|
||||
// Find all escape tags in this line. Escape them.
|
||||
if t.dynamicColors || t.regions {
|
||||
escapeIndices = escapePattern.FindAllStringIndex(str, -1)
|
||||
strippedStr = escapePattern.ReplaceAllString(strippedStr, "[$1$2]")
|
||||
}
|
||||
|
||||
// We don't need the original string anymore for now.
|
||||
str = strippedStr
|
||||
|
||||
// Split the line if required.
|
||||
var splitLines []string
|
||||
if t.wrap && len(str) > 0 {
|
||||
@ -585,15 +595,53 @@ func (t *TextView) reindexBuffer(width int) {
|
||||
|
||||
// Shift original position with tags.
|
||||
lineLength := len(splitLine)
|
||||
remainingLength := lineLength
|
||||
tagEnd := originalPos
|
||||
totalTagLength := 0
|
||||
for {
|
||||
if colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+lineLength {
|
||||
// Which tag comes next?
|
||||
nextTag := make([][3]int, 0, 3)
|
||||
if colorPos < len(colorTagIndices) {
|
||||
nextTag = append(nextTag, [3]int{colorTagIndices[colorPos][0], colorTagIndices[colorPos][1], 0}) // 0 = color tag.
|
||||
}
|
||||
if regionPos < len(regionIndices) {
|
||||
nextTag = append(nextTag, [3]int{regionIndices[regionPos][0], regionIndices[regionPos][1], 1}) // 1 = region tag.
|
||||
}
|
||||
if escapePos < len(escapeIndices) {
|
||||
nextTag = append(nextTag, [3]int{escapeIndices[escapePos][0], escapeIndices[escapePos][1], 2}) // 2 = escape tag.
|
||||
}
|
||||
minPos := -1
|
||||
tagIndex := -1
|
||||
for index, pair := range nextTag {
|
||||
if minPos < 0 || pair[0] < minPos {
|
||||
minPos = pair[0]
|
||||
tagIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
// Is the next tag in range?
|
||||
if tagIndex < 0 || minPos >= tagEnd+remainingLength {
|
||||
break // No. We're done with this line.
|
||||
}
|
||||
|
||||
// Advance.
|
||||
strippedTagStart := nextTag[tagIndex][0] - originalPos - totalTagLength
|
||||
tagEnd = nextTag[tagIndex][1]
|
||||
tagLength := tagEnd - nextTag[tagIndex][0]
|
||||
if nextTag[tagIndex][2] == 2 {
|
||||
tagLength = 1
|
||||
}
|
||||
totalTagLength += tagLength
|
||||
remainingLength = lineLength - (tagEnd - originalPos - totalTagLength)
|
||||
|
||||
// Process the tag.
|
||||
switch nextTag[tagIndex][2] {
|
||||
case 0:
|
||||
// Process color tags.
|
||||
originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
|
||||
colorPos++
|
||||
} else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength {
|
||||
case 1:
|
||||
// Process region tags.
|
||||
originalPos += regionIndices[regionPos][1] - regionIndices[regionPos][0]
|
||||
regionID = regions[regionPos][1]
|
||||
_, highlighted = t.highlights[regionID]
|
||||
|
||||
@ -602,23 +650,21 @@ func (t *TextView) reindexBuffer(width int) {
|
||||
line := len(t.index)
|
||||
if t.fromHighlight < 0 {
|
||||
t.fromHighlight, t.toHighlight = line, line
|
||||
t.posHighlight = runewidth.StringWidth(splitLine[:strippedTagStart])
|
||||
} else if line > t.toHighlight {
|
||||
t.toHighlight = line
|
||||
}
|
||||
}
|
||||
|
||||
regionPos++
|
||||
} else if escapePos < len(escapeIndices) && escapeIndices[escapePos][0] <= originalPos+lineLength {
|
||||
case 2:
|
||||
// Process escape tags.
|
||||
originalPos++
|
||||
escapePos++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Advance to next line.
|
||||
originalPos += lineLength
|
||||
originalPos += lineLength + totalTagLength
|
||||
|
||||
// Append this line.
|
||||
line.NextPos = originalPos
|
||||
@ -683,6 +729,16 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
||||
// No, let's move to the start of the highlights.
|
||||
t.lineOffset = t.fromHighlight
|
||||
}
|
||||
|
||||
// If the highlight is too far to the right, move it to the middle.
|
||||
if t.posHighlight-t.columnOffset > 3*width/4 {
|
||||
t.columnOffset = t.posHighlight - width/2
|
||||
}
|
||||
|
||||
// If the highlight is off-screen on the left, move it on-screen.
|
||||
if t.posHighlight-t.columnOffset < 0 {
|
||||
t.columnOffset = t.posHighlight - width/4
|
||||
}
|
||||
}
|
||||
t.scrollToHighlights = false
|
||||
|
||||
|
663
treeview.go
Normal file
663
treeview.go
Normal file
@ -0,0 +1,663 @@
|
||||
package tview
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
// Tree navigation events.
|
||||
const (
|
||||
treeNone int = iota
|
||||
treeHome
|
||||
treeEnd
|
||||
treeUp
|
||||
treeDown
|
||||
treePageUp
|
||||
treePageDown
|
||||
)
|
||||
|
||||
// TreeNode represents one node in a tree view.
|
||||
type TreeNode struct {
|
||||
// The reference object.
|
||||
reference interface{}
|
||||
|
||||
// This node's child nodes.
|
||||
children []*TreeNode
|
||||
|
||||
// The item's text.
|
||||
text string
|
||||
|
||||
// The text color.
|
||||
color tcell.Color
|
||||
|
||||
// Whether or not this node can be selected.
|
||||
selectable bool
|
||||
|
||||
// Whether or not this node's children should be displayed.
|
||||
expanded bool
|
||||
|
||||
// The additional horizontal indent of this node's text.
|
||||
indent int
|
||||
|
||||
// An optional function which is called when the user selects this node.
|
||||
selected func()
|
||||
|
||||
// Temporary member variables.
|
||||
parent *TreeNode // The parent node (nil for the root).
|
||||
level int // The hierarchy level (0 for the root, 1 for its children, and so on).
|
||||
graphicsX int // The x-coordinate of the left-most graphics rune.
|
||||
textX int // The x-coordinate of the first rune of the text.
|
||||
}
|
||||
|
||||
// NewTreeNode returns a new tree node.
|
||||
func NewTreeNode(text string) *TreeNode {
|
||||
return &TreeNode{
|
||||
text: text,
|
||||
color: Styles.PrimaryTextColor,
|
||||
indent: 2,
|
||||
expanded: true,
|
||||
selectable: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Walk traverses this node's subtree in depth-first, pre-order (NLR) order and
|
||||
// calls the provided callback function on each traversed node (which includes
|
||||
// this node) with the traversed node and its parent node (nil for this node).
|
||||
// The callback returns whether traversal should continue with the traversed
|
||||
// node's child nodes (true) or not recurse any deeper (false).
|
||||
func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {
|
||||
n.parent = nil
|
||||
nodes := []*TreeNode{n}
|
||||
for len(nodes) > 0 {
|
||||
// Pop the top node and process it.
|
||||
node := nodes[len(nodes)-1]
|
||||
nodes = nodes[:len(nodes)-1]
|
||||
if !callback(node, node.parent) {
|
||||
// Don't add any children.
|
||||
continue
|
||||
}
|
||||
|
||||
// Add children in reverse order.
|
||||
for index := len(node.children) - 1; index >= 0; index-- {
|
||||
node.children[index].parent = node
|
||||
nodes = append(nodes, node.children[index])
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// SetReference allows you to store a reference of any type in this node. This
|
||||
// will allow you to establish a mapping between the TreeView hierarchy and your
|
||||
// internal tree structure.
|
||||
func (n *TreeNode) SetReference(reference interface{}) *TreeNode {
|
||||
n.reference = reference
|
||||
return n
|
||||
}
|
||||
|
||||
// GetReference returns this node's reference object.
|
||||
func (n *TreeNode) GetReference() interface{} {
|
||||
return n.reference
|
||||
}
|
||||
|
||||
// SetChildren sets this node's child nodes.
|
||||
func (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode {
|
||||
n.children = childNodes
|
||||
return n
|
||||
}
|
||||
|
||||
// GetChildren returns this node's children.
|
||||
func (n *TreeNode) GetChildren() []*TreeNode {
|
||||
return n.children
|
||||
}
|
||||
|
||||
// ClearChildren removes all child nodes from this node.
|
||||
func (n *TreeNode) ClearChildren() *TreeNode {
|
||||
n.children = nil
|
||||
return n
|
||||
}
|
||||
|
||||
// AddChild adds a new child node to this node.
|
||||
func (n *TreeNode) AddChild(node *TreeNode) *TreeNode {
|
||||
n.children = append(n.children, node)
|
||||
return n
|
||||
}
|
||||
|
||||
// SetSelectable sets a flag indicating whether this node can be selected by
|
||||
// the user.
|
||||
func (n *TreeNode) SetSelectable(selectable bool) *TreeNode {
|
||||
n.selectable = selectable
|
||||
return n
|
||||
}
|
||||
|
||||
// SetSelectedFunc sets a function which is called when the user selects this
|
||||
// node by hitting Enter when it is selected.
|
||||
func (n *TreeNode) SetSelectedFunc(handler func()) *TreeNode {
|
||||
n.selected = handler
|
||||
return n
|
||||
}
|
||||
|
||||
// SetExpanded sets whether or not this node's child nodes should be displayed.
|
||||
func (n *TreeNode) SetExpanded(expanded bool) *TreeNode {
|
||||
n.expanded = expanded
|
||||
return n
|
||||
}
|
||||
|
||||
// Expand makes the child nodes of this node appear.
|
||||
func (n *TreeNode) Expand() *TreeNode {
|
||||
n.expanded = true
|
||||
return n
|
||||
}
|
||||
|
||||
// Collapse makes the child nodes of this node disappear.
|
||||
func (n *TreeNode) Collapse() *TreeNode {
|
||||
n.expanded = false
|
||||
return n
|
||||
}
|
||||
|
||||
// ExpandAll expands this node and all descendent nodes.
|
||||
func (n *TreeNode) ExpandAll() *TreeNode {
|
||||
n.Walk(func(node, parent *TreeNode) bool {
|
||||
node.expanded = true
|
||||
return true
|
||||
})
|
||||
return n
|
||||
}
|
||||
|
||||
// CollapseAll collapses this node and all descendent nodes.
|
||||
func (n *TreeNode) CollapseAll() *TreeNode {
|
||||
n.Walk(func(node, parent *TreeNode) bool {
|
||||
n.expanded = false
|
||||
return true
|
||||
})
|
||||
return n
|
||||
}
|
||||
|
||||
// IsExpanded returns whether the child nodes of this node are visible.
|
||||
func (n *TreeNode) IsExpanded() bool {
|
||||
return n.expanded
|
||||
}
|
||||
|
||||
// SetText sets the node's text which is displayed.
|
||||
func (n *TreeNode) SetText(text string) *TreeNode {
|
||||
n.text = text
|
||||
return n
|
||||
}
|
||||
|
||||
// SetColor sets the node's text color.
|
||||
func (n *TreeNode) SetColor(color tcell.Color) *TreeNode {
|
||||
n.color = color
|
||||
return n
|
||||
}
|
||||
|
||||
// SetIndent sets an additional indentation for this node's text. A value of 0
|
||||
// keeps the text as far left as possible with a minimum of line graphics. Any
|
||||
// value greater than that moves the text to the right.
|
||||
func (n *TreeNode) SetIndent(indent int) *TreeNode {
|
||||
n.indent = indent
|
||||
return n
|
||||
}
|
||||
|
||||
// TreeView displays tree structures. A tree consists of nodes (TreeNode
|
||||
// objects) where each node has zero or more child nodes and exactly one parent
|
||||
// node (except for the root node which has no parent node).
|
||||
//
|
||||
// The SetRoot() function is used to specify the root of the tree. Other nodes
|
||||
// are added locally to the root node or any of its descendents. See the
|
||||
// TreeNode documentation for details on node attributes. (You can use
|
||||
// SetReference() to store a reference to nodes of your own tree structure.)
|
||||
//
|
||||
// Nodes can be selected by calling SetCurrentNode(). The user can navigate the
|
||||
// selection or the tree by using the following keys:
|
||||
//
|
||||
// - j, down arrow, right arrow: Move (the selection) down by one node.
|
||||
// - k, up arrow, left arrow: Move (the selection) up by one node.
|
||||
// - g, home: Move (the selection) to the top.
|
||||
// - G, end: Move (the selection) to the bottom.
|
||||
// - Ctrl-F, page down: Move (the selection) down by one page.
|
||||
// - Ctrl-B, page up: Move (the selection) up by one page.
|
||||
//
|
||||
// Selected nodes can trigger the "selected" callback when the user hits Enter.
|
||||
//
|
||||
// The root node corresponds to level 0, its children correspond to level 1,
|
||||
// their children to level 2, and so on. Per default, the first level that is
|
||||
// displayed is 0, i.e. the root node. You can call SetTopLevel() to skip
|
||||
// levels.
|
||||
//
|
||||
// If graphics are turned on (see SetGraphics()), lines indicate the tree's
|
||||
// hierarchy. Alternative (or additionally), you can set different prefixes
|
||||
// using SetPrefixes() for different levels, for example to display hierarchical
|
||||
// bullet point lists.
|
||||
type TreeView struct {
|
||||
*Box
|
||||
|
||||
// The root node.
|
||||
root *TreeNode
|
||||
|
||||
// The currently selected node or nil if no node is selected.
|
||||
currentNode *TreeNode
|
||||
|
||||
// The movement to be performed during the call to Draw(), one of the
|
||||
// constants defined above.
|
||||
movement int
|
||||
|
||||
// The top hierarchical level shown. (0 corresponds to the root level.)
|
||||
topLevel int
|
||||
|
||||
// Strings drawn before the nodes, based on their level.
|
||||
prefixes []string
|
||||
|
||||
// Vertical scroll offset.
|
||||
offsetY int
|
||||
|
||||
// If set to true, all node texts will be aligned horizontally.
|
||||
align bool
|
||||
|
||||
// If set to true, the tree structure is drawn using lines.
|
||||
graphics bool
|
||||
|
||||
// The color of the lines.
|
||||
graphicsColor tcell.Color
|
||||
|
||||
// An optional function which is called when the user has navigated to a new
|
||||
// tree node.
|
||||
changed func(node *TreeNode)
|
||||
|
||||
// An optional function which is called when a tree item was selected.
|
||||
selected func(node *TreeNode)
|
||||
}
|
||||
|
||||
// NewTreeView returns a new tree view.
|
||||
func NewTreeView() *TreeView {
|
||||
return &TreeView{
|
||||
Box: NewBox(),
|
||||
graphics: true,
|
||||
graphicsColor: Styles.GraphicsColor,
|
||||
}
|
||||
}
|
||||
|
||||
// SetRoot sets the root node of the tree.
|
||||
func (t *TreeView) SetRoot(root *TreeNode) *TreeView {
|
||||
t.root = root
|
||||
return t
|
||||
}
|
||||
|
||||
// GetRoot returns the root node of the tree. If no such node was previously
|
||||
// set, nil is returned.
|
||||
func (t *TreeView) GetRoot() *TreeNode {
|
||||
return t.root
|
||||
}
|
||||
|
||||
// SetCurrentNode sets the currently selected node. Provide nil to clear all
|
||||
// selections. Selected nodes must be visible and selectable, or else the
|
||||
// selection will be changed to the top-most selectable and visible node.
|
||||
//
|
||||
// This function does NOT trigger the "changed" callback.
|
||||
func (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView {
|
||||
t.currentNode = node
|
||||
return t
|
||||
}
|
||||
|
||||
// GetCurrentNode returns the currently selected node or nil of no node is
|
||||
// currently selected.
|
||||
func (t *TreeView) GetCurrentNode() *TreeNode {
|
||||
return t.currentNode
|
||||
}
|
||||
|
||||
// SetTopLevel sets the first tree level that is visible with 0 referring to the
|
||||
// root, 1 to the root's child nodes, and so on. Nodes above the top level are
|
||||
// not displayed.
|
||||
func (t *TreeView) SetTopLevel(topLevel int) *TreeView {
|
||||
t.topLevel = topLevel
|
||||
return t
|
||||
}
|
||||
|
||||
// SetPrefixes defines the strings drawn before the nodes' texts. This is a
|
||||
// slice of strings where each element corresponds to a node's hierarchy level,
|
||||
// i.e. 0 for the root, 1 for the root's children, and so on (levels will
|
||||
// cycle).
|
||||
//
|
||||
// For example, to display a hierarchical list with bullet points:
|
||||
//
|
||||
// treeView.SetGraphics(false).
|
||||
// SetPrefixes([]string{"* ", "- ", "x "})
|
||||
func (t *TreeView) SetPrefixes(prefixes []string) *TreeView {
|
||||
t.prefixes = prefixes
|
||||
return t
|
||||
}
|
||||
|
||||
// SetAlign controls the horizontal alignment of the node texts. If set to true,
|
||||
// all texts except that of top-level nodes will be placed in the same column.
|
||||
// If set to false, they will indent with the hierarchy.
|
||||
func (t *TreeView) SetAlign(align bool) *TreeView {
|
||||
t.align = align
|
||||
return t
|
||||
}
|
||||
|
||||
// SetGraphics sets a flag which determines whether or not line graphics are
|
||||
// drawn to illustrate the tree's hierarchy.
|
||||
func (t *TreeView) SetGraphics(showGraphics bool) *TreeView {
|
||||
t.graphics = showGraphics
|
||||
return t
|
||||
}
|
||||
|
||||
// SetGraphicsColor sets the colors of the lines used to draw the tree structure.
|
||||
func (t *TreeView) SetGraphicsColor(color tcell.Color) *TreeView {
|
||||
t.graphicsColor = color
|
||||
return t
|
||||
}
|
||||
|
||||
// SetChangedFunc sets the function which is called when the user navigates to
|
||||
// a new tree node.
|
||||
func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView {
|
||||
t.changed = handler
|
||||
return t
|
||||
}
|
||||
|
||||
// SetSelectedFunc sets the function which is called when the user selects a
|
||||
// node by pressing Enter on the current selection.
|
||||
func (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) *TreeView {
|
||||
t.selected = handler
|
||||
return t
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (t *TreeView) Draw(screen tcell.Screen) {
|
||||
t.Box.Draw(screen)
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
x, y, width, height := t.GetInnerRect()
|
||||
|
||||
// Determine visible nodes and their placement.
|
||||
var graphicsOffset, maxTextX int
|
||||
var nodes []*TreeNode
|
||||
selectedIndex := -1
|
||||
topLevelGraphicsX := -1
|
||||
if t.graphics {
|
||||
graphicsOffset = 1
|
||||
}
|
||||
t.root.Walk(func(node, parent *TreeNode) bool {
|
||||
// Set node attributes.
|
||||
node.parent = parent
|
||||
if parent == nil {
|
||||
node.level = 0
|
||||
node.graphicsX = 0
|
||||
node.textX = 0
|
||||
} else {
|
||||
node.level = parent.level + 1
|
||||
node.graphicsX = parent.textX
|
||||
node.textX = node.graphicsX + graphicsOffset + node.indent
|
||||
}
|
||||
if !t.graphics && t.align {
|
||||
// Without graphics, we align nodes on the first column.
|
||||
node.textX = 0
|
||||
}
|
||||
if node.level == t.topLevel {
|
||||
// No graphics for top level nodes.
|
||||
node.graphicsX = 0
|
||||
node.textX = 0
|
||||
}
|
||||
if node.textX > maxTextX {
|
||||
maxTextX = node.textX
|
||||
}
|
||||
if node == t.currentNode && node.selectable {
|
||||
selectedIndex = len(nodes)
|
||||
}
|
||||
|
||||
// Maybe we want to skip this level.
|
||||
if t.topLevel == node.level && (topLevelGraphicsX < 0 || node.graphicsX < topLevelGraphicsX) {
|
||||
topLevelGraphicsX = node.graphicsX
|
||||
}
|
||||
|
||||
// Add and recurse (if desired).
|
||||
if node.level >= t.topLevel {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
return node.expanded
|
||||
})
|
||||
|
||||
// Post-process positions.
|
||||
for _, node := range nodes {
|
||||
// If text must align, we correct the positions.
|
||||
if t.align && node.level > t.topLevel {
|
||||
node.textX = maxTextX
|
||||
}
|
||||
|
||||
// If we skipped levels, shift to the left.
|
||||
if topLevelGraphicsX > 0 {
|
||||
node.graphicsX -= topLevelGraphicsX
|
||||
node.textX -= topLevelGraphicsX
|
||||
}
|
||||
}
|
||||
|
||||
// Process selection. (Also trigger events if necessary.)
|
||||
if selectedIndex >= 0 {
|
||||
// Move the selection.
|
||||
newSelectedIndex := selectedIndex
|
||||
MovementSwitch:
|
||||
switch t.movement {
|
||||
case treeUp:
|
||||
for newSelectedIndex > 0 {
|
||||
newSelectedIndex--
|
||||
if nodes[newSelectedIndex].selectable {
|
||||
break MovementSwitch
|
||||
}
|
||||
}
|
||||
newSelectedIndex = selectedIndex
|
||||
case treeDown:
|
||||
for newSelectedIndex < len(nodes)-1 {
|
||||
newSelectedIndex++
|
||||
if nodes[newSelectedIndex].selectable {
|
||||
break MovementSwitch
|
||||
}
|
||||
}
|
||||
newSelectedIndex = selectedIndex
|
||||
case treeHome:
|
||||
for newSelectedIndex = 0; newSelectedIndex < len(nodes); newSelectedIndex++ {
|
||||
if nodes[newSelectedIndex].selectable {
|
||||
break MovementSwitch
|
||||
}
|
||||
}
|
||||
newSelectedIndex = selectedIndex
|
||||
case treeEnd:
|
||||
for newSelectedIndex = len(nodes) - 1; newSelectedIndex >= 0; newSelectedIndex-- {
|
||||
if nodes[newSelectedIndex].selectable {
|
||||
break MovementSwitch
|
||||
}
|
||||
}
|
||||
newSelectedIndex = selectedIndex
|
||||
case treePageUp:
|
||||
if newSelectedIndex+height < len(nodes) {
|
||||
newSelectedIndex += height
|
||||
} else {
|
||||
newSelectedIndex = len(nodes) - 1
|
||||
}
|
||||
for ; newSelectedIndex < len(nodes); newSelectedIndex++ {
|
||||
if nodes[newSelectedIndex].selectable {
|
||||
break MovementSwitch
|
||||
}
|
||||
}
|
||||
newSelectedIndex = selectedIndex
|
||||
case treePageDown:
|
||||
if newSelectedIndex >= height {
|
||||
newSelectedIndex -= height
|
||||
} else {
|
||||
newSelectedIndex = 0
|
||||
}
|
||||
for ; newSelectedIndex >= 0; newSelectedIndex-- {
|
||||
if nodes[newSelectedIndex].selectable {
|
||||
break MovementSwitch
|
||||
}
|
||||
}
|
||||
newSelectedIndex = selectedIndex
|
||||
}
|
||||
t.currentNode = nodes[newSelectedIndex]
|
||||
if newSelectedIndex != selectedIndex {
|
||||
t.movement = treeNone
|
||||
if t.changed != nil {
|
||||
t.changed(t.currentNode)
|
||||
}
|
||||
}
|
||||
selectedIndex = newSelectedIndex
|
||||
|
||||
// Move selection into viewport.
|
||||
if selectedIndex-t.offsetY >= height {
|
||||
t.offsetY = selectedIndex - height + 1
|
||||
}
|
||||
if selectedIndex < t.offsetY {
|
||||
t.offsetY = selectedIndex
|
||||
}
|
||||
} else {
|
||||
// If selection is not visible or selectable, select the first candidate.
|
||||
if t.currentNode != nil {
|
||||
for index, node := range nodes {
|
||||
if node.selectable {
|
||||
selectedIndex = index
|
||||
t.currentNode = node
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if selectedIndex < 0 {
|
||||
t.currentNode = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll the tree.
|
||||
switch t.movement {
|
||||
case treeUp:
|
||||
t.offsetY--
|
||||
case treeDown:
|
||||
t.offsetY++
|
||||
case treeHome:
|
||||
t.offsetY = 0
|
||||
case treeEnd:
|
||||
t.offsetY = len(nodes)
|
||||
case treePageUp:
|
||||
t.offsetY -= height
|
||||
case treePageDown:
|
||||
t.offsetY += height
|
||||
}
|
||||
t.movement = treeNone
|
||||
|
||||
// Fix invalid offsets.
|
||||
if t.offsetY >= len(nodes)-height {
|
||||
t.offsetY = len(nodes) - height
|
||||
}
|
||||
if t.offsetY < 0 {
|
||||
t.offsetY = 0
|
||||
}
|
||||
|
||||
// Draw the tree.
|
||||
posY := y
|
||||
lineStyle := tcell.StyleDefault.Foreground(t.graphicsColor)
|
||||
for index, node := range nodes {
|
||||
// Skip invisible parts.
|
||||
if posY >= y+height+1 {
|
||||
break
|
||||
}
|
||||
if index < t.offsetY {
|
||||
continue
|
||||
}
|
||||
|
||||
// Draw the graphics.
|
||||
if t.graphics {
|
||||
// Draw ancestor branches.
|
||||
ancestor := node.parent
|
||||
for ancestor != nil && ancestor.parent != nil && ancestor.parent.level >= t.topLevel {
|
||||
if ancestor.graphicsX >= width {
|
||||
continue
|
||||
}
|
||||
|
||||
// Draw a branch if this ancestor is not a last child.
|
||||
if ancestor.parent.children[len(ancestor.parent.children)-1] != ancestor {
|
||||
if posY-1 >= y && ancestor.textX > ancestor.graphicsX {
|
||||
PrintJoinedSemigraphics(screen, x+ancestor.graphicsX, posY-1, Borders.Vertical, t.graphicsColor)
|
||||
}
|
||||
if posY < y+height {
|
||||
screen.SetContent(x+ancestor.graphicsX, posY, Borders.Vertical, nil, lineStyle)
|
||||
}
|
||||
}
|
||||
ancestor = ancestor.parent
|
||||
}
|
||||
|
||||
if node.textX > node.graphicsX && node.graphicsX < width {
|
||||
// Connect to the node above.
|
||||
if posY-1 >= y && nodes[index-1].graphicsX <= node.graphicsX && nodes[index-1].textX > node.graphicsX {
|
||||
PrintJoinedSemigraphics(screen, x+node.graphicsX, posY-1, Borders.TopLeft, t.graphicsColor)
|
||||
}
|
||||
|
||||
// Join this node.
|
||||
if posY < y+height {
|
||||
screen.SetContent(x+node.graphicsX, posY, Borders.BottomLeft, nil, lineStyle)
|
||||
for pos := node.graphicsX + 1; pos < node.textX && pos < width; pos++ {
|
||||
screen.SetContent(x+pos, posY, Borders.Horizontal, nil, lineStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the prefix and the text.
|
||||
if node.textX < width && posY < y+height {
|
||||
// Prefix.
|
||||
var prefixWidth int
|
||||
if len(t.prefixes) > 0 {
|
||||
_, prefixWidth = Print(screen, t.prefixes[(node.level-t.topLevel)%len(t.prefixes)], x+node.textX, posY, width-node.textX, AlignLeft, node.color)
|
||||
}
|
||||
|
||||
// Text.
|
||||
if node.textX+prefixWidth < width {
|
||||
style := tcell.StyleDefault.Foreground(node.color)
|
||||
if index == selectedIndex {
|
||||
style = tcell.StyleDefault.Background(node.color).Foreground(t.backgroundColor)
|
||||
}
|
||||
printWithStyle(screen, node.text, x+node.textX+prefixWidth, posY, width-node.textX-prefixWidth, AlignLeft, style)
|
||||
}
|
||||
}
|
||||
|
||||
// Advance.
|
||||
posY++
|
||||
}
|
||||
}
|
||||
|
||||
// InputHandler returns the handler for this primitive.
|
||||
func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
// Because the tree is flattened into a list only at drawing time, we also
|
||||
// postpone the (selection) movement to drawing time.
|
||||
switch key := event.Key(); key {
|
||||
case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight:
|
||||
t.movement = treeDown
|
||||
case tcell.KeyBacktab, tcell.KeyUp, tcell.KeyLeft:
|
||||
t.movement = treeUp
|
||||
case tcell.KeyHome:
|
||||
t.movement = treeHome
|
||||
case tcell.KeyEnd:
|
||||
t.movement = treeEnd
|
||||
case tcell.KeyPgDn, tcell.KeyCtrlF:
|
||||
t.movement = treePageDown
|
||||
case tcell.KeyPgUp, tcell.KeyCtrlB:
|
||||
t.movement = treePageUp
|
||||
case tcell.KeyRune:
|
||||
switch event.Rune() {
|
||||
case 'g':
|
||||
t.movement = treeHome
|
||||
case 'G':
|
||||
t.movement = treeEnd
|
||||
case 'j':
|
||||
t.movement = treeDown
|
||||
case 'k':
|
||||
t.movement = treeUp
|
||||
}
|
||||
case tcell.KeyEnter:
|
||||
if t.currentNode != nil {
|
||||
if t.selected != nil {
|
||||
t.selected(t.currentNode)
|
||||
}
|
||||
if t.currentNode.selected != nil {
|
||||
t.currentNode.selected()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
7
util.go
7
util.go
@ -120,13 +120,12 @@ func overlayStyle(background tcell.Color, defaultStyle tcell.Style, fgColor, bgC
|
||||
defFg, defBg, defAttr := defaultStyle.Decompose()
|
||||
style := defaultStyle.Background(background)
|
||||
|
||||
if fgColor == "-" {
|
||||
style = style.Foreground(defFg)
|
||||
} else if fgColor != "" {
|
||||
style = style.Foreground(defFg)
|
||||
if fgColor != "" {
|
||||
style = style.Foreground(tcell.GetColor(fgColor))
|
||||
}
|
||||
|
||||
if bgColor == "-" {
|
||||
if bgColor == "-" || bgColor == "" && defBg != tcell.ColorDefault {
|
||||
style = style.Background(defBg)
|
||||
} else if bgColor != "" {
|
||||
style = style.Background(tcell.GetColor(bgColor))
|
||||
|
Loading…
Reference in New Issue
Block a user