From 3dd5f02de21ce74dbc6e85097f8cb5fc5a41e94f Mon Sep 17 00:00:00 2001 From: "diamondburned (Forefront)" Date: Tue, 30 Jun 2020 18:34:13 -0700 Subject: [PATCH] Replaced the message completer with a similar popover --- .../messages/input/completion/completion.go | 159 +++--------------- internal/ui/messages/input/input.go | 21 +-- .../ui/primitives/completion/completer.go | 21 ++- internal/ui/primitives/completion/utils.go | 2 +- .../ui/primitives/scrollinput/scrollinput.go | 14 +- 5 files changed, 52 insertions(+), 165 deletions(-) diff --git a/internal/ui/messages/input/completion/completion.go b/internal/ui/messages/input/completion/completion.go index 7b693c0..522cc37 100644 --- a/internal/ui/messages/input/completion/completion.go +++ b/internal/ui/messages/input/completion/completion.go @@ -3,175 +3,61 @@ package completion import ( "github.com/diamondburned/cchat" "github.com/diamondburned/cchat-gtk/internal/gts/httputil" - "github.com/diamondburned/cchat-gtk/internal/ui/primitives" "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 + ImageSize = 25 + ImagePadding = 6 ) -// 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 + *completion.Completer + entries []cchat.CompletionEntry 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, - } - - 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) { - completion.SwapWord(v.buffer, v.entries[r.GetIndex()].Raw, v.offset) - v.Clear() - v.text.GrabFocus() // TODO: remove, maybe not needed - }) + v := &View{} + c := completion.NewCompleter(text, v) + v.Completer = c 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.Hide() 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() { +func (v *View) Update(words []string, i int) []gtk.IWidget { // If we don't have a completer, then don't run. if v.completer == nil { - return + return nil } - text, offset := v.getInputState() - words, index := split.SpaceIndexed(text, offset) + v.entries = v.completer.CompleteMessage(words, i) - // 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. + var widgets = make([]gtk.IWidget, len(v.entries)) 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 != "" { + img.SetSizeRequest(ImageSize, ImageSize) + img.Show() httputil.AsyncImageSized(img, entry.IconURL, ImageSize, ImageSize, imgutil.Round(true)) } @@ -180,19 +66,12 @@ func (v *View) Run() { 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) - } + widgets[i] = b } + + return widgets } -func (v *View) getInputState() (string, int) { - return completion.State(v.buffer) +func (v *View) Word(i int) string { + return v.entries[i].Raw } diff --git a/internal/ui/messages/input/input.go b/internal/ui/messages/input/input.go index 06d4674..b6384fb 100644 --- a/internal/ui/messages/input/input.go +++ b/internal/ui/messages/input/input.go @@ -16,7 +16,6 @@ type Controller interface { } type InputView struct { - *gtk.Box *Field Completer *completion.View } @@ -33,25 +32,19 @@ func NewView(ctrl Controller) *InputView { // Bind the text event handler to text first. c := completion.New(text) - c.Show() // Bind the input callback later. f := NewField(text, ctrl) f.Show() - b, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0) - b.PackStart(c, false, true, 0) - b.PackStart(f, false, false, 0) - b.Show() + // // Connect to the field's revealer. On resize, we want the autocompleter to + // // have the right padding too. + // f.username.Connect("size-allocate", func(w gtk.IWidget) { + // // Set the autocompleter's left margin to be the same. + // c.SetMarginStart(w.ToWidget().GetAllocatedWidth()) + // }) - // Connect to the field's revealer. On resize, we want the autocompleter to - // have the right padding too. - f.username.Connect("size-allocate", func(w gtk.IWidget) { - // Set the autocompleter's left margin to be the same. - c.SetMarginStart(w.ToWidget().GetAllocatedWidth()) - }) - - return &InputView{b, f, c} + return &InputView{f, c} } func (v *InputView) SetSender(session cchat.Session, sender cchat.ServerMessageSender) { diff --git a/internal/ui/primitives/completion/completer.go b/internal/ui/primitives/completion/completer.go index 9493c3a..ac83e93 100644 --- a/internal/ui/primitives/completion/completer.go +++ b/internal/ui/primitives/completion/completer.go @@ -1,6 +1,7 @@ package completion import ( + "github.com/diamondburned/cchat-gtk/internal/ui/primitives/scrollinput" "github.com/diamondburned/cchat/utils/split" "github.com/gotk3/gotk3/gtk" ) @@ -30,8 +31,12 @@ func NewCompleter(input *gtk.TextView, ctrl Completeable) *Completer { l, _ := gtk.ListBoxNew() l.Show() + s := scrollinput.NewVScroll(150) + s.Add(l) + s.Show() + p := NewPopover(input) - p.Add(l) + p.Add(s) c := &Completer{ Input: input, @@ -52,15 +57,19 @@ func NewCompleter(input *gtk.TextView, ctrl Completeable) *Completer { l.Connect("row-activated", func(l *gtk.ListBox, r *gtk.ListBoxRow) { SwapWord(ibuf, ctrl.Word(r.GetIndex()), c.Cursor) - c.clear() - c.Popover.Popdown() + c.Clear() + c.Hide() input.GrabFocus() }) return c } -func (c *Completer) clear() { +func (c *Completer) Hide() { + c.Popover.Popdown() +} + +func (c *Completer) Clear() { var children = c.List.GetChildren() if children.Length() == 0 { return @@ -74,7 +83,7 @@ func (c *Completer) clear() { } func (c *Completer) complete() { - c.clear() + c.Clear() var widgets []gtk.IWidget if len(c.Words) > 0 { @@ -85,7 +94,7 @@ func (c *Completer) complete() { c.Popover.SetPointingTo(CursorRect(c.Input)) c.Popover.Popup() } else { - c.Popover.Popdown() + c.Hide() } for i, widget := range widgets { diff --git a/internal/ui/primitives/completion/utils.go b/internal/ui/primitives/completion/utils.go index 057c973..37e0d0a 100644 --- a/internal/ui/primitives/completion/utils.go +++ b/internal/ui/primitives/completion/utils.go @@ -13,7 +13,7 @@ var popoverCSS = primitives.PrepareCSS(` `) const ( - MinPopoverWidth = 250 + MinPopoverWidth = 300 ) func NewPopover(relto gtk.IWidget) *gtk.Popover { diff --git a/internal/ui/primitives/scrollinput/scrollinput.go b/internal/ui/primitives/scrollinput/scrollinput.go index 1b01bf4..f74030e 100644 --- a/internal/ui/primitives/scrollinput/scrollinput.go +++ b/internal/ui/primitives/scrollinput/scrollinput.go @@ -2,15 +2,21 @@ package scrollinput import "github.com/gotk3/gotk3/gtk" +func NewVScroll(maxHeight int) *gtk.ScrolledWindow { + sw, _ := gtk.ScrolledWindowNew(nil, nil) + sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + sw.SetProperty("propagate-natural-height", true) + sw.SetProperty("max-content-height", maxHeight) + + return sw +} + func NewV(text *gtk.TextView, maxHeight int) *gtk.ScrolledWindow { // Wrap mode needed since we're not doing horizontal scrolling. text.SetWrapMode(gtk.WRAP_WORD_CHAR) - sw, _ := gtk.ScrolledWindowNew(nil, nil) + sw := NewVScroll(maxHeight) sw.Add(text) - sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - sw.SetProperty("propagate-natural-height", true) - sw.SetProperty("max-content-height", maxHeight) return sw }