cchat-gtk/internal/ui/messages/input/completion/completion.go

199 lines
4.2 KiB
Go
Raw Normal View History

package completion
import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts/httputil"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
2020-07-01 01:09:22 +00:00
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/completion"
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
"github.com/diamondburned/cchat/utils/split"
"github.com/diamondburned/imgutil"
"github.com/gotk3/gotk3/gtk"
)
const (
ImageSize = 20
ImagePadding = 10
)
// var completionQueue chan func()
// func init() {
// completionQueue = make(chan func(), 1)
// go func() {
// for fn := range completionQueue {
// fn()
// }
// }()
// }
type View struct {
*gtk.Revealer
Scroll *gtk.ScrolledWindow
List *gtk.ListBox
entries []cchat.CompletionEntry
text *gtk.TextView
buffer *gtk.TextBuffer
// state
completer cchat.ServerMessageSendCompleter
offset int
}
func New(text *gtk.TextView) *View {
list, _ := gtk.ListBoxNew()
list.SetSelectionMode(gtk.SELECTION_BROWSE)
list.Show()
primitives.AddClass(list, "completer")
scroll, _ := gtk.ScrolledWindowNew(nil, nil)
scroll.Add(list)
scroll.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scroll.SetProperty("propagate-natural-height", true)
scroll.SetProperty("max-content-height", 250)
scroll.Show()
// Bind scroll adjustments.
list.SetFocusHAdjustment(scroll.GetHAdjustment())
list.SetFocusVAdjustment(scroll.GetVAdjustment())
rev, _ := gtk.RevealerNew()
rev.SetRevealChild(false)
rev.SetTransitionDuration(50)
rev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_UP)
rev.Add(scroll)
rev.Show()
buffer, _ := text.GetBuffer()
v := &View{
Revealer: rev,
Scroll: scroll,
List: list,
text: text,
buffer: buffer,
}
2020-07-01 01:09:22 +00:00
text.Connect("key-press-event", completion.KeyDownHandler(list, text.GrabFocus))
buffer.Connect("changed", func() {
// Clear the list first.
v.Clear()
// Re-run the list.
v.Run()
})
list.Connect("row-activated", func(l *gtk.ListBox, r *gtk.ListBoxRow) {
2020-07-01 01:09:22 +00:00
completion.SwapWord(v.buffer, v.entries[r.GetIndex()].Raw, v.offset)
v.Clear()
2020-07-01 01:09:22 +00:00
v.text.GrabFocus() // TODO: remove, maybe not needed
})
return v
}
// SetMarginStart sets the left margin but account for images as well.
func (v *View) SetMarginStart(pad int) {
pad = pad - (ImagePadding*2 + ImageSize - 2) // subtracting 2 for no reason
if pad < 0 {
pad = 0
}
v.Revealer.SetMarginStart(pad)
}
func (v *View) Reset() {
v.SetCompleter(nil)
}
func (v *View) SetCompleter(completer cchat.ServerMessageSendCompleter) {
v.Clear()
v.completer = completer
}
func (v *View) Clear() {
// Do we have anything in the slice? If not, then we don't need to run
// again. We do need to keep RevealChild consistent with this, however.
if v.entries == nil {
return
}
// Since we don't store the widgets inside the list, we'll manually iterate
// and remove.
v.List.GetChildren().Foreach(func(i interface{}) {
w := i.(gtk.IWidget).ToWidget()
v.List.Remove(w)
w.Destroy()
})
// Set entries to nil to free up the slice.
v.entries = nil
// Set offset to 0 to reset.
v.offset = 0
// Hide the list.
v.SetRevealChild(false)
}
func (v *View) Run() {
// If we don't have a completer, then don't run.
if v.completer == nil {
return
}
text, offset := v.getInputState()
words, index := split.SpaceIndexed(text, offset)
// If the input is empty.
if len(words) == 0 {
return
}
v.offset = offset
v.entries = v.completer.CompleteMessage(words, index)
if len(v.entries) == 0 {
return
}
// Reveal if needed be.
v.SetRevealChild(true)
// TODO: make entries reuse pixbuf.
for i, entry := range v.entries {
l := rich.NewLabel(entry.Text)
l.Show()
img, _ := gtk.ImageNew()
img.SetSizeRequest(ImageSize, ImageSize)
img.Show()
// Do we have an icon?
if entry.IconURL != "" {
httputil.AsyncImageSized(img, entry.IconURL, ImageSize, ImageSize, imgutil.Round(true))
}
b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
b.PackStart(img, false, false, ImagePadding)
b.PackStart(l, true, true, 0)
b.Show()
r, _ := gtk.ListBoxRowNew()
r.Add(b)
r.Show()
v.List.Add(r)
// Select the first item.
if i == 0 {
v.List.SelectRow(r)
}
}
}
func (v *View) getInputState() (string, int) {
2020-07-01 01:09:22 +00:00
return completion.State(v.buffer)
}