1
0
Fork 0
mirror of https://github.com/diamondburned/cchat-gtk.git synced 2024-11-18 12:12:45 +00:00
cchat-gtk/internal/ui/messages/input/username/username.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

148 lines
3.6 KiB
Go

package username
import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/config"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
"github.com/diamondburned/cchat/text"
"github.com/diamondburned/imgutil"
"github.com/gotk3/gotk3/gtk"
)
const AvatarSize = 24
var showUser = true
var currentRevealer = func(bool) {} // noop by default
func init() {
// Bind this revealer in settings.
config.AppearanceAdd("Show Username in Input", config.Switch(
&showUser,
func(b bool) { currentRevealer(b) },
))
}
type Container struct {
*gtk.Revealer
main *gtk.Box
avatar *rich.Icon
label *rich.Label
}
var (
_ cchat.LabelContainer = (*Container)(nil)
_ cchat.IconContainer = (*Container)(nil)
)
var usernameCSS = primitives.PrepareCSS(`
.username-view {
margin: 8px 10px;
}
`)
func NewContainer() *Container {
avatar := rich.NewIcon(AvatarSize, imgutil.Round(true))
avatar.SetPlaceholderIcon("user-available-symbolic", AvatarSize)
avatar.Show()
label := rich.NewLabel(text.Rich{})
label.SetMaxWidthChars(35)
label.Show()
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 5)
box.PackStart(avatar, false, false, 0)
box.PackStart(label, false, false, 0)
box.SetVAlign(gtk.ALIGN_START)
box.Show()
primitives.AddClass(box, "username-view")
primitives.AttachCSS(box, usernameCSS)
rev, _ := gtk.RevealerNew()
rev.SetRevealChild(false)
rev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
rev.SetTransitionDuration(50)
rev.Add(box)
// Bind the current global revealer to this revealer for settings. This
// operation should be thread-safe, as everything is being done in the main
// thread.
currentRevealer = rev.SetRevealChild
return &Container{
Revealer: rev,
main: box,
avatar: avatar,
label: label,
}
}
func (u *Container) SetRevealChild(reveal bool) {
// Only reveal if showUser is true.
u.Revealer.SetRevealChild(reveal && showUser)
}
// shouldReveal returns whether or not the container should reveal.
func (u *Container) shouldReveal() bool {
return !u.label.GetLabel().Empty() && showUser
}
func (u *Container) Reset() {
u.SetRevealChild(false)
u.avatar.Reset()
u.label.Reset()
}
// Update is not thread-safe.
func (u *Container) Update(session cchat.Session, sender cchat.ServerMessageSender) {
// Set the fallback username.
u.label.SetLabelUnsafe(session.Name())
// Reveal the name if it's not empty.
u.SetRevealChild(u.shouldReveal())
// Does sender (aka Server) implement ServerNickname? If yes, use it.
if nicknamer, ok := sender.(cchat.ServerNickname); ok {
u.label.AsyncSetLabel(nicknamer.Nickname, "Error fetching server nickname")
}
// Does session implement an icon? Update if yes.
if iconer, ok := session.(cchat.Icon); ok {
u.avatar.AsyncSetIconer(iconer, "Error fetching session icon URL")
}
}
// GetLabel is not thread-safe.
func (u *Container) GetLabel() text.Rich {
return u.label.GetLabel()
}
// SetLabel is thread-safe.
func (u *Container) SetLabel(content text.Rich) {
gts.ExecAsync(func() {
u.label.SetLabelUnsafe(content)
// Reveal if the name is not empty.
u.SetRevealChild(u.shouldReveal())
})
}
// SetIcon is thread-safe.
func (u *Container) SetIcon(url string) {
gts.ExecAsync(func() {
u.avatar.SetIconUnsafe(url)
// Reveal if the icon URL is not empty. We don't touch anything if the
// URL is empty, as the name might not be.
if url != "" {
u.SetRevealChild(true)
}
})
}
// GetIconURL is not thread-safe.
func (u *Container) GetIconURL() string {
return u.avatar.URL()
}