Tui 重构代码逻辑
This commit is contained in:
321
pkg/wizard/focused.go
Normal file
321
pkg/wizard/focused.go
Normal file
@@ -0,0 +1,321 @@
|
||||
package wizard
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// Focusable 定义可聚焦组件的通用接口
|
||||
type Focusable interface {
|
||||
// Focus 激活焦点(比如输入框闪烁光标、按钮高亮)
|
||||
Focus() tea.Cmd
|
||||
// Blur 失活焦点(取消高亮/闪烁)
|
||||
Blur()
|
||||
// IsFocused 判断是否处于焦点状态
|
||||
IsFocused() bool
|
||||
// View 渲染组件(和 bubbletea 统一)
|
||||
View() string
|
||||
}
|
||||
|
||||
// --------------- 为常用组件实现 Focusable 接口 ---------------
|
||||
|
||||
// TextInput 适配 bubbles/textinput
|
||||
type TextInput struct {
|
||||
textinput.Model
|
||||
focused bool
|
||||
}
|
||||
|
||||
func NewTextInput(placeholder string, defaultValue string) *TextInput {
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = placeholder
|
||||
ti.SetValue(defaultValue)
|
||||
ti.Focus()
|
||||
return &TextInput{Model: ti, focused: true}
|
||||
}
|
||||
|
||||
func (t *TextInput) Focus() tea.Cmd {
|
||||
t.focused = true
|
||||
return t.Model.Focus()
|
||||
}
|
||||
|
||||
func (t *TextInput) Blur() {
|
||||
t.focused = false
|
||||
t.Model.Blur()
|
||||
}
|
||||
|
||||
func (t *TextInput) IsFocused() bool {
|
||||
return t.focused
|
||||
}
|
||||
|
||||
// Button 适配 bubbles/button
|
||||
type Button struct {
|
||||
label string
|
||||
focused bool
|
||||
buttonBlur lipgloss.Style
|
||||
buttonFocus lipgloss.Style
|
||||
}
|
||||
|
||||
func NewButton(label string) *Button {
|
||||
return &Button{
|
||||
label: label,
|
||||
focused: false,
|
||||
buttonBlur: btnBaseStyle,
|
||||
buttonFocus: btnSelectedStyle,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Button) Focus() tea.Cmd {
|
||||
b.focused = true
|
||||
b.buttonBlur = b.buttonFocus
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Button) Blur() {
|
||||
b.focused = false
|
||||
b.buttonBlur = btnBaseStyle
|
||||
}
|
||||
|
||||
func (b *Button) IsFocused() bool {
|
||||
return b.focused
|
||||
}
|
||||
|
||||
func (b *Button) View() string {
|
||||
if b.focused {
|
||||
return b.buttonFocus.Render(b.label)
|
||||
}
|
||||
return b.buttonBlur.Render(b.label)
|
||||
}
|
||||
|
||||
// List 适配 bubbles/list
|
||||
type List struct {
|
||||
list.Model
|
||||
focused bool
|
||||
}
|
||||
|
||||
func NewList(items []list.Item) List {
|
||||
l := list.New(items, list.NewDefaultDelegate(), 0, 0)
|
||||
l.SetShowHelp(false)
|
||||
return List{Model: l, focused: false}
|
||||
}
|
||||
|
||||
func (l *List) Focus() tea.Cmd {
|
||||
l.focused = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *List) Blur() {
|
||||
l.focused = false
|
||||
}
|
||||
|
||||
func (l *List) IsFocused() bool {
|
||||
return l.focused
|
||||
}
|
||||
|
||||
// FocusManager 焦点管理器
|
||||
type FocusManager struct {
|
||||
// 所有可聚焦组件(key=唯一标识,比如 "form1.ip_input"、"form1.next_btn")
|
||||
components map[string]Focusable
|
||||
// 组件切换顺序(按这个顺序切换焦点)
|
||||
order []string
|
||||
// 当前焦点组件的标识
|
||||
currentFocusID string
|
||||
// 是否循环切换(到最后一个后回到第一个)
|
||||
loop bool
|
||||
}
|
||||
|
||||
// NewFocusManager 创建焦点管理器
|
||||
func NewFocusManager(loop bool) *FocusManager {
|
||||
return &FocusManager{
|
||||
components: make(map[string]Focusable),
|
||||
order: make([]string, 0),
|
||||
currentFocusID: "",
|
||||
loop: loop,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册可聚焦组件(指定标识和切换顺序)
|
||||
func (fm *FocusManager) Register(id string, comp Focusable) {
|
||||
// 防御性检查:避免 components 为空导致 panic
|
||||
if fm.components == nil {
|
||||
fm.components = make(map[string]Focusable)
|
||||
}
|
||||
|
||||
// 避免重复注册
|
||||
if _, exists := fm.components[id]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
fm.components[id] = comp
|
||||
fm.order = append(fm.order, id)
|
||||
|
||||
// 如果是第一个注册的组件,默认聚焦
|
||||
if fm.currentFocusID == "" {
|
||||
fm.currentFocusID = id
|
||||
comp.Focus()
|
||||
}
|
||||
}
|
||||
|
||||
// Next 切换到下一个组件
|
||||
func (fm *FocusManager) Next() tea.Cmd {
|
||||
|
||||
if len(fm.order) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. 找到当前组件的索引
|
||||
currentIdx := -1
|
||||
for i, id := range fm.order {
|
||||
if id == fm.currentFocusID {
|
||||
currentIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 计算下一个索引
|
||||
nextIdx := currentIdx + 1
|
||||
if fm.loop && nextIdx >= len(fm.order) {
|
||||
nextIdx = 0
|
||||
}
|
||||
if nextIdx >= len(fm.order) {
|
||||
return nil // 不循环则到最后一个停止
|
||||
}
|
||||
|
||||
// 3. 切换焦点(当前组件失活,下一个激活)
|
||||
fm.components[fm.currentFocusID].Blur()
|
||||
nextID := fm.order[nextIdx]
|
||||
fm.currentFocusID = nextID
|
||||
return fm.components[nextID].Focus()
|
||||
}
|
||||
|
||||
// Prev 切换到上一个组件
|
||||
func (fm *FocusManager) Prev() tea.Cmd {
|
||||
if len(fm.order) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
currentIdx := -1
|
||||
for i, id := range fm.order {
|
||||
if id == fm.currentFocusID {
|
||||
currentIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
prevIdx := currentIdx - 1
|
||||
if fm.loop && prevIdx < 0 {
|
||||
prevIdx = len(fm.order) - 1
|
||||
}
|
||||
if prevIdx < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
fm.components[fm.currentFocusID].Blur()
|
||||
prevID := fm.order[prevIdx]
|
||||
fm.currentFocusID = prevID
|
||||
return fm.components[prevID].Focus()
|
||||
}
|
||||
|
||||
// GetCurrent 获取当前焦点组件
|
||||
func (fm *FocusManager) GetCurrent() (Focusable, bool) {
|
||||
comp, exists := fm.components[fm.currentFocusID]
|
||||
return comp, exists
|
||||
}
|
||||
|
||||
// HandleInput 统一处理焦点切换输入(比如 Tab/Shift+Tab)
|
||||
func (fm *FocusManager) HandleInput(msg tea.KeyMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "tab": // Tab 下一个
|
||||
return fm.Next()
|
||||
case "shift+tab": // Shift+Tab 上一个
|
||||
return fm.Prev()
|
||||
case "left": // Left 上一个
|
||||
return fm.Prev()
|
||||
case "right": // Right 下一个
|
||||
return fm.Next()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) switchPage(targetPage PageType) tea.Cmd {
|
||||
// 边界检查(不能超出 1-6 页面)
|
||||
if targetPage < PageAgreement || targetPage > PageSummary {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 更新当前页面
|
||||
m.currentPage = targetPage
|
||||
|
||||
// 初始化新页面的焦点
|
||||
m.initPageFocus(targetPage)
|
||||
|
||||
// 返回空指令(或返回第一个组件的Focus命令)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *model) initPageFocus(page PageType) {
|
||||
|
||||
m.focusManager = NewFocusManager(true)
|
||||
|
||||
// 获取当前页面的组件
|
||||
pageComps, exists := m.pageComponents[page]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
// 按 [业务逻辑顺序] 注册组件 (决定Tab切换的顺序)
|
||||
var componentOrder []string
|
||||
|
||||
// 按页面类型定义不同的注册顺序
|
||||
switch page {
|
||||
case PageAgreement:
|
||||
componentOrder = []string{"accept_btn", "reject_btn"}
|
||||
case PageData:
|
||||
componentOrder = []string{
|
||||
"Homepage_input",
|
||||
"Hostname_input",
|
||||
"Country_input",
|
||||
"Region_input",
|
||||
"Timezone_input",
|
||||
"DBPath_input",
|
||||
"Software_input",
|
||||
"next_btn",
|
||||
"prev_btn",
|
||||
}
|
||||
case PagePublicNetwork:
|
||||
componentOrder = []string{
|
||||
"PublicInterface_input",
|
||||
"PublicIPAddress_input",
|
||||
"PublicNetmask_input",
|
||||
"PublicGateway_input",
|
||||
"next_btn",
|
||||
"prev_btn",
|
||||
}
|
||||
case PageInternalNetwork:
|
||||
componentOrder = []string{
|
||||
"InternalInterface_input",
|
||||
"InternalIPAddress_input",
|
||||
"InternalNetmask_input",
|
||||
"next_btn",
|
||||
"prev_btn",
|
||||
}
|
||||
case PageDNS:
|
||||
componentOrder = []string{
|
||||
"Pri_DNS_input",
|
||||
"Sec_DNS_input",
|
||||
"next_btn",
|
||||
"prev_btn",
|
||||
}
|
||||
case PageSummary:
|
||||
componentOrder = []string{"confirm_btn", "cancel_btn"}
|
||||
}
|
||||
|
||||
// 注册组件到焦点管理器(按顺序)
|
||||
for _, compID := range componentOrder {
|
||||
if comp, exists := pageComps[compID]; exists {
|
||||
m.focusManager.Register(compID, comp)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user