diff --git a/go.mod b/go.mod
index 94741b5..1ce794f 100644
--- a/go.mod
+++ b/go.mod
@@ -8,9 +8,14 @@ require (
github.com/Xuanwo/go-locale v0.2.0
github.com/diamondburned/cchat v0.0.15
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/google/btree v1.0.0 // indirect
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/peterbourgon/diskv v2.0.1+incompatible
github.com/pkg/errors v0.9.1
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717
)
diff --git a/go.sum b/go.sum
index f83441c..375547e 100644
--- a/go.sum
+++ b/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/diamondburned/cchat v0.0.15 h1:1o4OX8zw/CdSv3Idaylz7vjHVOZKEi/xkg8BpEvtsHY=
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/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
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/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
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/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/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/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
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/markbates/pkger v0.17.0 h1:RFfyBPufP2V6cddUyyEVSHBpaAnM1WzaMNyqomeT+iY=
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
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/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/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
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/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=
diff --git a/internal/gts/httputil/httputil.go b/internal/gts/httputil/httputil.go
new file mode 100644
index 0000000..80c3733
--- /dev/null
+++ b/internal/gts/httputil/httputil.go
@@ -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
+}
diff --git a/internal/gts/httputil/image.go b/internal/gts/httputil/image.go
new file mode 100644
index 0000000..dc72d05
--- /dev/null
+++ b/internal/gts/httputil/image.go
@@ -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")
+}
diff --git a/internal/ui/message/compact/compact.go b/internal/ui/message/compact/compact.go
deleted file mode 100644
index 6ca8a41..0000000
--- a/internal/ui/message/compact/compact.go
+++ /dev/null
@@ -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(
- `%s`,
- 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)
- }
- })
-}
diff --git a/internal/ui/message/cozy/cozy.go b/internal/ui/message/cozy/cozy.go
deleted file mode 100644
index 2284c72..0000000
--- a/internal/ui/message/cozy/cozy.go
+++ /dev/null
@@ -1 +0,0 @@
-package cozy
diff --git a/internal/ui/message/autoscroll/autoscroll.go b/internal/ui/messages/autoscroll/autoscroll.go
similarity index 82%
rename from internal/ui/message/autoscroll/autoscroll.go
rename to internal/ui/messages/autoscroll/autoscroll.go
index 1efbdc4..adaafb1 100644
--- a/internal/ui/message/autoscroll/autoscroll.go
+++ b/internal/ui/messages/autoscroll/autoscroll.go
@@ -5,7 +5,7 @@ import "github.com/gotk3/gotk3/gtk"
type ScrolledWindow struct {
gtk.ScrolledWindow
vadj gtk.Adjustment
- bottomed bool // :floshed:
+ Bottomed bool // :floshed:
}
func NewScrolledWindow() *ScrolledWindow {
@@ -15,22 +15,18 @@ func NewScrolledWindow() *ScrolledWindow {
sw := &ScrolledWindow{*gtksw, *gtksw.GetVAdjustment(), true} // bottomed by default
sw.Connect("size-allocate", func(_ *gtk.ScrolledWindow) {
// We can't really trust Gtk to be competent.
- if sw.bottomed {
+ if sw.Bottomed {
sw.ScrollToBottom()
}
})
sw.vadj.Connect("value-changed", func(adj *gtk.Adjustment) {
// 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
}
-func (s *ScrolledWindow) Bottomed() bool {
- return s.bottomed
-}
-
// GetVAdjustment overrides gtk.ScrolledWindow's.
func (s *ScrolledWindow) GetVAdjustment() *gtk.Adjustment {
return &s.vadj
diff --git a/internal/ui/messages/container/compact/compact.go b/internal/ui/messages/container/compact/compact.go
new file mode 100644
index 0000000..0bc4216
--- /dev/null
+++ b/internal/ui/messages/container/compact/compact.go
@@ -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)
+}
diff --git a/internal/ui/messages/container/compact/message.go b/internal/ui/messages/container/compact/message.go
new file mode 100644
index 0000000..9bf48b9
--- /dev/null
+++ b/internal/ui/messages/container/compact/message.go
@@ -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)
+}
diff --git a/internal/ui/messages/container/container.go b/internal/ui/messages/container/container.go
new file mode 100644
index 0000000..6c992e5
--- /dev/null
+++ b/internal/ui/messages/container/container.go
@@ -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)
+ }
+ })
+}
diff --git a/internal/ui/messages/container/cozy/cozy.go b/internal/ui/messages/container/cozy/cozy.go
new file mode 100644
index 0000000..05d6989
--- /dev/null
+++ b/internal/ui/messages/container/cozy/cozy.go
@@ -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
+}
diff --git a/internal/ui/messages/container/cozy/message.go b/internal/ui/messages/container/cozy/message.go
new file mode 100644
index 0000000..6038c6a
--- /dev/null
+++ b/internal/ui/messages/container/cozy/message.go
@@ -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()
diff --git a/internal/ui/message/input/input.go b/internal/ui/messages/input/input.go
similarity index 100%
rename from internal/ui/message/input/input.go
rename to internal/ui/messages/input/input.go
diff --git a/internal/ui/message/input/keydown.go b/internal/ui/messages/input/keydown.go
similarity index 100%
rename from internal/ui/message/input/keydown.go
rename to internal/ui/messages/input/keydown.go
diff --git a/internal/ui/message/input/send.go b/internal/ui/messages/input/send.go
similarity index 100%
rename from internal/ui/message/input/send.go
rename to internal/ui/messages/input/send.go
diff --git a/internal/ui/message/compact/message.go b/internal/ui/messages/message/message.go
similarity index 51%
rename from internal/ui/message/compact/message.go
rename to internal/ui/messages/message/message.go
index 0c034b1..c5772ef 100644
--- a/internal/ui/message/compact/message.go
+++ b/internal/ui/messages/message/message.go
@@ -1,4 +1,4 @@
-package compact
+package message
import (
"time"
@@ -11,44 +11,46 @@ import (
"github.com/gotk3/gotk3/pango"
)
-type Message struct {
- index int
- ID string
- AuthorID string
- Nonce string
+type Container interface {
+ ID() string
+ AuthorID() 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
Username *gtk.Label
Content *gtk.Label
}
-func NewMessage(msg cchat.MessageCreate) Message {
- m := NewEmptyMessage()
- m.ID = msg.ID()
- m.UpdateTimestamp(msg.Time())
- m.UpdateAuthor(msg.Author())
- m.UpdateContent(msg.Content())
+var _ Container = (*GenericContainer)(nil)
+
+func NewContainer(msg cchat.MessageCreate) *GenericContainer {
+ c := NewEmptyContainer()
+ c.id = msg.ID()
+ c.UpdateTimestamp(msg.Time())
+ c.UpdateAuthor(msg.Author())
+ c.UpdateContent(msg.Content())
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 {
- 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 {
+func NewEmptyContainer() *GenericContainer {
ts, _ := gtk.LabelNew("")
ts.SetLineWrap(true)
ts.SetLineWrapMode(pango.WRAP_WORD)
@@ -75,39 +77,39 @@ func NewEmptyMessage() Message {
content.SetSelectable(true)
content.Show()
- return Message{
+ return &GenericContainer{
Timestamp: ts,
Username: user,
Content: content,
}
}
-func (m *Message) SetSensitive(sensitive bool) {
- m.Timestamp.SetSensitive(sensitive)
- m.Username.SetSensitive(sensitive)
- m.Content.SetSensitive(sensitive)
+func (m *GenericContainer) ID() string {
+ return m.id
}
-func (m *Message) Attach(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)
+func (m *GenericContainer) AuthorID() string {
+ return m.authorID
}
-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.SetTooltipText(t.Format(time.Stamp))
}
-func (m *Message) UpdateAuthor(author cchat.MessageAuthor) {
- m.AuthorID = author.ID()
- m.updateAuthorName(author.Name())
+func (m *GenericContainer) UpdateAuthor(author cchat.MessageAuthor) {
+ m.authorID = author.ID()
+ m.UpdateAuthorName(author.Name())
}
-func (m *Message) updateAuthorName(name text.Rich) {
+func (m *GenericContainer) UpdateAuthorName(name text.Rich) {
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))
}
diff --git a/internal/ui/messages/message/sending.go b/internal/ui/messages/message/sending.go
new file mode 100644
index 0000000..38aa02c
--- /dev/null
+++ b/internal/ui/messages/message/sending.go
@@ -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(`` + html.EscapeString(m.Content.GetLabel()) + ``)
+ m.Content.SetTooltipText(err.Error())
+}
diff --git a/internal/ui/message/view.go b/internal/ui/messages/view.go
similarity index 85%
rename from internal/ui/message/view.go
rename to internal/ui/messages/view.go
index 9ee1cf5..56e5b29 100644
--- a/internal/ui/message/view.go
+++ b/internal/ui/messages/view.go
@@ -1,10 +1,11 @@
-package message
+package messages
import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/log"
- "github.com/diamondburned/cchat-gtk/internal/ui/message/compact"
- "github.com/diamondburned/cchat-gtk/internal/ui/message/input"
+ "github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
+ "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/pkg/errors"
)
@@ -22,7 +23,7 @@ type Container interface {
type View struct {
*gtk.Box
- Container Container
+ Container container.Container
SendInput *input.Field
current cchat.ServerMessage
@@ -32,6 +33,7 @@ type View struct {
func NewView() *View {
view := &View{}
+ // TODO: change
view.Container = compact.NewContainer()
view.SendInput = input.NewField(view)
diff --git a/internal/ui/rich/image.go b/internal/ui/rich/image.go
new file mode 100644
index 0000000..843831a
--- /dev/null
+++ b/internal/ui/rich/image.go
@@ -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))
+ }
+ }
+}
diff --git a/internal/ui/rich/rich.go b/internal/ui/rich/rich.go
index f49decc..8178d14 100644
--- a/internal/ui/rich/rich.go
+++ b/internal/ui/rich/rich.go
@@ -79,47 +79,3 @@ func NewToggleButton(content text.Rich) *ToggleButton {
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,
- }
-}
diff --git a/internal/ui/service/service.go b/internal/ui/service/service.go
index 4f4f27b..5384e40 100644
--- a/internal/ui/service/service.go
+++ b/internal/ui/service/service.go
@@ -2,12 +2,14 @@ package service
import (
"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/rich"
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
"github.com/diamondburned/cchat/text"
"github.com/gotk3/gotk3/gtk"
+ "github.com/pkg/errors"
)
const IconSize = 32
@@ -115,14 +117,18 @@ type header struct {
}
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.Image.SetPlaceholderIcon("folder-remote-symbolic", IconSize)
reveal.SetRelief(gtk.RELIEF_NONE)
reveal.SetMode(true)
reveal.Show()
- // Set a custom icon.
- primitives.SetImageIcon(&reveal.Image, "folder-remote-symbolic", IconSize)
+ if iconer, ok := svc.(cchat.Icon); ok {
+ 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.SetRelief(gtk.RELIEF_NONE)
diff --git a/internal/ui/service/session/server/server.go b/internal/ui/service/session/server/server.go
index 85bfcc7..2637788 100644
--- a/internal/ui/service/session/server/server.go
+++ b/internal/ui/service/session/server/server.go
@@ -13,6 +13,7 @@ import (
)
const ChildrenMargin = 24
+const IconSize = 18
type Controller interface {
MessageRowSelected(*Row, cchat.ServerMessage)
@@ -34,15 +35,12 @@ type Row struct {
}
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.Image.SetSize(IconSize)
button.SetRelief(gtk.RELIEF_NONE)
button.Show()
-
- if err := server.Name(button); err != nil {
- log.Error(errors.Wrap(err, "Failed to get the server name"))
- button.SetLabel(text.Rich{Content: "Unknown"})
- }
+ button.Try(server, "server")
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
box.PackStart(button, false, false, 0)
diff --git a/internal/ui/service/session/session.go b/internal/ui/service/session/session.go
index 49e94e9..ca21153 100644
--- a/internal/ui/service/session/session.go
+++ b/internal/ui/service/session/session.go
@@ -2,14 +2,12 @@ package session
import (
"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/rich"
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
"github.com/diamondburned/cchat/text"
"github.com/gotk3/gotk3/gtk"
- "github.com/pkg/errors"
)
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.Button = rich.NewToggleButtonImage(text.Rich{}, "")
+ row.Button = rich.NewToggleButtonImage(text.Rich{})
row.Button.Box.SetHAlign(gtk.ALIGN_START)
+ row.Button.Image.SetPlaceholderIcon("user-available-symbolic", IconSize)
row.Button.SetRelief(gtk.RELIEF_NONE)
- row.Button.Show()
// On click, toggle reveal.
row.Button.Connect("clicked", func() {
revealed := !row.Servers.GetRevealChild()
row.Servers.SetRevealChild(revealed)
row.Button.SetActive(revealed)
})
-
- primitives.SetImageIcon(&row.Button.Image, "user-available-symbolic", IconSize)
-
- 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.Button.Show()
+ row.Button.Try(ses, "session")
row.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
row.Box.SetMarginStart(server.ChildrenMargin)
diff --git a/internal/ui/window.go b/internal/ui/window.go
index e4b4ef4..798caa7 100644
--- a/internal/ui/window.go
+++ b/internal/ui/window.go
@@ -1,7 +1,7 @@
package ui
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/gotk3/gotk3/gtk"
)
@@ -9,13 +9,13 @@ import (
type window struct {
*gtk.Box
Services *service.View
- MessageView *message.View
+ MessageView *messages.View
}
func newWindow() *window {
services := service.NewView()
services.SetSizeRequest(LeftWidth, -1)
- mesgview := message.NewView()
+ mesgview := messages.NewView()
separator, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
separator.Show()