|
| 1 | +package primitive |
| 2 | + |
| 3 | +import ( |
| 4 | + "strings" |
| 5 | + |
| 6 | + "github.com/gdamore/tcell/v2" |
| 7 | + "github.com/rivo/tview" |
| 8 | +) |
| 9 | + |
| 10 | +// ChoiceModal is a modal dialog that presents a list of choices to the user. |
| 11 | +type ChoiceModal struct { |
| 12 | + *tview.Box |
| 13 | + frame *tview.Frame |
| 14 | + text string |
| 15 | + list *tview.List |
| 16 | + footer *tview.TextView |
| 17 | + done func(index int, label string) |
| 18 | +} |
| 19 | + |
| 20 | +// Choice modal layout constants. |
| 21 | +const ( |
| 22 | + choiceFooterHeight = 2 |
| 23 | +) |
| 24 | + |
| 25 | +// NewChoiceModal creates a new choice modal. |
| 26 | +func NewChoiceModal() *ChoiceModal { |
| 27 | + m := &ChoiceModal{Box: tview.NewBox()} |
| 28 | + |
| 29 | + m.list = tview.NewList(). |
| 30 | + ShowSecondaryText(false). |
| 31 | + SetMainTextColor(tcell.ColorDefault) |
| 32 | + |
| 33 | + m.footer = tview.NewTextView() |
| 34 | + m.footer.SetTitleAlign(tview.AlignCenter) |
| 35 | + m.footer.SetTextAlign(tview.AlignCenter) |
| 36 | + m.footer.SetTextStyle(tcell.StyleDefault.Italic(true)) |
| 37 | + m.footer.SetBorderPadding(1, 0, 0, 0) |
| 38 | + |
| 39 | + flex := tview.NewFlex().SetDirection(tview.FlexRow). |
| 40 | + AddItem(m.list, 0, 1, true). |
| 41 | + AddItem(m.footer, choiceFooterHeight, 0, false) |
| 42 | + |
| 43 | + m.frame = tview.NewFrame(flex).SetBorders(0, 0, 1, 0, 0, 0) |
| 44 | + m.frame.SetBorder(true).SetBorderPadding(1, 1, 1, 1) |
| 45 | + |
| 46 | + return m |
| 47 | +} |
| 48 | + |
| 49 | +// SetText sets the text displayed in the modal. |
| 50 | +func (m *ChoiceModal) SetText(text string) { |
| 51 | + m.text = text |
| 52 | +} |
| 53 | + |
| 54 | +// SetDoneFunc sets the callback function when a choice is selected. |
| 55 | +func (m *ChoiceModal) SetDoneFunc(doneFunc func(index int, label string)) *ChoiceModal { |
| 56 | + m.done = doneFunc |
| 57 | + return m |
| 58 | +} |
| 59 | + |
| 60 | +// SetChoices sets the list of choices to display. |
| 61 | +func (m *ChoiceModal) SetChoices(choices []string) *ChoiceModal { |
| 62 | + m.list.Clear() |
| 63 | + for _, choice := range choices { |
| 64 | + m.list.AddItem(choice, "", 0, nil) |
| 65 | + } |
| 66 | + return m |
| 67 | +} |
| 68 | + |
| 69 | +// SetSelected sets the currently selected choice index. |
| 70 | +func (m *ChoiceModal) SetSelected(index int) *ChoiceModal { |
| 71 | + m.list.SetCurrentItem(index) |
| 72 | + return m |
| 73 | +} |
| 74 | + |
| 75 | +// GetFooter returns the footer text view. |
| 76 | +func (m *ChoiceModal) GetFooter() *tview.TextView { |
| 77 | + return m.footer |
| 78 | +} |
| 79 | + |
| 80 | +// Focus is called when this primitive receives focus. |
| 81 | +func (m *ChoiceModal) Focus(delegate func(p tview.Primitive)) { |
| 82 | + delegate(m.list) |
| 83 | +} |
| 84 | + |
| 85 | +// HasFocus returns whether or not this primitive has focus. |
| 86 | +func (m *ChoiceModal) HasFocus() bool { |
| 87 | + return m.list.HasFocus() |
| 88 | +} |
| 89 | + |
| 90 | +// InputHandler returns the handler for this primitive. |
| 91 | +func (m *ChoiceModal) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { |
| 92 | + return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { |
| 93 | + switch event.Key() { |
| 94 | + case tcell.KeyEnter: |
| 95 | + if m.done != nil { |
| 96 | + index := m.list.GetCurrentItem() |
| 97 | + label, _ := m.list.GetItemText(index) |
| 98 | + m.done(index, label) |
| 99 | + } |
| 100 | + default: |
| 101 | + if handler := m.frame.InputHandler(); handler != nil { |
| 102 | + handler(event, setFocus) |
| 103 | + } |
| 104 | + } |
| 105 | + }) |
| 106 | +} |
| 107 | + |
| 108 | +// MouseHandler returns the mouse handler for this primitive. |
| 109 | +func (m *ChoiceModal) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (bool, tview.Primitive) { |
| 110 | + return m.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (bool, tview.Primitive) { |
| 111 | + if handler := m.frame.MouseHandler(); handler != nil { |
| 112 | + return handler(action, event, setFocus) |
| 113 | + } |
| 114 | + return false, nil |
| 115 | + }) |
| 116 | +} |
| 117 | + |
| 118 | +// Choice modal Draw constants. |
| 119 | +const ( |
| 120 | + choiceVerticalMargin = 3 |
| 121 | + choiceFrameExtraHeight = 7 |
| 122 | + choiceModalWidth = 70 |
| 123 | + marginMultiplier = 2 |
| 124 | +) |
| 125 | + |
| 126 | +// Draw draws this primitive onto the screen. |
| 127 | +func (m *ChoiceModal) Draw(screen tcell.Screen) { |
| 128 | + screenWidth, screenHeight := screen.Size() |
| 129 | + width := choiceModalWidth |
| 130 | + |
| 131 | + m.frame.Clear() |
| 132 | + var lines []string |
| 133 | + for _, line := range strings.Split(m.text, "\n") { |
| 134 | + if line == "" { |
| 135 | + lines = append(lines, line) |
| 136 | + continue |
| 137 | + } |
| 138 | + lines = append(lines, tview.WordWrap(line, width)...) |
| 139 | + } |
| 140 | + |
| 141 | + for _, line := range lines { |
| 142 | + m.frame.AddText(line, true, tview.AlignCenter, tcell.ColorDefault) |
| 143 | + } |
| 144 | + |
| 145 | + height := len(lines) + m.list.GetItemCount() + choiceFrameExtraHeight |
| 146 | + maxHeight := screenHeight - choiceVerticalMargin*marginMultiplier |
| 147 | + if height > maxHeight { |
| 148 | + height = maxHeight |
| 149 | + } |
| 150 | + |
| 151 | + x := (screenWidth - width) / centerDivisor |
| 152 | + y := (screenHeight - height) / centerDivisor |
| 153 | + m.frame.SetRect(x, y, width, height) |
| 154 | + m.frame.Draw(screen) |
| 155 | +} |
0 commit comments