1
0
Fork 0
mirror of https://github.com/diamondburned/cchat-gtk.git synced 2024-12-23 12:46:45 +00:00
cchat-gtk/internal/ui/service/session/commander/commander.go
diamondburned (Forefront) f10aa71003 UI changes; typing state working
This commit refactors the input container's UI as well as fixing some
bugs related to asynchronous fetching of images.

It also adds complete typing indicator capabilities, all without using a
single mutex!
2020-07-03 21:41:12 -07:00

201 lines
4.2 KiB
Go

package commander
import (
"fmt"
"io"
"time"
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/autoscroll"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/completion"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/scrollinput"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
"github.com/gotk3/gotk3/pango"
"github.com/pkg/errors"
)
var monospace = primitives.PrepareCSS(`
* {
font-family: monospace;
border-radius: 0;
}
`)
var commandPadding = primitives.PrepareCSS(`
* { padding: 8px 12px; }
`)
type Session struct {
*gtk.Box
cmder cchat.Commander
buffer *Buffer
cmplt *completer
inputbuf *gtk.TextBuffer
// words []string
// index int
}
func SpawnDialog(buf *Buffer) {
s := NewSession(buf.cmder, buf)
s.Show()
h, _ := gtk.HeaderBarNew()
h.SetTitle(fmt.Sprintf(
"Commander: %s on %s",
buf.cmder.Name().Content, buf.svcname,
))
h.SetShowCloseButton(true)
h.Show()
d, _ := gts.NewEmptyModalDialog()
d.SetDefaultSize(450, 250)
d.SetTitlebar(h)
d.Add(s)
d.Show()
}
func NewSession(cmder cchat.Commander, buf *Buffer) *Session {
view, _ := gtk.TextViewNewWithBuffer(buf.TextBuffer)
view.SetEditable(false)
view.SetProperty("monospace", true)
view.SetPixelsAboveLines(1)
view.SetWrapMode(gtk.WRAP_WORD_CHAR)
view.Show()
scroll := autoscroll.NewScrolledWindow()
scroll.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scroll.Add(view)
scroll.Show()
input, _ := gtk.TextViewNew()
input.SetSizeRequest(-1, 35) // magic height 35px
primitives.AttachCSS(input, monospace)
input.Show()
inputbuf, _ := input.GetBuffer()
inputscroll := scrollinput.NewH(input)
inputscroll.Show()
sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_HORIZONTAL)
sep.Show()
b, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
b.PackStart(scroll, true, true, 0)
b.PackStart(sep, false, false, 0)
b.PackStart(inputscroll, false, false, 0)
session := &Session{
Box: b,
cmder: cmder,
buffer: buf,
cmplt: newCompleter(input, cmder),
inputbuf: inputbuf,
}
input.Connect("key-press-event", session.inputActivate)
input.GrabFocus()
primitives.AddClass(b, "commander")
primitives.AddClass(view, "command-buffer")
primitives.AddClass(input, "command-input")
primitives.AttachCSS(view, commandPadding)
primitives.AttachCSS(input, commandPadding)
return session
}
func (s *Session) inputActivate(v *gtk.TextView, ev *gdk.Event) bool {
// If the keypress is not enter, then ignore.
if kev := gdk.EventKeyNewFromEvent(ev); kev.KeyVal() != gdk.KEY_Return {
return false
}
// If the input is empty, then ignore.
if len(s.cmplt.Words) == 0 {
return true
}
r, err := s.cmder.RunCommand(s.cmplt.Words)
if err != nil {
s.buffer.WriteError(err)
return true
}
// Clear the entry.
s.inputbuf.Delete(s.inputbuf.GetBounds())
var then = time.Now()
s.buffer.Printlnf("%s: Running command...", then.Format(time.Kitchen))
go func() {
_, err := io.Copy(s.buffer, r)
r.Close()
gts.ExecAsync(func() {
if err != nil {
s.buffer.WriteError(errors.Wrap(err, "Internal error"))
}
var now = time.Now()
s.buffer.Printlnf(
"%s: Finished running command, took %s.",
now.Format(time.Kitchen),
now.Sub(then).String(),
)
})
}()
return true
}
type completer struct {
*completion.Completer
completer cchat.CommandCompleter
choices []string
}
func newCompleter(input *gtk.TextView, v cchat.Commander) *completer {
completer := &completer{}
completer.Completer = completion.NewCompleter(input, completer)
c, ok := v.(cchat.CommandCompleter)
if ok {
completer.completer = c
}
return completer
}
func (c *completer) Update(words []string, offset int) []gtk.IWidget {
if c.completer == nil {
return nil
}
c.choices = c.completer.CompleteCommand(words, offset)
var widgets = make([]gtk.IWidget, 0, len(c.choices))
for _, choice := range c.choices {
l, _ := gtk.LabelNew(choice)
l.SetXAlign(0)
l.SetEllipsize(pango.ELLIPSIZE_END)
primitives.AttachCSS(l, monospace)
l.Show()
widgets = append(widgets, l)
}
return widgets
}
func (c *completer) Word(i int) string {
return c.choices[i]
}