menu.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package door
  2. import (
  3. "bytes"
  4. "log"
  5. "strings"
  6. "unicode"
  7. )
  8. /*
  9. type MenuOption struct {
  10. Ch rune
  11. Text string
  12. }
  13. */
  14. type Menu struct {
  15. Chosen int
  16. SelectedR ColorRender
  17. UnselectedR ColorRender
  18. Options []rune
  19. Activated func(int, *Door) // Which option is active
  20. // MenuOptions []MenuOption
  21. Panel
  22. }
  23. func MakeMenuRender(bracketColor, optionColor, upperColor, lowerColor []byte) ColorRender {
  24. f := func(output *bytes.Buffer, text []byte) {
  25. output.Reset()
  26. var lastColor *[]byte
  27. option := true
  28. for _, c := range text {
  29. if option {
  30. if c == '[' || c == ']' {
  31. if lastColor != &bracketColor {
  32. output.Write(bracketColor)
  33. lastColor = &bracketColor
  34. }
  35. output.WriteByte(c)
  36. option = (c == '[')
  37. } else {
  38. if lastColor != &optionColor {
  39. output.Write(optionColor)
  40. lastColor = &optionColor
  41. }
  42. output.WriteByte(c)
  43. }
  44. } else {
  45. if unicode.IsUpper(rune(c)) {
  46. if lastColor != &upperColor {
  47. output.Write(upperColor)
  48. lastColor = &upperColor
  49. }
  50. output.WriteByte(c)
  51. } else {
  52. if lastColor != &lowerColor {
  53. output.Write(lowerColor)
  54. lastColor = &lowerColor
  55. }
  56. output.WriteByte(c)
  57. }
  58. }
  59. }
  60. // return output.Bytes()
  61. }
  62. return f
  63. }
  64. func (m *Menu) AddSelection(key string, text string) {
  65. key = key[:1] // Make sure it is just 1 character
  66. m.Options = append(m.Options, rune(key[0]))
  67. // 4 = [ key ] + " "
  68. if len(text)+4 > m.Width {
  69. log.Panicf("Menu (not wide enough) Width %d : text size %d + 4 = %d\n", m.Width, len(text), len(text)+4)
  70. }
  71. var linetext *bytes.Buffer = &bytes.Buffer{}
  72. linetext.WriteString("[" + key + "] " + text + strings.Repeat(" ", m.Width-(4+len(text))))
  73. m.Lines = append(m.Lines, &Line{Text: linetext, RenderF: m.UnselectedR})
  74. }
  75. // Should I be using this, or write a function like the original --
  76. // m.AddSelection("K", "Text") to build? (And throw away the
  77. // redundant) MenuOptions/MenuOption parts? They do let me create
  78. // the menu in larger chunks -- but it is redundant.
  79. // Once the menu is built, I really could delete the MenuOptions.
  80. /*
  81. func (m *Menu) Build() {
  82. // Take MenuOptions and build the Menu
  83. // Reset
  84. m.Lines = make([]Line, 0)
  85. m.Options = make([]rune, 0)
  86. m.Chosen = 0
  87. for _, option := range m.MenuOptions {
  88. m.Options = append(m.Options, option.Ch)
  89. text := "[" + string(option.Ch) + "] " + option.Text + strings.Repeat(" ", m.Width-(4+len(option.Text)))
  90. m.Lines = append(m.Lines, Line{Text: text, RenderF: m.UnselectedR})
  91. }
  92. }
  93. */
  94. // Get the character that was pressed from the choice
  95. func (m *Menu) GetOption(choice int) rune {
  96. return m.Options[choice-1]
  97. }
  98. func (m *Menu) Choose(d *Door) int {
  99. var changed []int
  100. updated := true
  101. update_and_exit := false
  102. blank := ColorText("BLACK")
  103. for {
  104. if updated {
  105. for x := range m.Lines {
  106. if x == m.Chosen {
  107. m.Lines[x].RenderF = m.SelectedR
  108. } else {
  109. m.Lines[x].RenderF = m.UnselectedR
  110. }
  111. }
  112. if len(changed) == 0 {
  113. var output bytes.Buffer
  114. output.Write(m.Output())
  115. output.Write(blank)
  116. d.Write(output.Bytes()) //m.Output() + blank)
  117. } else {
  118. // Update just the lines that have changed
  119. var output bytes.Buffer
  120. for _, line := range changed {
  121. output.Write(m.UpdateLine(line))
  122. }
  123. output.Write(m.GotoEnd())
  124. output.Write(blank)
  125. d.Write(output.Bytes())
  126. }
  127. if m.Activated != nil {
  128. m.Activated(m.Chosen, d)
  129. // Reposition to the end of the menu
  130. var output bytes.Buffer
  131. output.Write(m.GotoEnd())
  132. output.Write(blank)
  133. d.Write(output.Bytes())
  134. }
  135. }
  136. if update_and_exit {
  137. return m.Chosen + 1
  138. }
  139. updated = false
  140. var r rune
  141. var ex Extended
  142. var err error
  143. r, ex, err = d.WaitKey(DefaultTimeout)
  144. if err != nil {
  145. return -1
  146. }
  147. previous_choice := m.Chosen
  148. changed = make([]int, 0)
  149. use_numberpad := true
  150. for _, option := range m.Options {
  151. if option == rune('8') || option == rune('2') {
  152. use_numberpad = false
  153. }
  154. }
  155. if ex == MOUSE {
  156. mouse, ok := d.GetMouse()
  157. if ok {
  158. if mouse.Button == 65 {
  159. // Translate Mouse Wheel Up
  160. ex = UP_ARROW
  161. }
  162. if mouse.Button == 66 {
  163. // Translate Mouse Wheel Down
  164. ex = DOWN_ARROW
  165. }
  166. // Look at Mouse X/Y to determine where they clicked
  167. if mouse.Button == 1 || mouse.Button == 2 {
  168. // TODO: log.Println("Mouse (", mouse.X, ",", mouse.Y, ")")
  169. clicked, cx, cy := m.Panel.Within(int(mouse.X), int(mouse.Y))
  170. log.Printf("%t : (%d, %d)\n", clicked, cx, cy)
  171. if clicked {
  172. if m.Panel.HasBorder() {
  173. // Did they click the border?
  174. if cy > 0 && cy <= m.Panel.Length() {
  175. updated = true
  176. m.Chosen = cy - 1
  177. update_and_exit = true
  178. }
  179. } else {
  180. // Borderless -- yes, they clicked something!
  181. updated = true
  182. m.Chosen = cy
  183. update_and_exit = true
  184. }
  185. }
  186. }
  187. }
  188. }
  189. switch ex {
  190. case UP_ARROW:
  191. if m.Chosen > 0 {
  192. m.Chosen--
  193. updated = true
  194. }
  195. case DOWN_ARROW:
  196. if m.Chosen < len(m.Lines)-1 {
  197. m.Chosen++
  198. updated = true
  199. }
  200. case HOME:
  201. if m.Chosen != 0 {
  202. m.Chosen = 0
  203. updated = true
  204. }
  205. case END:
  206. if m.Chosen != len(m.Lines)-1 {
  207. m.Chosen = len(m.Lines) - 1
  208. updated = true
  209. }
  210. }
  211. switch r {
  212. case '8':
  213. if use_numberpad {
  214. if m.Chosen > 0 {
  215. m.Chosen--
  216. updated = true
  217. }
  218. }
  219. case '2':
  220. if use_numberpad {
  221. if m.Chosen < len(m.Lines)-1 {
  222. m.Chosen++
  223. updated = true
  224. }
  225. }
  226. case 0x0d:
  227. // use current selection
  228. return m.Chosen + 1
  229. default:
  230. // Is the key in the list of options?
  231. // fmt.Printf("Event: %d\n", event)
  232. for x, option := range m.Options {
  233. // fmt.Printf("Checking %d, %d\n", x, option)
  234. if unicode.ToUpper(option) == unicode.ToUpper(r) {
  235. if m.Chosen == x {
  236. return x + 1
  237. }
  238. updated = true
  239. m.Chosen = x
  240. update_and_exit = true
  241. }
  242. }
  243. }
  244. if previous_choice != m.Chosen {
  245. changed = append(changed, previous_choice)
  246. changed = append(changed, m.Chosen)
  247. updated = true
  248. }
  249. }
  250. }