menu.go 5.6 KB

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