// package desktop -- консольный клиент для вар-танка package desktop import ( "fmt" "os" "time" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" ) // Desktop -- консольный клиент для вар-танка type Desktop struct { } var p *tea.Program func NewDesktop() *Desktop { sf := &Desktop{} p = tea.NewProgram(initialModel(), tea.WithAltScreen(), tea.WithANSICompressor()) if _, err := p.Run(); err != nil { fmt.Printf("Опа! здесь какая-то ошибочка: %v", err) os.Exit(1) } return sf } // AMode -- режим обработки клавиш type AMode int const ( ModeDefault AMode = iota ModeConfig ModeConfigServer ModeInputHostServer ) type model struct { choices []string // выбранный пункт в списке cursor int // позиция курсора в списке selected map[int]struct{} // что выбрано из списка mode AMode // Режим показа на экране textInput textinput.Model host string } func initialModel() model { ti := textinput.New() ti.Placeholder = "localhost" ti.Focus() ti.CharLimit = 156 ti.Width = 20 m := model{ // Список дел choices: []string{"Купить картошку", "Оплатить мобилку", "Заплатить за кредит"}, selected: make(map[int]struct{}), textInput: ti, } go m.tick() return m } func (m *model) tick() { for { time.Sleep(time.Second * 1) msg := tea.MouseEvent{} // m.Update(msg) p.Send(msg) } } // Run -- запускает в работу консольный клиент func (sf *Desktop) Run() error { return nil } func (m model) Init() tea.Cmd { // Просто вернуть `nil`, т.е. никаких команд return textinput.Blink } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch m.mode { case ModeDefault: switch msg := msg.(type) { // Может клавиша нажата case tea.KeyMsg: switch msg.String() { // Да, нажато, а что именно? case "ctrl+q": // Надо выйти из программы return m, tea.Quit case "up", "k": // Если "up" или "k" перемещение курсора вверх if m.cursor > 0 { m.cursor-- } case "down", "j": // Если "down" или "j" перемещение курсора вниз if m.cursor < len(m.choices)-1 { m.cursor++ } case "enter", " ": // Нажатие "enter" или "пробел" переключает выбранный элемент списка _, ok := m.selected[m.cursor] if ok { delete(m.selected, m.cursor) } else { m.selected[m.cursor] = struct{}{} } case "w": // Настройки графического клиента m.mode = ModeConfig } } case ModeConfig: switch msg := msg.(type) { // Может клавиша нажата case tea.KeyMsg: switch msg.String() { // Да, нажато, а что именно? case "ctrl+q": // Надо выйти из программы return m, tea.Quit case "q": // Вернуться на шаг назад m.mode = ModeDefault case "1": // Выбор один m.mode = ModeConfigServer } } case ModeConfigServer: switch msg := msg.(type) { // Может клавиша нажата case tea.KeyMsg: switch msg.String() { // Да, нажато, а что именно? case "ctrl+q": // Надо выйти из программы return m, tea.Quit case "q": // Вернуться на шаг назад m.mode = ModeConfig case "1": // Выбор один m.mode = ModeInputHostServer m.textInput.Reset() } } case ModeInputHostServer: switch msg := msg.(type) { // Может клавиша нажата case tea.KeyMsg: switch msg.String() { // Да, нажато, а что именно? case "ctrl+q": // Надо выйти из программы return m, tea.Quit case "q": // Вернуться на шаг назад m.mode = ModeConfigServer case "enter": // Нажатие "enter" или "пробел" переключает выбранный элемент списка m.host = m.textInput.Value() m.mode = ModeConfigServer default: m.textInput, cmd = m.textInput.Update(msg) } } } // Возвращает обновлённую модель дляe Bubble Tea runtime для обработки. //Здесь команду возвращать надо. return m, cmd } func (m model) View() string { s := time.Now().Local().Format("[ WarTank ] = 2006-01-02 15:04:05.000") s += fmt.Sprintf("\tХост=%q\n\n", m.host) switch m.mode { case ModeDefault: // Исходное содержимое s += m.win1() case ModeConfig: // Показать настройки клиента s += m.win2() case ModeConfigServer: // Конфигурация сервера s += m.win3() case ModeInputHostServer: // Ввод хоста для сервера s += m.win4() } return s } func (m model) win4() string { s := "=Ввод хоста для сервера вар-танк=\n\n" s += fmt.Sprintf(">%v\n", m.textInput.View()) s += "\n\n[ctrl+q]-выход [q]-назад\n" return s } func (m model) win3() string { s := "Настройки сервера вар-танк\n\n" s += "[1]-Настройки хоста сервера\n" s += "[2]-Логин для входа\n" s += "[3]-Пароль для входа\n" s += "\n\n[ctrl+q]-выход [q]-назад\n" return s } func (m model) win2() string { s := "Настройки клиента\n\n" s += "[1]-Настройки сервера\n" s += "\n\n[ctrl+q]-выход [q]-назад\n" return s } func (m model) win1() string { // Заголовок s := "Что нам нужно сделать?\n\n" // Перебор по списку выбора for i, choice := range m.choices { // Курсор указывает на этот выбор? cursor := " " // нет курсора if m.cursor == i { cursor = ">" // есть курсор } // Этот элемент выбран? checked := " " // не выбран if _, ok := m.selected[i]; ok { checked = "x" // выбран! } // отрендерить заново s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice) } // Подвал s += "\n\n[ctrl+q]-выход [w]-настройки\n" // Вернуть строку экрана для отрисовки return s }