package tk
import (
)
type ListBox interface {
Widget
CopyState() ListBoxState
Reset(it Items, selected int)
Select(f func(ListBoxState) int)
Accept()
}
type ListBoxSpec struct {
Bindings Bindings
Placeholder ui.Text
OnSelect func(it Items, i int)
OnAccept func(it Items, i int)
Horizontal bool
Padding int
ExtendStyle bool
State ListBoxState
}
type listBox struct {
StateMutex sync.RWMutex
ListBoxSpec
}
func ( ListBoxSpec) ListBox {
if .Bindings == nil {
.Bindings = DummyBindings{}
}
if .OnAccept == nil {
.OnAccept = func(Items, int) {}
}
if .OnSelect == nil {
.OnSelect = func(Items, int) {}
} else {
:= .State
if .Items != nil && 0 <= .Selected && .Selected < .Items.Len() {
.OnSelect(.Items, .Selected)
}
}
return &listBox{ListBoxSpec: }
}
var stylingForSelected = ui.Inverse
func ( *listBox) (, int) *term.Buffer {
if .Horizontal {
return .renderHorizontal(, )
}
return .renderVertical(, )
}
const listBoxColGap = 2
func ( *listBox) (, int) *term.Buffer {
var ListBoxState
.mutate(func( *ListBoxState) {
if .Items == nil || .Items.Len() == 0 {
.First = 0
} else {
.First, .Height = getHorizontalWindow(*, .Padding, , )
= .Height
}
= *
})
if .Items == nil || .Items.Len() == 0 {
return Label{Content: .Placeholder}.Render(, )
}
, , := .Items, .Selected, .First
:= .Len()
:= term.NewBuffer(0)
:=
:= false
:=
for := ; < ; += {
:= -1
:= make([]ui.Text, 0, )
for := ; < + && < ; ++ {
=
:= .Show()
if == {
= -
}
= append(, )
}
:= maxWidth(, .Padding, , +)
if > {
=
= true
}
:= croppedLines{
lines: , padding: .Padding,
selectFrom: , selectTo: + 1,
extendStyle: .ExtendStyle}.Render(, )
.ExtendRight()
-=
if <= listBoxColGap {
break
}
-= listBoxColGap
.Width += listBoxColGap
}
.Width =
if != 0 || != -1 || {
:= HScrollbar{Total: , Low: , High: + 1}
.Extend(.Render(, 1), false)
}
return
}
func ( *listBox) (, int) *term.Buffer {
var ListBoxState
var int
.mutate(func( *ListBoxState) {
if .Items == nil || .Items.Len() == 0 {
.First = 0
} else {
.First, = getVerticalWindow(*, )
}
.Height =
= *
})
if .Items == nil || .Items.Len() == 0 {
return Label{Content: .Placeholder}.Render(, )
}
, , := .Items, .Selected, .First
:= .Len()
:= []ui.Text{}
:= > 0
var , , int
for = ; < && len() < ; ++ {
:= .Show()
:= .SplitByRune('\n')
if == {
= [:]
}
if == {
, = len(), len()+len()
}
if len()+len() > {
= [:len()+len()-]
= true
}
= append(, ...)
}
var Renderer = croppedLines{
lines: , padding: .Padding,
selectFrom: , selectTo: , extendStyle: .ExtendStyle}
if > 0 || < || {
= VScrollbarContainer{
Content: ,
Scrollbar: VScrollbar{Total: , Low: , High: },
}
}
return .Render(, )
}
type croppedLines struct {
lines []ui.Text
padding int
selectFrom int
selectTo int
extendStyle bool
}
func ( croppedLines) (, int) *term.Buffer {
:= term.NewBufferBuilder()
:= ui.T(strings.Repeat(" ", .padding))
:= ui.T(strings.Repeat(" ", -.padding))
for , := range .lines {
if > 0 {
.Newline()
}
:= .selectFrom <= && < .selectTo
:= .extendStyle && len() > 0
:= .Clone()
if {
[0].Style = [0].Style
}
:= ui.Concat(, .TrimWcwidth(-2*.padding))
if || {
:= .Clone()
if {
[0].Style = [len()-1].Style
}
= ui.Concat(, ).TrimWcwidth()
}
if {
= ui.StyleText(, stylingForSelected)
}
.WriteStyled()
}
return .Buffer()
}
func ( *listBox) ( term.Event) bool {
if .Bindings.Handle(, ) {
return true
}
switch {
case term.K(ui.Up):
.Select(Prev)
return true
case term.K(ui.Down):
.Select(Next)
return true
case term.K(ui.Enter):
.Accept()
return true
}
return false
}
func ( *listBox) () ListBoxState {
.StateMutex.RLock()
defer .StateMutex.RUnlock()
return .State
}
func ( *listBox) ( Items, int) {
.mutate(func( *ListBoxState) { * = ListBoxState{Items: , Selected: } })
if 0 <= && < .Len() {
.OnSelect(, )
}
}
func ( *listBox) ( func(ListBoxState) int) {
var Items
var , int
.mutate(func( *ListBoxState) {
, = .Selected, .Items
= (*)
.Selected =
})
if != && 0 <= && < .Len() {
.OnSelect(, )
}
}
func ( ListBoxState) int {
return fixIndex(.Selected-1, .Items.Len())
}
func ( ListBoxState) int {
return fixIndex(.Selected-.Height, .Items.Len())
}
func ( ListBoxState) int {
return fixIndex(.Selected+1, .Items.Len())
}
func ( ListBoxState) int {
return fixIndex(.Selected+.Height, .Items.Len())
}
func ( ListBoxState) int {
, := .Selected, .Items.Len()
switch {
case >= :
return - 1
case <= 0:
return - 1
default:
return - 1
}
}
func ( ListBoxState) int {
, := .Selected, .Items.Len()
switch {
case >= -1:
return 0
case < 0:
return 0
default:
return + 1
}
}
func ( ListBoxState) int {
return horizontal(.Selected, .Items.Len(), -.Height)
}
func ( ListBoxState) int {
return horizontal(.Selected, .Items.Len(), .Height)
}
func (, , int) int {
= fixIndex(, )
:= +
if < 0 || >= {
return
}
return
}
func (, int) int {
switch {
case < 0:
return 0
case >= :
return - 1
default:
return
}
}
func ( *listBox) () {
:= .CopyState()
if 0 <= .Selected && .Selected < .Items.Len() {
.OnAccept(.Items, .Selected)
}
}
func ( *listBox) ( func( *ListBoxState)) {
.StateMutex.Lock()
defer .StateMutex.Unlock()
(&.State)
}