desktop.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. // package desktop -- консольный клиент для вар-танка
  2. package desktop
  3. import (
  4. "fmt"
  5. "os"
  6. "time"
  7. "github.com/charmbracelet/bubbles/textinput"
  8. tea "github.com/charmbracelet/bubbletea"
  9. )
  10. // Desktop -- консольный клиент для вар-танка
  11. type Desktop struct {
  12. }
  13. var p *tea.Program
  14. func NewDesktop() *Desktop {
  15. sf := &Desktop{}
  16. p = tea.NewProgram(initialModel(), tea.WithAltScreen(), tea.WithANSICompressor())
  17. if _, err := p.Run(); err != nil {
  18. fmt.Printf("Опа! здесь какая-то ошибочка: %v", err)
  19. os.Exit(1)
  20. }
  21. return sf
  22. }
  23. // AMode -- режим обработки клавиш
  24. type AMode int
  25. const (
  26. ModeDefault AMode = iota
  27. ModeConfig
  28. ModeConfigServer
  29. ModeInputHostServer
  30. )
  31. type model struct {
  32. choices []string // выбранный пункт в списке
  33. cursor int // позиция курсора в списке
  34. selected map[int]struct{} // что выбрано из списка
  35. mode AMode // Режим показа на экране
  36. textInput textinput.Model
  37. host string
  38. }
  39. func initialModel() model {
  40. ti := textinput.New()
  41. ti.Placeholder = "localhost"
  42. ti.Focus()
  43. ti.CharLimit = 156
  44. ti.Width = 20
  45. m := model{
  46. // Список дел
  47. choices: []string{"Купить картошку", "Оплатить мобилку", "Заплатить за кредит"},
  48. selected: make(map[int]struct{}),
  49. textInput: ti,
  50. }
  51. go m.tick()
  52. return m
  53. }
  54. func (m *model) tick() {
  55. for {
  56. time.Sleep(time.Second * 1)
  57. msg := tea.MouseEvent{}
  58. // m.Update(msg)
  59. p.Send(msg)
  60. }
  61. }
  62. // Run -- запускает в работу консольный клиент
  63. func (sf *Desktop) Run() error {
  64. return nil
  65. }
  66. func (m model) Init() tea.Cmd {
  67. // Просто вернуть `nil`, т.е. никаких команд
  68. return textinput.Blink
  69. }
  70. func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  71. var cmd tea.Cmd
  72. switch m.mode {
  73. case ModeDefault:
  74. switch msg := msg.(type) {
  75. // Может клавиша нажата
  76. case tea.KeyMsg:
  77. switch msg.String() { // Да, нажато, а что именно?
  78. case "ctrl+q": // Надо выйти из программы
  79. return m, tea.Quit
  80. case "up", "k": // Если "up" или "k" перемещение курсора вверх
  81. if m.cursor > 0 {
  82. m.cursor--
  83. }
  84. case "down", "j": // Если "down" или "j" перемещение курсора вниз
  85. if m.cursor < len(m.choices)-1 {
  86. m.cursor++
  87. }
  88. case "enter", " ": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  89. _, ok := m.selected[m.cursor]
  90. if ok {
  91. delete(m.selected, m.cursor)
  92. } else {
  93. m.selected[m.cursor] = struct{}{}
  94. }
  95. case "w": // Настройки графического клиента
  96. m.mode = ModeConfig
  97. }
  98. }
  99. case ModeConfig:
  100. switch msg := msg.(type) {
  101. // Может клавиша нажата
  102. case tea.KeyMsg:
  103. switch msg.String() { // Да, нажато, а что именно?
  104. case "ctrl+q": // Надо выйти из программы
  105. return m, tea.Quit
  106. case "q": // Вернуться на шаг назад
  107. m.mode = ModeDefault
  108. case "1": // Выбор один
  109. m.mode = ModeConfigServer
  110. }
  111. }
  112. case ModeConfigServer:
  113. switch msg := msg.(type) {
  114. // Может клавиша нажата
  115. case tea.KeyMsg:
  116. switch msg.String() { // Да, нажато, а что именно?
  117. case "ctrl+q": // Надо выйти из программы
  118. return m, tea.Quit
  119. case "q": // Вернуться на шаг назад
  120. m.mode = ModeConfig
  121. case "1": // Выбор один
  122. m.mode = ModeInputHostServer
  123. m.textInput.Reset()
  124. }
  125. }
  126. case ModeInputHostServer:
  127. switch msg := msg.(type) {
  128. // Может клавиша нажата
  129. case tea.KeyMsg:
  130. switch msg.String() { // Да, нажато, а что именно?
  131. case "ctrl+q": // Надо выйти из программы
  132. return m, tea.Quit
  133. case "q": // Вернуться на шаг назад
  134. m.mode = ModeConfigServer
  135. case "enter": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  136. m.host = m.textInput.Value()
  137. m.mode = ModeConfigServer
  138. default:
  139. m.textInput, cmd = m.textInput.Update(msg)
  140. }
  141. }
  142. }
  143. // Возвращает обновлённую модель дляe Bubble Tea runtime для обработки.
  144. //Здесь команду возвращать надо.
  145. return m, cmd
  146. }
  147. func (m model) View() string {
  148. s := time.Now().Local().Format("[ WarTank ] = 2006-01-02 15:04:05.000")
  149. s += fmt.Sprintf("\tХост=%q\n\n", m.host)
  150. switch m.mode {
  151. case ModeDefault: // Исходное содержимое
  152. s += m.win1()
  153. case ModeConfig: // Показать настройки клиента
  154. s += m.win2()
  155. case ModeConfigServer: // Конфигурация сервера
  156. s += m.win3()
  157. case ModeInputHostServer: // Ввод хоста для сервера
  158. s += m.win4()
  159. }
  160. return s
  161. }
  162. func (m model) win4() string {
  163. s := "=Ввод хоста для сервера вар-танк=\n\n"
  164. s += fmt.Sprintf(">%v\n", m.textInput.View())
  165. s += "\n\n[ctrl+q]-выход [q]-назад\n"
  166. return s
  167. }
  168. func (m model) win3() string {
  169. s := "Настройки сервера вар-танк\n\n"
  170. s += "[1]-Настройки хоста сервера\n"
  171. s += "[2]-Логин для входа\n"
  172. s += "[3]-Пароль для входа\n"
  173. s += "\n\n[ctrl+q]-выход [q]-назад\n"
  174. return s
  175. }
  176. func (m model) win2() string {
  177. s := "Настройки клиента\n\n"
  178. s += "[1]-Настройки сервера\n"
  179. s += "\n\n[ctrl+q]-выход [q]-назад\n"
  180. return s
  181. }
  182. func (m model) win1() string {
  183. // Заголовок
  184. s := "Что нам нужно сделать?\n\n"
  185. // Перебор по списку выбора
  186. for i, choice := range m.choices {
  187. // Курсор указывает на этот выбор?
  188. cursor := " " // нет курсора
  189. if m.cursor == i {
  190. cursor = ">" // есть курсор
  191. }
  192. // Этот элемент выбран?
  193. checked := " " // не выбран
  194. if _, ok := m.selected[i]; ok {
  195. checked = "x" // выбран!
  196. }
  197. // отрендерить заново
  198. s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
  199. }
  200. // Подвал
  201. s += "\n\n[ctrl+q]-выход [w]-настройки\n"
  202. // Вернуть строку экрана для отрисовки
  203. return s
  204. }