package door import ( "bytes" "log" "strings" "unicode" ) /* type MenuOption struct { Ch rune Text string } */ type Menu struct { Chosen int SelectedR ColorRender UnselectedR ColorRender Options []rune Activated func(int, *Door) // Which option is active // MenuOptions []MenuOption Panel } func MakeMenuRender(bracketColor, optionColor, upperColor, lowerColor []byte) ColorRender { f := func(output *bytes.Buffer, text []byte) { output.Reset() var lastColor *[]byte option := true for _, c := range text { if option { if c == '[' || c == ']' { if lastColor != &bracketColor { output.Write(bracketColor) lastColor = &bracketColor } output.WriteByte(c) option = (c == '[') } else { if lastColor != &optionColor { output.Write(optionColor) lastColor = &optionColor } output.WriteByte(c) } } else { if unicode.IsUpper(rune(c)) { if lastColor != &upperColor { output.Write(upperColor) lastColor = &upperColor } output.WriteByte(c) } else { if lastColor != &lowerColor { output.Write(lowerColor) lastColor = &lowerColor } output.WriteByte(c) } } } // return output.Bytes() } return f } func (m *Menu) AddSelection(key string, text string) { key = key[:1] // Make sure it is just 1 character m.Options = append(m.Options, rune(key[0])) // 4 = [ key ] + " " if len(text)+4 > m.Width { log.Panicf("Menu (not wide enough) Width %d : text size %d + 4 = %d\n", m.Width, len(text), len(text)+4) } var linetext *bytes.Buffer = &bytes.Buffer{} linetext.WriteString("[" + key + "] " + text + strings.Repeat(" ", m.Width-(4+len(text)))) m.Lines = append(m.Lines, &Line{Text: linetext, RenderF: m.UnselectedR}) } // Should I be using this, or write a function like the original -- // m.AddSelection("K", "Text") to build? (And throw away the // redundant) MenuOptions/MenuOption parts? They do let me create // the menu in larger chunks -- but it is redundant. // Once the menu is built, I really could delete the MenuOptions. /* func (m *Menu) Build() { // Take MenuOptions and build the Menu // Reset m.Lines = make([]Line, 0) m.Options = make([]rune, 0) m.Chosen = 0 for _, option := range m.MenuOptions { m.Options = append(m.Options, option.Ch) text := "[" + string(option.Ch) + "] " + option.Text + strings.Repeat(" ", m.Width-(4+len(option.Text))) m.Lines = append(m.Lines, Line{Text: text, RenderF: m.UnselectedR}) } } */ // Get the character that was pressed from the choice func (m *Menu) GetOption(choice int) rune { return m.Options[choice-1] } func (m *Menu) Choose(d *Door) int { var changed []int updated := true update_and_exit := false blank := ColorText("BLACK") for { if updated { for x := range m.Lines { if x == m.Chosen { m.Lines[x].RenderF = m.SelectedR } else { m.Lines[x].RenderF = m.UnselectedR } } if len(changed) == 0 { var output bytes.Buffer output.Write(m.Output()) output.Write(blank) d.Write(output.Bytes()) //m.Output() + blank) } else { // Update just the lines that have changed var output bytes.Buffer for _, line := range changed { output.Write(m.UpdateLine(line)) } output.Write(m.GotoEnd()) output.Write(blank) d.Write(output.Bytes()) } if m.Activated != nil { m.Activated(m.Chosen, d) // Reposition to the end of the menu var output bytes.Buffer output.Write(m.GotoEnd()) output.Write(blank) d.Write(output.Bytes()) } } if update_and_exit { return m.Chosen + 1 } updated = false var r rune var ex Extended var err error r, ex, err = d.WaitKey(DefaultTimeout) if err != nil { return -1 } previous_choice := m.Chosen changed = make([]int, 0) use_numberpad := true for _, option := range m.Options { if option == rune('8') || option == rune('2') { use_numberpad = false } } if ex == MOUSE { mouse, ok := d.GetMouse() if ok { if mouse.Button == 65 { // Translate Mouse Wheel Up ex = UP_ARROW } if mouse.Button == 66 { // Translate Mouse Wheel Down ex = DOWN_ARROW } // Look at Mouse X/Y to determine where they clicked if mouse.Button == 1 || mouse.Button == 2 { // TODO: log.Println("Mouse (", mouse.X, ",", mouse.Y, ")") clicked, cx, cy := m.Panel.Within(int(mouse.X), int(mouse.Y)) log.Printf("%t : (%d, %d)\n", clicked, cx, cy) if clicked { if m.Panel.HasBorder() { // Did they click the border? if cy > 0 && cy <= m.Panel.Length() { updated = true m.Chosen = cy - 1 update_and_exit = true } } else { // Borderless -- yes, they clicked something! updated = true m.Chosen = cy update_and_exit = true } } } } } switch ex { case UP_ARROW: if m.Chosen > 0 { m.Chosen-- updated = true } case DOWN_ARROW: if m.Chosen < len(m.Lines)-1 { m.Chosen++ updated = true } case HOME: if m.Chosen != 0 { m.Chosen = 0 updated = true } case END: if m.Chosen != len(m.Lines)-1 { m.Chosen = len(m.Lines) - 1 updated = true } } switch r { case '8': if use_numberpad { if m.Chosen > 0 { m.Chosen-- updated = true } } case '2': if use_numberpad { if m.Chosen < len(m.Lines)-1 { m.Chosen++ updated = true } } case 0x0d: // use current selection return m.Chosen + 1 default: // Is the key in the list of options? // fmt.Printf("Event: %d\n", event) for x, option := range m.Options { // fmt.Printf("Checking %d, %d\n", x, option) if unicode.ToUpper(option) == unicode.ToUpper(r) { if m.Chosen == x { return x + 1 } updated = true m.Chosen = x update_and_exit = true } } } if previous_choice != m.Chosen { changed = append(changed, previous_choice) changed = append(changed, m.Chosen) updated = true } } }