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:
parent
135be0faca
commit
89c2fd3456
71
internal/ui/messages/memberlist/eventqueue.go
Normal file
71
internal/ui/messages/memberlist/eventqueue.go
Normal 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()
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue