mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-11-16 11:12:44 +00:00
171 lines
3.9 KiB
Go
171 lines
3.9 KiB
Go
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
|
|
}
|