cchat-gtk/internal/ui/messages/container/cozy/message_full.go

236 lines
6.4 KiB
Go
Raw Normal View History

2020-06-07 04:27:28 +00:00
package cozy
import (
"time"
2020-06-07 04:27:28 +00:00
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts/httputil"
"github.com/diamondburned/cchat-gtk/internal/humanize"
2020-06-07 04:27:28 +00:00
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
2020-07-16 05:41:21 +00:00
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/roundimage"
"github.com/diamondburned/cchat-gtk/internal/ui/rich/labeluri"
2021-01-05 02:05:33 +00:00
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
"github.com/diamondburned/cchat/text"
2020-12-25 08:32:08 +00:00
"github.com/gotk3/gotk3/cairo"
2020-06-07 04:27:28 +00:00
"github.com/gotk3/gotk3/gtk"
)
// TopFullMargin is the margin on top of every full message.
2020-06-26 04:11:31 +00:00
const TopFullMargin = 4
2020-06-07 04:27:28 +00:00
type FullMessage struct {
*message.GenericContainer
// Grid widgets.
Avatar *Avatar
2020-06-07 04:27:28 +00:00
MainBox *gtk.Box // wraps header and content
2021-01-05 02:05:33 +00:00
Header *labeluri.Label
timestamp string // markup
2020-06-07 04:27:28 +00:00
}
type AvatarPixbufCopier interface {
2021-01-05 02:05:33 +00:00
CopyAvatarPixbuf(img httputil.SurfaceContainer) bool
}
2020-06-07 04:27:28 +00:00
var (
_ AvatarPixbufCopier = (*FullMessage)(nil)
_ message.Container = (*FullMessage)(nil)
_ container.MessageRow = (*FullMessage)(nil)
2020-06-07 04:27:28 +00:00
)
var avatarCSS = primitives.PrepareClassCSS("cozy-avatar", `
/* Slightly dip down on click */
.cozy-avatar:active {
margin-top: 1px;
}
`)
2020-06-07 04:27:28 +00:00
func NewFullMessage(msg cchat.MessageCreate) *FullMessage {
msgc := WrapFullMessage(message.NewContainer(msg))
// Don't update the avatar. NewMessage in controller will try and reuse the
// pixbuf if possible.
2020-06-07 04:27:28 +00:00
msgc.UpdateAuthorName(msg.Author().Name())
msgc.UpdateTimestamp(msg.Time())
msgc.UpdateContent(msg.Content(), false)
2020-06-07 04:27:28 +00:00
return msgc
}
func WrapFullMessage(gc *message.GenericContainer) *FullMessage {
2021-01-05 02:05:33 +00:00
header := labeluri.NewLabel(text.Rich{})
header.SetHAlign(gtk.ALIGN_START) // left-align
header.SetMaxWidthChars(100)
header.Show()
avatar := NewAvatar()
avatar.SetMarginTop(TopFullMargin / 2)
2020-06-07 04:27:28 +00:00
avatar.SetMarginStart(container.ColumnSpacing * 2)
avatar.Connect("clicked", func(w gtk.IWidget) {
2021-01-05 02:05:33 +00:00
if output := header.Output(); len(output.Mentions) > 0 {
labeluri.PopoverMentioner(w, output.Input, output.Mentions[0])
}
})
avatar.Show()
// Attach the class and CSS for the left avatar.
avatarCSS(avatar)
2020-06-07 04:27:28 +00:00
2020-07-06 00:19:48 +00:00
// Attach the username style provider.
2021-01-05 02:05:33 +00:00
// primitives.AttachCSS(gc.Username, boldCSS)
2020-06-07 04:27:28 +00:00
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
main.PackStart(header, false, false, 0)
2020-06-26 04:11:31 +00:00
main.PackStart(gc.Content, false, false, 0)
main.SetMarginTop(TopFullMargin)
2020-06-07 04:27:28 +00:00
main.SetMarginEnd(container.ColumnSpacing * 2)
main.SetMarginStart(container.ColumnSpacing)
2020-06-07 04:27:28 +00:00
main.Show()
// Also attach a class for the main box shown on the right.
primitives.AddClass(main, "cozy-main")
gc.PackStart(avatar, false, false, 0)
gc.PackStart(main, true, true, 0)
gc.SetClass("cozy-full")
2020-06-07 04:27:28 +00:00
return &FullMessage{
GenericContainer: gc,
2021-01-05 02:05:33 +00:00
Avatar: avatar,
MainBox: main,
Header: header,
2020-06-07 04:27:28 +00:00
}
}
func (m *FullMessage) Collapsed() bool { return false }
func (m *FullMessage) Unwrap() *message.GenericContainer {
// Remove GenericContainer's widgets from the containers.
2021-01-05 02:05:33 +00:00
m.Header.Destroy()
m.MainBox.Remove(m.Content) // not ours, so don't destroy.
2020-08-20 23:53:13 +00:00
// Remove the message from the grid.
2021-01-05 02:05:33 +00:00
m.Avatar.Destroy()
m.MainBox.Destroy()
2020-08-20 23:53:13 +00:00
// Return after removing.
return m.GenericContainer
}
func (m *FullMessage) UpdateTimestamp(t time.Time) {
m.GenericContainer.UpdateTimestamp(t)
2021-01-05 02:05:33 +00:00
m.timestamp = " " +
`<span alpha="70%" size="small">` + humanize.TimeAgoLong(t) + `</span>`
// Update the timestamp.
m.Header.SetMarkup(m.Header.Output().Markup + m.timestamp)
}
2020-10-15 06:32:11 +00:00
func (m *FullMessage) UpdateAuthor(author cchat.Author) {
2021-01-05 02:05:33 +00:00
// Call the parent's method to update the state.
2020-06-07 04:27:28 +00:00
m.GenericContainer.UpdateAuthor(author)
2021-01-05 02:05:33 +00:00
m.UpdateAuthorName(author.Name())
2020-10-15 06:32:11 +00:00
m.Avatar.SetURL(author.Avatar())
2020-06-07 04:27:28 +00:00
}
2021-01-05 02:05:33 +00:00
func (m *FullMessage) UpdateAuthorName(name text.Rich) {
cfg := markup.RenderConfig{}
cfg.NoReferencing = true
cfg.SetForegroundAnchor(m.ContentBodyStyle)
output := markup.RenderCmplxWithConfig(name, cfg)
output.Markup = `<span font_weight="600">` + output.Markup + "</span>"
m.Header.SetMarkup(output.Markup + m.timestamp)
m.Header.SetUnderlyingOutput(output)
}
// CopyAvatarPixbuf sets the pixbuf into the given container. This shares the
// same pixbuf, but gtk.Image should take its own reference from the pixbuf.
2021-01-05 02:05:33 +00:00
func (m *FullMessage) CopyAvatarPixbuf(dst httputil.SurfaceContainer) bool {
2020-12-25 08:32:08 +00:00
switch img := m.Avatar.Image.GetImage(); img.GetStorageType() {
2020-06-07 04:27:28 +00:00
case gtk.IMAGE_PIXBUF:
dst.SetFromPixbuf(img.GetPixbuf())
2020-06-07 04:27:28 +00:00
case gtk.IMAGE_ANIMATION:
dst.SetFromAnimation(img.GetAnimation())
2020-12-25 08:32:08 +00:00
case gtk.IMAGE_SURFACE:
v, _ := img.GetProperty("surface")
dst.SetFromSurface(v.(*cairo.Surface))
2021-01-05 02:05:33 +00:00
default:
return false
2020-06-07 04:27:28 +00:00
}
2021-01-05 02:05:33 +00:00
return true
2020-06-07 04:27:28 +00:00
}
func (m *FullMessage) AttachMenu(items []menu.Item) {
// Bind to parent's container as well.
m.GenericContainer.AttachMenu(items)
// Bind to the box.
// TODO lol
}
2020-06-07 04:27:28 +00:00
type FullSendingMessage struct {
message.PresendContainer
FullMessage
}
var (
_ message.Container = (*FullSendingMessage)(nil)
_ container.MessageRow = (*FullSendingMessage)(nil)
2020-06-07 04:27:28 +00:00
)
func NewFullSendingMessage(msg input.PresendMessage) *FullSendingMessage {
var msgc = message.NewPresendContainer(msg)
return &FullSendingMessage{
PresendContainer: msgc,
FullMessage: *WrapFullMessage(msgc.GenericContainer),
}
}
2020-06-07 07:06:13 +00:00
type Avatar struct {
roundimage.Button
2020-12-25 08:32:08 +00:00
Image *roundimage.StaticImage
url string
}
func NewAvatar() *Avatar {
2020-08-20 23:53:13 +00:00
img, _ := roundimage.NewStaticImage(nil, 0)
img.SetSizeRequest(AvatarSize, AvatarSize)
2020-08-17 00:13:47 +00:00
img.Show()
2020-08-20 23:53:13 +00:00
avatar, _ := roundimage.NewCustomButton(img)
avatar.SetVAlign(gtk.ALIGN_START)
2020-08-17 00:13:47 +00:00
// Default icon.
2020-08-17 00:13:47 +00:00
primitives.SetImageIcon(img, "user-available-symbolic", AvatarSize)
return &Avatar{*avatar, img, ""}
}
// SetURL updates the Avatar to be that URL. It does nothing if URL is empty or
// matches the existing one.
func (a *Avatar) SetURL(url string) {
// Check if the URL is the same. This will save us quite a few requests, as
// some methods rely on the side-effects of other methods, and they may call
// UpdateAuthor multiple times.
if a.url == url || url == "" {
2020-06-07 07:06:13 +00:00
return
}
a.url = url
2020-12-25 08:32:08 +00:00
a.Image.SetImageURL(url)
}
// ManuallySetURL sets the URL without downloading the image. It assumes the
// pixbuf is borrowed elsewhere.
func (a *Avatar) ManuallySetURL(url string) {
a.url = url
2020-06-07 07:06:13 +00:00
}