1
0
Fork 0
mirror of https://github.com/diamondburned/cchat-gtk.git synced 2024-12-22 20:27:07 +00:00

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
Sections map[string]*Section
stop func()
eventQueue eventQueue
}
var memberListCSS = primitives.PrepareClassCSS("member-list", `
@ -110,15 +112,20 @@ func (c *Container) TryAsyncList(server cchat.Messenger) {
}
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) {
gts.ExecAsync(func() { c.SetMemberUnsafe(sectionID, member) })
c.eventQueue.Add(func() { c.SetMemberUnsafe(sectionID, member) })
}
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) {
@ -127,7 +134,7 @@ func (c *Container) SetSectionsUnsafe(sections []cchat.MemberSection) {
for i, section := range sections {
sc, ok := c.Sections[section.ID()]
if !ok {
sc = NewSection(section)
sc = NewSection(section, &c.eventQueue)
} else {
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.Show()
sectionHeaderCSS(header)
@ -216,8 +223,7 @@ func NewSection(sect cchat.MemberSection) *Section {
// Cold path; we can afford searching in the map.
for _, member := range members {
if member.ListBoxRow.GetIndex() == i {
member.Popup()
return
member.Popup(evq)
}
}
})
@ -291,6 +297,8 @@ type Member struct {
Avatar *rich.Icon
Name *gtk.Label
output markup.RenderOutput
parent *gtk.ListBox
}
const AvatarSize = 34
@ -382,12 +390,18 @@ func (m *Member) Update(member cchat.ListMember) {
}
// Popup pops up the mention popover if any.
func (m *Member) Popup() {
func (m *Member) Popup(evq EventQueuer) {
if len(m.output.Mentions) > 0 {
p := labeluri.NewPopoverMentioner(m, m.output.Input, m.output.Mentions[0])
p.Ref() // prevent the popover from closing itself
p.SetPosition(gtk.POS_LEFT)
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()
}
}