303 lines
5.2 KiB
Go
303 lines
5.2 KiB
Go
package menu
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/gdamore/tcell"
|
|
"github.com/gdamore/tcell/views"
|
|
|
|
runewidth "github.com/mattn/go-runewidth"
|
|
)
|
|
|
|
type MenuView struct {
|
|
style tcell.Style
|
|
selectedStyle tcell.Style
|
|
|
|
model MenuModel
|
|
view views.View
|
|
port *views.ViewPort
|
|
views.WidgetWatchers
|
|
once sync.Once
|
|
}
|
|
|
|
func (mv *MenuView) HandleEvent(e tcell.Event) bool {
|
|
if mv.model == nil {
|
|
return false
|
|
}
|
|
switch e := e.(type) {
|
|
case *tcell.EventKey:
|
|
switch e.Key() {
|
|
case tcell.KeyUp, tcell.KeyCtrlP:
|
|
mv.keyUp()
|
|
return true
|
|
case tcell.KeyDown, tcell.KeyCtrlN:
|
|
mv.keyDown()
|
|
return true
|
|
case tcell.KeyPgDn, tcell.KeyCtrlF:
|
|
mv.keyPgDn()
|
|
return true
|
|
case tcell.KeyPgUp, tcell.KeyCtrlB:
|
|
mv.keyPgUp()
|
|
return true
|
|
case tcell.KeyEnd:
|
|
mv.keyEnd()
|
|
return true
|
|
case tcell.KeyHome:
|
|
mv.keyHome()
|
|
return true
|
|
case tcell.KeyRune:
|
|
switch e.Rune() {
|
|
case 'j':
|
|
mv.keyDown()
|
|
return true
|
|
case 'k':
|
|
mv.keyUp()
|
|
return true
|
|
case 'g':
|
|
mv.keyHome()
|
|
return true
|
|
case 'G':
|
|
mv.keyEnd()
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (mv *MenuView) Draw() {
|
|
port := mv.port
|
|
port.Fill(' ', mv.style)
|
|
model := mv.model
|
|
if mv.view == nil {
|
|
return
|
|
}
|
|
if model == nil {
|
|
return
|
|
}
|
|
vw, vh := mv.view.Size()
|
|
for y := 0; y < vh; y++ {
|
|
for x := 0; x < vw; x++ {
|
|
mv.view.SetContent(x, y, ' ', nil, mv.style)
|
|
}
|
|
}
|
|
ex, ey := model.GetBounds()
|
|
vx, vy := port.Size()
|
|
if ex < vx {
|
|
ex = vx
|
|
}
|
|
if ey < vy {
|
|
ey = vy
|
|
}
|
|
cy, en, sh := mv.model.GetCursor()
|
|
for y := 0; y < ey; y++ {
|
|
ch, style := mv.model.GetLine(y)
|
|
if en && y == cy && sh {
|
|
style = mv.selectedStyle
|
|
}
|
|
puts(port, style, 0, y, string(ch))
|
|
}
|
|
}
|
|
|
|
func puts(s views.View, style tcell.Style, x, y int, str string) {
|
|
i := 0
|
|
var deferred []rune
|
|
dwidth := 0
|
|
zwj := false
|
|
for _, r := range str {
|
|
if r == '\u200d' {
|
|
if len(deferred) == 0 {
|
|
deferred = append(deferred, ' ')
|
|
dwidth = 1
|
|
|
|
}
|
|
deferred = append(deferred, r)
|
|
zwj = true
|
|
continue
|
|
|
|
}
|
|
if zwj {
|
|
deferred = append(deferred, r)
|
|
zwj = false
|
|
continue
|
|
|
|
}
|
|
switch runewidth.RuneWidth(r) {
|
|
case 0:
|
|
if len(deferred) == 0 {
|
|
deferred = append(deferred, ' ')
|
|
dwidth = 1
|
|
|
|
}
|
|
case 1:
|
|
if len(deferred) != 0 {
|
|
s.SetContent(x+i, y, deferred[0], deferred[1:], style)
|
|
i += dwidth
|
|
|
|
}
|
|
deferred = nil
|
|
dwidth = 1
|
|
case 2:
|
|
if len(deferred) != 0 {
|
|
s.SetContent(x+i, y, deferred[0], deferred[1:], style)
|
|
i += dwidth
|
|
|
|
}
|
|
deferred = nil
|
|
dwidth = 2
|
|
|
|
}
|
|
deferred = append(deferred, r)
|
|
|
|
}
|
|
if len(deferred) != 0 {
|
|
s.SetContent(x+i, y, deferred[0], deferred[1:], style)
|
|
i += dwidth
|
|
|
|
}
|
|
}
|
|
|
|
func (mv *MenuView) keyUp() {
|
|
if _, en, _ := mv.model.GetCursor(); !en {
|
|
mv.port.ScrollUp(1)
|
|
return
|
|
}
|
|
mv.model.MoveCursor(0, -1)
|
|
mv.MakeCursorVisible()
|
|
}
|
|
|
|
func (mv *MenuView) keyDown() {
|
|
if _, en, _ := mv.model.GetCursor(); !en {
|
|
mv.port.ScrollDown(1)
|
|
return
|
|
}
|
|
mv.model.MoveCursor(0, 1)
|
|
mv.MakeCursorVisible()
|
|
}
|
|
|
|
func (mv *MenuView) keyPgUp() {
|
|
_, vy := mv.port.Size()
|
|
if _, en, _ := mv.model.GetCursor(); !en {
|
|
mv.port.ScrollUp(vy)
|
|
return
|
|
}
|
|
mv.model.MoveCursor(0, -vy)
|
|
mv.MakeCursorVisible()
|
|
}
|
|
|
|
func (mv *MenuView) keyPgDn() {
|
|
_, vy := mv.port.Size()
|
|
if _, en, _ := mv.model.GetCursor(); !en {
|
|
mv.port.ScrollDown(vy)
|
|
return
|
|
}
|
|
mv.model.MoveCursor(0, +vy)
|
|
mv.MakeCursorVisible()
|
|
}
|
|
|
|
func (mv *MenuView) keyHome() {
|
|
vx, vy := mv.model.GetBounds()
|
|
if _, en, _ := mv.model.GetCursor(); !en {
|
|
mv.port.ScrollUp(vy)
|
|
mv.port.ScrollLeft(vx)
|
|
return
|
|
}
|
|
mv.model.SetCursor(0)
|
|
mv.MakeCursorVisible()
|
|
}
|
|
|
|
func (mv *MenuView) keyEnd() {
|
|
vx, vy := mv.model.GetBounds()
|
|
if _, en, _ := mv.model.GetCursor(); !en {
|
|
mv.port.ScrollDown(vy)
|
|
mv.port.ScrollRight(vx)
|
|
return
|
|
}
|
|
mv.model.SetCursor(vy)
|
|
mv.MakeCursorVisible()
|
|
}
|
|
|
|
func (mv *MenuView) MakeCursorVisible() {
|
|
if mv.model == nil {
|
|
return
|
|
}
|
|
y, enabled, _ := mv.model.GetCursor()
|
|
if enabled {
|
|
mv.MakeVisible(y)
|
|
}
|
|
}
|
|
|
|
func (mv *MenuView) Size() (int, int) {
|
|
w, h := mv.model.GetBounds()
|
|
if w > 2 {
|
|
w = 2
|
|
}
|
|
if h > 2 {
|
|
h = 2
|
|
}
|
|
return w, h
|
|
}
|
|
|
|
func (mv *MenuView) SetModel(model MenuModel) {
|
|
w, h := model.GetBounds()
|
|
model.SetCursor(0)
|
|
mv.model = model
|
|
mv.port.SetContentSize(w, h, true)
|
|
mv.port.ValidateView()
|
|
mv.PostEventWidgetContent(mv)
|
|
}
|
|
|
|
func (mv *MenuView) SetView(view views.View) {
|
|
port := mv.port
|
|
port.SetView(view)
|
|
mv.view = view
|
|
if view == nil {
|
|
return
|
|
}
|
|
width, height := view.Size()
|
|
mv.port.Resize(0, 0, width, height)
|
|
if mv.model != nil {
|
|
w, h := mv.model.GetBounds()
|
|
mv.port.SetContentSize(w, h, true)
|
|
}
|
|
mv.Resize()
|
|
}
|
|
|
|
func (mv *MenuView) Resize() {
|
|
width, height := mv.view.Size()
|
|
mv.port.Resize(0, 0, width, height)
|
|
mv.port.ValidateView()
|
|
mv.MakeCursorVisible()
|
|
}
|
|
|
|
func (mv *MenuView) SetCursor(y int) {
|
|
mv.model.SetCursor(y)
|
|
}
|
|
|
|
func (mv *MenuView) MakeVisible(y int) {
|
|
mv.port.MakeVisible(0, y)
|
|
}
|
|
|
|
func (mv *MenuView) SetStyle(s tcell.Style) {
|
|
mv.style = s
|
|
}
|
|
|
|
func (mv *MenuView) SetSelectedStyle(s tcell.Style) {
|
|
mv.selectedStyle = s
|
|
}
|
|
|
|
func (mv *MenuView) Init() {
|
|
mv.selectedStyle = mv.style.Reverse(true)
|
|
|
|
mv.once.Do(func() {
|
|
mv.port = views.NewViewPort(nil, 0, 0, 0, 0)
|
|
mv.style = tcell.StyleDefault
|
|
})
|
|
}
|
|
|
|
func NewMenuView() *MenuView {
|
|
mv := &MenuView{}
|
|
mv.Init()
|
|
return mv
|
|
}
|