Golang中的TUI网格/矩阵控件实现
Golang中的TUI网格/矩阵控件实现 我想构建一个带有用户界面的终端/控制台应用程序,并大量使用行和列组成的网格中的多个可选选项。类似矩阵的形状:
±-----------------------------------------+
| [选项11] [选项12] [选项13] |
| [选项21] [选项22] [选项23] |
| [选项31] [选项32] [选项33] |
±-----------------------------------------+
我已经开始尝试使用广泛使用的 Bubbletea 和 LipGloss 包,但它们的表格组件逻辑只能处理光标的行位置,不能处理列位置。
我想问,我是必须自己实现这样一个组件(比如一组多个列表组件),还是已经有现成的解决方案可以使用?
更多关于Golang中的TUI网格/矩阵控件实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html
2 回复
嗯,现在我明白了,lipgloss/table 确实为行和列的参数提供了 StyleFunc 方法。所以之前折腾 bubbles/table 包完全是走错了路……
更多关于Golang中的TUI网格/矩阵控件实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Golang中实现TUI网格/矩阵控件,确实需要自己构建或寻找合适的库。Bubbletea本身不提供直接的网格控件,但可以通过组合现有组件或自定义实现。以下是两种实现方案:
方案一:基于Bubbletea的自定义网格实现
package main
import (
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// 网格项结构
type GridItem struct {
Text string
Selected bool
Row int
Col int
}
// 网格模型
type GridModel struct {
items [][]GridItem
rows int
cols int
cursorRow int
cursorCol int
width int
height int
}
// 初始化网格
func NewGridModel(rows, cols int) GridModel {
items := make([][]GridItem, rows)
for r := 0; r < rows; r++ {
items[r] = make([]GridItem, cols)
for c := 0; c < cols; c++ {
items[r][c] = GridItem{
Text: fmt.Sprintf("选项%d%d", r+1, c+1),
Selected: false,
Row: r,
Col: c,
}
}
}
return GridModel{
items: items,
rows: rows,
cols: cols,
cursorRow: 0,
cursorCol: 0,
}
}
// 视图渲染
func (m GridModel) View() string {
var builder strings.Builder
// 定义样式
normalStyle := lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
Padding(0, 1).
Margin(0, 1)
selectedStyle := normalStyle.Copy().
Foreground(lipgloss.Color("#FFFFFF")).
Background(lipgloss.Color("#FF6B6B")).
BorderForeground(lipgloss.Color("#FF6B6B"))
cursorStyle := normalStyle.Copy().
BorderForeground(lipgloss.Color("#00FF00")).
Bold(true)
// 渲染网格
for r := 0; r < m.rows; r++ {
for c := 0; c < m.cols; c++ {
item := m.items[r][c]
var style lipgloss.Style
switch {
case r == m.cursorRow && c == m.cursorCol:
style = cursorStyle
case item.Selected:
style = selectedStyle
default:
style = normalStyle
}
builder.WriteString(style.Render(item.Text))
}
builder.WriteString("\n")
}
return builder.String()
}
// 更新逻辑
func (m GridModel) Update(msg tea.Msg) (GridModel, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "up":
if m.cursorRow > 0 {
m.cursorRow--
}
case "down":
if m.cursorRow < m.rows-1 {
m.cursorRow++
}
case "left":
if m.cursorCol > 0 {
m.cursorCol--
}
case "right":
if m.cursorCol < m.cols-1 {
m.cursorCol++
}
case "enter", " ":
// 切换选择状态
m.items[m.cursorRow][m.cursorCol].Selected =
!m.items[m.cursorRow][m.cursorCol].Selected
}
}
return m, nil
}
// 主程序
func main() {
model := NewGridModel(3, 3)
p := tea.NewProgram(model)
if _, err := p.Run(); err != nil {
panic(err)
}
}
方案二:使用tview库的网格实现
package main
import (
"github.com/rivo/tview"
)
func main() {
app := tview.NewApplication()
// 创建网格
grid := tview.NewGrid().
SetRows(3, 3, 3).
SetColumns(10, 10, 10).
SetBorders(true)
// 创建可选择的按钮
buttons := make([][]*tview.Button, 3)
for row := 0; row < 3; row++ {
buttons[row] = make([]*tview.Button, 3)
for col := 0; col < 3; col++ {
text := fmt.Sprintf("选项%d%d", row+1, col+1)
button := tview.NewButton(text).
SetSelectedFunc(func() {
// 处理选择逻辑
app.Stop()
})
buttons[row][col] = button
grid.AddItem(button, row, col, 1, 1, 0, 0, true)
}
}
// 设置网格导航
for row := 0; row < 3; row++ {
for col := 0; col < 3; col++ {
// 设置导航关系
if col > 0 {
buttons[row][col].SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyLeft {
app.SetFocus(buttons[row][col-1])
return nil
}
return event
})
}
if col < 2 {
buttons[row][col].SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyRight {
app.SetFocus(buttons[row][col+1])
return nil
}
return event
})
}
}
}
if err := app.SetRoot(grid, true).Run(); err != nil {
panic(err)
}
}
方案三:增强型网格组件(支持多选)
type EnhancedGrid struct {
cells [][]*Cell
rows, cols int
focusRow int
focusCol int
selected map[[2]int]bool
}
type Cell struct {
content string
focused bool
selected bool
}
func NewEnhancedGrid(rows, cols int) *EnhancedGrid {
cells := make([][]*Cell, rows)
for i := range cells {
cells[i] = make([]*Cell, cols)
for j := range cells[i] {
cells[i][j] = &Cell{
content: fmt.Sprintf("选项%d%d", i+1, j+1),
}
}
}
return &EnhancedGrid{
cells: cells,
rows: rows,
cols: cols,
selected: make(map[[2]int]bool),
}
}
func (g *EnhancedGrid) MoveFocus(direction string) {
switch direction {
case "up":
if g.focusRow > 0 {
g.cells[g.focusRow][g.focusCol].focused = false
g.focusRow--
g.cells[g.focusRow][g.focusCol].focused = true
}
case "down":
if g.focusRow < g.rows-1 {
g.cells[g.focusRow][g.focusCol].focused = false
g.focusRow++
g.cells[g.focusRow][g.focusCol].focused = true
}
case "left":
if g.focusCol > 0 {
g.cells[g.focusRow][g.focusCol].focused = false
g.focusCol--
g.cells[g.focusRow][g.focusCol].focused = true
}
case "right":
if g.focusCol < g.cols-1 {
g.cells[g.focusRow][g.focusCol].focused = false
g.focusCol++
g.cells[g.focusRow][g.focusCol].focused = true
}
}
}
func (g *EnhancedGrid) ToggleSelection() {
key := [2]int{g.focusRow, g.focusCol}
if g.selected[key] {
delete(g.selected, key)
g.cells[g.focusRow][g.focusCol].selected = false
} else {
g.selected[key] = true
g.cells[g.focusRow][g.focusCol].selected = true
}
}
func (g *EnhancedGrid) GetSelected() []string {
var selected []string
for key := range g.selected {
selected = append(selected, g.cells[key[0]][key[1]].content)
}
return selected
}
这些实现提供了完整的行列导航、选择状态管理和视觉反馈。第一种方案使用纯Bubbletea,第二种方案使用tview库,第三种方案展示了更复杂的网格逻辑。根据具体需求选择合适的实现方式。

