mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-12-22 20:27:07 +00:00
Added colors and stuff
This commit is contained in:
parent
b852498ee4
commit
0171ac6b52
5
go.mod
5
go.mod
|
@ -6,12 +6,11 @@ replace github.com/diamondburned/cchat-mock => ../cchat-mock/
|
|||
|
||||
require (
|
||||
github.com/Xuanwo/go-locale v0.2.0
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/diamondburned/cchat v0.0.15
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200604043646-de5384bd320d
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200605224934-31a53c555ea2
|
||||
github.com/goodsign/monday v1.0.0
|
||||
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
||||
github.com/markbates/pkger v0.17.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717
|
||||
golang.org/x/tools v0.0.0-20200529172331-a64b76657301 // indirect
|
||||
)
|
||||
|
|
36
go.sum
36
go.sum
|
@ -7,16 +7,12 @@ github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3E
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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.10 h1:aiUVgGre5E/HV+Iw6tmBVbuGctQI+JndV9nIDoYuRPY=
|
||||
github.com/diamondburned/cchat v0.0.10/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||
github.com/diamondburned/cchat v0.0.13 h1:p8SyFjiRVCTjvwSJ4FsICGVYVZ3g0Iu02FrwmLuKiKE=
|
||||
github.com/diamondburned/cchat v0.0.13/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||
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/cchat-mock v0.0.0-20200529184140-47fa2491d2fc h1:xYSN3re1QOd5af5zG15pLKfBM+fergw7Rg62UHmE22g=
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200529184140-47fa2491d2fc/go.mod h1:rQm5EKhNyBRYHKtirSkf+Db23nr3mTs2bnOThfTfzec=
|
||||
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=
|
||||
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
||||
|
@ -27,6 +23,13 @@ github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194 h1:bB6XWpxMt2isCWqzj
|
|||
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
||||
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=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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/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=
|
||||
|
@ -39,35 +42,22 @@ 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.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
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/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200529172331-a64b76657301 h1:G6CNEgFU8/XwexSnuFw+Jq/WePjRitgy6ofBcPnAIPo=
|
||||
golang.org/x/tools v0.0.0-20200529172331-a64b76657301/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
64
internal/gts/css.go
Normal file
64
internal/gts/css.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package gts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"github.com/gotk3/gotk3/gdk"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/markbates/pkger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var cssRepos = map[string]*gtk.CssProvider{}
|
||||
|
||||
func getDefaultScreen() *gdk.Screen {
|
||||
d, _ := gdk.DisplayGetDefault()
|
||||
s, _ := d.GetDefaultScreen()
|
||||
return s
|
||||
}
|
||||
|
||||
func loadProviders(screen *gdk.Screen) {
|
||||
for file, repo := range cssRepos {
|
||||
gtk.AddProviderForScreen(
|
||||
screen, repo,
|
||||
uint(gtk.STYLE_PROVIDER_PRIORITY_APPLICATION),
|
||||
)
|
||||
// mark as done
|
||||
delete(cssRepos, file)
|
||||
}
|
||||
}
|
||||
|
||||
func LoadCSS(files ...string) {
|
||||
var buf bytes.Buffer
|
||||
for _, file := range files {
|
||||
buf.Reset()
|
||||
|
||||
if err := readFile(&buf, file); err != nil {
|
||||
log.Error(errors.Wrap(err, "Failed to load a CSS file"))
|
||||
continue
|
||||
}
|
||||
|
||||
prov, _ := gtk.CssProviderNew()
|
||||
if err := prov.LoadFromData(buf.String()); err != nil {
|
||||
log.Error(errors.Wrap(err, "Failed to parse CSS "+file))
|
||||
continue
|
||||
}
|
||||
|
||||
cssRepos[file] = prov
|
||||
}
|
||||
}
|
||||
|
||||
func readFile(buf *bytes.Buffer, file string) error {
|
||||
f, err := pkger.Open(file)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to load a CSS file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := buf.ReadFrom(f); err != nil {
|
||||
return errors.Wrap(err, "Failed to read file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -9,6 +9,8 @@ import (
|
|||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
const AppID = "com.github.diamondburned.cchat-gtk"
|
||||
|
||||
var Args = append([]string{}, os.Args...)
|
||||
var recvPool *sync.Pool
|
||||
|
||||
|
@ -27,7 +29,7 @@ func init() {
|
|||
},
|
||||
}
|
||||
|
||||
App.Application, _ = gtk.ApplicationNew("com.github.diamondburned.cchat-gtk", 0)
|
||||
App.Application, _ = gtk.ApplicationNew(AppID, 0)
|
||||
}
|
||||
|
||||
type Windower interface {
|
||||
|
@ -47,10 +49,16 @@ type WindowHeaderer interface {
|
|||
|
||||
func Main(wfn func() WindowHeaderer) {
|
||||
App.Application.Connect("activate", func() {
|
||||
// Load all CSS onto the default screen.
|
||||
loadProviders(getDefaultScreen())
|
||||
|
||||
App.Header, _ = gtk.HeaderBarNew()
|
||||
App.Header.SetShowCloseButton(true)
|
||||
App.Header.Show()
|
||||
|
||||
b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
App.Header.SetCustomTitle(b)
|
||||
|
||||
App.Window, _ = gtk.ApplicationWindowNew(App.Application)
|
||||
App.Window.SetDefaultSize(1000, 500)
|
||||
App.Window.SetTitlebar(App.Header)
|
||||
|
|
|
@ -2,6 +2,7 @@ package log
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -59,3 +60,7 @@ func WriteEntry(entry Entry) {
|
|||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func Println(v ...interface{}) {
|
||||
log.Println(v...)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,73 @@
|
|||
package ui
|
||||
|
||||
import "github.com/gotk3/gotk3/gtk"
|
||||
import (
|
||||
"html"
|
||||
"strings"
|
||||
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
type header struct {
|
||||
*gtk.Box
|
||||
left *gtk.Box // TODO
|
||||
right *headerRight
|
||||
}
|
||||
|
||||
func newHeader() *header {
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
left, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
left.SetSizeRequest(LeftWidth, -1)
|
||||
left.Show()
|
||||
|
||||
right := newHeaderRight()
|
||||
right.Show()
|
||||
|
||||
separator, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
||||
separator.Show()
|
||||
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
box.PackStart(left, false, false, 0)
|
||||
box.PackStart(separator, false, false, 0)
|
||||
box.PackStart(right, true, true, 0)
|
||||
box.Show()
|
||||
|
||||
// TODO
|
||||
return &header{box}
|
||||
return &header{
|
||||
box,
|
||||
left,
|
||||
right,
|
||||
}
|
||||
}
|
||||
|
||||
const BreadcrumbSlash = `<span weight="light" rise="-1024" size="x-large">/</span>`
|
||||
|
||||
func (h *header) SetBreadcrumb(b breadcrumb.Breadcrumb) {
|
||||
for i := range b {
|
||||
b[i] = html.EscapeString(b[i])
|
||||
}
|
||||
|
||||
h.right.breadcrumb.SetMarkup(
|
||||
BreadcrumbSlash + " " + strings.Join(b, " "+BreadcrumbSlash+" "),
|
||||
)
|
||||
}
|
||||
|
||||
type headerRight struct {
|
||||
*gtk.Box
|
||||
breadcrumb *gtk.Label
|
||||
}
|
||||
|
||||
func newHeaderRight() *headerRight {
|
||||
bc, _ := gtk.LabelNew(BreadcrumbSlash)
|
||||
bc.SetUseMarkup(true)
|
||||
bc.SetXAlign(0.0)
|
||||
bc.Show()
|
||||
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
box.PackStart(bc, true, true, 14)
|
||||
box.Show()
|
||||
|
||||
return &headerRight{
|
||||
Box: box,
|
||||
breadcrumb: bc,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/gotk3/gotk3/pango"
|
||||
|
@ -104,10 +105,9 @@ func (m *Message) UpdateAuthor(author cchat.MessageAuthor) {
|
|||
}
|
||||
|
||||
func (m *Message) updateAuthorName(name text.Rich) {
|
||||
m.Username.SetLabel(name.Content)
|
||||
m.Username.SetTooltipText(name.Content)
|
||||
m.Username.SetMarkup(parser.RenderMarkup(name))
|
||||
}
|
||||
|
||||
func (m *Message) UpdateContent(content text.Rich) {
|
||||
m.Content.SetLabel(content.Content)
|
||||
m.Content.SetMarkup(parser.RenderMarkup(content))
|
||||
}
|
||||
|
|
1
internal/ui/message/cozy/cozy.go
Normal file
1
internal/ui/message/cozy/cozy.go
Normal file
|
@ -0,0 +1 @@
|
|||
package cozy
|
|
@ -10,11 +10,48 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type usernameContainer struct {
|
||||
*gtk.Revealer
|
||||
label *rich.Label
|
||||
}
|
||||
|
||||
func newUsernameContainer() *usernameContainer {
|
||||
label := rich.NewLabel(text.Rich{})
|
||||
label.SetMaxWidthChars(35)
|
||||
label.SetVAlign(gtk.ALIGN_START)
|
||||
label.SetMarginTop(inputmargin)
|
||||
label.SetMarginBottom(inputmargin)
|
||||
label.SetMarginStart(10)
|
||||
label.SetMarginEnd(10)
|
||||
label.Show()
|
||||
|
||||
rev, _ := gtk.RevealerNew()
|
||||
rev.SetRevealChild(false)
|
||||
rev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
|
||||
rev.SetTransitionDuration(50)
|
||||
rev.Add(label)
|
||||
|
||||
return &usernameContainer{rev, label}
|
||||
}
|
||||
|
||||
// GetLabel is not thread-safe.
|
||||
func (u *usernameContainer) GetLabel() text.Rich {
|
||||
return u.label.GetLabel()
|
||||
}
|
||||
|
||||
// SetLabel is thread-safe.
|
||||
func (u *usernameContainer) SetLabel(content text.Rich) {
|
||||
gts.ExecAsync(func() {
|
||||
u.label.SetLabelUnsafe(content)
|
||||
|
||||
// Reveal if the name is not empty.
|
||||
u.SetRevealChild(!u.label.GetLabel().Empty())
|
||||
})
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
*gtk.Box
|
||||
|
||||
namerev *gtk.Revealer
|
||||
username *rich.Label // TODO
|
||||
username *usernameContainer
|
||||
|
||||
TextScroll *gtk.ScrolledWindow
|
||||
text *gtk.TextView
|
||||
|
@ -30,25 +67,12 @@ type Controller interface {
|
|||
PresendMessage(msg PresendMessage) (onErr func(error))
|
||||
}
|
||||
|
||||
const inputmargin = 3
|
||||
const inputmargin = 4
|
||||
|
||||
func NewField(ctrl Controller) *Field {
|
||||
username := rich.NewLabel(text.Rich{})
|
||||
username.SetMaxWidthChars(35)
|
||||
username.SetVAlign(gtk.ALIGN_START)
|
||||
username.SetMarginTop(inputmargin)
|
||||
username.SetMarginBottom(inputmargin)
|
||||
username.SetMarginStart(10)
|
||||
username.SetMarginEnd(10)
|
||||
username := newUsernameContainer()
|
||||
username.Show()
|
||||
|
||||
namerev, _ := gtk.RevealerNew()
|
||||
namerev.SetRevealChild(false)
|
||||
namerev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
|
||||
namerev.SetTransitionDuration(50)
|
||||
namerev.Add(username)
|
||||
namerev.Show()
|
||||
|
||||
text, _ := gtk.TextViewNew()
|
||||
text.SetSensitive(false)
|
||||
text.SetWrapMode(gtk.WRAP_WORD_CHAR)
|
||||
|
@ -68,13 +92,12 @@ func NewField(ctrl Controller) *Field {
|
|||
sw.Show()
|
||||
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
box.PackStart(namerev, false, false, 0)
|
||||
box.PackStart(username, false, false, 0)
|
||||
box.PackStart(sw, true, true, 0)
|
||||
box.Show()
|
||||
|
||||
field := &Field{
|
||||
Box: box,
|
||||
namerev: namerev,
|
||||
username: username,
|
||||
TextScroll: sw,
|
||||
text: text,
|
||||
|
@ -107,9 +130,6 @@ func (f *Field) SetSender(session cchat.Session, sender cchat.ServerMessageSende
|
|||
log.Warn(err)
|
||||
}
|
||||
|
||||
// Reveal if the name is not empty.
|
||||
f.namerev.SetRevealChild(!f.username.GetLabel().Empty())
|
||||
|
||||
// Set the sender.
|
||||
f.sender = sender
|
||||
f.text.SetSensitive(sender != nil) // grey if sender is nil
|
||||
|
|
|
@ -1 +1,83 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html"
|
||||
"sort"
|
||||
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
type attrAppendMap struct {
|
||||
appended map[int]string
|
||||
indices []int
|
||||
}
|
||||
|
||||
func newAttrAppendedMap() attrAppendMap {
|
||||
return attrAppendMap{
|
||||
appended: make(map[int]string),
|
||||
indices: []int{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *attrAppendMap) add(ind int, attr string) {
|
||||
if _, ok := a.appended[ind]; ok {
|
||||
a.appended[ind] += attr
|
||||
return
|
||||
}
|
||||
|
||||
a.appended[ind] = attr
|
||||
a.indices = append(a.indices, ind)
|
||||
}
|
||||
|
||||
func (a attrAppendMap) get(ind int) string {
|
||||
return a.appended[ind]
|
||||
}
|
||||
|
||||
func (a *attrAppendMap) finalize(strlen int) []int {
|
||||
// make sure there's always a closing tag at the end so the entire string
|
||||
// gets flushed.
|
||||
a.add(strlen, "")
|
||||
sort.Ints(a.indices)
|
||||
return a.indices
|
||||
}
|
||||
|
||||
func RenderMarkup(content text.Rich) string {
|
||||
buf := bytes.Buffer{}
|
||||
buf.Grow(len(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
|
||||
// })
|
||||
|
||||
// map to append strings to indices
|
||||
var appended = newAttrAppendedMap()
|
||||
|
||||
// Parse all segments.
|
||||
for _, segment := range content.Segments {
|
||||
start, end := segment.Bounds()
|
||||
|
||||
switch segment := segment.(type) {
|
||||
case text.Colorer:
|
||||
appended.add(start, fmt.Sprintf("<span color=\"#%06X\">", segment.Color()))
|
||||
appended.add(end, "</span>")
|
||||
}
|
||||
}
|
||||
|
||||
var lastIndex = 0
|
||||
|
||||
for _, index := range appended.finalize(len(content.Content)) {
|
||||
// Write the content.
|
||||
buf.WriteString(html.EscapeString(content.Content[lastIndex:index]))
|
||||
// Write the tags.
|
||||
buf.WriteString(appended.get(index))
|
||||
// Set the last index.
|
||||
lastIndex = index
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
|
55
internal/ui/rich/parser/parser_test.go
Normal file
55
internal/ui/rich/parser/parser_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
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
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
type Labeler interface {
|
||||
cchat.LabelContainer // thread-safe
|
||||
GetLabel() text.Rich // not thread-safe
|
||||
GetText() string
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
|
@ -26,7 +28,8 @@ var (
|
|||
)
|
||||
|
||||
func NewLabel(content text.Rich) *Label {
|
||||
label, _ := gtk.LabelNew(content.Content)
|
||||
label, _ := gtk.LabelNew("")
|
||||
label.SetMarkup(parser.RenderMarkup(content))
|
||||
label.SetHAlign(gtk.ALIGN_START)
|
||||
return &Label{*label, content}
|
||||
}
|
||||
|
@ -34,16 +37,27 @@ func NewLabel(content text.Rich) *Label {
|
|||
// SetLabel is thread-safe.
|
||||
func (l *Label) SetLabel(content text.Rich) {
|
||||
gts.ExecAsync(func() {
|
||||
l.current = content
|
||||
l.SetText(content.Content)
|
||||
l.SetLabelUnsafe(content)
|
||||
})
|
||||
}
|
||||
|
||||
// SetLabelUnsafe sets the label in the current thread, meaning it's not
|
||||
// thread-safe.
|
||||
func (l *Label) SetLabelUnsafe(content text.Rich) {
|
||||
l.current = content
|
||||
l.SetMarkup(parser.RenderMarkup(content))
|
||||
}
|
||||
|
||||
// GetLabel is NOT thread-safe.
|
||||
func (l *Label) GetLabel() text.Rich {
|
||||
return l.current
|
||||
}
|
||||
|
||||
// GetText is NOT thread-safe.
|
||||
func (l *Label) GetText() string {
|
||||
return l.current.Content
|
||||
}
|
||||
|
||||
type ToggleButton struct {
|
||||
gtk.ToggleButton
|
||||
Label
|
||||
|
|
21
internal/ui/service/breadcrumb/breadcrumb.go
Normal file
21
internal/ui/service/breadcrumb/breadcrumb.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package breadcrumb
|
||||
|
||||
import "strings"
|
||||
|
||||
type Breadcrumb []string
|
||||
|
||||
func (b Breadcrumb) String() string {
|
||||
return strings.Join([]string(b), "/")
|
||||
}
|
||||
|
||||
type Breadcrumber interface {
|
||||
Breadcrumb() Breadcrumb
|
||||
}
|
||||
|
||||
// Try accepts a nilable breadcrumber and handles it appropriately.
|
||||
func Try(i Breadcrumber, appended ...string) []string {
|
||||
if i == nil {
|
||||
return appended
|
||||
}
|
||||
return append(i.Breadcrumb(), appended...)
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/diamondburned/cchat"
|
||||
"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"
|
||||
|
@ -91,7 +92,7 @@ func NewContainer(svc cchat.Service, ctrl Controller) *Container {
|
|||
}
|
||||
|
||||
func (c *Container) AddSession(ses cchat.Session) {
|
||||
srow := session.New(ses, c.rowctrl)
|
||||
srow := session.New(c, ses, c.rowctrl)
|
||||
c.children.addSessionRow(srow)
|
||||
}
|
||||
|
||||
|
@ -103,6 +104,10 @@ func (c *Container) Sessions() []cchat.Session {
|
|||
return sessions
|
||||
}
|
||||
|
||||
func (c *Container) Breadcrumb() breadcrumb.Breadcrumb {
|
||||
return breadcrumb.Try(nil, c.header.reveal.GetText())
|
||||
}
|
||||
|
||||
type header struct {
|
||||
*gtk.Box
|
||||
reveal *rich.ToggleButtonImage // no rich text here but it's left aligned
|
||||
|
|
177
internal/ui/service/session/server/server.go
Normal file
177
internal/ui/service/session/server/server.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
package server
|
||||
|
||||
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/primitives"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const ChildrenMargin = 24
|
||||
|
||||
type Controller interface {
|
||||
MessageRowSelected(*Row, cchat.ServerMessage)
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
*gtk.Box
|
||||
Button *rich.ToggleButtonImage
|
||||
Server cchat.Server
|
||||
Parent breadcrumb.Breadcrumber
|
||||
|
||||
ctrl Controller
|
||||
|
||||
// enum 1
|
||||
message cchat.ServerMessage
|
||||
|
||||
// enum 2
|
||||
children *Children
|
||||
}
|
||||
|
||||
func NewRow(parent breadcrumb.Breadcrumber, server cchat.Server, ctrl Controller) *Row {
|
||||
button := rich.NewToggleButtonImage(text.Rich{}, "")
|
||||
button.Box.SetHAlign(gtk.ALIGN_START)
|
||||
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"})
|
||||
}
|
||||
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
box.PackStart(button, false, false, 0)
|
||||
box.Show()
|
||||
|
||||
primitives.AddClass(box, "server")
|
||||
|
||||
// TODO: images
|
||||
|
||||
var row = &Row{
|
||||
Box: box,
|
||||
Button: button,
|
||||
Server: server,
|
||||
Parent: parent,
|
||||
ctrl: ctrl,
|
||||
}
|
||||
|
||||
switch server := server.(type) {
|
||||
case cchat.ServerList:
|
||||
row.children = NewChildren(row, server, ctrl)
|
||||
box.PackStart(row.children, false, false, 0)
|
||||
|
||||
primitives.AddClass(box, "server-list")
|
||||
|
||||
case cchat.ServerMessage:
|
||||
row.message = server
|
||||
|
||||
primitives.AddClass(box, "server-message")
|
||||
}
|
||||
|
||||
button.Connect("clicked", row.onClick)
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
func (row *Row) GetActive() bool {
|
||||
return row.Button.GetActive()
|
||||
}
|
||||
|
||||
func (row *Row) onClick() {
|
||||
switch {
|
||||
|
||||
// If the server is a message server. We're only selected if the button is
|
||||
// pressed.
|
||||
case row.message != nil && row.GetActive():
|
||||
row.ctrl.MessageRowSelected(row, row.message)
|
||||
|
||||
// If the server is a list of smaller servers.
|
||||
case row.children != nil:
|
||||
row.children.SetRevealChild(!row.children.GetRevealChild())
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
|
||||
return breadcrumb.Try(r.Parent, r.Button.GetText())
|
||||
}
|
||||
|
||||
// Children is a children server with a reference to the parent.
|
||||
type Children struct {
|
||||
*gtk.Revealer
|
||||
Main *gtk.Box
|
||||
List cchat.ServerList
|
||||
|
||||
rowctrl Controller
|
||||
|
||||
Rows []*Row
|
||||
Parent breadcrumb.Breadcrumber
|
||||
}
|
||||
|
||||
func NewChildren(parent breadcrumb.Breadcrumber, list cchat.ServerList, ctrl Controller) *Children {
|
||||
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
main.SetMarginStart(ChildrenMargin)
|
||||
main.Show()
|
||||
|
||||
rev, _ := gtk.RevealerNew()
|
||||
rev.SetRevealChild(false)
|
||||
rev.Add(main)
|
||||
rev.Show()
|
||||
|
||||
children := &Children{
|
||||
Revealer: rev,
|
||||
Main: main,
|
||||
List: list,
|
||||
rowctrl: ctrl,
|
||||
Parent: parent,
|
||||
}
|
||||
|
||||
if err := list.Servers(children); err != nil {
|
||||
log.Error(errors.Wrap(err, "Failed to get servers"))
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
func (c *Children) SetServers(servers []cchat.Server) {
|
||||
gts.ExecAsync(func() {
|
||||
// Save the current state.
|
||||
var oldID string
|
||||
for _, row := range c.Rows {
|
||||
if row.GetActive() {
|
||||
oldID = row.Server.ID()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Update the server list.
|
||||
for _, row := range c.Rows {
|
||||
c.Main.Remove(row)
|
||||
}
|
||||
|
||||
c.Rows = make([]*Row, len(servers))
|
||||
|
||||
for i, server := range servers {
|
||||
row := NewRow(c, server, c.rowctrl)
|
||||
c.Rows[i] = row
|
||||
c.Main.Add(row)
|
||||
}
|
||||
|
||||
// Update parent reference? Only if it's activated.
|
||||
if oldID != "" {
|
||||
for _, row := range c.Rows {
|
||||
if row.Server.ID() == oldID {
|
||||
row.Button.SetActive(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Children) Breadcrumb() breadcrumb.Breadcrumb {
|
||||
return breadcrumb.Try(c.Parent)
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const ChildrenMargin = 24
|
||||
|
||||
type Controller interface {
|
||||
MessageRowSelected(*Row, cchat.ServerMessage)
|
||||
}
|
||||
|
||||
// Children is a children server with a reference to the parent.
|
||||
type Children struct {
|
||||
*gtk.Revealer
|
||||
Main *gtk.Box
|
||||
List cchat.ServerList
|
||||
|
||||
rowctrl Controller
|
||||
|
||||
Rows []*Row
|
||||
ParentRow *Row
|
||||
}
|
||||
|
||||
func NewChildren(parent *Row, list cchat.ServerList, ctrl Controller) *Children {
|
||||
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
main.SetMarginStart(ChildrenMargin)
|
||||
main.Show()
|
||||
|
||||
rev, _ := gtk.RevealerNew()
|
||||
rev.SetRevealChild(false)
|
||||
rev.Add(main)
|
||||
rev.Show()
|
||||
|
||||
children := &Children{
|
||||
Revealer: rev,
|
||||
Main: main,
|
||||
List: list,
|
||||
rowctrl: ctrl,
|
||||
ParentRow: parent,
|
||||
}
|
||||
|
||||
if err := list.Servers(children); err != nil {
|
||||
log.Error(errors.Wrap(err, "Failed to get servers"))
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
func (c *Children) SetServers(servers []cchat.Server) {
|
||||
gts.ExecAsync(func() {
|
||||
// Save the current state.
|
||||
var oldID string
|
||||
for _, row := range c.Rows {
|
||||
if row.GetActive() {
|
||||
oldID = row.Server.ID()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Update the server list.
|
||||
for _, row := range c.Rows {
|
||||
c.Main.Remove(row)
|
||||
}
|
||||
|
||||
c.Rows = make([]*Row, len(servers))
|
||||
|
||||
for i, server := range servers {
|
||||
row := NewRow(c, server, c.rowctrl)
|
||||
c.Rows[i] = row
|
||||
c.Main.Add(row)
|
||||
}
|
||||
|
||||
// Update parent reference? Only if it's activated.
|
||||
if oldID != "" {
|
||||
for _, row := range c.Rows {
|
||||
if row.Server.ID() == oldID {
|
||||
row.Button.SetActive(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package server
|
||||
|
||||
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/text"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Row struct {
|
||||
*gtk.Box
|
||||
Button *rich.ToggleButtonImage
|
||||
Server cchat.Server
|
||||
Parent *Children
|
||||
|
||||
ctrl Controller
|
||||
|
||||
// enum 1
|
||||
message cchat.ServerMessage
|
||||
|
||||
// enum 2
|
||||
children *Children
|
||||
}
|
||||
|
||||
func NewRow(parent *Children, server cchat.Server, ctrl Controller) *Row {
|
||||
button := rich.NewToggleButtonImage(text.Rich{}, "")
|
||||
button.Box.SetHAlign(gtk.ALIGN_START)
|
||||
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"})
|
||||
}
|
||||
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
box.PackStart(button, false, false, 0)
|
||||
box.Show()
|
||||
|
||||
primitives.AddClass(box, "server")
|
||||
|
||||
// TODO: images
|
||||
|
||||
var row = &Row{
|
||||
Box: box,
|
||||
Button: button,
|
||||
Server: server,
|
||||
Parent: parent,
|
||||
ctrl: ctrl,
|
||||
}
|
||||
|
||||
switch server := server.(type) {
|
||||
case cchat.ServerList:
|
||||
row.children = NewChildren(row, server, ctrl)
|
||||
box.PackStart(row.children, false, false, 0)
|
||||
|
||||
primitives.AddClass(box, "server-list")
|
||||
|
||||
case cchat.ServerMessage:
|
||||
row.message = server
|
||||
|
||||
primitives.AddClass(box, "server-message")
|
||||
}
|
||||
|
||||
button.Connect("clicked", row.onClick)
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
func (row *Row) GetActive() bool {
|
||||
return row.Button.GetActive()
|
||||
}
|
||||
|
||||
func (row *Row) onClick() {
|
||||
switch {
|
||||
|
||||
// If the server is a message server. We're only selected if the button is
|
||||
// pressed.
|
||||
case row.message != nil && row.GetActive():
|
||||
row.ctrl.MessageRowSelected(row, row.message)
|
||||
|
||||
// If the server is a list of smaller servers.
|
||||
case row.children != nil:
|
||||
row.children.SetRevealChild(!row.children.GetRevealChild())
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Row) Breadcrumb() string {
|
||||
var label = r.Button.GetLabel().Content
|
||||
|
||||
// Does the row have a parent?
|
||||
if r.Parent != nil {
|
||||
return r.Parent.ParentRow.Breadcrumb() + "/" + label
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"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"
|
||||
|
@ -25,15 +26,17 @@ type Row struct {
|
|||
|
||||
Servers *server.Children
|
||||
|
||||
ctrl Controller
|
||||
ctrl Controller
|
||||
parent breadcrumb.Breadcrumber
|
||||
}
|
||||
|
||||
func New(ses cchat.Session, ctrl Controller) *Row {
|
||||
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Row {
|
||||
row := &Row{
|
||||
Session: ses,
|
||||
ctrl: ctrl,
|
||||
parent: parent,
|
||||
}
|
||||
row.Servers = server.NewChildren(ses, row)
|
||||
row.Servers = server.NewChildren(row, ses, row)
|
||||
|
||||
row.Button = rich.NewToggleButtonImage(text.Rich{}, "")
|
||||
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
||||
|
@ -67,3 +70,7 @@ func New(ses cchat.Session, ctrl Controller) *Row {
|
|||
func (r *Row) MessageRowSelected(server *server.Row, smsg cchat.ServerMessage) {
|
||||
r.ctrl.MessageRowSelected(r, server, smsg)
|
||||
}
|
||||
|
||||
func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
|
||||
return breadcrumb.Try(r.parent, r.Button.GetLabel().Content)
|
||||
}
|
||||
|
|
1
internal/ui/style.css
Normal file
1
internal/ui/style.css
Normal file
|
@ -0,0 +1 @@
|
|||
headerbar { padding: 0; }
|
|
@ -10,8 +10,14 @@ import (
|
|||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/markbates/pkger"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Load the local CSS.
|
||||
gts.LoadCSS(pkger.Include("/internal/ui/style.css"))
|
||||
}
|
||||
|
||||
const LeftWidth = 220
|
||||
|
||||
type App struct {
|
||||
|
@ -54,7 +60,7 @@ func (app *App) MessageRowSelected(ses *session.Row, srv *server.Row, smsg cchat
|
|||
app.lastRowHighlighter = srv.Button.SetActive
|
||||
app.lastRowHighlighter(true)
|
||||
|
||||
log.Println("Breadcrumb:")
|
||||
app.header.SetBreadcrumb(srv.Breadcrumb())
|
||||
|
||||
// Show the messages.
|
||||
app.window.MessageView.JoinServer(ses.Session, smsg)
|
||||
|
|
12
pkged.go
Normal file
12
pkged.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Code generated by pkger; DO NOT EDIT.
|
||||
|
||||
// +build !skippkger
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/markbates/pkger"
|
||||
"github.com/markbates/pkger/pkging/mem"
|
||||
)
|
||||
|
||||
var _ = pkger.Apply(mem.UnmarshalEmbed([]byte(`1f8b08000000000000ffec58dd8fdb380eff570a3da7a366da29ae792eaeb88739146d713860512c688996b5d1d7ea639a7430fffb42b293c8f99ccc2eb058c00fb145f2474aa4258ae12391a6b5812c1e8990b14bcd0db39a7209da1ade246f9053c63a88af455c66d447e9c982d0ce6adcc0e857e6a58ba10756f019f98f76d6c7cf103bb278ce0433f25fd0481644833464463e5a461684ccc837f002e3c1cc37c2d2469a91852fd61e024f2ff11e22ebc8e2177243becfc8d7080ac922fa8403f105215843162464ea15478786a361ebc5abca21619bd4b6a02cedd02399914ff6df5261c886b32f37c266f37d3c0af77234a83411bd0145450c64769586b2e24a8d242f2b68cb96175134a07f900c4376f823bae26d935a69c98c34eb88d91766b5f318026d1544ac19e2a774853611a4414f950c7160e0aa8cfcda45bb1d50e82df60493ae43bfa3792de4017604b231c96fefeee61f0e18bb0821ff019e877d9852d245c9769c4e43456dd53d189ea2544744213551e14ea0f9dd8ec87a15c5de5544ed40e8603ea26eefde8fe8bbf96d45ef4d195515a7d5dd9b0f638abaa55c911941c32c974654430ac1cc6bba8180efdf8d38d2805fd71c619b9aecb0364e7fcb27aea21dea4c7a6f7d5e65abe378177e06a5a4e1e8a9b0af73bcace610610cfa7f02f3c36684b20c8aefe737f29507e8fa43da250d46febcbc92b1da12d7beff047f7f46d853c898974c44358600e2da48ec1429a46803f356a997dbc83908d8b5dfbd32208d4b2f50775e6a19e5035ebb8192a45eb2ee7aad2145bf583187fbe5d3d2c62370e6936e5e6e236008b2a4893f67a0d0e5c6f82b2ebf82a2018546f38c7cb0bb2b2f2287cbef5ccd3192f126059a1ffb02cb831486e60960bd2f8ccbb7c39381f4f6b458f0e519a192cd19693ca3eac088bd6935f865031103754bb1ffa5f6844742b18fd8ee050d2e9c87baa53848b4c73134446ef7acb9a5a0db3babe2ff0495afa87c131d4de587721a90798cbf56c7d62a30e2c67a4157656b54891e8c48432ebd8caa93de697404711450cdd541e88617659ebdcdb54617754ec75b33b9fa03256a1673a9265b1d83f5b166198cd143f17acbb3a104b666b93ef557e5d658c563ab904525e3881da4110a5b2545379a35ac0303a528ae90a17938264aa69444bb206188fde5574a5d69a9b443c1d7b33594a4995fb49162372c99a28c87624f4b8dc38beaa4a27450825218bf271b913b2f4d84a6d4310663ffa45d8cae1a96c7267a5be666c5032f7f49e76d29a9339d7c9694dd6c4309403f4aa11c3ed77b915fb4950a077a886f19095cb9ed8086b5899023e59389bd63c388b272d637d4369210ad2e15f5816408e1013facf37287ad13a267b67cb310f3f129a2b561c36b677ef8926446ca6e0e0c8c293e0ecb4c4632cbab114db19dbf1fd3ff2a648036e31ed0709bcbd0eaa46cfe9974c03ab87df33c94b36a3d7ffbe6ee02babcf2b17a2e6e53ad9f0327ff809b7f396770dd92b7e711877f70ce802f789c77263721ff76c5e229e076ef8b54ced6459cf376b5be00bca59d8352039c42496ee08438acc390eb8e49fbed872c79a48de4d2a793d12ad0e8c184d67a7d0eb4d9a3d9e0737026dbfb3e23df30c46dfbc224a57ad6b677d1b3ee2dcf8b5c3c926bda3bf720cda6bd725d2be993bdb7fcb9782aec8db6bca8fd0f7d291417647e337f479e9e9e66a4ed9d7b7c9a9152304c8dada9b13535b6a6c6d6d4d89a1a5b53636b6a6c4d8dada9b13535b6a6c6d6d4d89a1a5b53636b6a6c4d8dada9b1f50f686cfd010000ffff010000ffff0d1f6f41ce250000`)))
|
Loading…
Reference in a new issue