talc/pkg/menu/menu_view.go
2020-03-17 16:36:19 +01:00

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
}