cchat-gtk/internal/ui/primitives/roundimage/roundimage.go

147 lines
3.1 KiB
Go

package roundimage
import (
"context"
"math"
"github.com/diamondburned/cchat-gtk/internal/gts/httputil"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/imgutil"
"github.com/gotk3/gotk3/cairo"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
)
const (
pi = math.Pi
circle = 2 * math.Pi
)
type RadiusSetter interface {
SetRadius(float64)
}
type Connector interface {
ConnectHandlers(connector primitives.Connector)
}
type Imager interface {
gtk.IWidget
RadiusSetter
SetSizeRequest(w, h int)
// Embed setters.
httputil.ImageContainer
GetPixbuf() *gdk.Pixbuf
GetAnimation() *gdk.PixbufAnimation
GetImage() *gtk.Image
}
type Image struct {
*gtk.Image
Radius float64
procs []imgutil.Processor
}
var _ Imager = (*Image)(nil)
// NewImage creates a new round image. If radius is 0, then it will be half the
// dimensions. If the radius is less than 0, then nothing is rounded.
func NewImage(radius float64) (*Image, error) {
i, err := gtk.ImageNew()
if err != nil {
return nil, err
}
image := &Image{Image: i, Radius: radius}
// Connect to the draw callback and clip the context.
i.Connect("draw", image.drawer)
return image, nil
}
func (i *Image) AddProcessor(procs ...imgutil.Processor) {
i.procs = append(i.procs, procs...)
}
func (i *Image) GetImage() *gtk.Image {
return i.Image
}
func (i *Image) SetImageURL(url string) {
// No dynamic sizing support; yolo.
httputil.AsyncImage(context.Background(), i, url, i.procs...)
}
func (i *Image) SetRadius(r float64) {
i.Radius = r
}
func (i *Image) drawer(widget gtk.IWidget, cc *cairo.Context) bool {
var w = float64(i.GetAllocatedWidth())
var h = float64(i.GetAllocatedHeight())
var min = w
// Use the smallest side for radius calculation.
if h < w {
min = h
}
// Copy the variables in case we need to change them.
var r = i.Radius
switch {
// If radius is less than 0, then don't round.
case r < 0:
return false
// If radius is 0, then we have to calculate our own radius.:This only
// works if the image is a square.
case r == 0:
// Calculate the radius by dividing a side by 2.
r = (min / 2)
// Draw an arc from 0deg to 360deg.
cc.Arc(w/2, h/2, r, 0, circle)
// We have to do this so the arc paint doesn't leave back a black
// background instead of the usual alpha.
cc.SetSourceRGBA(255, 255, 255, 0)
// Clip the image with the arc we drew.
cc.Clip()
// If radius is more than 0, then we have to calculate the radius from
// the edges.
case r > 0:
// StackOverflow is godly.
// https://stackoverflow.com/a/6959843.
// Radius should be largest a single side divided by 2.
if max := min / 2; r > max {
r = max
}
// Draw 4 arcs at 4 corners.
cc.Arc(0+r, 0+r, r, 2*(pi/2), 3*(pi/2)) // top left
cc.Arc(w-r, 0+r, r, 3*(pi/2), 4*(pi/2)) // top right
cc.Arc(w-r, h-r, r, 0*(pi/2), 1*(pi/2)) // bottom right
cc.Arc(0+r, h-r, r, 1*(pi/2), 2*(pi/2)) // bottom left
// Close the created path.
cc.ClosePath()
cc.SetSourceRGBA(255, 255, 255, 0)
// Clip the image with the arc we drew.
cc.Clip()
}
// Paint the changes.
cc.Paint()
return false
}