diff --git a/go.mod b/go.mod
index 963edac..e0eb03a 100644
--- a/go.mod
+++ b/go.mod
@@ -7,8 +7,8 @@ replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20200630
require (
github.com/Xuanwo/go-locale v0.2.0
github.com/alecthomas/chroma v0.7.3
- github.com/diamondburned/cchat v0.0.43
- github.com/diamondburned/cchat-discord v0.0.0-20200717002543-508f355b9657
+ github.com/diamondburned/cchat v0.0.45
+ github.com/diamondburned/cchat-discord v0.0.0-20200717063909-2f4cb5f246c4
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
github.com/disintegration/imaging v1.6.2
diff --git a/go.sum b/go.sum
index ecaa939..e8a4fbe 100644
--- a/go.sum
+++ b/go.sum
@@ -44,20 +44,22 @@ 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/aqs v0.0.0-20200704043812-99b676ee44eb h1:Ja/niwykeFoSkYxdRRzM8QUAuCswfLmaiBTd2UIU+54=
github.com/diamondburned/aqs v0.0.0-20200704043812-99b676ee44eb/go.mod h1:q1MbMBfZrv7xqV8n7LgMwhHs3oBbNwWJes8exs2AmDs=
-github.com/diamondburned/arikawa v0.10.5 h1:o5lBopooA+8cXlKZdct5qF0xztuZZ35phvQrwGS5vYM=
-github.com/diamondburned/arikawa v0.10.5/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
+github.com/diamondburned/arikawa v0.12.4 h1:lhWJqcGkIIMiOYWdsoEuGlri2UbMkzMeh+VfuJPkXt4=
+github.com/diamondburned/arikawa v0.12.4/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
github.com/diamondburned/cchat v0.0.43 h1:HetAujSaUSdnQgAUZgprNLARjf/MSWXpCfWdvX2wOCU=
github.com/diamondburned/cchat v0.0.43/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
-github.com/diamondburned/cchat-discord v0.0.0-20200717002543-508f355b9657 h1:/ZwVENnNKBioK34qA4sr/2B0pcJbDpPyw6DpiI3Cjr0=
-github.com/diamondburned/cchat-discord v0.0.0-20200717002543-508f355b9657/go.mod h1:cX6rGfvIv2rfNPrhfcRx88bfNxyL7eFmiYZLCWGfchw=
+github.com/diamondburned/cchat v0.0.45 h1:HMVSKx1h6lh2OenWaBTvMSK531hWaXAW7I0tKZepYug=
+github.com/diamondburned/cchat v0.0.45/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
+github.com/diamondburned/cchat-discord v0.0.0-20200717063909-2f4cb5f246c4 h1:otasqokx6kIGo9KnJt1F22MdCyUIvZmxZkLv+kcdKiI=
+github.com/diamondburned/cchat-discord v0.0.0-20200717063909-2f4cb5f246c4/go.mod h1:Z0uWBUaheEtozKj4NMgsSK4X5a3Du5tYakDb5plEluY=
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b h1:sq0MXjJc3yAOZvuolRxOpKQNvpMLyTmsECxQqdYgF5E=
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b/go.mod h1:+bAf0m2o5qH54DmYJ/lR1HeITV53ol0JaoKyFFx3m3E=
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d h1:Ha/I6PMKi+B4hpWclwlXj0tUMehR7Q0TNxPczzBwzPI=
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972 h1:OWxllHbUptXzDias6YI4MM0R3o50q8MfhkkwVIlfiNo=
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
-github.com/diamondburned/ningen v0.1.1-0.20200715040340-2395a0dbd0fa h1:ntHcz6GNzxn3TovtYZVwOBvL3xn7Iq1luaV/KEIEXrk=
-github.com/diamondburned/ningen v0.1.1-0.20200715040340-2395a0dbd0fa/go.mod h1:SKPY3387RHCbMrnefex9D+zlrA2yB+LCtaaQAgatAuc=
+github.com/diamondburned/ningen v0.1.1-0.20200717013108-297a3bdf84dc h1:YZ84Kdlv91tdcyLfGfQ+LG9kWZN8dTKIic0KlEtGV0U=
+github.com/diamondburned/ningen v0.1.1-0.20200717013108-297a3bdf84dc/go.mod h1:Sunqp1b9Tc0+DtWKslhf83Zepgj/TELB6h8J9HZCPqQ=
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/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
diff --git a/internal/ui/messages/input/input.go b/internal/ui/messages/input/input.go
index 13ca7b7..3752199 100644
--- a/internal/ui/messages/input/input.go
+++ b/internal/ui/messages/input/input.go
@@ -1,6 +1,8 @@
package input
import (
+ "time"
+
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/log"
@@ -96,12 +98,14 @@ type Field struct {
UserID string
Sender cchat.ServerMessageSender
editor cchat.ServerMessageEditor
+ typer cchat.ServerMessageTypingIndicator
ctrl Controller
// states
editingID string // never empty
- sendings []PresendMessage
+ lastTyped time.Time
+ typerDura time.Duration
}
var inputFieldCSS = primitives.PrepareCSS(`
@@ -178,6 +182,10 @@ func (f *Field) Reset() {
f.UserID = ""
f.Sender = nil
f.editor = nil
+ f.typer = nil
+ f.lastTyped = time.Time{}
+ f.typerDura = 0
+
f.Username.Reset()
// reset the input
@@ -197,11 +205,14 @@ func (f *Field) SetSender(session cchat.Session, sender cchat.ServerMessageSende
f.text.SetSensitive(true)
// Allow editor to be nil.
- ed, ok := sender.(cchat.ServerMessageEditor)
- if !ok {
- log.Printlnf("Editor is not implemented for %T", sender)
+ f.editor, _ = sender.(cchat.ServerMessageEditor)
+ // Allow typer to be nil.
+ f.typer, _ = sender.(cchat.ServerMessageTypingIndicator)
+
+ // Populate the duration state if typer is not nil.
+ if f.typer != nil {
+ f.typerDura = f.typer.TypingTimeout()
}
- f.editor = ed
}
}
diff --git a/internal/ui/messages/input/keydown.go b/internal/ui/messages/input/keydown.go
index a0314a2..b1a49aa 100644
--- a/internal/ui/messages/input/keydown.go
+++ b/internal/ui/messages/input/keydown.go
@@ -1,6 +1,8 @@
package input
import (
+ "time"
+
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/log"
"github.com/gotk3/gotk3/gdk"
@@ -95,6 +97,23 @@ func (f *Field) keyDown(tv *gtk.TextView, ev *gdk.Event) bool {
}
}
+ // If the server supports typing indication, then announce that we are
+ // typing with a proper rate limit.
+ if f.typer != nil {
+ // Get the current time; if the next timestamp is before now, then that
+ // means it's time for us to update it and send a typing indication.
+ if now := time.Now(); f.lastTyped.Add(f.typerDura).Before(now) {
+ // Update.
+ f.lastTyped = now
+ // Send asynchronously.
+ go func() {
+ if err := f.typer.Typing(); err != nil {
+ log.Error(errors.Wrap(err, "Failed to announce typing"))
+ }
+ }()
+ }
+ }
+
// Passthrough.
return false
}
diff --git a/internal/ui/primitives/primitives.go b/internal/ui/primitives/primitives.go
index 91af3d2..a543a29 100644
--- a/internal/ui/primitives/primitives.go
+++ b/internal/ui/primitives/primitives.go
@@ -64,6 +64,19 @@ func RemoveClass(styleCtx StyleContexter, classes ...string) {
}
}
+type ClassEnum struct{ class string }
+
+func (c *ClassEnum) SetClass(ctx StyleContexter, class string) {
+ var style, _ = ctx.GetStyleContext()
+ if c.class != "" {
+ style.RemoveClass(c.class)
+ }
+
+ if c.class = class; class != "" {
+ style.AddClass(class)
+ }
+}
+
type StyleContextFocuser interface {
StyleContexter
GrabFocus()
diff --git a/internal/ui/rich/parser/attrmap/attrmap.go b/internal/ui/rich/parser/attrmap/attrmap.go
index 3157269..ae08642 100644
--- a/internal/ui/rich/parser/attrmap/attrmap.go
+++ b/internal/ui/rich/parser/attrmap/attrmap.go
@@ -39,7 +39,7 @@ func (a *AppendMap) Anchor(start, end int, href string) {
a.Close(end, "")
}
-// AnchorNU makes a new tag without underlines.
+// AnchorNU makes a new tag without underlines and colors.
func (a *AppendMap) AnchorNU(start, end int, href string) {
a.Anchor(start, end, href)
a.Span(start, end, `underline="none"`)
diff --git a/internal/ui/service/button/button.go b/internal/ui/service/session/server/button/button.go
similarity index 79%
rename from internal/ui/service/button/button.go
rename to internal/ui/service/session/server/button/button.go
index 84b1015..754b9d2 100644
--- a/internal/ui/service/button/button.go
+++ b/internal/ui/service/session/server/button/button.go
@@ -3,8 +3,9 @@ package button
import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
- "github.com/diamondburned/cchat-gtk/internal/ui/rich"
+ "github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
+ "github.com/diamondburned/cchat-gtk/internal/ui/rich"
"github.com/diamondburned/cchat/text"
)
@@ -15,6 +16,7 @@ type ToggleButtonImage struct {
menu *menu.LazyMenu
clicked func(bool)
+ readcss primitives.ClassEnum
err error
icon string // whether or not the button has an icon
@@ -23,6 +25,12 @@ type ToggleButtonImage struct {
var _ cchat.IconContainer = (*ToggleButtonImage)(nil)
+var serverButtonCSS = primitives.PrepareCSS(`
+ .read { color: alpha(@theme_fg_color, 0.5) }
+ .unread { color: @theme_fg_color }
+ .mentioned { color: red }
+`)
+
func NewToggleButtonImage(content text.Rich) *ToggleButtonImage {
b := rich.NewToggleButtonImage(content)
b.Show()
@@ -33,10 +41,8 @@ func NewToggleButtonImage(content text.Rich) *ToggleButtonImage {
clicked: func(bool) {},
menu: menu.NewLazyMenu(b.ToggleButton),
}
-
- tb.Connect("clicked", func() {
- tb.clicked(tb.GetActive())
- })
+ tb.Connect("clicked", func() { tb.clicked(tb.GetActive()) })
+ primitives.AttachCSS(tb, serverButtonCSS)
return tb
}
@@ -92,6 +98,17 @@ func (b *ToggleButtonImage) SetFailed(err error, retry func()) {
}
}
+func (b *ToggleButtonImage) SetUnreadUnsafe(unread, mentioned bool) {
+ switch {
+ case unread:
+ b.readcss.SetClass(b, "unread")
+ case mentioned:
+ b.readcss.SetClass(b, "mentioned")
+ default:
+ b.readcss.SetClass(b, "read")
+ }
+}
+
func (b *ToggleButtonImage) SetPlaceholderIcon(iconName string, iconSzPx int) {
b.icon = iconName
b.Image.SetPlaceholderIcon(iconName, iconSzPx)
diff --git a/internal/ui/service/session/server/server.go b/internal/ui/service/session/server/server.go
index 355733c..a72b162 100644
--- a/internal/ui/service/session/server/server.go
+++ b/internal/ui/service/session/server/server.go
@@ -7,7 +7,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"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/button"
+ "github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/button"
"github.com/diamondburned/cchat/text"
"github.com/gotk3/gotk3/gtk"
"github.com/pkg/errors"
@@ -45,6 +45,21 @@ func NewServerRow(p breadcrumb.Breadcrumber, server cchat.Server, ctrl Controlle
case cchat.ServerMessage:
row.Button.SetClickedIfTrue(func() { ctrl.RowSelected(serverRow, server) })
primitives.AddClass(row, "server-message")
+
+ // Check if the server is capable of indicating unread state.
+ if unreader, ok := server.(cchat.ServerMessageUnreadIndicator); ok {
+ // Set as read by default.
+ row.Button.SetUnreadUnsafe(false, false)
+
+ gts.Async(func() (func(), error) {
+ c, err := unreader.UnreadIndicate(row)
+ if err != nil {
+ return nil, errors.Wrap(err, "Failed to use unread indicator")
+ }
+
+ return func() { row.Connect("destroy", c) }, nil
+ })
+ }
}
return serverRow
@@ -208,6 +223,15 @@ func (r *Row) SetRevealChild(reveal bool) {
}
}
+// GetRevealChild returns whether or not the server list is expanded, or always
+// false if there is no server list.
+func (r *Row) GetRevealChild() bool {
+ if r.childrev != nil {
+ return r.childrev.GetRevealChild()
+ }
+ return false
+}
+
// Load loads the row without uncollapsing it.
func (r *Row) Load() {
// Safeguard.
@@ -239,11 +263,11 @@ func (r *Row) Load() {
}()
}
-// GetRevealChild returns whether or not the server list is expanded, or always
-// false if there is no server list.
-func (r *Row) GetRevealChild() bool {
- if r.childrev != nil {
- return r.childrev.GetRevealChild()
- }
- return false
+// SetUnread is thread-safe.
+func (r *Row) SetUnread(unread, mentioned bool) {
+ gts.ExecAsync(func() { r.SetUnreadUnsafe(unread, mentioned) })
+}
+
+func (r *Row) SetUnreadUnsafe(unread, mentioned bool) {
+ r.Button.SetUnreadUnsafe(unread, mentioned)
}
diff --git a/internal/ui/service/session/session.go b/internal/ui/service/session/session.go
index d4fd0d5..78d05a5 100644
--- a/internal/ui/service/session/session.go
+++ b/internal/ui/service/session/session.go
@@ -62,12 +62,8 @@ type Row struct {
ActionsMenu *actions.Menu // session.*
- // TODO: enum class? having the button be red on fail would be good
-
// put commander in either a hover menu or a right click menu. maybe in the
// headerbar as well.
- // TODO headerbar how? custom interface to get menu items and callbacks in
- // controller?
cmder *commander.Buffer
}