From f64af6fa2956ca7e88e007b5ceddb5db7d54f05e Mon Sep 17 00:00:00 2001 From: "Luke I. Wilson" Date: Fri, 2 Apr 2021 13:30:37 -0500 Subject: [PATCH] DrawStr: made more efficient and renders newline characters Before, DrawStr would copy all bytes into a []rune slice, and later copy the rune slice (by accident) in the for i, v := range loop. Now, it uses a []byte slice and iterates with for i := range slice. --- ui/drawfunctions.go | 16 +++++- ui/messagedialog.go | 135 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 3 deletions(-) create mode 100755 ui/messagedialog.go diff --git a/ui/drawfunctions.go b/ui/drawfunctions.go index 77f2729..24d748b 100644 --- a/ui/drawfunctions.go +++ b/ui/drawfunctions.go @@ -18,9 +18,19 @@ func DrawRect(s tcell.Screen, x, y, width, height int, char rune, style tcell.St // DrawStr will render each character of a string at `x` and `y`. func DrawStr(s tcell.Screen, x, y int, str string, style tcell.Style) { - runes := []rune(str) - for idx, r := range runes { - s.SetContent(x+idx, y, r, nil, style) + var col int + bytes := []byte(str) + var i int + for i < len(bytes) { + r, size := utf8.DecodeRune(bytes[i:]) + if r == '\n' { + col = 0 + y++ + } else { + s.SetContent(x+col, y, r, nil, style) + } + i += size + col++ } } diff --git a/ui/messagedialog.go b/ui/messagedialog.go new file mode 100755 index 0000000..c5f90dc --- /dev/null +++ b/ui/messagedialog.go @@ -0,0 +1,135 @@ +package ui + +import ( + "strings" + + "github.com/gdamore/tcell/v2" + "github.com/mattn/go-runewidth" +) + +type MessageDialogKind uint8 + +const ( + MessageKindNormal MessageDialogKind = iota + MessageKindWarning + MessageKindError +) + +// Index of messageDialogKindTitles is any MessageDialogKind. +var messageDialogKindTitles [3]string = [3]string{ + "Message", + "Warning!", + "Error!", +} + +type MessageDialog struct { + Title string + Kind MessageDialogKind + Callback func(string) + + message string + messageWrapped string + + x, y int + width, height int + focused bool + theme *Theme + + buttons []*Button + selectedIdx int +} + +func NewMessageDialog(title string, message string, kind MessageDialogKind, options []string, theme *Theme, callback func(string)) *MessageDialog { + if title == "" { + title = messageDialogKindTitles[kind] // Use default title + } + + if options == nil || len(options) == 0 { + options = []string{"OK"} + } + + dialog := MessageDialog{ + Title: title, + Kind: kind, + Callback: callback, + + theme: theme, + } + + dialog.buttons = make([]*Button, len(options)) + for i := range options { + dialog.buttons[i] = NewButton(options[i], theme, func() { + if dialog.Callback != nil { + dialog.Callback(dialog.buttons[dialog.selectedIdx].Text) + } + }) + } + + // Set the dialog's size to its minimum size + dialog.SetSize(0, 0) + dialog.SetMessage(message) + + return &dialog +} + +func (d *MessageDialog) SetMessage(message string) { + d.message = message + d.messageWrapped = runewidth.Wrap(message, d.width-2) + // Update height: + _, minHeight := d.GetMinSize() + d.height = Max(d.height, minHeight) +} + +func (d *MessageDialog) Draw(s tcell.Screen) { + DrawWindow(s, d.x, d.y, d.width, d.height, d.Title, d.theme) + + // DrawStr will handle '\n' characters and wrap for us. + DrawStr(s, d.x+1, d.y+2, d.messageWrapped, d.theme.GetOrDefault("Window")) + + col := d.width // Start from the right side + for i := range d.buttons { + width, _ := d.buttons[i].GetSize() + col -= width + 1 // Move left enough for each button (1 for padding) + d.buttons[i].SetPos(d.x+col, d.y+d.height-2) + d.buttons[i].Draw(s) + } +} + +func (d *MessageDialog) SetFocused(v bool) { + d.focused = v + d.buttons[d.selectedIdx].SetFocused(v) +} + +func (d *MessageDialog) SetTheme(theme *Theme) { + d.theme = theme + for i := range d.buttons { + d.buttons[i].SetTheme(theme) + } +} + +func (d *MessageDialog) GetPos() (int, int) { + return d.x, d.y +} + +func (d *MessageDialog) SetPos(x, y int) { + d.x, d.y = x, y +} + +func (d *MessageDialog) GetMinSize() (int, int) { + lines := strings.Count(d.messageWrapped, "\n") + 1 + + return Max(len(d.Title)+2, 30), 2+lines+2 +} + +func (d *MessageDialog) GetSize() (int, int) { + return d.width, d.height +} + +func (d *MessageDialog) SetSize(width, height int) { + minWidth, minHeight := d.GetMinSize() + d.width, d.height = Max(width, minWidth), Max(height, minHeight) +} + +func (d *MessageDialog) HandleEvent(event tcell.Event) bool { + return d.buttons[d.selectedIdx].HandleEvent(event) +}