2020-06-06 07:44:36 +00:00
|
|
|
package rich
|
|
|
|
|
|
|
|
import (
|
2020-06-17 07:06:34 +00:00
|
|
|
"context"
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
"github.com/diamondburned/cchat"
|
|
|
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
|
|
|
"github.com/diamondburned/cchat-gtk/internal/gts/httputil"
|
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
2020-07-11 06:48:44 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/roundimage"
|
2020-06-06 07:44:36 +00:00
|
|
|
"github.com/diamondburned/cchat/text"
|
|
|
|
"github.com/diamondburned/imgutil"
|
2020-08-17 00:13:47 +00:00
|
|
|
"github.com/gotk3/gotk3/gdk"
|
2020-06-06 07:44:36 +00:00
|
|
|
"github.com/gotk3/gotk3/gtk"
|
2020-07-14 07:24:55 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-06-06 07:44:36 +00:00
|
|
|
)
|
|
|
|
|
2020-06-30 03:39:42 +00:00
|
|
|
type IconerFn = func(context.Context, cchat.IconContainer) (func(), error)
|
|
|
|
|
2020-08-17 00:13:47 +00:00
|
|
|
type RoundIconContainer interface {
|
|
|
|
gtk.IWidget
|
|
|
|
httputil.ImageContainer
|
|
|
|
primitives.ImageIconSetter
|
|
|
|
roundimage.RadiusSetter
|
|
|
|
|
|
|
|
GetStorageType() gtk.ImageType
|
|
|
|
GetPixbuf() *gdk.Pixbuf
|
|
|
|
GetAnimation() *gdk.PixbufAnimation
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
_ RoundIconContainer = (*roundimage.Image)(nil)
|
|
|
|
_ RoundIconContainer = (*roundimage.StaticImage)(nil)
|
|
|
|
)
|
|
|
|
|
2020-07-11 06:48:44 +00:00
|
|
|
// Icon represents a rounded image container.
|
2020-06-06 07:44:36 +00:00
|
|
|
type Icon struct {
|
|
|
|
*gtk.Revealer
|
2020-08-17 00:13:47 +00:00
|
|
|
Image RoundIconContainer
|
2020-07-04 04:41:12 +00:00
|
|
|
procs []imgutil.Processor
|
|
|
|
size int
|
2020-06-17 07:06:34 +00:00
|
|
|
|
2020-07-04 04:41:12 +00:00
|
|
|
r *Reusable
|
2020-06-07 07:06:13 +00:00
|
|
|
|
|
|
|
// state
|
|
|
|
url string
|
2020-06-06 07:44:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const DefaultIconSize = 16
|
|
|
|
|
|
|
|
var _ cchat.IconContainer = (*Icon)(nil)
|
|
|
|
|
|
|
|
func NewIcon(sizepx int, procs ...imgutil.Processor) *Icon {
|
2020-08-17 00:13:47 +00:00
|
|
|
img, _ := roundimage.NewImage(0)
|
|
|
|
img.Show()
|
|
|
|
return NewCustomIcon(img, sizepx, procs...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewCustomIcon(img RoundIconContainer, sizepx int, procs ...imgutil.Processor) *Icon {
|
2020-06-06 07:44:36 +00:00
|
|
|
if sizepx == 0 {
|
|
|
|
sizepx = DefaultIconSize
|
|
|
|
}
|
|
|
|
|
|
|
|
rev, _ := gtk.RevealerNew()
|
|
|
|
rev.Add(img)
|
|
|
|
rev.SetRevealChild(false)
|
|
|
|
rev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
|
|
|
|
rev.SetTransitionDuration(50)
|
|
|
|
|
2020-06-17 07:06:34 +00:00
|
|
|
i := &Icon{
|
2020-06-06 07:44:36 +00:00
|
|
|
Revealer: rev,
|
|
|
|
Image: img,
|
|
|
|
procs: procs,
|
|
|
|
}
|
2020-06-17 07:06:34 +00:00
|
|
|
i.SetSize(sizepx)
|
2020-07-04 04:41:12 +00:00
|
|
|
i.r = NewReusable(func(ni *nullIcon) {
|
|
|
|
i.SetIconUnsafe(ni.url)
|
|
|
|
})
|
2020-06-17 07:06:34 +00:00
|
|
|
|
|
|
|
return i
|
2020-06-06 07:44:36 +00:00
|
|
|
}
|
|
|
|
|
2020-06-07 07:06:13 +00:00
|
|
|
// Reset wipes the state to be just after construction.
|
|
|
|
func (i *Icon) Reset() {
|
|
|
|
i.url = ""
|
2020-07-04 04:41:12 +00:00
|
|
|
i.r.Invalidate() // invalidate async fetching images
|
2020-06-07 07:06:13 +00:00
|
|
|
i.Revealer.SetRevealChild(false)
|
2020-06-13 07:29:32 +00:00
|
|
|
i.Image.SetFromPixbuf(nil) // destroy old pb
|
2020-06-07 07:06:13 +00:00
|
|
|
}
|
|
|
|
|
2020-06-07 04:27:28 +00:00
|
|
|
// URL is not thread-safe.
|
|
|
|
func (i *Icon) URL() string {
|
|
|
|
return i.url
|
|
|
|
}
|
|
|
|
|
2020-06-17 07:06:34 +00:00
|
|
|
func (i *Icon) Size() int {
|
|
|
|
return i.size
|
|
|
|
}
|
|
|
|
|
2020-06-07 04:27:28 +00:00
|
|
|
func (i *Icon) CopyPixbuf(dst httputil.ImageContainer) {
|
|
|
|
switch i.Image.GetStorageType() {
|
|
|
|
case gtk.IMAGE_PIXBUF:
|
|
|
|
dst.SetFromPixbuf(i.Image.GetPixbuf())
|
|
|
|
case gtk.IMAGE_ANIMATION:
|
|
|
|
dst.SetFromAnimation(i.Image.GetAnimation())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Thread-unsafe setter methods should only be called right after construction.
|
2020-06-06 07:44:36 +00:00
|
|
|
|
|
|
|
// SetPlaceholderIcon is not thread-safe.
|
|
|
|
func (i *Icon) SetPlaceholderIcon(iconName string, iconSzPx int) {
|
2020-07-14 07:24:55 +00:00
|
|
|
i.Image.SetRadius(-1) // square
|
2020-06-06 07:44:36 +00:00
|
|
|
i.SetRevealChild(true)
|
|
|
|
|
|
|
|
if iconName != "" {
|
2020-08-17 00:13:47 +00:00
|
|
|
primitives.SetImageIcon(i.Image, iconName, iconSzPx)
|
2020-06-06 07:44:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetSize is not thread-safe.
|
|
|
|
func (i *Icon) SetSize(szpx int) {
|
2020-06-17 07:06:34 +00:00
|
|
|
i.size = szpx
|
2020-06-06 07:44:36 +00:00
|
|
|
i.Image.SetSizeRequest(szpx, szpx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddProcessors is not thread-safe.
|
|
|
|
func (i *Icon) AddProcessors(procs ...imgutil.Processor) {
|
|
|
|
i.procs = append(i.procs, procs...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetIcon is thread-safe.
|
|
|
|
func (i *Icon) SetIcon(url string) {
|
2020-06-17 07:06:34 +00:00
|
|
|
gts.ExecAsync(func() { i.SetIconUnsafe(url) })
|
|
|
|
}
|
|
|
|
|
2020-07-14 07:24:55 +00:00
|
|
|
func (i *Icon) AsyncSetIconer(iconer cchat.Icon, errwrap string) {
|
2020-07-04 04:41:12 +00:00
|
|
|
AsyncUse(i.r, func(ctx context.Context) (interface{}, func(), error) {
|
2020-06-30 03:39:42 +00:00
|
|
|
ni := &nullIcon{}
|
|
|
|
f, err := iconer.Icon(ctx, ni)
|
2020-07-14 07:24:55 +00:00
|
|
|
return ni, f, errors.Wrap(err, errwrap)
|
2020-06-17 07:06:34 +00:00
|
|
|
})
|
2020-06-13 07:29:32 +00:00
|
|
|
}
|
|
|
|
|
2020-06-07 04:27:28 +00:00
|
|
|
// SetIconUnsafe is not thread-safe.
|
|
|
|
func (i *Icon) SetIconUnsafe(url string) {
|
2020-07-14 07:24:55 +00:00
|
|
|
i.Image.SetRadius(0) // round
|
2020-06-07 04:27:28 +00:00
|
|
|
i.SetRevealChild(true)
|
2020-06-06 07:44:36 +00:00
|
|
|
i.url = url
|
|
|
|
i.updateAsync()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Icon) updateAsync() {
|
2020-07-11 06:48:44 +00:00
|
|
|
httputil.AsyncImageSized(i.Image, i.url, i.size, i.size, i.procs...)
|
2020-06-06 07:44:36 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 05:41:21 +00:00
|
|
|
type EventIcon struct {
|
|
|
|
*gtk.EventBox
|
|
|
|
Icon *Icon
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewEventIcon(sizepx int, pp ...imgutil.Processor) *EventIcon {
|
|
|
|
icn := NewIcon(sizepx, pp...)
|
|
|
|
icn.Show()
|
|
|
|
|
|
|
|
evb, _ := gtk.EventBoxNew()
|
|
|
|
evb.Add(icn)
|
|
|
|
|
|
|
|
return &EventIcon{
|
|
|
|
EventBox: evb,
|
|
|
|
Icon: icn,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
type ToggleButtonImage struct {
|
|
|
|
gtk.ToggleButton
|
|
|
|
Labeler
|
|
|
|
cchat.IconContainer
|
|
|
|
|
|
|
|
Label *gtk.Label
|
|
|
|
Image *Icon
|
|
|
|
|
|
|
|
Box *gtk.Box
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
_ gtk.IWidget = (*ToggleButton)(nil)
|
|
|
|
_ cchat.LabelContainer = (*ToggleButton)(nil)
|
|
|
|
)
|
|
|
|
|
|
|
|
func NewToggleButtonImage(content text.Rich) *ToggleButtonImage {
|
|
|
|
l := NewLabel(content)
|
|
|
|
l.Show()
|
|
|
|
|
2020-08-17 00:13:47 +00:00
|
|
|
img, _ := roundimage.NewStaticImage(nil, 0)
|
|
|
|
img.Show()
|
|
|
|
i := NewCustomIcon(img, 0)
|
2020-06-06 07:44:36 +00:00
|
|
|
i.Show()
|
|
|
|
|
|
|
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
|
|
|
box.PackStart(i, false, false, 0)
|
|
|
|
box.PackStart(l, true, true, 5)
|
|
|
|
box.Show()
|
|
|
|
|
|
|
|
b, _ := gtk.ToggleButtonNew()
|
|
|
|
b.Add(box)
|
|
|
|
|
2020-08-17 00:13:47 +00:00
|
|
|
img.ConnectHandlers(b)
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
return &ToggleButtonImage{
|
|
|
|
ToggleButton: *b,
|
|
|
|
Labeler: l, // easy inheritance of methods
|
|
|
|
IconContainer: i,
|
|
|
|
|
|
|
|
Label: &l.Label,
|
|
|
|
Image: i,
|
|
|
|
Box: box,
|
|
|
|
}
|
|
|
|
}
|
2020-06-17 07:06:34 +00:00
|
|
|
|
|
|
|
func (t *ToggleButtonImage) Reset() {
|
|
|
|
t.Labeler.Reset()
|
|
|
|
t.Image.Reset()
|
|
|
|
}
|