2020-07-01 19:56:32 +00:00
|
|
|
package typing
|
|
|
|
|
|
|
|
import (
|
2020-07-04 04:41:12 +00:00
|
|
|
"strings"
|
|
|
|
|
2020-07-01 19:56:32 +00:00
|
|
|
"github.com/diamondburned/cchat"
|
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
2020-07-14 07:24:55 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
|
2021-01-03 04:52:30 +00:00
|
|
|
"github.com/diamondburned/handy"
|
2020-07-01 19:56:32 +00:00
|
|
|
"github.com/gotk3/gotk3/gtk"
|
|
|
|
"github.com/gotk3/gotk3/pango"
|
|
|
|
)
|
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
var typingIndicatorCSS = primitives.PrepareClassCSS("typing-indicator", `
|
2020-07-01 19:56:32 +00:00
|
|
|
.typing-indicator {
|
2021-01-01 10:00:11 +00:00
|
|
|
margin: 0 6px;
|
|
|
|
margin-top: 2px;
|
2021-01-02 09:24:14 +00:00
|
|
|
padding: 0 4px;
|
|
|
|
|
2020-07-04 04:41:12 +00:00
|
|
|
border-radius: 6px 6px 0 0;
|
2021-01-02 09:24:14 +00:00
|
|
|
|
2020-07-01 19:56:32 +00:00
|
|
|
color: alpha(@theme_fg_color, 0.8);
|
|
|
|
background-color: @theme_base_color;
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
var typingLabelCSS = primitives.PrepareClassCSS("typing-label", `
|
|
|
|
.typing-label {
|
|
|
|
padding-left: 2px;
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
|
2020-07-01 19:56:32 +00:00
|
|
|
var smallfonts = primitives.PrepareCSS(`
|
|
|
|
* { font-size: 0.9em; }
|
|
|
|
`)
|
|
|
|
|
2021-01-03 04:52:30 +00:00
|
|
|
const (
|
|
|
|
// Keep the same as input.
|
|
|
|
ClampMaxSize = 1000 - 6*2 // account for margin
|
|
|
|
ClampThreshold = ClampMaxSize
|
|
|
|
)
|
|
|
|
|
2020-07-01 19:56:32 +00:00
|
|
|
type Container struct {
|
|
|
|
*gtk.Revealer
|
2020-07-04 04:41:12 +00:00
|
|
|
state *State
|
2021-01-02 09:24:14 +00:00
|
|
|
|
2021-01-03 04:52:30 +00:00
|
|
|
clamp *handy.Clamp
|
2021-01-02 09:24:14 +00:00
|
|
|
dots *gtk.Box
|
|
|
|
label *gtk.Label
|
|
|
|
|
|
|
|
// borrow, if true, will not update the label until it is set to false.
|
|
|
|
borrow bool
|
|
|
|
// markup stores the label if the label view is not borrowed.
|
|
|
|
markup string
|
2020-07-01 19:56:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func New() *Container {
|
|
|
|
d := NewDots()
|
|
|
|
d.Show()
|
|
|
|
|
2020-07-04 04:41:12 +00:00
|
|
|
l, _ := gtk.LabelNew("")
|
2020-07-01 19:56:32 +00:00
|
|
|
l.SetXAlign(0)
|
|
|
|
l.SetEllipsize(pango.ELLIPSIZE_END)
|
|
|
|
l.Show()
|
2021-01-02 09:24:14 +00:00
|
|
|
typingLabelCSS(l)
|
2020-07-01 19:56:32 +00:00
|
|
|
primitives.AttachCSS(l, smallfonts)
|
|
|
|
|
|
|
|
b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
2021-01-02 09:24:14 +00:00
|
|
|
b.PackStart(d, false, false, 0)
|
2020-07-01 19:56:32 +00:00
|
|
|
b.PackStart(l, true, true, 0)
|
|
|
|
b.Show()
|
|
|
|
|
2021-01-03 04:52:30 +00:00
|
|
|
c := handy.ClampNew()
|
|
|
|
c.SetMaximumSize(ClampMaxSize)
|
|
|
|
c.SetTighteningThreshold(ClampThreshold)
|
|
|
|
c.SetHExpand(true)
|
|
|
|
c.Add(b)
|
|
|
|
c.Show()
|
|
|
|
|
2020-07-01 19:56:32 +00:00
|
|
|
r, _ := gtk.RevealerNew()
|
2021-01-01 10:00:11 +00:00
|
|
|
r.SetTransitionDuration(100)
|
2020-07-01 19:56:32 +00:00
|
|
|
r.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_CROSSFADE)
|
2020-07-04 04:41:12 +00:00
|
|
|
r.SetRevealChild(false)
|
2021-01-03 04:52:30 +00:00
|
|
|
r.Add(c)
|
2020-07-01 19:56:32 +00:00
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
typingIndicatorCSS(b)
|
|
|
|
|
|
|
|
container := &Container{
|
|
|
|
Revealer: r,
|
|
|
|
dots: d,
|
|
|
|
label: l,
|
|
|
|
}
|
|
|
|
|
|
|
|
container.state = NewState(func(s *State, empty bool) {
|
|
|
|
if !empty {
|
|
|
|
container.markup = render(s.typers)
|
|
|
|
} else {
|
|
|
|
container.markup = ""
|
|
|
|
}
|
2020-07-01 19:56:32 +00:00
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
if !container.borrow {
|
|
|
|
r.SetRevealChild(!empty)
|
|
|
|
l.SetMarkup(container.markup)
|
|
|
|
}
|
2020-07-04 04:41:12 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// On label destroy, stop the state loop as well.
|
2021-01-02 09:24:14 +00:00
|
|
|
l.Connect("destroy", func(interface{}) { container.state.stopper() })
|
2020-07-04 04:41:12 +00:00
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
return container
|
2020-07-01 19:56:32 +00:00
|
|
|
}
|
2020-07-04 04:41:12 +00:00
|
|
|
|
|
|
|
func (c *Container) Reset() {
|
|
|
|
c.state.reset()
|
|
|
|
c.SetRevealChild(false)
|
|
|
|
}
|
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
// BorrowLabel borrows the container label. The typing indicator will display
|
|
|
|
// the given markup string instead of the markup it is intended to display until
|
|
|
|
// Unborrow is called.
|
|
|
|
func (c *Container) BorrowLabel(markup string) {
|
|
|
|
c.borrow = true
|
|
|
|
c.label.SetMarkup(markup)
|
|
|
|
c.dots.Hide() // bad, TODO use revealer
|
|
|
|
c.SetRevealChild(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unborrow stops borrowing the typing indicator, returning it to the state it
|
|
|
|
// is supposed to show. Calling Unborrow multiple times will only take effect
|
|
|
|
// for the first time.
|
|
|
|
func (c *Container) Unborrow() {
|
|
|
|
if c.borrow {
|
|
|
|
c.label.SetMarkup(c.markup)
|
|
|
|
c.SetRevealChild(c.markup != "")
|
|
|
|
c.dots.Show() // bad, TODO use revealer
|
|
|
|
c.borrow = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-15 06:32:11 +00:00
|
|
|
func (c *Container) RemoveAuthor(author cchat.Author) {
|
2020-07-04 04:41:12 +00:00
|
|
|
c.state.removeTyper(author.ID())
|
|
|
|
}
|
|
|
|
|
2020-10-15 06:32:11 +00:00
|
|
|
func (c *Container) TrySubscribe(svmsg cchat.Messenger) bool {
|
|
|
|
var tindicator = svmsg.AsTypingIndicator()
|
|
|
|
if tindicator == nil {
|
2020-07-04 04:41:12 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-10-15 06:32:11 +00:00
|
|
|
c.state.Subscribe(tindicator)
|
2020-07-04 04:41:12 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-01-01 10:00:11 +00:00
|
|
|
var noMentionLinks = markup.RenderConfig{
|
|
|
|
NoMentionLinks: true,
|
|
|
|
}
|
|
|
|
|
2020-07-04 04:41:12 +00:00
|
|
|
func render(typers []cchat.Typer) string {
|
|
|
|
// fast path
|
|
|
|
if len(typers) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
var builder strings.Builder
|
|
|
|
|
|
|
|
for i, typer := range typers {
|
2021-01-01 10:00:11 +00:00
|
|
|
output := markup.RenderCmplxWithConfig(typer.Name(), noMentionLinks)
|
|
|
|
|
2020-07-04 04:41:12 +00:00
|
|
|
builder.WriteString("<b>")
|
2021-01-01 10:00:11 +00:00
|
|
|
builder.WriteString(output.Markup)
|
2020-07-04 04:41:12 +00:00
|
|
|
builder.WriteString("</b>")
|
|
|
|
|
|
|
|
switch i {
|
|
|
|
case len(typers) - 2:
|
|
|
|
builder.WriteString(" and ")
|
|
|
|
case len(typers) - 1:
|
|
|
|
// Write nothing if this is the last item.
|
|
|
|
default:
|
|
|
|
builder.WriteString(", ")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(typers) == 1 {
|
|
|
|
builder.WriteString(" is typing.")
|
|
|
|
} else {
|
|
|
|
builder.WriteString(" are typing.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return builder.String()
|
|
|
|
}
|