workaround for member popovers disappearing

This commit is contained in:
diamondburned 2020-10-21 22:23:01 -07:00
parent 135be0faca
commit 89c2fd3456
2 changed files with 93 additions and 8 deletions

View File

@ -0,0 +1,71 @@
package memberlist
import (
"sync"
"github.com/diamondburned/cchat-gtk/internal/gts"
)
type EventQueuer interface {
Activate()
Deactivate()
}
// eventQueue is a rough unbounded event queue. A zero-value instance is valid.
type eventQueue struct {
mutex sync.Mutex
activated bool
// idleQueue contains the incoming callbacks to update events. This is a
// temporary hack to the issue of popovers disappearing when its parent
// widget, that is the list box, changes. This slice should then contain all
// those events to be executed only when the Popover is popped down.
idleQueue []func()
}
func (evq *eventQueue) Add(fn func()) {
evq.mutex.Lock()
defer evq.mutex.Unlock()
if evq.activated {
evq.idleQueue = append(evq.idleQueue, fn)
} else {
gts.ExecAsync(fn)
}
}
func (evq *eventQueue) Activate() {
evq.mutex.Lock()
defer evq.mutex.Unlock()
evq.activated = true
}
func (evq *eventQueue) pop() []func() {
evq.mutex.Lock()
defer evq.mutex.Unlock()
popped := evq.idleQueue
evq.idleQueue = nil
evq.activated = false
return popped
}
func (evq *eventQueue) Deactivate() {
var popped = evq.pop()
// We shouldn't try and run more than a certain amount of callbacks within a
// single loop, as it will freeze up the UI.
if len(popped) > 25 {
for _, fn := range popped {
gts.ExecAsync(fn)
}
return
}
gts.ExecAsync(func() {
for _, fn := range popped {
fn()
}
})
}

View File

@ -36,6 +36,8 @@ type Container struct {
// map id -> *Section // map id -> *Section
Sections map[string]*Section Sections map[string]*Section
stop func() stop func()
eventQueue eventQueue
} }
var memberListCSS = primitives.PrepareClassCSS("member-list", ` var memberListCSS = primitives.PrepareClassCSS("member-list", `
@ -110,15 +112,20 @@ func (c *Container) TryAsyncList(server cchat.Messenger) {
} }
func (c *Container) SetSections(sections []cchat.MemberSection) { func (c *Container) SetSections(sections []cchat.MemberSection) {
gts.ExecAsync(func() { c.SetSectionsUnsafe(sections) }) c.eventQueue.Add(func() { c.SetSectionsUnsafe(sections) })
} }
func (c *Container) SetMember(sectionID string, member cchat.ListMember) { func (c *Container) SetMember(sectionID string, member cchat.ListMember) {
gts.ExecAsync(func() { c.SetMemberUnsafe(sectionID, member) }) c.eventQueue.Add(func() { c.SetMemberUnsafe(sectionID, member) })
} }
func (c *Container) RemoveMember(sectionID string, id string) { func (c *Container) RemoveMember(sectionID string, id string) {
gts.ExecAsync(func() { c.RemoveMemberUnsafe(sectionID, id) }) c.eventQueue.Add(func() { c.RemoveMemberUnsafe(sectionID, id) })
}
type sectionInsert struct {
front *Section
sections []*Section
} }
func (c *Container) SetSectionsUnsafe(sections []cchat.MemberSection) { func (c *Container) SetSectionsUnsafe(sections []cchat.MemberSection) {
@ -127,7 +134,7 @@ func (c *Container) SetSectionsUnsafe(sections []cchat.MemberSection) {
for i, section := range sections { for i, section := range sections {
sc, ok := c.Sections[section.ID()] sc, ok := c.Sections[section.ID()]
if !ok { if !ok {
sc = NewSection(section) sc = NewSection(section, &c.eventQueue)
} else { } else {
sc.Update(section.Name(), section.Total()) sc.Update(section.Name(), section.Total())
} }
@ -191,7 +198,7 @@ var sectionBodyCSS = primitives.PrepareClassCSS("section-body", `
} }
`) `)
func NewSection(sect cchat.MemberSection) *Section { func NewSection(sect cchat.MemberSection, evq EventQueuer) *Section {
header := rich.NewLabel(text.Rich{}) header := rich.NewLabel(text.Rich{})
header.Show() header.Show()
sectionHeaderCSS(header) sectionHeaderCSS(header)
@ -216,8 +223,7 @@ func NewSection(sect cchat.MemberSection) *Section {
// Cold path; we can afford searching in the map. // Cold path; we can afford searching in the map.
for _, member := range members { for _, member := range members {
if member.ListBoxRow.GetIndex() == i { if member.ListBoxRow.GetIndex() == i {
member.Popup() member.Popup(evq)
return
} }
} }
}) })
@ -291,6 +297,8 @@ type Member struct {
Avatar *rich.Icon Avatar *rich.Icon
Name *gtk.Label Name *gtk.Label
output markup.RenderOutput output markup.RenderOutput
parent *gtk.ListBox
} }
const AvatarSize = 34 const AvatarSize = 34
@ -382,12 +390,18 @@ func (m *Member) Update(member cchat.ListMember) {
} }
// Popup pops up the mention popover if any. // Popup pops up the mention popover if any.
func (m *Member) Popup() { func (m *Member) Popup(evq EventQueuer) {
if len(m.output.Mentions) > 0 { if len(m.output.Mentions) > 0 {
p := labeluri.NewPopoverMentioner(m, m.output.Input, m.output.Mentions[0]) p := labeluri.NewPopoverMentioner(m, m.output.Input, m.output.Mentions[0])
p.Ref() // prevent the popover from closing itself p.Ref() // prevent the popover from closing itself
p.SetPosition(gtk.POS_LEFT) p.SetPosition(gtk.POS_LEFT)
p.Connect("closed", p.Unref) p.Connect("closed", p.Unref)
// Unbounded concurrency is kind of bad. We should deal with
// this in the future.
evq.Activate()
p.Connect("closed", evq.Deactivate)
p.Popup() p.Popup()
} }
} }