ABSTRACTIONS! THEY'RE EVERYWHERE!
This commit is contained in:
parent
0171ac6b52
commit
2d628fbbb3
5
go.mod
5
go.mod
|
@ -8,9 +8,14 @@ require (
|
||||||
github.com/Xuanwo/go-locale v0.2.0
|
github.com/Xuanwo/go-locale v0.2.0
|
||||||
github.com/diamondburned/cchat v0.0.15
|
github.com/diamondburned/cchat v0.0.15
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200605224934-31a53c555ea2
|
github.com/diamondburned/cchat-mock v0.0.0-20200605224934-31a53c555ea2
|
||||||
|
github.com/diamondburned/imgutil v0.0.0-20200606035324-63abbc0fdea6
|
||||||
|
github.com/die-net/lrucache v0.0.0-20190707192454-883874fe3947
|
||||||
github.com/goodsign/monday v1.0.0
|
github.com/goodsign/monday v1.0.0
|
||||||
|
github.com/google/btree v1.0.0 // indirect
|
||||||
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
||||||
github.com/markbates/pkger v0.17.0
|
github.com/markbates/pkger v0.17.0
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717
|
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717
|
||||||
)
|
)
|
||||||
|
|
16
go.sum
16
go.sum
|
@ -9,6 +9,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/diamondburned/cchat v0.0.15 h1:1o4OX8zw/CdSv3Idaylz7vjHVOZKEi/xkg8BpEvtsHY=
|
github.com/diamondburned/cchat v0.0.15 h1:1o4OX8zw/CdSv3Idaylz7vjHVOZKEi/xkg8BpEvtsHY=
|
||||||
github.com/diamondburned/cchat v0.0.15/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
github.com/diamondburned/cchat v0.0.15/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||||
|
github.com/diamondburned/imgutil v0.0.0-20200606035324-63abbc0fdea6 h1:APALM1hskCByjOVW9CoUwjg0TIJgKZ62dgFr/9soqss=
|
||||||
|
github.com/diamondburned/imgutil v0.0.0-20200606035324-63abbc0fdea6/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
|
||||||
|
github.com/die-net/lrucache v0.0.0-20190707192454-883874fe3947 h1:U/5Sq2nJQ0XDyks+8ATghtHSuquIGq7JYrqSrvtR2dg=
|
||||||
|
github.com/die-net/lrucache v0.0.0-20190707192454-883874fe3947/go.mod h1:KsMcjmY1UCGl7ozPbdVPDOvLaFeXnptSvtNRczhxNto=
|
||||||
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
|
github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
|
||||||
|
@ -17,10 +23,14 @@ github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7
|
||||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
||||||
github.com/goodsign/monday v1.0.0/go.mod h1:r4T4breXpoFwspQNM+u2sLxJb2zyTaxVGqUfTBjWOu8=
|
github.com/goodsign/monday v1.0.0/go.mod h1:r4T4breXpoFwspQNM+u2sLxJb2zyTaxVGqUfTBjWOu8=
|
||||||
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194 h1:bB6XWpxMt2isCWqzjXN8tfVazjxvD8nRJrNoKcL0xAc=
|
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194 h1:bB6XWpxMt2isCWqzjXN8tfVazjxvD8nRJrNoKcL0xAc=
|
||||||
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
@ -30,6 +40,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/markbates/pkger v0.17.0 h1:RFfyBPufP2V6cddUyyEVSHBpaAnM1WzaMNyqomeT+iY=
|
github.com/markbates/pkger v0.17.0 h1:RFfyBPufP2V6cddUyyEVSHBpaAnM1WzaMNyqomeT+iY=
|
||||||
github.com/markbates/pkger v0.17.0/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
|
github.com/markbates/pkger v0.17.0/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
|
||||||
|
github.com/peterbourgon/diskv v1.0.0 h1:bRU92KzrX3TQ6IYobfie/PnZkFC+1opBfHpf/PHPDoo=
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
@ -40,11 +53,14 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717 h1:3M/uUZajYn/082wzUajekePxpUAZhMTfXvI9R+26SJ0=
|
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717 h1:3M/uUZajYn/082wzUajekePxpUAZhMTfXvI9R+26SJ0=
|
||||||
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
|
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package httputil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
|
"github.com/die-net/lrucache"
|
||||||
|
"github.com/gregjones/httpcache"
|
||||||
|
"github.com/gregjones/httpcache/diskcache"
|
||||||
|
"github.com/peterbourgon/diskv"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dskcached *http.Client
|
||||||
|
var memcached *http.Client
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var basePath = filepath.Join(os.TempDir(), "cchat-gtk-pridemonth")
|
||||||
|
|
||||||
|
http.DefaultClient.Timeout = 15 * time.Second
|
||||||
|
|
||||||
|
dskcached = &(*http.DefaultClient)
|
||||||
|
dskcached.Transport = httpcache.NewTransport(
|
||||||
|
diskcache.NewWithDiskv(diskv.New(diskv.Options{
|
||||||
|
BasePath: basePath,
|
||||||
|
TempDir: filepath.Join(basePath, "tmp"),
|
||||||
|
PathPerm: 0750,
|
||||||
|
FilePerm: 0750,
|
||||||
|
Compression: diskv.NewZlibCompressionLevel(2),
|
||||||
|
CacheSizeMax: 25 * 1024 * 1024, // 25 MiB in memory
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
memcached = &(*http.DefaultClient)
|
||||||
|
memcached.Transport = httpcache.NewTransport(lrucache.New(
|
||||||
|
25*1024*1024, // 25 MiB in memory
|
||||||
|
secs(2*time.Hour), // 2 hours cache
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func secs(dura time.Duration) int64 {
|
||||||
|
return int64(dura / time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AsyncStreamUncached(url string, fn func(r io.Reader)) {
|
||||||
|
gts.Async(func() (func(), error) {
|
||||||
|
r, err := get(url, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
fn(r.Body)
|
||||||
|
r.Body.Close()
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func AsyncStream(url string, fn func(r io.Reader)) {
|
||||||
|
gts.Async(func() (func(), error) {
|
||||||
|
r, err := get(url, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
fn(r.Body)
|
||||||
|
r.Body.Close()
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(url string, cached bool) (r *http.Response, err error) {
|
||||||
|
if cached {
|
||||||
|
r, err = dskcached.Get(url)
|
||||||
|
} else {
|
||||||
|
r, err = memcached.Get(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode < 200 || r.StatusCode > 299 {
|
||||||
|
r.Body.Close()
|
||||||
|
return nil, errors.Errorf("Unexpected status %d", r.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package httputil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
|
"github.com/diamondburned/imgutil"
|
||||||
|
"github.com/gotk3/gotk3/gdk"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AsyncImage loads an image. This method uses the cache.
|
||||||
|
func AsyncImage(img *gtk.Image, url string, procs ...imgutil.Processor) {
|
||||||
|
go asyncImage(img, url, procs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncImage(img *gtk.Image, url string, procs ...imgutil.Processor) {
|
||||||
|
r, err := get(url, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
l, err := gdk.PixbufLoaderNew()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to make pixbuf loader"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gif := strings.Contains(url, ".gif")
|
||||||
|
|
||||||
|
// This is a very important signal, so we must do it synchronously. Gotk3's
|
||||||
|
// callback implementation requires all connects to be synchronous to a
|
||||||
|
// certain thread.
|
||||||
|
gts.ExecSync(func() {
|
||||||
|
l.Connect("area-prepared", func() {
|
||||||
|
if gif {
|
||||||
|
p, err := l.GetPixbuf()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to get pixbuf"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
img.SetFromPixbuf(p)
|
||||||
|
} else {
|
||||||
|
p, err := l.GetAnimation()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to get animation"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
img.SetFromAnimation(p)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// If we have processors, then write directly in there.
|
||||||
|
if len(procs) > 0 {
|
||||||
|
if !gif {
|
||||||
|
err = imgutil.ProcessStream(l, r.Body, procs)
|
||||||
|
} else {
|
||||||
|
err = imgutil.ProcessAnimationStream(l, r.Body, procs)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Else, directly copy the body over.
|
||||||
|
_, err = io.Copy(l, r.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Error processing image"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.Close(); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to close pixbuf"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncImageSized resizes using GdkPixbuf. This method does not use the cache.
|
||||||
|
func AsyncImageSized(img *gtk.Image, url string, w, h int, procs ...imgutil.Processor) {
|
||||||
|
// TODO
|
||||||
|
panic("TODO")
|
||||||
|
}
|
|
@ -1,156 +0,0 @@
|
||||||
package compact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"html"
|
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/message/autoscroll"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/message/input"
|
|
||||||
"github.com/gotk3/gotk3/gtk"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Container struct {
|
|
||||||
*autoscroll.ScrolledWindow
|
|
||||||
main *gtk.Grid
|
|
||||||
messages map[string]*Message
|
|
||||||
nonceMsgs map[string]*Message
|
|
||||||
|
|
||||||
bottomed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewContainer() *Container {
|
|
||||||
grid, _ := gtk.GridNew()
|
|
||||||
grid.SetColumnSpacing(10)
|
|
||||||
grid.SetRowSpacing(5)
|
|
||||||
grid.SetMarginStart(5)
|
|
||||||
grid.SetMarginEnd(5)
|
|
||||||
grid.SetMarginBottom(5)
|
|
||||||
grid.Show()
|
|
||||||
|
|
||||||
sw := autoscroll.NewScrolledWindow()
|
|
||||||
sw.Add(grid)
|
|
||||||
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
|
|
||||||
sw.Show()
|
|
||||||
|
|
||||||
container := Container{
|
|
||||||
ScrolledWindow: sw,
|
|
||||||
main: grid,
|
|
||||||
messages: map[string]*Message{},
|
|
||||||
nonceMsgs: map[string]*Message{},
|
|
||||||
bottomed: true, // bottomed by default.
|
|
||||||
}
|
|
||||||
|
|
||||||
return &container
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Reset() {
|
|
||||||
// does this actually work?
|
|
||||||
var rows = c.len()
|
|
||||||
for i := 0; i < rows; i++ {
|
|
||||||
c.main.RemoveRow(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.messages = map[string]*Message{}
|
|
||||||
c.nonceMsgs = map[string]*Message{}
|
|
||||||
|
|
||||||
// default to being bottomed
|
|
||||||
c.bottomed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) len() int {
|
|
||||||
return len(c.messages) + len(c.nonceMsgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PresendMessage is not thread-safe.
|
|
||||||
func (c *Container) PresendMessage(msg input.PresendMessage) func(error) {
|
|
||||||
msgc := NewPresendMessage(msg.Content(), msg.Author(), msg.AuthorID(), msg.Nonce())
|
|
||||||
msgc.index = c.len()
|
|
||||||
|
|
||||||
c.nonceMsgs[msgc.Nonce] = &msgc
|
|
||||||
msgc.Attach(c.main, msgc.index)
|
|
||||||
|
|
||||||
return func(err error) {
|
|
||||||
msgc.SetSensitive(true)
|
|
||||||
|
|
||||||
// Did we fail?
|
|
||||||
if err != nil {
|
|
||||||
msgc.Content.SetMarkup(fmt.Sprintf(
|
|
||||||
`<span color="red">%s</span>`,
|
|
||||||
html.EscapeString(msgc.Content.GetLabel()),
|
|
||||||
))
|
|
||||||
msgc.Content.SetTooltipText(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindMessage is not thread-safe.
|
|
||||||
func (c *Container) FindMessage(msg cchat.MessageHeader) *Message {
|
|
||||||
// Search using the ID first.
|
|
||||||
m, ok := c.messages[msg.ID()]
|
|
||||||
if ok {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this an existing message?
|
|
||||||
if noncer, ok := msg.(cchat.MessageNonce); ok {
|
|
||||||
var nonce = noncer.Nonce()
|
|
||||||
|
|
||||||
m, ok := c.nonceMsgs[nonce]
|
|
||||||
if ok {
|
|
||||||
// Move the message outside nonceMsgs.
|
|
||||||
delete(c.nonceMsgs, nonce)
|
|
||||||
c.messages[msg.ID()] = m
|
|
||||||
|
|
||||||
// Set the right ID.
|
|
||||||
m.ID = msg.ID()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) CreateMessage(msg cchat.MessageCreate) {
|
|
||||||
gts.ExecAsync(func() {
|
|
||||||
// Attempt update before insert (aka upsert).
|
|
||||||
if msgc := c.FindMessage(msg); msgc != nil {
|
|
||||||
msgc.SetSensitive(true)
|
|
||||||
msgc.UpdateAuthor(msg.Author())
|
|
||||||
msgc.UpdateContent(msg.Content())
|
|
||||||
msgc.UpdateTimestamp(msg.Time())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msgc := NewMessage(msg)
|
|
||||||
msgc.index = c.len() // unsure
|
|
||||||
|
|
||||||
c.messages[msgc.ID] = &msgc
|
|
||||||
msgc.Attach(c.main, msgc.index)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) UpdateMessage(msg cchat.MessageUpdate) {
|
|
||||||
gts.ExecAsync(func() {
|
|
||||||
if msgc := c.FindMessage(msg); msgc != nil {
|
|
||||||
if author := msg.Author(); author != nil {
|
|
||||||
msgc.UpdateAuthor(author)
|
|
||||||
}
|
|
||||||
if content := msg.Content(); !content.Empty() {
|
|
||||||
msgc.UpdateContent(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) DeleteMessage(msg cchat.MessageDelete) {
|
|
||||||
gts.ExecAsync(func() {
|
|
||||||
// TODO: add nonce check.
|
|
||||||
if m, ok := c.messages[msg.ID()]; ok {
|
|
||||||
delete(c.messages, msg.ID())
|
|
||||||
c.main.RemoveRow(m.index)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package cozy
|
|
|
@ -5,7 +5,7 @@ import "github.com/gotk3/gotk3/gtk"
|
||||||
type ScrolledWindow struct {
|
type ScrolledWindow struct {
|
||||||
gtk.ScrolledWindow
|
gtk.ScrolledWindow
|
||||||
vadj gtk.Adjustment
|
vadj gtk.Adjustment
|
||||||
bottomed bool // :floshed:
|
Bottomed bool // :floshed:
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScrolledWindow() *ScrolledWindow {
|
func NewScrolledWindow() *ScrolledWindow {
|
||||||
|
@ -15,22 +15,18 @@ func NewScrolledWindow() *ScrolledWindow {
|
||||||
sw := &ScrolledWindow{*gtksw, *gtksw.GetVAdjustment(), true} // bottomed by default
|
sw := &ScrolledWindow{*gtksw, *gtksw.GetVAdjustment(), true} // bottomed by default
|
||||||
sw.Connect("size-allocate", func(_ *gtk.ScrolledWindow) {
|
sw.Connect("size-allocate", func(_ *gtk.ScrolledWindow) {
|
||||||
// We can't really trust Gtk to be competent.
|
// We can't really trust Gtk to be competent.
|
||||||
if sw.bottomed {
|
if sw.Bottomed {
|
||||||
sw.ScrollToBottom()
|
sw.ScrollToBottom()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
sw.vadj.Connect("value-changed", func(adj *gtk.Adjustment) {
|
sw.vadj.Connect("value-changed", func(adj *gtk.Adjustment) {
|
||||||
// Manually check if we're anchored on scroll.
|
// Manually check if we're anchored on scroll.
|
||||||
sw.bottomed = (adj.GetUpper() - adj.GetPageSize()) <= adj.GetValue()
|
sw.Bottomed = (adj.GetUpper() - adj.GetPageSize()) <= adj.GetValue()
|
||||||
})
|
})
|
||||||
|
|
||||||
return sw
|
return sw
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScrolledWindow) Bottomed() bool {
|
|
||||||
return s.bottomed
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVAdjustment overrides gtk.ScrolledWindow's.
|
// GetVAdjustment overrides gtk.ScrolledWindow's.
|
||||||
func (s *ScrolledWindow) GetVAdjustment() *gtk.Adjustment {
|
func (s *ScrolledWindow) GetVAdjustment() *gtk.Adjustment {
|
||||||
return &s.vadj
|
return &s.vadj
|
|
@ -0,0 +1,25 @@
|
||||||
|
package compact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
*container.GridContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContainer() *Container {
|
||||||
|
c := &Container{}
|
||||||
|
c.GridContainer = container.NewGridContainer(c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) NewMessage(msg cchat.MessageCreate) container.GridMessage {
|
||||||
|
return NewMessage(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) NewPresendMessage(msg input.PresendMessage) container.PresendGridMessage {
|
||||||
|
return NewPresendMessage(msg)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package compact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"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/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PresendMessage struct {
|
||||||
|
*message.GenericPresendContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPresendMessage(msg input.PresendMessage) PresendMessage {
|
||||||
|
return PresendMessage{
|
||||||
|
GenericPresendContainer: message.NewPresendContainer(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PresendMessage) Attach(grid *gtk.Grid, row int) {
|
||||||
|
attachGenericContainer(p.GenericContainer, grid, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
*message.GenericContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ container.GridMessage = (*Message)(nil)
|
||||||
|
|
||||||
|
func NewMessage(msg cchat.MessageCreate) Message {
|
||||||
|
return Message{
|
||||||
|
GenericContainer: message.NewContainer(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEmptyMessage() Message {
|
||||||
|
return Message{
|
||||||
|
GenericContainer: message.NewEmptyContainer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fix a bug here related to new messages overlapping
|
||||||
|
func (m Message) Attach(grid *gtk.Grid, row int) {
|
||||||
|
attachGenericContainer(m.GenericContainer, grid, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
func attachGenericContainer(m *message.GenericContainer, grid *gtk.Grid, row int) {
|
||||||
|
grid.Attach(m.Timestamp, 0, row, 1, 1)
|
||||||
|
grid.Attach(m.Username, 1, row, 1, 1)
|
||||||
|
grid.Attach(m.Content, 2, row, 1, 1)
|
||||||
|
}
|
|
@ -0,0 +1,210 @@
|
||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/autoscroll"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GridMessage interface {
|
||||||
|
message.Container
|
||||||
|
Attach(grid *gtk.Grid, row int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PresendGridMessage interface {
|
||||||
|
GridMessage
|
||||||
|
message.PresendContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// gridMessage w/ required internals
|
||||||
|
type gridMessage struct {
|
||||||
|
GridMessage
|
||||||
|
presend message.PresendContainer // this shouldn't be here but i'm lazy
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor is an interface for making custom message implementations which
|
||||||
|
// allows GridContainer to generically work with.
|
||||||
|
type Constructor interface {
|
||||||
|
NewMessage(cchat.MessageCreate) GridMessage
|
||||||
|
NewPresendMessage(input.PresendMessage) PresendGridMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container is a generic messages container.
|
||||||
|
type Container interface {
|
||||||
|
gtk.IWidget
|
||||||
|
cchat.MessagesContainer
|
||||||
|
|
||||||
|
Reset()
|
||||||
|
ScrollToBottom()
|
||||||
|
|
||||||
|
// PresendMessage is for unsent messages.
|
||||||
|
PresendMessage(input.PresendMessage) (done func(sendError error))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GridContainer is an implementation of Container, which allows flexible
|
||||||
|
// message grids.
|
||||||
|
type GridContainer struct {
|
||||||
|
*autoscroll.ScrolledWindow
|
||||||
|
Main *gtk.Grid
|
||||||
|
|
||||||
|
construct Constructor
|
||||||
|
|
||||||
|
messages map[string]*gridMessage
|
||||||
|
nonceMsgs map[string]*gridMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Container = (*GridContainer)(nil)
|
||||||
|
_ cchat.MessagesContainer = (*GridContainer)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewGridContainer(constr Constructor) *GridContainer {
|
||||||
|
grid, _ := gtk.GridNew()
|
||||||
|
grid.SetColumnSpacing(10)
|
||||||
|
grid.SetRowSpacing(5)
|
||||||
|
grid.SetMarginStart(5)
|
||||||
|
grid.SetMarginEnd(5)
|
||||||
|
grid.SetMarginBottom(5)
|
||||||
|
grid.Show()
|
||||||
|
|
||||||
|
sw := autoscroll.NewScrolledWindow()
|
||||||
|
sw.Add(grid)
|
||||||
|
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
|
||||||
|
sw.Show()
|
||||||
|
|
||||||
|
container := GridContainer{
|
||||||
|
ScrolledWindow: sw,
|
||||||
|
Main: grid,
|
||||||
|
construct: constr,
|
||||||
|
messages: map[string]*gridMessage{},
|
||||||
|
nonceMsgs: map[string]*gridMessage{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &container
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GridContainer) Reset() {
|
||||||
|
// does this actually work?
|
||||||
|
var rows = c.len()
|
||||||
|
for i := 0; i < rows; i++ {
|
||||||
|
c.Main.RemoveRow(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.messages = map[string]*gridMessage{}
|
||||||
|
c.nonceMsgs = map[string]*gridMessage{}
|
||||||
|
|
||||||
|
c.ScrolledWindow.Bottomed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GridContainer) len() int {
|
||||||
|
return len(c.messages) + len(c.nonceMsgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresendMessage is not thread-safe.
|
||||||
|
func (c *GridContainer) PresendMessage(msg input.PresendMessage) func(error) {
|
||||||
|
presend := c.construct.NewPresendMessage(msg)
|
||||||
|
|
||||||
|
msgc := gridMessage{
|
||||||
|
GridMessage: presend,
|
||||||
|
presend: presend,
|
||||||
|
index: c.len(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.nonceMsgs[presend.Nonce()] = &msgc
|
||||||
|
msgc.Attach(c.Main, msgc.index)
|
||||||
|
|
||||||
|
return func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
presend.SetSentError(err)
|
||||||
|
log.Error(errors.Wrap(err, "Failed to send message"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindMessage is not thread-safe. This exists for backwards compatibility.
|
||||||
|
func (c *GridContainer) FindMessage(msg cchat.MessageHeader) GridMessage {
|
||||||
|
if m := c.findMessage(msg); m != nil {
|
||||||
|
return m.GridMessage
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GridContainer) findMessage(msg cchat.MessageHeader) *gridMessage {
|
||||||
|
// Search using the ID first.
|
||||||
|
m, ok := c.messages[msg.ID()]
|
||||||
|
if ok {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this an existing message?
|
||||||
|
if noncer, ok := msg.(cchat.MessageNonce); ok {
|
||||||
|
var nonce = noncer.Nonce()
|
||||||
|
|
||||||
|
// Things in this map are guaranteed to have presend != nil.
|
||||||
|
m, ok := c.nonceMsgs[nonce]
|
||||||
|
if ok {
|
||||||
|
// Move the message outside nonceMsgs.
|
||||||
|
delete(c.nonceMsgs, nonce)
|
||||||
|
c.messages[msg.ID()] = m
|
||||||
|
|
||||||
|
// Set the right ID.
|
||||||
|
m.presend.SetID(msg.ID())
|
||||||
|
m.presend.SetDone()
|
||||||
|
// Destroy the presend struct.
|
||||||
|
m.presend = nil
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GridContainer) CreateMessage(msg cchat.MessageCreate) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
// Attempt update before insert (aka upsert).
|
||||||
|
if msgc := c.FindMessage(msg); msgc != nil {
|
||||||
|
msgc.UpdateAuthor(msg.Author())
|
||||||
|
msgc.UpdateContent(msg.Content())
|
||||||
|
msgc.UpdateTimestamp(msg.Time())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msgc := gridMessage{
|
||||||
|
GridMessage: c.construct.NewMessage(msg),
|
||||||
|
index: c.len(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.messages[msgc.ID()] = &msgc
|
||||||
|
msgc.Attach(c.Main, msgc.index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GridContainer) UpdateMessage(msg cchat.MessageUpdate) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
if msgc := c.FindMessage(msg); msgc != nil {
|
||||||
|
if author := msg.Author(); author != nil {
|
||||||
|
msgc.UpdateAuthor(author)
|
||||||
|
}
|
||||||
|
if content := msg.Content(); !content.Empty() {
|
||||||
|
msgc.UpdateContent(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GridContainer) DeleteMessage(msg cchat.MessageDelete) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
// TODO: add nonce check.
|
||||||
|
if m, ok := c.messages[msg.ID()]; ok {
|
||||||
|
delete(c.messages, msg.ID())
|
||||||
|
c.Main.RemoveRow(m.index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package cozy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/autoscroll"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
*autoscroll.ScrolledWindow
|
||||||
|
main *gtk.Grid
|
||||||
|
messages map[string]Message
|
||||||
|
nonceMsgs map[string]Message
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package cozy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message interface {
|
||||||
|
gtk.IWidget
|
||||||
|
message.Container
|
||||||
|
}
|
||||||
|
|
||||||
|
type FullMessage struct {
|
||||||
|
*gtk.Box
|
||||||
|
|
||||||
|
Avatar *gtk.Image
|
||||||
|
*message.GenericContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFullMessage()
|
|
@ -1,4 +1,4 @@
|
||||||
package compact
|
package message
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
@ -11,44 +11,46 @@ import (
|
||||||
"github.com/gotk3/gotk3/pango"
|
"github.com/gotk3/gotk3/pango"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Container interface {
|
||||||
index int
|
ID() string
|
||||||
ID string
|
AuthorID() string
|
||||||
AuthorID string
|
Nonce() string
|
||||||
Nonce string
|
|
||||||
|
UpdateAuthor(cchat.MessageAuthor)
|
||||||
|
UpdateAuthorName(text.Rich)
|
||||||
|
UpdateContent(text.Rich)
|
||||||
|
UpdateTimestamp(time.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericContainer provides a single generic message container for subpackages
|
||||||
|
// to use.
|
||||||
|
type GenericContainer struct {
|
||||||
|
id string
|
||||||
|
authorID string
|
||||||
|
nonce string
|
||||||
|
|
||||||
Timestamp *gtk.Label
|
Timestamp *gtk.Label
|
||||||
Username *gtk.Label
|
Username *gtk.Label
|
||||||
Content *gtk.Label
|
Content *gtk.Label
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessage(msg cchat.MessageCreate) Message {
|
var _ Container = (*GenericContainer)(nil)
|
||||||
m := NewEmptyMessage()
|
|
||||||
m.ID = msg.ID()
|
func NewContainer(msg cchat.MessageCreate) *GenericContainer {
|
||||||
m.UpdateTimestamp(msg.Time())
|
c := NewEmptyContainer()
|
||||||
m.UpdateAuthor(msg.Author())
|
c.id = msg.ID()
|
||||||
m.UpdateContent(msg.Content())
|
c.UpdateTimestamp(msg.Time())
|
||||||
|
c.UpdateAuthor(msg.Author())
|
||||||
|
c.UpdateContent(msg.Content())
|
||||||
|
|
||||||
if noncer, ok := msg.(cchat.MessageNonce); ok {
|
if noncer, ok := msg.(cchat.MessageNonce); ok {
|
||||||
m.Nonce = noncer.Nonce()
|
c.nonce = noncer.Nonce()
|
||||||
}
|
}
|
||||||
|
|
||||||
return m
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPresendMessage(content string, author text.Rich, authorID, nonce string) Message {
|
func NewEmptyContainer() *GenericContainer {
|
||||||
msgc := NewEmptyMessage()
|
|
||||||
msgc.Nonce = nonce
|
|
||||||
msgc.AuthorID = authorID
|
|
||||||
msgc.SetSensitive(false)
|
|
||||||
msgc.UpdateContent(text.Rich{Content: content})
|
|
||||||
msgc.UpdateTimestamp(time.Now())
|
|
||||||
msgc.updateAuthorName(author)
|
|
||||||
|
|
||||||
return msgc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEmptyMessage() Message {
|
|
||||||
ts, _ := gtk.LabelNew("")
|
ts, _ := gtk.LabelNew("")
|
||||||
ts.SetLineWrap(true)
|
ts.SetLineWrap(true)
|
||||||
ts.SetLineWrapMode(pango.WRAP_WORD)
|
ts.SetLineWrapMode(pango.WRAP_WORD)
|
||||||
|
@ -75,39 +77,39 @@ func NewEmptyMessage() Message {
|
||||||
content.SetSelectable(true)
|
content.SetSelectable(true)
|
||||||
content.Show()
|
content.Show()
|
||||||
|
|
||||||
return Message{
|
return &GenericContainer{
|
||||||
Timestamp: ts,
|
Timestamp: ts,
|
||||||
Username: user,
|
Username: user,
|
||||||
Content: content,
|
Content: content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) SetSensitive(sensitive bool) {
|
func (m *GenericContainer) ID() string {
|
||||||
m.Timestamp.SetSensitive(sensitive)
|
return m.id
|
||||||
m.Username.SetSensitive(sensitive)
|
|
||||||
m.Content.SetSensitive(sensitive)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) Attach(grid *gtk.Grid, row int) {
|
func (m *GenericContainer) AuthorID() string {
|
||||||
grid.Attach(m.Timestamp, 0, row, 1, 1)
|
return m.authorID
|
||||||
grid.Attach(m.Username, 1, row, 1, 1)
|
|
||||||
grid.Attach(m.Content, 2, row, 1, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) UpdateTimestamp(t time.Time) {
|
func (m *GenericContainer) Nonce() string {
|
||||||
|
return m.nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenericContainer) UpdateTimestamp(t time.Time) {
|
||||||
m.Timestamp.SetLabel(humanize.TimeAgo(t))
|
m.Timestamp.SetLabel(humanize.TimeAgo(t))
|
||||||
m.Timestamp.SetTooltipText(t.Format(time.Stamp))
|
m.Timestamp.SetTooltipText(t.Format(time.Stamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) UpdateAuthor(author cchat.MessageAuthor) {
|
func (m *GenericContainer) UpdateAuthor(author cchat.MessageAuthor) {
|
||||||
m.AuthorID = author.ID()
|
m.authorID = author.ID()
|
||||||
m.updateAuthorName(author.Name())
|
m.UpdateAuthorName(author.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) updateAuthorName(name text.Rich) {
|
func (m *GenericContainer) UpdateAuthorName(name text.Rich) {
|
||||||
m.Username.SetMarkup(parser.RenderMarkup(name))
|
m.Username.SetMarkup(parser.RenderMarkup(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) UpdateContent(content text.Rich) {
|
func (m *GenericContainer) UpdateContent(content text.Rich) {
|
||||||
m.Content.SetMarkup(parser.RenderMarkup(content))
|
m.Content.SetMarkup(parser.RenderMarkup(content))
|
||||||
}
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
||||||
|
"github.com/diamondburned/cchat/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PresendContainer interface {
|
||||||
|
Container
|
||||||
|
SetID(id string)
|
||||||
|
SetDone()
|
||||||
|
SetSentError(err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresendGenericContainer is the generic container with extra methods
|
||||||
|
// implemented for mutability of the generic message container.
|
||||||
|
type GenericPresendContainer struct {
|
||||||
|
*GenericContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PresendContainer = (*GenericPresendContainer)(nil)
|
||||||
|
|
||||||
|
func NewPresendContainer(msg input.PresendMessage) *GenericPresendContainer {
|
||||||
|
c := NewEmptyContainer()
|
||||||
|
c.nonce = msg.Nonce()
|
||||||
|
c.authorID = msg.AuthorID()
|
||||||
|
c.UpdateContent(text.Rich{Content: msg.Content()})
|
||||||
|
c.UpdateTimestamp(time.Now())
|
||||||
|
c.UpdateAuthorName(msg.Author())
|
||||||
|
|
||||||
|
p := &GenericPresendContainer{
|
||||||
|
GenericContainer: c,
|
||||||
|
}
|
||||||
|
p.SetSensitive(false)
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenericPresendContainer) SetID(id string) {
|
||||||
|
m.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenericPresendContainer) SetSensitive(sensitive bool) {
|
||||||
|
m.Content.SetSensitive(sensitive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenericPresendContainer) SetDone() {
|
||||||
|
m.SetSensitive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GenericPresendContainer) SetSentError(err error) {
|
||||||
|
m.Content.SetMarkup(`<span color="red">` + html.EscapeString(m.Content.GetLabel()) + `</span>`)
|
||||||
|
m.Content.SetTooltipText(err.Error())
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
package message
|
package messages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/message/compact"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/message/input"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container/compact"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -22,7 +23,7 @@ type Container interface {
|
||||||
|
|
||||||
type View struct {
|
type View struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
Container Container
|
Container container.Container
|
||||||
SendInput *input.Field
|
SendInput *input.Field
|
||||||
|
|
||||||
current cchat.ServerMessage
|
current cchat.ServerMessage
|
||||||
|
@ -32,6 +33,7 @@ type View struct {
|
||||||
func NewView() *View {
|
func NewView() *View {
|
||||||
view := &View{}
|
view := &View{}
|
||||||
|
|
||||||
|
// TODO: change
|
||||||
view.Container = compact.NewContainer()
|
view.Container = compact.NewContainer()
|
||||||
view.SendInput = input.NewField(view)
|
view.SendInput = input.NewField(view)
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
package rich
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/log"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat/text"
|
||||||
|
"github.com/diamondburned/imgutil"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Icon struct {
|
||||||
|
*gtk.Revealer
|
||||||
|
Image *gtk.Image
|
||||||
|
|
||||||
|
resizer imgutil.Processor
|
||||||
|
procs []imgutil.Processor
|
||||||
|
url string // state
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultIconSize = 16
|
||||||
|
|
||||||
|
var _ cchat.IconContainer = (*Icon)(nil)
|
||||||
|
|
||||||
|
func NewIcon(sizepx int, procs ...imgutil.Processor) *Icon {
|
||||||
|
if sizepx == 0 {
|
||||||
|
sizepx = DefaultIconSize
|
||||||
|
}
|
||||||
|
|
||||||
|
img, _ := gtk.ImageNew()
|
||||||
|
img.Show()
|
||||||
|
img.SetSizeRequest(sizepx, sizepx)
|
||||||
|
|
||||||
|
rev, _ := gtk.RevealerNew()
|
||||||
|
rev.Add(img)
|
||||||
|
rev.SetRevealChild(false)
|
||||||
|
rev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
|
||||||
|
rev.SetTransitionDuration(50)
|
||||||
|
|
||||||
|
return &Icon{
|
||||||
|
Revealer: rev,
|
||||||
|
Image: img,
|
||||||
|
resizer: imgutil.Resize(sizepx, sizepx),
|
||||||
|
procs: procs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread-unsafe methods should only be called right after construction.
|
||||||
|
|
||||||
|
// SetPlaceholderIcon is not thread-safe.
|
||||||
|
func (i *Icon) SetPlaceholderIcon(iconName string, iconSzPx int) {
|
||||||
|
i.SetRevealChild(true)
|
||||||
|
i.SetSize(iconSzPx)
|
||||||
|
|
||||||
|
if iconName != "" {
|
||||||
|
primitives.SetImageIcon(i.Image, iconName, iconSzPx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSize is not thread-safe.
|
||||||
|
func (i *Icon) SetSize(szpx int) {
|
||||||
|
i.Image.SetSizeRequest(szpx, szpx)
|
||||||
|
i.resizer = imgutil.Resize(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) {
|
||||||
|
gts.ExecAsync(func() { i.SetRevealChild(true) })
|
||||||
|
i.url = url
|
||||||
|
i.updateAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Icon) updateAsync() {
|
||||||
|
httputil.AsyncImage(i.Image, i.url, imgutil.Prepend(i.resizer, i.procs)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
i := NewIcon(0)
|
||||||
|
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)
|
||||||
|
|
||||||
|
return &ToggleButtonImage{
|
||||||
|
ToggleButton: *b,
|
||||||
|
Labeler: l, // easy inheritance of methods
|
||||||
|
IconContainer: i,
|
||||||
|
|
||||||
|
Label: &l.Label,
|
||||||
|
Image: i,
|
||||||
|
Box: box,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Namer interface {
|
||||||
|
Name(cchat.LabelContainer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try tries to set the name from namer. It also tries Icon.
|
||||||
|
func (b *ToggleButtonImage) Try(namer Namer, desc string) {
|
||||||
|
if err := namer.Name(b); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to get name for "+desc))
|
||||||
|
b.SetLabel(text.Rich{Content: "Unknown"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if iconer, ok := namer.(cchat.Icon); ok {
|
||||||
|
if err := iconer.Icon(b); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to get icon for "+desc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -79,47 +79,3 @@ func NewToggleButton(content text.Rich) *ToggleButton {
|
||||||
|
|
||||||
return &ToggleButton{*b, *l}
|
return &ToggleButton{*b, *l}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToggleButtonImage struct {
|
|
||||||
gtk.ToggleButton
|
|
||||||
Labeler
|
|
||||||
|
|
||||||
Label gtk.Label
|
|
||||||
Image gtk.Image
|
|
||||||
|
|
||||||
Box gtk.Box
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ gtk.IWidget = (*ToggleButton)(nil)
|
|
||||||
_ cchat.LabelContainer = (*ToggleButton)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewToggleButtonImage(content text.Rich, iconName string) *ToggleButtonImage {
|
|
||||||
l := NewLabel(content)
|
|
||||||
l.Show()
|
|
||||||
|
|
||||||
var i *gtk.Image
|
|
||||||
if iconName != "" {
|
|
||||||
i, _ = gtk.ImageNewFromIconName(iconName, gtk.ICON_SIZE_BUTTON)
|
|
||||||
} else {
|
|
||||||
i, _ = gtk.ImageNew()
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
|
|
||||||
return &ToggleButtonImage{
|
|
||||||
ToggleButton: *b,
|
|
||||||
Labeler: l, // easy inheritance of methods
|
|
||||||
Label: l.Label,
|
|
||||||
Image: *i,
|
|
||||||
Box: *box,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,12 +2,14 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const IconSize = 32
|
const IconSize = 32
|
||||||
|
@ -115,14 +117,18 @@ type header struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHeader(svc cchat.Service) *header {
|
func newHeader(svc cchat.Service) *header {
|
||||||
reveal := rich.NewToggleButtonImage(text.Rich{Content: svc.Name()}, "")
|
reveal := rich.NewToggleButtonImage(text.Rich{Content: svc.Name()})
|
||||||
reveal.Box.SetHAlign(gtk.ALIGN_START)
|
reveal.Box.SetHAlign(gtk.ALIGN_START)
|
||||||
|
reveal.Image.SetPlaceholderIcon("folder-remote-symbolic", IconSize)
|
||||||
reveal.SetRelief(gtk.RELIEF_NONE)
|
reveal.SetRelief(gtk.RELIEF_NONE)
|
||||||
reveal.SetMode(true)
|
reveal.SetMode(true)
|
||||||
reveal.Show()
|
reveal.Show()
|
||||||
|
|
||||||
// Set a custom icon.
|
if iconer, ok := svc.(cchat.Icon); ok {
|
||||||
primitives.SetImageIcon(&reveal.Image, "folder-remote-symbolic", IconSize)
|
if err := iconer.Icon(reveal); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Error getting session logo"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
add, _ := gtk.ButtonNewFromIconName("list-add-symbolic", gtk.ICON_SIZE_BUTTON)
|
add, _ := gtk.ButtonNewFromIconName("list-add-symbolic", gtk.ICON_SIZE_BUTTON)
|
||||||
add.SetRelief(gtk.RELIEF_NONE)
|
add.SetRelief(gtk.RELIEF_NONE)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const ChildrenMargin = 24
|
const ChildrenMargin = 24
|
||||||
|
const IconSize = 18
|
||||||
|
|
||||||
type Controller interface {
|
type Controller interface {
|
||||||
MessageRowSelected(*Row, cchat.ServerMessage)
|
MessageRowSelected(*Row, cchat.ServerMessage)
|
||||||
|
@ -34,15 +35,12 @@ type Row struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRow(parent breadcrumb.Breadcrumber, server cchat.Server, ctrl Controller) *Row {
|
func NewRow(parent breadcrumb.Breadcrumber, server cchat.Server, ctrl Controller) *Row {
|
||||||
button := rich.NewToggleButtonImage(text.Rich{}, "")
|
button := rich.NewToggleButtonImage(text.Rich{})
|
||||||
button.Box.SetHAlign(gtk.ALIGN_START)
|
button.Box.SetHAlign(gtk.ALIGN_START)
|
||||||
|
button.Image.SetSize(IconSize)
|
||||||
button.SetRelief(gtk.RELIEF_NONE)
|
button.SetRelief(gtk.RELIEF_NONE)
|
||||||
button.Show()
|
button.Show()
|
||||||
|
button.Try(server, "server")
|
||||||
if err := server.Name(button); err != nil {
|
|
||||||
log.Error(errors.Wrap(err, "Failed to get the server name"))
|
|
||||||
button.SetLabel(text.Rich{Content: "Unknown"})
|
|
||||||
}
|
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
box.PackStart(button, false, false, 0)
|
box.PackStart(button, false, false, 0)
|
||||||
|
|
|
@ -2,14 +2,12 @@ package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const IconSize = 32
|
const IconSize = 32
|
||||||
|
@ -38,23 +36,18 @@ func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Ro
|
||||||
}
|
}
|
||||||
row.Servers = server.NewChildren(row, ses, row)
|
row.Servers = server.NewChildren(row, ses, row)
|
||||||
|
|
||||||
row.Button = rich.NewToggleButtonImage(text.Rich{}, "")
|
row.Button = rich.NewToggleButtonImage(text.Rich{})
|
||||||
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
||||||
|
row.Button.Image.SetPlaceholderIcon("user-available-symbolic", IconSize)
|
||||||
row.Button.SetRelief(gtk.RELIEF_NONE)
|
row.Button.SetRelief(gtk.RELIEF_NONE)
|
||||||
row.Button.Show()
|
|
||||||
// On click, toggle reveal.
|
// On click, toggle reveal.
|
||||||
row.Button.Connect("clicked", func() {
|
row.Button.Connect("clicked", func() {
|
||||||
revealed := !row.Servers.GetRevealChild()
|
revealed := !row.Servers.GetRevealChild()
|
||||||
row.Servers.SetRevealChild(revealed)
|
row.Servers.SetRevealChild(revealed)
|
||||||
row.Button.SetActive(revealed)
|
row.Button.SetActive(revealed)
|
||||||
})
|
})
|
||||||
|
row.Button.Show()
|
||||||
primitives.SetImageIcon(&row.Button.Image, "user-available-symbolic", IconSize)
|
row.Button.Try(ses, "session")
|
||||||
|
|
||||||
if err := ses.Name(row.Button); err != nil {
|
|
||||||
log.Error(errors.Wrap(err, "Failed to get the username"))
|
|
||||||
row.Button.SetLabel(text.Rich{Content: "Unknown"})
|
|
||||||
}
|
|
||||||
|
|
||||||
row.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
row.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
row.Box.SetMarginStart(server.ChildrenMargin)
|
row.Box.SetMarginStart(server.ChildrenMargin)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/message"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
@ -9,13 +9,13 @@ import (
|
||||||
type window struct {
|
type window struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
Services *service.View
|
Services *service.View
|
||||||
MessageView *message.View
|
MessageView *messages.View
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow() *window {
|
func newWindow() *window {
|
||||||
services := service.NewView()
|
services := service.NewView()
|
||||||
services.SetSizeRequest(LeftWidth, -1)
|
services.SetSizeRequest(LeftWidth, -1)
|
||||||
mesgview := message.NewView()
|
mesgview := messages.NewView()
|
||||||
|
|
||||||
separator, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
separator, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
||||||
separator.Show()
|
separator.Show()
|
||||||
|
|
Loading…
Reference in New Issue