func (w *Table) ColumnNames() []string {
	keys := make([]string, len(w.columns))
	for i := range w.columns {
		keys[i] = w.columns[i].key
	}
	return keys
}
ColumnNames returns a list of the visible columns
package widgets
import (
	"fmt"
	"math"
	"time"
	"github.com/ambientsound/visp/list"
	"github.com/ambientsound/visp/log"
	"github.com/ambientsound/visp/options"
	"github.com/ambientsound/visp/spotify/devices"
	"github.com/ambientsound/visp/spotify/tracklist"
	"github.com/ambientsound/visp/api"
	"github.com/ambientsound/visp/style"
	"github.com/ambientsound/visp/utils"
	"github.com/gdamore/tcell/v2"
	"github.com/gdamore/tcell/v2/views"
)
type lineStyler func(row list.Row) (string, bool)
type Table struct {
	api     api.API
	columns []column
	list    list.List
	view     views.View
	viewport views.ViewPort
	lastDraw time.Time
	style.Styled
	views.WidgetWatchers
}
func NewTable(a api.API) *Table {
	return &Table{
		api: a,
	}
}
func (w *Table) drawNext(v views.View, x, y, strmin, strmax int, runes []rune, style tcell.Style) int {
	strmin = utils.Min(len(runes), strmin)
	n := 0
	for n < strmin {
		v.SetContent(x, y, runes[n], nil, style)
		n++
		x++
	}
	for n < strmax {
		v.SetContent(x, y, ' ', nil, style)
		n++
		x++
	}
	return x
}
func (w *Table) List() list.List {
	return w.list
}
func (w *Table) SetList(lst list.List) {
	w.list = lst
	w.SetColumns(lst.VisibleColumns())
	log.Debugf("SetList: %v", w.list.Name())
	log.Debugf("SetColumns: %v", w.ColumnNames())
}
func (w *Table) Draw() {
	var styler lineStyler
	var specialStyler lineStyler
	var st tcell.Style
	w.SetStylesheet(w.api.Styles())
	w.setViewportSize()
	w.lastDraw = time.Now()
	_, ymin, xmax, ymax := w.viewport.GetVisible()
	x, y := 0, 0
	xmax += 1
	cursor := false
	_, isTracklist := w.list.(*spotify_tracklist.List)
	_, isDevicelist := w.list.(*spotify_devices.List)
	switch {
	case isTracklist && w.api.PlayerStatus().Item != nil:
		trackID := w.api.PlayerStatus().Item.ID.String()
		specialStyler = func(row list.Row) (string, bool) {
			return `currentSong`, trackID == row.ID()
		}
	case isDevicelist:
		id := w.api.PlayerStatus().Device.ID
		deviceID := id.String()
		specialStyler = func(row list.Row) (string, bool) {
			return `currentDevice`, deviceID == row.ID()
		}
	default:
		specialStyler = func(row list.Row) (string, bool) {
			return ``, false
		}
	}
	styler = func(row list.Row) (string, bool) {
		st, special := specialStyler(row)
		switch {
		case cursor:
			return `cursor`, true
		case special:
			return st, special
		case w.list.Selected(y):
			return `selection`, true
		default:
			return `default`, false
		}
	}
	w.drawHeaders()
	for y = ymin; y <= ymax; y++ {
		row := w.list.Row(y)
		if row == nil {
			panic(fmt.Sprintf("nil row: %d", y))
		}
		x = 0
		cursor = y == w.list.Cursor()
		styleName, lineStyled := styler(row)
		st = w.Style(styleName)
		for _, col := range w.columns {
			runes := []rune(row.Fields()[col.key])
			if !lineStyled {
				st = w.Style(col.key)
			}
			strmin := col.width - col.rightPadding
			x = w.drawNext(&w.viewport, x, y, strmin, col.width, runes, st)
		}
	}
}
func (w *Table) drawHeaders() {
	x := 0
	st := w.Style("header")
	for _, col := range w.columns {
		runes := []rune(col.title)
		strmin := col.width - col.rightPadding
		x = w.drawNext(w.view, x, 0, strmin, col.width, runes, st)
	}
}
func (w *Table) GetVisibleBoundaries() (ymin, ymax int) {
	_, ymin, _, ymax = w.viewport.GetVisible()
	return
}
func (w *Table) Width() int {
	_, _, xmax, _ := w.viewport.GetVisible()
	return xmax
}
func (w *Table) Height() int {
	_, ymin, _, ymax := w.viewport.GetVisible()
	return ymax - ymin
}
func (w *Table) setViewportSize() {
	x, y := w.Size()
	w.viewport.SetContentSize(x, w.list.Len(), true)
	w.viewport.SetSize(x, utils.Min(y-1, w.list.Len()))
	w.validateViewport()
}
func (w *Table) validateViewport() {
	cursor := w.list.Cursor()
	if !options.GetBool(options.Center) {
		w.viewport.MakeVisible(0, cursor)
		return
	}
	half := w.Height() / 2
	min := utils.Max(0, cursor-half)
	max := utils.Min(w.list.Len()-1, cursor+half)
	w.viewport.MakeVisible(0, min)
	w.viewport.MakeVisible(0, max)
}
func (w *Table) Resize() {
	x, y := w.Size()
	w.viewport.Resize(0, 1, x, y-1)
	w.SetColumns(w.ColumnNames())
}
func (w *Table) HandleEvent(ev tcell.Event) bool {
	return false
}
func (w *Table) SetView(v views.View) {
	w.view = v
	w.viewport = *views.NewViewPort(v, 0, 0, -1, -1)
}
func (w *Table) Size() (int, int) {
	return w.view.Size()
}
func (w *Table) Name() string {
	return w.list.Name()
}
func (w *Table) PositionReadout() string {
	return fmt.Sprintf("%s    %s", w.PositionLongReadout(), w.PositionShortReadout())
}
func (w *Table) PositionLongReadout() string {
	ymin, ymax := w.GetVisibleBoundaries()
	return fmt.Sprintf("%d,%d-%d/%d", w.list.Cursor()+1, ymin+1, ymax+1, w.list.Len())
}
func (w *Table) PositionShortReadout() string {
	ymin, ymax := w.GetVisibleBoundaries()
	if ymin == 0 && ymax+1 == w.list.Len() {
		return `All`
	}
	if ymin == 0 {
		return `Top`
	}
	if ymax+1 == w.list.Len() {
		return `Bot`
	}
	fraction := float64(float64(ymin) / float64(w.list.Len()))
	percent := int(math.Floor(fraction * 100))
	return fmt.Sprintf("%2d%%", percent)
}