menu.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package door
  2. import (
  3. "strings"
  4. "unicode"
  5. )
  6. type MenuOption struct {
  7. Ch rune
  8. Text string
  9. }
  10. type Menu struct {
  11. Chosen int
  12. SelectedR func(string) string
  13. UnselectedR func(string) string
  14. Options []rune
  15. MenuOptions []MenuOption
  16. Panel
  17. }
  18. func MakeMenuRender(bracketColor, optionColor, upperColor, lowerColor string) func(string) string {
  19. f := func(text string) string {
  20. var output string
  21. var lastColor string
  22. option := true
  23. for _, c := range text {
  24. if option {
  25. if c == '[' || c == ']' {
  26. if lastColor != bracketColor {
  27. output += bracketColor
  28. lastColor = bracketColor
  29. }
  30. output += string(c)
  31. option = (c == '[')
  32. } else {
  33. if lastColor != optionColor {
  34. output += optionColor
  35. lastColor = optionColor
  36. }
  37. output += string(c)
  38. }
  39. } else {
  40. if unicode.IsUpper(c) {
  41. if lastColor != upperColor {
  42. output += upperColor
  43. lastColor = upperColor
  44. }
  45. output += string(c)
  46. } else {
  47. if lastColor != lowerColor {
  48. output += lowerColor
  49. lastColor = lowerColor
  50. }
  51. output += string(c)
  52. }
  53. }
  54. }
  55. return output
  56. }
  57. return f
  58. }
  59. func (m *Menu) AddSelection(key string, text string) {
  60. key = key[:1] // Make sure it is just 1 character
  61. m.Options = append(m.Options, rune(key[0]))
  62. linetext := "[" + key + "] " + text + strings.Repeat(" ", m.Width-(4+len(text)))
  63. m.Lines = append(m.Lines, Line{Text: linetext, RenderF: m.UnselectedR})
  64. }
  65. // Should I be using this, or write a function like the original --
  66. // m.AddSelection("K", "Text") to build? (And throw away the
  67. // redundant) MenuOptions/MenuOption parts? They do let me create
  68. // the menu in larger chunks -- but it is redundant.
  69. // Once the menu is built, I really could delete the MenuOptions.
  70. func (m *Menu) Build() {
  71. // Take MenuOptions and build the Menu
  72. // Reset
  73. m.Lines = make([]Line, 0)
  74. m.Options = make([]rune, 0)
  75. m.Chosen = 0
  76. for _, option := range m.MenuOptions {
  77. m.Options = append(m.Options, option.Ch)
  78. text := "[" + string(option.Ch) + "] " + option.Text + strings.Repeat(" ", m.Width-(4+len(option.Text)))
  79. m.Lines = append(m.Lines, Line{Text: text, RenderF: m.UnselectedR})
  80. }
  81. }
  82. func (m *Menu) Choose(d *Door) int {
  83. var changed []int
  84. updated := true
  85. update_and_exit := false
  86. blank := ColorText("BLACK")
  87. for {
  88. if updated {
  89. for x := range m.Lines {
  90. if x == m.Chosen {
  91. m.Lines[x].RenderF = m.SelectedR
  92. } else {
  93. m.Lines[x].RenderF = m.UnselectedR
  94. }
  95. }
  96. if len(changed) == 0 {
  97. d.Write(m.Output() + blank)
  98. } else {
  99. // Update just the lines that have changed
  100. for _, line := range changed {
  101. d.Write(m.UpdateLine(line))
  102. }
  103. d.Write(m.GotoEnd() + blank)
  104. }
  105. }
  106. if update_and_exit {
  107. return m.Chosen + 1
  108. }
  109. updated = false
  110. var event int = d.Key()
  111. if event < 0 {
  112. return event
  113. }
  114. previous_choice := m.Chosen
  115. changed = make([]int, 0)
  116. use_numberpad := true
  117. for _, option := range m.MenuOptions {
  118. if option.Ch == rune('8') || option.Ch == rune('2') {
  119. use_numberpad = false
  120. }
  121. }
  122. switch event {
  123. case '8':
  124. if use_numberpad {
  125. goto use8
  126. }
  127. break
  128. use8:
  129. fallthrough
  130. case XKEY_UP_ARROW:
  131. if m.Chosen > 0 {
  132. m.Chosen--
  133. updated = true
  134. }
  135. case '2':
  136. if use_numberpad {
  137. goto use2
  138. }
  139. break
  140. use2:
  141. fallthrough
  142. case XKEY_DOWN_ARROW:
  143. if m.Chosen < len(m.Lines)-1 {
  144. m.Chosen++
  145. updated = true
  146. }
  147. case XKEY_HOME:
  148. if m.Chosen != 0 {
  149. m.Chosen = 0
  150. updated = true
  151. }
  152. case XKEY_END:
  153. if m.Chosen != len(m.Lines)-1 {
  154. m.Chosen = len(m.Lines) - 1
  155. updated = true
  156. }
  157. case 0x0d:
  158. // use current selection
  159. return m.Chosen + 1
  160. default:
  161. // Is the key in the list of options?
  162. if event < 0x1000 {
  163. // fmt.Printf("Event: %d\n", event)
  164. for x, option := range m.Options {
  165. // fmt.Printf("Checking %d, %d\n", x, option)
  166. if unicode.ToUpper(option) == unicode.ToUpper(rune(event)) {
  167. if m.Chosen == x {
  168. return x + 1
  169. }
  170. updated = true
  171. m.Chosen = x
  172. update_and_exit = true
  173. }
  174. }
  175. }
  176. }
  177. if previous_choice != m.Chosen {
  178. changed = append(changed, previous_choice)
  179. changed = append(changed, m.Chosen)
  180. updated = true
  181. }
  182. }
  183. }