desktop.go 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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. TeaModel
  13. p *tea.Program // Объект приложения
  14. }
  15. func NewDesktop() *Desktop {
  16. sf := &Desktop{}
  17. sf.TeaModel = NewModel()
  18. go sf.tick()
  19. sf.p = tea.NewProgram(sf, tea.WithAltScreen(), tea.WithANSICompressor())
  20. if _, err := sf.p.Run(); err != nil {
  21. fmt.Printf("Опа! здесь какая-то ошибочка: %v", err)
  22. os.Exit(1)
  23. }
  24. return sf
  25. }
  26. // AMode -- режим обработки клавиш
  27. type AMode int
  28. const (
  29. ModeDefault AMode = iota
  30. ModeConfig // Режим конфигурирования
  31. ModeConfigServer // Режим конфигуррования сервера
  32. ModeInputHostServer // Режим ввода хоста сервера
  33. ModeInputLoginServer // Режим ввода логина на сервер
  34. ModeInputPassServer // Режим ввода пароля входа на сервер
  35. ModeConnectServer // Режим подключения к серверу
  36. )
  37. type TeaModel struct {
  38. choices []string // выбранный пункт в списке
  39. cursor int // позиция курсора в списке
  40. selected map[int]struct{} // что выбрано из списка
  41. mode AMode // Режим показа на экране
  42. textInput textinput.Model
  43. host string // Имя хоста сервера (с портом)
  44. login string // Логин для сервера
  45. pass string // Пароль для сервера
  46. }
  47. func NewModel() TeaModel {
  48. ti := textinput.New()
  49. ti.Placeholder = "localhost"
  50. ti.Focus()
  51. ti.CharLimit = 156
  52. ti.Width = 20
  53. m := TeaModel{
  54. // Список дел
  55. choices: []string{"Купить картошку", "Оплатить мобилку", "Заплатить за кредит"},
  56. selected: make(map[int]struct{}),
  57. textInput: ti,
  58. }
  59. return m
  60. }
  61. func (sf *Desktop) tick() {
  62. for {
  63. time.Sleep(time.Second * 1)
  64. msg := tea.MouseEvent{}
  65. // m.Update(msg)
  66. sf.p.Send(msg)
  67. }
  68. }
  69. // Run -- запускает в работу консольный клиент
  70. func (sf *Desktop) Run() error {
  71. return nil
  72. }
  73. func (sf Desktop) Init() tea.Cmd {
  74. // Просто вернуть `nil`, т.е. никаких команд
  75. return textinput.Blink
  76. }
  77. func (sf Desktop) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  78. var cmd tea.Cmd
  79. switch msg := msg.(type) {
  80. // Может клавиша нажата
  81. case tea.KeyMsg:
  82. switch msg.String() { // Да, нажато, а что именно?
  83. case "ctrl+q": // Надо выйти из программы
  84. return sf, tea.Quit
  85. }
  86. }
  87. switch sf.mode {
  88. case ModeDefault:
  89. switch msg := msg.(type) {
  90. // Может клавиша нажата
  91. case tea.KeyMsg:
  92. switch msg.String() { // Да, нажато, а что именно?
  93. case "up", "k": // Если "up" или "k" перемещение курсора вверх
  94. if sf.cursor > 0 {
  95. sf.cursor--
  96. }
  97. case "down", "j": // Если "down" или "j" перемещение курсора вниз
  98. if sf.cursor < len(sf.choices)-1 {
  99. sf.cursor++
  100. }
  101. case "enter", " ": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  102. _, ok := sf.selected[sf.cursor]
  103. if ok {
  104. delete(sf.selected, sf.cursor)
  105. } else {
  106. sf.selected[sf.cursor] = struct{}{}
  107. }
  108. case "w": // Настройки графического клиента
  109. sf.mode = ModeConfig
  110. }
  111. }
  112. case ModeConfig:
  113. switch msg := msg.(type) {
  114. // Может клавиша нажата
  115. case tea.KeyMsg:
  116. switch msg.String() { // Да, нажато, а что именно?
  117. case "q": // Вернуться на шаг назад
  118. sf.mode = ModeDefault
  119. case "1": // Выбор один
  120. sf.mode = ModeConfigServer
  121. case "2": // Вход на сервер
  122. sf.mode = ModeConnectServer
  123. }
  124. }
  125. case ModeConfigServer:
  126. switch msg := msg.(type) {
  127. // Может клавиша нажата
  128. case tea.KeyMsg:
  129. switch msg.String() { // Да, нажато, а что именно?
  130. case "q": // Вернуться на шаг назад
  131. sf.mode = ModeConfig
  132. case "1": // Выбор хоста сервера
  133. sf.mode = ModeInputHostServer
  134. sf.textInput.Reset()
  135. sf.textInput.Placeholder = "localhost:18050"
  136. case "2": // Выбор логина для входа
  137. sf.mode = ModeInputLoginServer
  138. sf.textInput.Reset()
  139. sf.textInput.Placeholder = "login"
  140. case "3": // Выбор пароля для входа
  141. sf.mode = ModeInputPassServer
  142. sf.textInput.Reset()
  143. sf.textInput.Placeholder = "pass"
  144. }
  145. }
  146. case ModeInputHostServer:
  147. switch msg := msg.(type) {
  148. // Может клавиша нажата
  149. case tea.KeyMsg:
  150. switch msg.String() { // Да, нажато, а что именно?
  151. case "q": // Вернуться на шаг назад
  152. sf.mode = ModeConfigServer
  153. case "enter": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  154. sf.host = sf.textInput.Value()
  155. sf.mode = ModeConfigServer
  156. default:
  157. sf.textInput, cmd = sf.textInput.Update(msg)
  158. }
  159. }
  160. case ModeInputLoginServer:
  161. switch msg := msg.(type) {
  162. // Может клавиша нажата
  163. case tea.KeyMsg:
  164. switch msg.String() { // Да, нажато, а что именно?
  165. case "q": // Вернуться на шаг назад
  166. sf.mode = ModeConfigServer
  167. case "enter": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  168. sf.login = sf.textInput.Value()
  169. sf.mode = ModeConfigServer
  170. default:
  171. sf.textInput, cmd = sf.textInput.Update(msg)
  172. }
  173. }
  174. case ModeInputPassServer:
  175. switch msg := msg.(type) {
  176. // Может клавиша нажата
  177. case tea.KeyMsg:
  178. switch msg.String() { // Да, нажато, а что именно?
  179. case "q": // Вернуться на шаг назад
  180. sf.mode = ModeConfigServer
  181. case "enter": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  182. sf.pass = sf.textInput.Value()
  183. sf.mode = ModeConfigServer
  184. default:
  185. sf.textInput, cmd = sf.textInput.Update(msg)
  186. }
  187. }
  188. case ModeConnectServer:
  189. switch msg := msg.(type) {
  190. // Может клавиша нажата
  191. case tea.KeyMsg:
  192. switch msg.String() { // Да, нажато, а что именно?
  193. case "q": // Вернуться на шаг назад
  194. sf.mode = ModeConfigServer
  195. case "esc": // Нажатие "esc" или "пробел" прекращает подключение к серверу
  196. sf.pass = sf.textInput.Value()
  197. sf.mode = ModeConfigServer
  198. default:
  199. sf.textInput, cmd = sf.textInput.Update(msg)
  200. }
  201. }
  202. }
  203. // Возвращает обновлённую модель дляe Bubble Tea runtime для обработки.
  204. //Здесь команду возвращать надо.
  205. return sf, cmd
  206. }
  207. func (sf Desktop) View() string {
  208. s := time.Now().Local().Format("[ WarTank ] = 2006-01-02 15:04:05.000")
  209. s += fmt.Sprintf("\tХост=%q\tЛогин=%q\tПароль=%q\n\n", sf.host, sf.login, sf.pass)
  210. switch sf.mode {
  211. case ModeDefault: // Исходное содержимое
  212. s += sf.winDefault()
  213. case ModeConfig: // Показать настройки клиента
  214. s += sf.winConfig()
  215. case ModeConfigServer: // Конфигурация сервера
  216. s += sf.winConfigServer()
  217. case ModeInputHostServer: // Ввод хоста для сервера
  218. s += sf.winHostServer()
  219. case ModeInputLoginServer: // Ввод логина для входа на сервер
  220. s += sf.winLoginServer()
  221. case ModeInputPassServer: // ввод пароля
  222. s += sf.winPassServer()
  223. }
  224. return s
  225. }
  226. func (sf Desktop) winPassServer() string {
  227. s := "=Ввод пароля для сервера вар-танк=\n\n"
  228. s += fmt.Sprintf(">%v\n", sf.textInput.View())
  229. s += "\n\n[ctrl+q]-выход [q]-назад\n"
  230. return s
  231. }
  232. func (sf Desktop) winLoginServer() string {
  233. s := "=Ввод логина для сервера вар-танк=\n\n"
  234. s += fmt.Sprintf(">%v\n", sf.textInput.View())
  235. s += "\n\n[ctrl+q]-выход [q]-назад\n"
  236. return s
  237. }
  238. func (sf Desktop) winHostServer() string {
  239. s := "=Ввод хоста для сервера вар-танк=\n\n"
  240. s += fmt.Sprintf(">%v\n", sf.textInput.View())
  241. s += "\n\n[ctrl+q]-выход [q]-назад\n"
  242. return s
  243. }
  244. func (sf Desktop) winConfigServer() string {
  245. s := "Настройки сервера вар-танк\n\n"
  246. s += "[1]-Настройки хоста сервера\n"
  247. s += "[2]-Логин для входа\n"
  248. s += "[3]-Пароль для входа\n"
  249. s += "\n\n[ctrl+q]-выход [q]-назад\n"
  250. return s
  251. }
  252. func (m TeaModel) winConfig() string {
  253. s := "Настройки клиента\n\n"
  254. s += "[1]-Настройки сервера\n"
  255. s += "[2]-Вход на сервер\n"
  256. s += "\n\n[ctrl+q]-выход [q]-назад\n"
  257. return s
  258. }
  259. func (m TeaModel) winDefault() string {
  260. // Заголовок
  261. s := "Что нам нужно сделать?\n\n"
  262. // Перебор по списку выбора
  263. for i, choice := range m.choices {
  264. // Курсор указывает на этот выбор?
  265. cursor := " " // нет курсора
  266. if m.cursor == i {
  267. cursor = ">" // есть курсор
  268. }
  269. // Этот элемент выбран?
  270. checked := " " // не выбран
  271. if _, ok := m.selected[i]; ok {
  272. checked = "x" // выбран!
  273. }
  274. // отрендерить заново
  275. s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
  276. }
  277. // Подвал
  278. s += "\n\n[ctrl+q]-выход [w]-настройки\n"
  279. // Вернуть строку экрана для отрисовки
  280. return s
  281. }