Fixed minor bugs; improved appearances; removed some dead code
This commit is contained in:
parent
ab4c949428
commit
f2f59b3c2b
2
go.mod
2
go.mod
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/Xuanwo/go-locale v0.2.0
|
||||
github.com/alecthomas/chroma v0.7.3
|
||||
github.com/diamondburned/cchat v0.0.42
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3
|
||||
github.com/diamondburned/imgutil v0.0.0-20200708012333-53c9e45dd28b
|
||||
github.com/goodsign/monday v1.0.0
|
||||
|
|
6
go.sum
6
go.sum
|
@ -60,6 +60,10 @@ github.com/diamondburned/cchat-discord v0.0.0-20200708083530-d0e43cc63b03 h1:Xx4
|
|||
github.com/diamondburned/cchat-discord v0.0.0-20200708083530-d0e43cc63b03/go.mod h1:hjaCeQfhSqANH+ZtxXPMWzpgobb4syy2VcqDK4PXcPU=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b h1:c01OHXPE/HXcZdaPDgNNzmz3xN7MC0yIyL5i+6qILAc=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b/go.mod h1:IDBm0RqdQUASjPGP/RQkhmC1nGhGTTFoaHn9NbD/l7I=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708212314-e08c31b895a6 h1:Nc9B6i7siInzh/uu3bTu7mcw7ckcFDTu4TtSNoxUMis=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708212314-e08c31b895a6/go.mod h1:QHPtnxNrnMFCYB/b9kUP93D30Kf3AuGmkM91tScIpB8=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c h1:4F7IJqMfpF02/j6ZbVO9x29rWExQncLHFbxZrsAhhvM=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c/go.mod h1:QHPtnxNrnMFCYB/b9kUP93D30Kf3AuGmkM91tScIpB8=
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3 h1:xr07/2cwINyrMqh92pQQJVDfQqG0u6gHAK+ZcGfpSew=
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3/go.mod h1:SRu3OOeggELFr2Wd3/+SpYV1eNcvSk2LBhM70NOZSG8=
|
||||
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d h1:Ha/I6PMKi+B4hpWclwlXj0tUMehR7Q0TNxPczzBwzPI=
|
||||
|
@ -72,6 +76,8 @@ github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249 h1:yP7kJ+xC
|
|||
github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249/go.mod h1:xW9hpBZsGi8KpAh10TyP+YQlYBo+Xc+2w4TR6N0951A=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200708090333-227e90d19851 h1:xf1aLPnwK/Yn2z7dBIgQROSVOEc2wtivgnnwBItdEVM=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200708090333-227e90d19851/go.mod h1:FNezDLQIhoDS+RkXLSQ7dJNrt6BW/nVl1krzDgWMQwg=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200708211706-57c712372ede h1:qRmfQCOS+ZnH4G0+8O09PUx3HQTdQwzsDoo1ucTgm2E=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200708211706-57c712372ede/go.mod h1:FNezDLQIhoDS+RkXLSQ7dJNrt6BW/nVl1krzDgWMQwg=
|
||||
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=
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
# c
|
||||
|
||||
**HERE BE DRAGONS!!**
|
||||
|
||||
This package and all its subpackages contain either C code or Cgo code. This
|
||||
bugs me and I hate it. PROCEED WITH CAUTION!
|
|
@ -1,101 +0,0 @@
|
|||
package labelutils
|
||||
|
||||
// #cgo pkg-config: gdk-3.0 gio-2.0 glib-2.0 gobject-2.0 gtk+-3.0
|
||||
// #include <glib.h>
|
||||
// #include <gtk/gtk.h>
|
||||
// #include <pango/pango.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/gotk3/gotk3/pango"
|
||||
)
|
||||
|
||||
func gbool(b bool) C.gboolean {
|
||||
if b {
|
||||
return C.gboolean(C.TRUE)
|
||||
} else {
|
||||
return C.gboolean(C.FALSE)
|
||||
}
|
||||
}
|
||||
|
||||
type Attribute = func() *C.PangoAttribute
|
||||
|
||||
func InsertHyphens(hyphens bool) Attribute {
|
||||
return func() *C.PangoAttribute {
|
||||
return C.pango_attr_insert_hyphens_new(gbool(hyphens))
|
||||
}
|
||||
}
|
||||
|
||||
func Scale(factor float64) Attribute {
|
||||
return func() *C.PangoAttribute {
|
||||
return C.pango_attr_scale_new(C.double(factor))
|
||||
}
|
||||
}
|
||||
|
||||
func Underline(underline pango.Underline) Attribute {
|
||||
return func() *C.PangoAttribute {
|
||||
return C.pango_attr_underline_new(C.PangoUnderline(underline))
|
||||
}
|
||||
}
|
||||
|
||||
func Strikethrough(strikethrough bool) Attribute {
|
||||
return func() *C.PangoAttribute {
|
||||
return C.pango_attr_strikethrough_new(gbool(strikethrough))
|
||||
}
|
||||
}
|
||||
|
||||
const u16divu8 = 65535 / 255
|
||||
|
||||
func rgb(hex uint32) (r, g, b uint16) {
|
||||
r = uint16(hex>>16&255) * u16divu8
|
||||
g = uint16(hex>>8&255) * u16divu8
|
||||
b = uint16(hex&255) * u16divu8
|
||||
return
|
||||
}
|
||||
|
||||
func Background(hex uint32) Attribute {
|
||||
r, g, b := rgb(hex)
|
||||
|
||||
return func() *C.PangoAttribute {
|
||||
return C.pango_attr_background_new(C.guint16(r), C.guint16(g), C.guint16(b))
|
||||
}
|
||||
}
|
||||
|
||||
func Foreground(hex uint32) Attribute {
|
||||
r, g, b := rgb(hex)
|
||||
|
||||
return func() *C.PangoAttribute {
|
||||
return C.pango_attr_foreground_new(C.guint16(r), C.guint16(g), C.guint16(b))
|
||||
}
|
||||
}
|
||||
|
||||
func Style(style pango.Style) Attribute {
|
||||
return func() *C.PangoAttribute {
|
||||
return C.pango_attr_style_new(C.PangoStyle(style))
|
||||
}
|
||||
}
|
||||
|
||||
func Family(family string) Attribute {
|
||||
return func() *C.PangoAttribute {
|
||||
str := C.CString(family)
|
||||
defer C.free(unsafe.Pointer(str))
|
||||
return C.pango_attr_family_new(str)
|
||||
}
|
||||
}
|
||||
|
||||
func AddAttr(l *gtk.Label, attrs ...Attribute) {
|
||||
attrlist := C.pango_attr_list_new()
|
||||
defer C.pango_attr_list_unref(attrlist)
|
||||
|
||||
for _, attr := range attrs {
|
||||
// attr() should not unref; insert is transfer-full
|
||||
// https://discourse.gnome.org/t/pango-how-to-turn-off-hyphenation-for-char-wrapping/2101/2
|
||||
C.pango_attr_list_insert(attrlist, attr())
|
||||
}
|
||||
|
||||
v := (*C.GtkLabel)(unsafe.Pointer(l.Native()))
|
||||
C.gtk_label_set_attributes(v, attrlist)
|
||||
}
|
|
@ -7,7 +7,6 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/diamondburned/cchat-gtk/internal/c/labelutils"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts/httputil"
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/dialog"
|
||||
|
@ -87,7 +86,7 @@ func BindTooltip(connector WidgetConnector) {
|
|||
}
|
||||
|
||||
const urlPrompt = `This link leads to the following URL:
|
||||
<span weight="bold"><a href="%[1]s">%[1]s</a></span>
|
||||
<span weight="bold" insert_hyphens="false"><a href="%[1]s">%[1]s</a></span>
|
||||
Click <b>Open</b> to proceed.`
|
||||
|
||||
var warnLabelCSS = primitives.PrepareCSS(`
|
||||
|
@ -109,9 +108,6 @@ func PromptOpen(uri string) {
|
|||
// Style the label.
|
||||
primitives.AttachCSS(l, warnLabelCSS)
|
||||
|
||||
// Disable hyphens on line wraps.
|
||||
labelutils.AddAttr(l, labelutils.InsertHyphens(false))
|
||||
|
||||
open := func() {
|
||||
if err := open.Start(uri); err != nil {
|
||||
log.Error(errors.Wrap(err, "Failed to open URL after confirm"))
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
package completion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts/httputil"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/completion"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/imgutil"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
const (
|
||||
ImageSize = 25
|
||||
ImageSmall = 25
|
||||
ImageLarge = 40
|
||||
ImagePadding = 6
|
||||
)
|
||||
|
||||
|
@ -51,30 +56,53 @@ func (v *View) Update(words []string, i int) []gtk.IWidget {
|
|||
var widgets = make([]gtk.IWidget, len(v.entries))
|
||||
|
||||
for i, entry := range v.entries {
|
||||
// Container that holds the label.
|
||||
lbox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
lbox.SetVAlign(gtk.ALIGN_CENTER)
|
||||
lbox.Show()
|
||||
|
||||
// Label for the primary text.
|
||||
l := rich.NewLabel(entry.Text)
|
||||
l.Show()
|
||||
lbox.PackStart(l, false, false, 0)
|
||||
|
||||
img, _ := gtk.ImageNew()
|
||||
// Get the iamge size so we can change and use if needed. The default
|
||||
var size = ImageSmall
|
||||
if !entry.Secondary.Empty() {
|
||||
size = ImageLarge
|
||||
|
||||
s := rich.NewLabel(text.Rich{})
|
||||
s.SetMarkup(fmt.Sprintf(
|
||||
`<span alpha="50%%" size="small">%s</span>`,
|
||||
parser.RenderMarkup(entry.Secondary),
|
||||
))
|
||||
s.Show()
|
||||
|
||||
lbox.PackStart(s, false, false, 0)
|
||||
}
|
||||
|
||||
b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
b.PackEnd(lbox, true, true, ImagePadding)
|
||||
b.Show()
|
||||
|
||||
// Do we have an icon?
|
||||
if entry.IconURL != "" {
|
||||
img, _ := gtk.ImageNew()
|
||||
img.SetMarginStart(ImagePadding)
|
||||
img.SetSizeRequest(ImageSize, ImageSize)
|
||||
img.SetSizeRequest(size, size)
|
||||
img.Show()
|
||||
|
||||
// Prepend the image into the box.
|
||||
b.PackEnd(img, false, false, 0)
|
||||
|
||||
var pps []imgutil.Processor
|
||||
if !entry.Image {
|
||||
pps = ppIcon
|
||||
}
|
||||
|
||||
httputil.AsyncImageSized(img, entry.IconURL, ImageSize, ImageSize, pps...)
|
||||
httputil.AsyncImageSized(img, entry.IconURL, size, size, pps...)
|
||||
}
|
||||
|
||||
b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
b.PackStart(img, false, false, 0) // image has pad left
|
||||
b.PackStart(l, true, true, ImagePadding)
|
||||
b.Show()
|
||||
|
||||
widgets[i] = b
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/c/labelutils"
|
||||
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/imgview"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
|
@ -91,7 +90,6 @@ func NewEmptyContainer() *GenericContainer {
|
|||
ts.SetEllipsize(pango.ELLIPSIZE_MIDDLE)
|
||||
ts.SetXAlign(1) // right align
|
||||
ts.SetVAlign(gtk.ALIGN_END)
|
||||
// ts.SetSelectable(true)
|
||||
ts.Show()
|
||||
|
||||
user, _ := gtk.LabelNew("")
|
||||
|
@ -100,7 +98,6 @@ func NewEmptyContainer() *GenericContainer {
|
|||
user.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
||||
user.SetXAlign(1) // right align
|
||||
user.SetVAlign(gtk.ALIGN_START)
|
||||
// user.SetSelectable(true)
|
||||
user.Show()
|
||||
|
||||
content, _ := gtk.LabelNew("")
|
||||
|
@ -110,8 +107,14 @@ func NewEmptyContainer() *GenericContainer {
|
|||
content.SetSelectable(true)
|
||||
content.Show()
|
||||
|
||||
// Never insert hyphens on line breaks in the message content.
|
||||
labelutils.AddAttr(content, labelutils.InsertHyphens(false))
|
||||
// content.Connect("grab-notify", func(l *gtk.Label, grabbed bool) {
|
||||
// if grabbed {
|
||||
// // Hack to stop the label from selecting everything after being
|
||||
// // refocused.
|
||||
// content.SetSelectable(false)
|
||||
// gts.ExecAsync(func() { content.SetSelectable(true) })
|
||||
// }
|
||||
// })
|
||||
|
||||
// Add CSS classes.
|
||||
primitives.AddClass(ts, "message-time")
|
||||
|
|
|
@ -193,7 +193,10 @@ func (v *View) AddPresendMessage(msg input.PresendMessage) func(error) {
|
|||
|
||||
// AuthorEvent should be called on message create/update/delete.
|
||||
func (v *View) AuthorEvent(author cchat.MessageAuthor) {
|
||||
v.Typing.RemoveAuthor(author)
|
||||
// Remove the author from the typing list if it's not nil.
|
||||
if author != nil {
|
||||
v.Typing.RemoveAuthor(author)
|
||||
}
|
||||
}
|
||||
|
||||
// LatestMessageFrom returns the last message ID with that author.
|
||||
|
|
|
@ -3,56 +3,82 @@ package attrmap
|
|||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AppendMap struct {
|
||||
appended map[int]string
|
||||
indices []int
|
||||
appended map[int]string // for opening tags
|
||||
prepended map[int]string // for closing tags
|
||||
indices []int
|
||||
}
|
||||
|
||||
func NewAppendedMap() AppendMap {
|
||||
return AppendMap{
|
||||
appended: make(map[int]string),
|
||||
indices: []int{},
|
||||
appended: map[int]string{},
|
||||
prepended: map[int]string{},
|
||||
indices: []int{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AppendMap) Span(start, end int, attr string) {
|
||||
a.Add(start, `<span `+attr+`>`)
|
||||
a.Add(end, "</span>")
|
||||
func (a *AppendMap) appendIndex(ind int) {
|
||||
// Backwards search which should make things faster.
|
||||
for i := len(a.indices) - 1; i >= 0; i-- {
|
||||
if a.indices[i] == ind {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
a.indices = append(a.indices, ind)
|
||||
}
|
||||
|
||||
func (a *AppendMap) Span(start, end int, attrs ...string) {
|
||||
a.Openf(start, "<span %s>", strings.Join(attrs, " "))
|
||||
a.Close(end, "</span>")
|
||||
}
|
||||
|
||||
func (a *AppendMap) Pair(start, end int, open, close string) {
|
||||
a.Add(start, open)
|
||||
a.Add(end, close)
|
||||
a.Open(start, open)
|
||||
a.Close(end, close)
|
||||
}
|
||||
|
||||
func (a *AppendMap) Addf(ind int, f string, argv ...interface{}) {
|
||||
a.Add(ind, fmt.Sprintf(f, argv...))
|
||||
func (a *AppendMap) Openf(ind int, f string, argv ...interface{}) {
|
||||
a.Open(ind, fmt.Sprintf(f, argv...))
|
||||
}
|
||||
|
||||
func (a *AppendMap) Pad(ind int) {
|
||||
a.Add(ind, "\n")
|
||||
}
|
||||
|
||||
func (a *AppendMap) Add(ind int, attr string) {
|
||||
if _, ok := a.appended[ind]; ok {
|
||||
a.appended[ind] += attr
|
||||
func (a *AppendMap) Open(ind int, attr string) {
|
||||
if str, ok := a.appended[ind]; ok {
|
||||
a.appended[ind] = str + attr // append
|
||||
return
|
||||
}
|
||||
|
||||
a.appended[ind] = attr
|
||||
a.indices = append(a.indices, ind)
|
||||
a.appendIndex(ind)
|
||||
}
|
||||
|
||||
func (a AppendMap) Get(ind int) string {
|
||||
return a.appended[ind]
|
||||
func (a *AppendMap) Close(ind int, attr string) {
|
||||
if str, ok := a.prepended[ind]; ok {
|
||||
a.prepended[ind] = attr + str // prepend
|
||||
return
|
||||
}
|
||||
|
||||
a.prepended[ind] = attr
|
||||
a.appendIndex(ind)
|
||||
}
|
||||
|
||||
func (a AppendMap) Get(ind int) (tags string) {
|
||||
if t, ok := a.appended[ind]; ok {
|
||||
tags += t
|
||||
}
|
||||
if t, ok := a.prepended[ind]; ok {
|
||||
tags += t
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *AppendMap) Finalize(strlen int) []int {
|
||||
// make sure there's always a closing tag at the end so the entire string
|
||||
// gets flushed.
|
||||
a.Add(strlen, "")
|
||||
a.Close(strlen, "")
|
||||
sort.Ints(a.indices)
|
||||
return a.indices
|
||||
}
|
||||
|
|
|
@ -42,7 +42,11 @@ func Tokenize(language, source string) chroma.Iterator {
|
|||
|
||||
func Segments(appendmap *attrmap.AppendMap, src string, seg text.Codeblocker) {
|
||||
var start, end = seg.Bounds()
|
||||
appendmap.Span(start, end, `font_family="monospace"`)
|
||||
appendmap.Span(
|
||||
start, end,
|
||||
`font_family="monospace"`,
|
||||
`insert_hyphens="false"`, // all my homies hate hyphens
|
||||
)
|
||||
|
||||
if i := Tokenize(seg.CodeblockLanguage(), src[start:end]); i != nil {
|
||||
fmtter.segments(appendmap, start, i)
|
||||
|
@ -92,13 +96,13 @@ func (f *formatter) segments(appendmap *attrmap.AppendMap, offset int, iter chro
|
|||
attr := f.styleAttr(token.Type)
|
||||
|
||||
if attr != "" {
|
||||
appendmap.Addf(offset, `<span %s>`, attr)
|
||||
appendmap.Openf(offset, `<span %s>`, attr)
|
||||
}
|
||||
|
||||
offset += len(token.Value)
|
||||
|
||||
if attr != "" {
|
||||
appendmap.Add(offset, "</span>")
|
||||
appendmap.Close(offset, "</span>")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,17 +65,26 @@ func RenderMarkup(content text.Rich) string {
|
|||
start, end := segment.Bounds()
|
||||
|
||||
if segment, ok := segment.(text.Linker); ok {
|
||||
appended.Addf(start, `<a href="%s">`, html.EscapeString(segment.Link()))
|
||||
appended.Add(end, "</a>")
|
||||
appended.Openf(start, `<a href="%s">`, html.EscapeString(segment.Link()))
|
||||
appended.Close(end, "</a>")
|
||||
}
|
||||
|
||||
if segment, ok := segment.(text.Imager); ok {
|
||||
// Ends don't matter with images.
|
||||
appended.Add(start, composeImageMarkup(segment))
|
||||
appended.Open(start, composeImageMarkup(segment))
|
||||
}
|
||||
|
||||
if segment, ok := segment.(text.Colorer); ok {
|
||||
appended.Span(start, end, fmt.Sprintf(`color="#%06X"`, segment.Color()))
|
||||
var attrs = []string{fmt.Sprintf(`color="#%06X"`, segment.Color())}
|
||||
// If the color segment only covers a segment, then add some more
|
||||
// formatting.
|
||||
if start > 0 && end < len(content.Content) {
|
||||
attrs = append(attrs,
|
||||
`bgalpha="10%"`,
|
||||
fmt.Sprintf(`bgcolor="#%06X"`, segment.Color()),
|
||||
)
|
||||
}
|
||||
appended.Span(start, end, attrs...)
|
||||
}
|
||||
|
||||
if segment, ok := segment.(text.Attributor); ok {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/diamondburned/cchat-mock/segments"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
func TestRenderMarkup(t *testing.T) {
|
||||
content := text.Rich{Content: "astolfo is the best trap"}
|
||||
content.Segments = []text.Segment{
|
||||
segments.NewColored(content.Content, 0x55CDFC),
|
||||
}
|
||||
expect := `<span color="#55CDFC">` + content.Content + "</span>"
|
||||
|
||||
if text := RenderMarkup(content); text != expect {
|
||||
t.Fatal("Unexpected text:", text)
|
||||
}
|
||||
}
|
||||
|
||||
// Test no longer works, and should not work anyway.
|
||||
|
||||
// func TestRenderMarkupPartial(t *testing.T) {
|
||||
// content := text.Rich{Content: "random placeholder text go brrr"}
|
||||
// content.Segments = []text.Segment{
|
||||
// // This is absolutely jankery that should not work at all, but we'll try
|
||||
// // it anyway.
|
||||
// coloredSegment{0, 4, 0x55CDFC},
|
||||
// coloredSegment{2, 6, 0xFFFFFF}, // naive parsing, so spans close unexpectedly.
|
||||
// coloredSegment{4, 6, 0xF7A8B8},
|
||||
// }
|
||||
// const expect = "" +
|
||||
// <span color="#55CDFC">ra<span color="#FFFFFF" bgalpha="10%" bgcolor="#FFFFFF">nd<span color="#F7A8B8" bgalpha="10%" bgcolor="#F7A8B8"></span>om</span></span>
|
||||
// `<span color="#55CDFC">ra<span color="#FFFFFF">nd</span>` +
|
||||
// `<span color="#F7A8B8">om</span></span>`
|
||||
|
||||
// if text := RenderMarkup(content); !strings.HasPrefix(text, expect) {
|
||||
// t.Fatal("Unexpected text:", text)
|
||||
// }
|
||||
// }
|
||||
|
||||
type coloredSegment struct {
|
||||
start int
|
||||
end int
|
||||
color uint32
|
||||
}
|
||||
|
||||
var _ text.Colorer = (*coloredSegment)(nil)
|
||||
|
||||
func (c coloredSegment) Bounds() (start, end int) {
|
||||
return c.start, c.end
|
||||
}
|
||||
|
||||
func (c coloredSegment) Color() uint32 {
|
||||
return c.color
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/gotk3/gotk3/gdk"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/gotk3/gotk3/pango"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
func AppendEditBadge(b *gtk.TextBuffer, editedAt time.Time) {
|
||||
r := newRenderCtx(b)
|
||||
|
||||
t := r.createTag(map[string]interface{}{
|
||||
"scale": 0.84,
|
||||
"scale-set": true,
|
||||
"foreground": "#808080", // blue-ish URL color
|
||||
})
|
||||
|
||||
bindClicker(t, func(_ *gtk.TextView, ev *gdk.Event) {
|
||||
switch ev := gdk.EventMotionNewFromEvent(ev); ev.Type() {
|
||||
case gdk.EVENT_PROXIMITY_IN:
|
||||
log.Println("Proximity in")
|
||||
case gdk.EVENT_PROXIMITY_OUT:
|
||||
log.Println("Proximity out")
|
||||
}
|
||||
})
|
||||
|
||||
b.InsertWithTag(b.GetEndIter(), " (edited)", t)
|
||||
}
|
||||
|
||||
func RenderTextBuffer(b *gtk.TextBuffer, content text.Rich) {
|
||||
r := newRenderCtx(b)
|
||||
b.SetText(content.Content)
|
||||
|
||||
// Sort so that all starting points are sorted incrementally.
|
||||
sort.Slice(content.Segments, func(i, j int) bool {
|
||||
i, _ = content.Segments[i].Bounds()
|
||||
j, _ = content.Segments[j].Bounds()
|
||||
return i < j
|
||||
})
|
||||
|
||||
for _, segment := range content.Segments {
|
||||
start, end := segment.Bounds()
|
||||
|
||||
switch segment := segment.(type) {
|
||||
case text.Attributor:
|
||||
r.tagAttr(start, end, segment.Attribute())
|
||||
|
||||
case text.Colorer:
|
||||
color := fmt.Sprintf("#%06X", segment.Color())
|
||||
r.applyProps(start, end, map[string]interface{}{
|
||||
"foreground": color,
|
||||
})
|
||||
|
||||
case text.Codeblocker:
|
||||
r.applyProps(start, end, map[string]interface{}{
|
||||
"family": "Monospace",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type renderCtx struct {
|
||||
b *gtk.TextBuffer
|
||||
t *gtk.TextTagTable
|
||||
}
|
||||
|
||||
func newRenderCtx(b *gtk.TextBuffer) *renderCtx {
|
||||
t, _ := b.GetTagTable()
|
||||
return &renderCtx{b, t}
|
||||
}
|
||||
|
||||
type OnClicker func(tv *gtk.TextView, ev *gdk.Event)
|
||||
|
||||
func bindClicker(v primitives.Connector, fn OnClicker) {
|
||||
v.Connect("event", func(_ *gtk.TextTag, tv *gtk.TextView, ev *gdk.Event) {
|
||||
evButton := gdk.EventButtonNewFromEvent(ev)
|
||||
if evButton.Type() != gdk.EVENT_BUTTON_RELEASE || evButton.Button() != gdk.BUTTON_PRIMARY {
|
||||
return
|
||||
}
|
||||
|
||||
fn(tv, ev)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *renderCtx) applyHyperlink(start, end int, url string) {
|
||||
t := r.createTag(map[string]interface{}{
|
||||
"underline": pango.UNDERLINE_SINGLE,
|
||||
"foreground": "#3F7CE0", // blue-ish URL color
|
||||
})
|
||||
|
||||
bindClicker(t, func(*gtk.TextView, *gdk.Event) {
|
||||
if err := open.Start(url); err != nil {
|
||||
log.Error(errors.Wrap(err, "Failed to open image URL"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (r *renderCtx) applyProps(start, end int, props map[string]interface{}) {
|
||||
tag := r.createTag(props)
|
||||
r.applyTag(start, end, tag)
|
||||
}
|
||||
|
||||
func (r *renderCtx) applyTag(start, end int, tag *gtk.TextTag) {
|
||||
istart, iend := r.iters(start, end)
|
||||
r.b.ApplyTag(tag, istart, iend)
|
||||
}
|
||||
|
||||
func (r *renderCtx) createTag(props map[string]interface{}) *gtk.TextTag {
|
||||
t, _ := gtk.TextTagNew("")
|
||||
r.t.Add(t)
|
||||
|
||||
if props != nil {
|
||||
for k, v := range props {
|
||||
t.SetProperty(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (r *renderCtx) iters(start, end int) (is, ie *gtk.TextIter) {
|
||||
return r.b.GetIterAtOffset(start), r.b.GetIterAtOffset(end)
|
||||
}
|
||||
|
||||
func (r *renderCtx) tagAttr(start, end int, attr text.Attribute) {
|
||||
var props = tagAttrMap(attr)
|
||||
if props == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.applyTag(start, end, r.createTag(props))
|
||||
}
|
||||
|
||||
func tagAttrMap(attr text.Attribute) map[string]interface{} {
|
||||
if attr == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var props = make(map[string]interface{}, 1)
|
||||
|
||||
if attr.Has(text.AttrBold) {
|
||||
props["weight"] = pango.WEIGHT_BOLD
|
||||
}
|
||||
if attr.Has(text.AttrItalics) {
|
||||
props["style"] = pango.STYLE_ITALIC
|
||||
}
|
||||
if attr.Has(text.AttrUnderline) {
|
||||
props["underline"] = pango.UNDERLINE_SINGLE
|
||||
}
|
||||
if attr.Has(text.AttrStrikethrough) {
|
||||
props["strikethrough"] = true
|
||||
}
|
||||
if attr.Has(text.AttrSpoiler) {
|
||||
props["foreground"] = "#808080"
|
||||
}
|
||||
if attr.Has(text.AttrMonospace) {
|
||||
props["family"] = "Monospace"
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/diamondburned/cchat-mock/segments"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
func TestRenderMarkup(t *testing.T) {
|
||||
content := text.Rich{Content: "astolfo is the best trap"}
|
||||
content.Segments = []text.Segment{
|
||||
segments.NewColored(content.Content, 0x55CDFC),
|
||||
}
|
||||
expect := `<span color="#55CDFC">` + content.Content + "</span>"
|
||||
|
||||
if text := RenderMarkup(content); text != expect {
|
||||
t.Fatal("Unexpected text:", text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderMarkupPartial(t *testing.T) {
|
||||
content := text.Rich{Content: "random placeholder text go brrr"}
|
||||
content.Segments = []text.Segment{
|
||||
// This is absolutely jankery that should not work at all, but we'll try
|
||||
// it anyway.
|
||||
coloredSegment{0, 4, 0x55CDFC},
|
||||
coloredSegment{2, 6, 0xFFFFFF}, // naive parsing, so spans close unexpectedly.
|
||||
coloredSegment{4, 6, 0xF7A8B8},
|
||||
}
|
||||
const expect = "" +
|
||||
`<span color="#55CDFC">ra<span color="#FFFFFF">nd</span>` +
|
||||
`<span color="#F7A8B8">om</span></span>`
|
||||
|
||||
if text := RenderMarkup(content); !strings.HasPrefix(text, expect) {
|
||||
t.Fatal("Unexpected text:", text)
|
||||
}
|
||||
}
|
||||
|
||||
type coloredSegment struct {
|
||||
start int
|
||||
end int
|
||||
color uint32
|
||||
}
|
||||
|
||||
var _ text.Colorer = (*coloredSegment)(nil)
|
||||
|
||||
func (c coloredSegment) Bounds() (start, end int) {
|
||||
return c.start, c.end
|
||||
}
|
||||
|
||||
func (c coloredSegment) Color() uint32 {
|
||||
return c.color
|
||||
}
|
Loading…
Reference in New Issue