Added drag-and-drop; minor tweaks and bug fixes
This commit is contained in:
parent
cebc5f58b4
commit
af3f3ec178
4
go.mod
4
go.mod
|
@ -4,11 +4,13 @@ go 1.14
|
||||||
|
|
||||||
replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d
|
replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d
|
||||||
|
|
||||||
|
replace github.com/diamondburned/cchat-discord => ../cchat-discord/
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Xuanwo/go-locale v0.2.0
|
github.com/Xuanwo/go-locale v0.2.0
|
||||||
github.com/alecthomas/chroma v0.7.3
|
github.com/alecthomas/chroma v0.7.3
|
||||||
github.com/diamondburned/cchat v0.0.43
|
github.com/diamondburned/cchat v0.0.43
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20200715034853-d1e516c919c4
|
github.com/diamondburned/cchat-discord v0.0.0-20200716062508-093bedb3048e
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b
|
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b
|
||||||
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
|
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -50,6 +50,8 @@ github.com/diamondburned/cchat v0.0.43 h1:HetAujSaUSdnQgAUZgprNLARjf/MSWXpCfWdvX
|
||||||
github.com/diamondburned/cchat v0.0.43/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
github.com/diamondburned/cchat v0.0.43/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20200715034853-d1e516c919c4 h1:aealHwitg0XO8Nfgitq73FRsNCETeKX7M9Rqnwm32No=
|
github.com/diamondburned/cchat-discord v0.0.0-20200715034853-d1e516c919c4 h1:aealHwitg0XO8Nfgitq73FRsNCETeKX7M9Rqnwm32No=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20200715034853-d1e516c919c4/go.mod h1:ZmeZUjT3TVEItYUTi274ICTi+xDf2CCimD2yXRCaWdo=
|
github.com/diamondburned/cchat-discord v0.0.0-20200715034853-d1e516c919c4/go.mod h1:ZmeZUjT3TVEItYUTi274ICTi+xDf2CCimD2yXRCaWdo=
|
||||||
|
github.com/diamondburned/cchat-discord v0.0.0-20200716062508-093bedb3048e h1:VeRejmVomD2fHsFfhkEdg0FWocd8Gz00gI38cCRyGRw=
|
||||||
|
github.com/diamondburned/cchat-discord v0.0.0-20200716062508-093bedb3048e/go.mod h1:cX6rGfvIv2rfNPrhfcRx88bfNxyL7eFmiYZLCWGfchw=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b h1:sq0MXjJc3yAOZvuolRxOpKQNvpMLyTmsECxQqdYgF5E=
|
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/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 h1:Ha/I6PMKi+B4hpWclwlXj0tUMehR7Q0TNxPczzBwzPI=
|
||||||
|
@ -58,6 +60,8 @@ github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972 h1:OWxllHbUp
|
||||||
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
|
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
|
||||||
github.com/diamondburned/ningen v0.1.1-0.20200715034121-61de7138eb56 h1:DAk3bEwJZycjfZu4OXDYrR/nmpy3ZS/dfUF0rskfVj0=
|
github.com/diamondburned/ningen v0.1.1-0.20200715034121-61de7138eb56 h1:DAk3bEwJZycjfZu4OXDYrR/nmpy3ZS/dfUF0rskfVj0=
|
||||||
github.com/diamondburned/ningen v0.1.1-0.20200715034121-61de7138eb56/go.mod h1:SKPY3387RHCbMrnefex9D+zlrA2yB+LCtaaQAgatAuc=
|
github.com/diamondburned/ningen v0.1.1-0.20200715034121-61de7138eb56/go.mod h1:SKPY3387RHCbMrnefex9D+zlrA2yB+LCtaaQAgatAuc=
|
||||||
|
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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
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/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
package icons
|
package icons
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/gotk3/gotk3/gdk"
|
"github.com/gotk3/gotk3/gdk"
|
||||||
"github.com/markbates/pkger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// static assets
|
// static assets
|
||||||
var assets = map[string]*gdk.Pixbuf{}
|
var assets = map[string]*gdk.Pixbuf{}
|
||||||
|
|
||||||
func Logo256Variant2() *gdk.Pixbuf {
|
func Logo256Variant2(sz int) *gdk.Pixbuf {
|
||||||
return loadPixbuf(__cchat_variant2_256)
|
return loadPixbuf(__cchat_variant2_256, sz)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Logo256() *gdk.Pixbuf {
|
func Logo256(sz int) *gdk.Pixbuf {
|
||||||
return loadPixbuf(__cchat_256)
|
return loadPixbuf(__cchat_256, sz)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPixbuf(data []byte) *gdk.Pixbuf {
|
func loadPixbuf(data []byte, sz int) *gdk.Pixbuf {
|
||||||
l, err := gdk.PixbufLoaderNew()
|
l, err := gdk.PixbufLoaderNew()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to create a pixbuf loader for icons:", err)
|
log.Fatalln("Failed to create a pixbuf loader for icons:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sz > 0 {
|
||||||
|
l.Connect("size-prepared", func() { l.SetSize(sz, sz) })
|
||||||
|
}
|
||||||
|
|
||||||
p, err := l.WriteAndReturnPixbuf(data)
|
p, err := l.WriteAndReturnPixbuf(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to write and return pixbuf:", err)
|
log.Fatalln("Failed to write and return pixbuf:", err)
|
||||||
|
@ -32,18 +34,3 @@ func loadPixbuf(data []byte) *gdk.Pixbuf {
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFile(name string) []byte {
|
|
||||||
f, err := pkger.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("Failed to open pkger file:", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := buf.ReadFrom(f); err != nil {
|
|
||||||
log.Fatalln("Failed to read from pkger file:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
package gts
|
package gts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
"github.com/gotk3/gotk3/gdk"
|
"github.com/gotk3/gotk3/gdk"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/markbates/pkger"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,17 +35,3 @@ func LoadCSS(name, css string) {
|
||||||
|
|
||||||
cssRepos[name] = prov
|
cssRepos[name] = 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
|
|
||||||
}
|
|
||||||
|
|
|
@ -75,6 +75,29 @@ func init() {
|
||||||
App.Throttler = throttler.Bind(App.Application)
|
App.Throttler = throttler.Bind(App.Application)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // AppMenuWidget returns the box that holds the app menu.
|
||||||
|
// func AppMenuWidget() (widget *gtk.Widget) {
|
||||||
|
// App.Header.For().Foreach(func(v interface{}) {
|
||||||
|
// // If we've already found the widget, then stop finding.
|
||||||
|
// if widget != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Cast the interface to a widget.
|
||||||
|
// curr := v.(gtk.IWidget).ToWidget()
|
||||||
|
|
||||||
|
// log.Println("testing")
|
||||||
|
|
||||||
|
// // Check if the widget has a class named "left".
|
||||||
|
// if sctx, _ := curr.GetStyleContext(); sctx.HasClass("left") {
|
||||||
|
// log.Println("has class .left")
|
||||||
|
// widget = curr
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
type Window interface {
|
type Window interface {
|
||||||
Window() gtk.IWidget
|
Window() gtk.IWidget
|
||||||
Header() gtk.IWidget
|
Header() gtk.IWidget
|
||||||
|
@ -88,28 +111,30 @@ func Main(wfn func() Window) {
|
||||||
// Load all CSS onto the default screen.
|
// Load all CSS onto the default screen.
|
||||||
loadProviders(getDefaultScreen())
|
loadProviders(getDefaultScreen())
|
||||||
|
|
||||||
|
App.Header, _ = gtk.HeaderBarNew()
|
||||||
|
// Right buttons only.
|
||||||
|
App.Header.SetDecorationLayout(":minimize,close")
|
||||||
|
App.Header.SetShowCloseButton(true)
|
||||||
|
App.Header.SetProperty("spacing", 0)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
// Execute the function later, because we need it to run after
|
// Execute the function later, because we need it to run after
|
||||||
// initialization.
|
// initialization.
|
||||||
w := wfn()
|
w := wfn()
|
||||||
App.Application.SetAppMenu(w.Menu())
|
App.Application.SetAppMenu(w.Menu())
|
||||||
|
|
||||||
App.Header, _ = gtk.HeaderBarNew()
|
|
||||||
// Right buttons only.
|
|
||||||
App.Header.SetDecorationLayout("menu:minimize,close")
|
|
||||||
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)
|
|
||||||
App.Window.SetIcon(w.Icon())
|
App.Window.SetIcon(w.Icon())
|
||||||
|
App.Window.Add(w.Window())
|
||||||
App.Window.Show()
|
App.Window.Show()
|
||||||
|
|
||||||
App.Window.Add(w.Window())
|
|
||||||
App.Header.Add(w.Header())
|
App.Header.Add(w.Header())
|
||||||
|
App.Header.Show()
|
||||||
|
|
||||||
// Connect extra actions.
|
// Connect extra actions.
|
||||||
AddAppAction("quit", App.Window.Destroy)
|
AddAppAction("quit", App.Window.Destroy)
|
||||||
|
@ -129,7 +154,6 @@ func Main(wfn func() Window) {
|
||||||
w.Close()
|
w.Close()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Use a special function to run the application. Exit with the appropriate
|
// Use a special function to run the application. Exit with the appropriate
|
||||||
|
|
|
@ -4,9 +4,12 @@ import (
|
||||||
"html"
|
"html"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat-gtk/icons"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/actions"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/actions"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
||||||
|
"github.com/gotk3/gotk3/glib"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,6 +17,7 @@ type header struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
left *headerLeft // middle-ish
|
left *headerLeft // middle-ish
|
||||||
right *headerRight
|
right *headerRight
|
||||||
|
menu *glib.Menu
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHeader() *header {
|
func newHeader() *header {
|
||||||
|
@ -32,15 +36,22 @@ func newHeader() *header {
|
||||||
box.PackStart(right, true, true, 0)
|
box.PackStart(right, true, true, 0)
|
||||||
box.Show()
|
box.Show()
|
||||||
|
|
||||||
|
menu := glib.MenuNew()
|
||||||
|
menu.Append("Preferences", "app.preferences")
|
||||||
|
menu.Append("Quit", "app.quit")
|
||||||
|
|
||||||
|
left.appmenu.SetMenuModel(&menu.MenuModel)
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
return &header{
|
return &header{
|
||||||
box,
|
box,
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
|
menu,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BreadcrumbSlash = `<span weight="light" rise="-1024" size="x-large">/</span>`
|
const BreadcrumbSlash = `<span rise="-1024" size="x-large">/</span>`
|
||||||
|
|
||||||
func (h *header) SetBreadcrumber(b breadcrumb.Breadcrumber) {
|
func (h *header) SetBreadcrumber(b breadcrumb.Breadcrumber) {
|
||||||
if b == nil {
|
if b == nil {
|
||||||
|
@ -59,24 +70,63 @@ func (h *header) SetBreadcrumber(b breadcrumb.Breadcrumber) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *header) SetSessionMenu(s *session.Row) {
|
func (h *header) SetSessionMenu(s *session.Row) {
|
||||||
h.left.openmenu.Bind(s.ActionsMenu)
|
h.left.sesmenu.Bind(s.ActionsMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
type appMenu struct {
|
||||||
|
*gtk.MenuButton
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAppMenu() *appMenu {
|
||||||
|
img, _ := gtk.ImageNew()
|
||||||
|
img.SetFromPixbuf(icons.Logo256(24))
|
||||||
|
img.Show()
|
||||||
|
|
||||||
|
appmenu, _ := gtk.MenuButtonNew()
|
||||||
|
appmenu.SetImage(img)
|
||||||
|
appmenu.SetUsePopover(true)
|
||||||
|
appmenu.SetHAlign(gtk.ALIGN_CENTER)
|
||||||
|
appmenu.SetMarginStart(8)
|
||||||
|
appmenu.SetMarginEnd(8)
|
||||||
|
|
||||||
|
return &appMenu{appmenu}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *appMenu) SetSizeRequest(w, h int) {
|
||||||
|
// Subtract the margin size.
|
||||||
|
if w -= 8 * 2; w < 0 {
|
||||||
|
w = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
a.MenuButton.SetSizeRequest(w, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
type headerLeft struct {
|
type headerLeft struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
openmenu *actions.MenuButton
|
appmenu *appMenu
|
||||||
|
sesmenu *actions.MenuButton
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHeaderLeft() *headerLeft {
|
func newHeaderLeft() *headerLeft {
|
||||||
openmenu := actions.NewMenuButton()
|
appmenu := newAppMenu()
|
||||||
openmenu.Show()
|
appmenu.Show()
|
||||||
|
|
||||||
|
sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
||||||
|
sep.Show()
|
||||||
|
primitives.AddClass(sep, "titlebutton")
|
||||||
|
|
||||||
|
sesmenu := actions.NewMenuButton()
|
||||||
|
sesmenu.Show()
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
box.PackStart(openmenu, false, false, 5)
|
box.PackStart(appmenu, false, false, 0)
|
||||||
|
box.PackStart(sep, false, false, 0)
|
||||||
|
box.PackStart(sesmenu, false, false, 5)
|
||||||
|
|
||||||
return &headerLeft{
|
return &headerLeft{
|
||||||
Box: box,
|
Box: box,
|
||||||
openmenu: openmenu,
|
appmenu: appmenu,
|
||||||
|
sesmenu: sesmenu,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ func (c *Container) reuseAvatar(authorID, avatarURL string, full *FullMessage) {
|
||||||
// Borrow the avatar pixbuf, but only if the avatar URL is the same.
|
// Borrow the avatar pixbuf, but only if the avatar URL is the same.
|
||||||
p, ok := lastAuthorMsg.(AvatarPixbufCopier)
|
p, ok := lastAuthorMsg.(AvatarPixbufCopier)
|
||||||
if ok && lastAuthorMsg.AvatarURL() == avatarURL {
|
if ok && lastAuthorMsg.AvatarURL() == avatarURL {
|
||||||
p.CopyAvatarPixbuf(full.Avatar)
|
p.CopyAvatarPixbuf(full.Avatar.Image)
|
||||||
full.Avatar.ManuallySetURL(avatarURL)
|
full.Avatar.ManuallySetURL(avatarURL)
|
||||||
} else {
|
} else {
|
||||||
// We can't borrow, so we need to fetch it anew.
|
// We can't borrow, so we need to fetch it anew.
|
||||||
|
|
|
@ -10,8 +10,9 @@ import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/roundimage"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/roundimage"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/labeluri"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,6 +44,13 @@ var boldCSS = primitives.PrepareCSS(`
|
||||||
* { font-weight: 600; }
|
* { font-weight: 600; }
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
var avatarCSS = primitives.PrepareClassCSS("cozy-avatar", `
|
||||||
|
/* Slightly dip down on click */
|
||||||
|
.cozy-avatar:active {
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
func NewFullMessage(msg cchat.MessageCreate) *FullMessage {
|
func NewFullMessage(msg cchat.MessageCreate) *FullMessage {
|
||||||
msgc := WrapFullMessage(message.NewContainer(msg))
|
msgc := WrapFullMessage(message.NewContainer(msg))
|
||||||
// Don't update the avatar. NewMessage in controller will try and reuse the
|
// Don't update the avatar. NewMessage in controller will try and reuse the
|
||||||
|
@ -57,6 +65,11 @@ func WrapFullMessage(gc *message.GenericContainer) *FullMessage {
|
||||||
avatar := NewAvatar()
|
avatar := NewAvatar()
|
||||||
avatar.SetMarginTop(TopFullMargin)
|
avatar.SetMarginTop(TopFullMargin)
|
||||||
avatar.SetMarginStart(container.ColumnSpacing * 2)
|
avatar.SetMarginStart(container.ColumnSpacing * 2)
|
||||||
|
avatar.Connect("clicked", func() {
|
||||||
|
if output := gc.Username.Output(); len(output.Mentions) > 0 {
|
||||||
|
labeluri.PopoverMentioner(avatar, output.Mentions[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
// We don't call avatar.Show(). That's called in Attach.
|
// We don't call avatar.Show(). That's called in Attach.
|
||||||
|
|
||||||
// Style the timestamp accordingly.
|
// Style the timestamp accordingly.
|
||||||
|
@ -64,8 +77,8 @@ func WrapFullMessage(gc *message.GenericContainer) *FullMessage {
|
||||||
gc.Timestamp.SetVAlign(gtk.ALIGN_END) // bottom-align
|
gc.Timestamp.SetVAlign(gtk.ALIGN_END) // bottom-align
|
||||||
gc.Timestamp.SetMarginStart(0) // clear margins
|
gc.Timestamp.SetMarginStart(0) // clear margins
|
||||||
|
|
||||||
// Attach the class for the left avatar.
|
// Attach the class and CSS for the left avatar.
|
||||||
primitives.AddClass(avatar, "cozy-avatar")
|
avatarCSS(avatar)
|
||||||
|
|
||||||
// Attach the username style provider.
|
// Attach the username style provider.
|
||||||
primitives.AttachCSS(gc.Username, boldCSS)
|
primitives.AttachCSS(gc.Username, boldCSS)
|
||||||
|
@ -123,11 +136,11 @@ func (m *FullMessage) UpdateAuthor(author cchat.MessageAuthor) {
|
||||||
// CopyAvatarPixbuf sets the pixbuf into the given container. This shares the
|
// CopyAvatarPixbuf sets the pixbuf into the given container. This shares the
|
||||||
// same pixbuf, but gtk.Image should take its own reference from the pixbuf.
|
// same pixbuf, but gtk.Image should take its own reference from the pixbuf.
|
||||||
func (m *FullMessage) CopyAvatarPixbuf(dst httputil.ImageContainer) {
|
func (m *FullMessage) CopyAvatarPixbuf(dst httputil.ImageContainer) {
|
||||||
switch m.Avatar.GetStorageType() {
|
switch img := m.Avatar.Image; img.GetStorageType() {
|
||||||
case gtk.IMAGE_PIXBUF:
|
case gtk.IMAGE_PIXBUF:
|
||||||
dst.SetFromPixbuf(m.Avatar.GetPixbuf())
|
dst.SetFromPixbuf(img.GetPixbuf())
|
||||||
case gtk.IMAGE_ANIMATION:
|
case gtk.IMAGE_ANIMATION:
|
||||||
dst.SetFromAnimation(m.Avatar.GetAnimation())
|
dst.SetFromAnimation(img.GetAnimation())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,17 +178,19 @@ func NewFullSendingMessage(msg input.PresendMessage) *FullSendingMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Avatar struct {
|
type Avatar struct {
|
||||||
roundimage.Image
|
roundimage.Button
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAvatar() *Avatar {
|
func NewAvatar() *Avatar {
|
||||||
avatar, _ := roundimage.NewImage(0)
|
avatar, _ := roundimage.NewButton()
|
||||||
avatar.SetSizeRequest(AvatarSize, AvatarSize)
|
avatar.SetSizeRequest(AvatarSize, AvatarSize)
|
||||||
avatar.SetVAlign(gtk.ALIGN_START)
|
avatar.SetVAlign(gtk.ALIGN_START)
|
||||||
|
|
||||||
// Default icon.
|
// Default icon.
|
||||||
primitives.SetImageIcon(avatar.Image, "user-available-symbolic", AvatarSize)
|
primitives.SetImageIcon(
|
||||||
|
avatar.Image.Image, "user-available-symbolic", AvatarSize,
|
||||||
|
)
|
||||||
|
|
||||||
return &Avatar{*avatar, ""}
|
return &Avatar{*avatar, ""}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +206,7 @@ func (a *Avatar) SetURL(url string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
a.url = url
|
a.url = url
|
||||||
httputil.AsyncImageSized(a, url, AvatarSize, AvatarSize)
|
httputil.AsyncImageSized(a.Image, url, AvatarSize, AvatarSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManuallySetURL sets the URL without downloading the image. It assumes the
|
// ManuallySetURL sets the URL without downloading the image. It assumes the
|
||||||
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"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-gtk/internal/ui/rich"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/labeluri"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/labeluri"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/gotk3/gotk3/pango"
|
"github.com/gotk3/gotk3/pango"
|
||||||
|
@ -100,6 +100,7 @@ func NewEmptyContainer() *GenericContainer {
|
||||||
user.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
user.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
||||||
user.SetXAlign(1) // right align
|
user.SetXAlign(1) // right align
|
||||||
user.SetVAlign(gtk.ALIGN_START)
|
user.SetVAlign(gtk.ALIGN_START)
|
||||||
|
user.SetTrackVisitedLinks(false)
|
||||||
user.Show()
|
user.Show()
|
||||||
|
|
||||||
ctbody := labeluri.NewLabel(text.Rich{})
|
ctbody := labeluri.NewLabel(text.Rich{})
|
||||||
|
@ -108,6 +109,7 @@ func NewEmptyContainer() *GenericContainer {
|
||||||
ctbody.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
ctbody.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
||||||
ctbody.SetXAlign(0) // left align
|
ctbody.SetXAlign(0) // left align
|
||||||
ctbody.SetSelectable(true)
|
ctbody.SetSelectable(true)
|
||||||
|
ctbody.SetTrackVisitedLinks(false)
|
||||||
ctbody.Show()
|
ctbody.Show()
|
||||||
|
|
||||||
// Wrap the content label inside a content box.
|
// Wrap the content label inside a content box.
|
||||||
|
|
|
@ -85,7 +85,7 @@ func NewView() *View {
|
||||||
primitives.AddClass(view.Box, "message-view")
|
primitives.AddClass(view.Box, "message-view")
|
||||||
|
|
||||||
// placeholder logo
|
// placeholder logo
|
||||||
logo, _ := gtk.ImageNewFromPixbuf(icons.Logo256Variant2())
|
logo, _ := gtk.ImageNewFromPixbuf(icons.Logo256Variant2(128))
|
||||||
logo.Show()
|
logo.Show()
|
||||||
|
|
||||||
view.FaceView = sadface.New(view.Box, logo)
|
view.FaceView = sadface.New(view.Box, logo)
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
package drag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/gotk3/gotk3/gdk"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTargetEntry(target string) gtk.TargetEntry {
|
||||||
|
e, _ := gtk.TargetEntryNew(target, gtk.TARGET_SAME_APP, 0)
|
||||||
|
return *e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find searches the given container for the draggable widget with the given
|
||||||
|
// name.
|
||||||
|
func Find(w primitives.Container, id string) int {
|
||||||
|
var index = -1 // not found default
|
||||||
|
|
||||||
|
primitives.EachChildren(w, func(i int, v interface{}) bool {
|
||||||
|
if primitives.GetName(v.(primitives.Namer)) == id {
|
||||||
|
index = i
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
type MainDraggable interface {
|
||||||
|
ID() string
|
||||||
|
SetName(string)
|
||||||
|
SetSensitive(bool)
|
||||||
|
|
||||||
|
gtk.IWidget
|
||||||
|
Draggable
|
||||||
|
}
|
||||||
|
|
||||||
|
type Draggable interface {
|
||||||
|
DragSourceSet(gdk.ModifierType, []gtk.TargetEntry, gdk.DragAction)
|
||||||
|
DragDestSet(gtk.DestDefaults, []gtk.TargetEntry, gdk.DragAction)
|
||||||
|
|
||||||
|
primitives.Connector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swapper is the type for a swap function.
|
||||||
|
type Swapper = func(targetID, movingID string)
|
||||||
|
|
||||||
|
// BindDraggable binds the draggable widget and make it drag-and-droppable. The
|
||||||
|
// parent MUST have its own state of children and MUST NOT rely on its container
|
||||||
|
// states.
|
||||||
|
//
|
||||||
|
// This function can take additional draggers, which will override the main
|
||||||
|
// draggable and will be the only widgets that can be dragged away. The source
|
||||||
|
// ID will be taken from the main draggable.
|
||||||
|
func BindDraggable(dg MainDraggable, icon string, fn Swapper, draggers ...Draggable) {
|
||||||
|
var atom = "data_" + icon
|
||||||
|
var dragEntries = []gtk.TargetEntry{NewTargetEntry(atom)}
|
||||||
|
var dragAtom = gdk.GdkAtomIntern(atom, false)
|
||||||
|
|
||||||
|
// Set the ID for Find().
|
||||||
|
dg.SetName(dg.ID())
|
||||||
|
|
||||||
|
// Make closures function so we can use twice.
|
||||||
|
srcSet := func(dragger Draggable) {
|
||||||
|
// Drag source so you can drag the button away.
|
||||||
|
dragger.DragSourceSet(gdk.BUTTON1_MASK, dragEntries, gdk.ACTION_MOVE)
|
||||||
|
|
||||||
|
dragger.Connect("drag-data-get",
|
||||||
|
func(_ gtk.IWidget, ctx *gdk.DragContext, data *gtk.SelectionData) {
|
||||||
|
// Set the index-in-bytes.
|
||||||
|
data.SetData(dragAtom, []byte(dg.ID()))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
dragger.Connect("drag-begin",
|
||||||
|
func(_ gtk.IWidget, ctx *gdk.DragContext) {
|
||||||
|
gtk.DragSetIconName(ctx, icon, 0, 0)
|
||||||
|
dg.SetSensitive(false)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
dragger.Connect("drag-end",
|
||||||
|
func() {
|
||||||
|
dg.SetSensitive(true)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dstSet := func(dragger Draggable) {
|
||||||
|
// Drag destination so you can drag the button here.
|
||||||
|
dragger.DragDestSet(gtk.DEST_DEFAULT_ALL, dragEntries, gdk.ACTION_MOVE)
|
||||||
|
|
||||||
|
dragger.Connect("drag-data-received",
|
||||||
|
func(_ gtk.IWidget, ctx *gdk.DragContext, x, y uint, data *gtk.SelectionData) {
|
||||||
|
// Receive the incoming row's ID and call MoveSession.
|
||||||
|
fn(dg.ID(), string(data.GetData()))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no extra draggers given, then the MainDraggable should also be
|
||||||
|
// a source.
|
||||||
|
if len(draggers) == 0 {
|
||||||
|
srcSet(dg)
|
||||||
|
} else {
|
||||||
|
// Else, set drag sources only on those extra draggables.
|
||||||
|
for _, dragger := range draggers {
|
||||||
|
srcSet(dragger)
|
||||||
|
dstSet(dragger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make MainDraggable a drag destination as well.
|
||||||
|
dstSet(dg)
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ func GetName(namer Namer) string {
|
||||||
return nm
|
return nm
|
||||||
}
|
}
|
||||||
|
|
||||||
func EachChildren(w interface{ GetChildren() *glib.List }, fn func(i int, v interface{}) bool) {
|
func EachChildren(w Container, fn func(i int, v interface{}) bool) {
|
||||||
var cursor int = -1
|
var cursor int = -1
|
||||||
for ptr := w.GetChildren(); ptr != nil; ptr = ptr.Next() {
|
for ptr := w.GetChildren(); ptr != nil; ptr = ptr.Next() {
|
||||||
cursor++
|
cursor++
|
||||||
|
@ -46,45 +46,6 @@ func EachChildren(w interface{ GetChildren() *glib.List }, fn func(i int, v inte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DragSortable interface {
|
|
||||||
DragSourceSet(gdk.ModifierType, []gtk.TargetEntry, gdk.DragAction)
|
|
||||||
DragDestSet(gtk.DestDefaults, []gtk.TargetEntry, gdk.DragAction)
|
|
||||||
GetAllocation() *gtk.Allocation
|
|
||||||
Connector
|
|
||||||
}
|
|
||||||
|
|
||||||
func BindDragSortable(ds DragSortable, target, id string, fn func(id, target string)) {
|
|
||||||
var dragEntries = []gtk.TargetEntry{NewTargetEntry(target)}
|
|
||||||
var dragAtom = gdk.GdkAtomIntern(target, true)
|
|
||||||
|
|
||||||
// Drag source so you can drag the button away.
|
|
||||||
ds.DragSourceSet(gdk.BUTTON1_MASK, dragEntries, gdk.ACTION_MOVE)
|
|
||||||
|
|
||||||
// Drag destination so you can drag the button here.
|
|
||||||
ds.DragDestSet(gtk.DEST_DEFAULT_ALL, dragEntries, gdk.ACTION_MOVE)
|
|
||||||
|
|
||||||
ds.Connect("drag-data-get",
|
|
||||||
// TODO change ToggleButton.
|
|
||||||
func(ds DragSortable, ctx *gdk.DragContext, data *gtk.SelectionData) {
|
|
||||||
// Set the index-in-bytes.
|
|
||||||
data.SetData(dragAtom, []byte(id))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ds.Connect("drag-data-received",
|
|
||||||
func(ds DragSortable, ctx *gdk.DragContext, x, y uint, data *gtk.SelectionData) {
|
|
||||||
// Receive the incoming row's ID and call MoveSession.
|
|
||||||
fn(id, string(data.GetData()))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ds.Connect("drag-begin",
|
|
||||||
func(ds DragSortable, ctx *gdk.DragContext) {
|
|
||||||
gtk.DragSetIconName(ctx, "user-available-symbolic", 0, 0)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type StyleContexter interface {
|
type StyleContexter interface {
|
||||||
GetStyleContext() (*gtk.StyleContext, error)
|
GetStyleContext() (*gtk.StyleContext, error)
|
||||||
}
|
}
|
||||||
|
@ -199,11 +160,6 @@ func BindDynamicMenu(connector Connector, constr func(menu *gtk.Menu)) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTargetEntry(target string) gtk.TargetEntry {
|
|
||||||
e, _ := gtk.TargetEntryNew(target, gtk.TARGET_SAME_APP, 0)
|
|
||||||
return *e
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMenuActionButton is the same as NewActionButton, but it uses the
|
// NewMenuActionButton is the same as NewActionButton, but it uses the
|
||||||
// open-menu-symbolic icon.
|
// open-menu-symbolic icon.
|
||||||
func NewMenuActionButton(actions [][2]string) *gtk.MenuButton {
|
func NewMenuActionButton(actions [][2]string) *gtk.MenuButton {
|
||||||
|
@ -286,3 +242,7 @@ func AttachCSS(ctx StyleContexter, prov *gtk.CssProvider) {
|
||||||
s, _ := ctx.GetStyleContext()
|
s, _ := ctx.GetStyleContext()
|
||||||
s.AddProvider(prov, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
s.AddProvider(prov, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InlineCSS(ctx StyleContexter, css string) {
|
||||||
|
AttachCSS(ctx, PrepareCSS(css))
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package roundimage
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/gotk3/gotk3/cairo"
|
"github.com/gotk3/gotk3/cairo"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
@ -12,6 +13,32 @@ const (
|
||||||
circle = 2 * math.Pi
|
circle = 2 * math.Pi
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Button implements a rounded button with a rounded image. This widget only
|
||||||
|
// supports a full circle for rounding.
|
||||||
|
type Button struct {
|
||||||
|
*gtk.Button
|
||||||
|
Image *Image
|
||||||
|
}
|
||||||
|
|
||||||
|
var roundButtonCSS = primitives.PrepareClassCSS("round-button", `
|
||||||
|
.round-button {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
func NewButton() (*Button, error) {
|
||||||
|
image, _ := NewImage(0)
|
||||||
|
image.Show()
|
||||||
|
|
||||||
|
b, _ := gtk.ButtonNew()
|
||||||
|
b.SetImage(image)
|
||||||
|
b.SetRelief(gtk.RELIEF_NONE)
|
||||||
|
roundButtonCSS(b)
|
||||||
|
|
||||||
|
return &Button{Button: b, Image: image}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
*gtk.Image
|
*gtk.Image
|
||||||
Radius float64
|
Radius float64
|
||||||
|
@ -28,74 +55,7 @@ func NewImage(radius float64) (*Image, error) {
|
||||||
image := &Image{Image: i, Radius: radius}
|
image := &Image{Image: i, Radius: radius}
|
||||||
|
|
||||||
// Connect to the draw callback and clip the context.
|
// Connect to the draw callback and clip the context.
|
||||||
i.Connect("draw", func(i *gtk.Image, cc *cairo.Context) bool {
|
i.Connect("draw", image.drawer)
|
||||||
var w = float64(i.GetAllocatedWidth())
|
|
||||||
var h = float64(i.GetAllocatedHeight())
|
|
||||||
|
|
||||||
var min = w
|
|
||||||
// Use the smallest side for radius calculation.
|
|
||||||
if h < w {
|
|
||||||
min = h
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the variables in case we need to change them.
|
|
||||||
var r = image.Radius
|
|
||||||
|
|
||||||
// We have to do this so the arc paint doesn't leave back a black
|
|
||||||
// background instead of the usual alpha.
|
|
||||||
// cc.SetSourceRGBA(255, 255, 255, 0)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
// If radius is less than 0, then don't round.
|
|
||||||
case r < 0:
|
|
||||||
return false
|
|
||||||
|
|
||||||
// If radius is 0, then we have to calculate our own radius.:This only
|
|
||||||
// works if the image is a square.
|
|
||||||
case r == 0:
|
|
||||||
// Calculate the radius by dividing a side by 2.
|
|
||||||
r = (min / 2)
|
|
||||||
|
|
||||||
// Draw an arc from 0deg to 360deg.
|
|
||||||
cc.Arc(w/2, h/2, r, 0, circle)
|
|
||||||
cc.SetSourceRGBA(255, 255, 255, 0)
|
|
||||||
|
|
||||||
// Clip the image with the arc we drew.
|
|
||||||
cc.Clip()
|
|
||||||
|
|
||||||
// If radius is more than 0, then we have to calculate the radius from
|
|
||||||
// the edges.
|
|
||||||
case r > 0:
|
|
||||||
// StackOverflow is godly.
|
|
||||||
// https://stackoverflow.com/a/6959843.
|
|
||||||
|
|
||||||
// Account for the offset.
|
|
||||||
// r += o
|
|
||||||
|
|
||||||
// Radius should be largest a single side divided by 2.
|
|
||||||
if max := min / 2; r > max {
|
|
||||||
r = max
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw 4 arcs at 4 corners.
|
|
||||||
cc.Arc(0+r, 0+r, r, 2*(pi/2), 3*(pi/2)) // top left
|
|
||||||
cc.Arc(w-r, 0+r, r, 3*(pi/2), 4*(pi/2)) // top right
|
|
||||||
cc.Arc(w-r, h-r, r, 0*(pi/2), 1*(pi/2)) // bottom right
|
|
||||||
cc.Arc(0+r, h-r, r, 1*(pi/2), 2*(pi/2)) // bottom left
|
|
||||||
|
|
||||||
// Close the created path.
|
|
||||||
cc.ClosePath()
|
|
||||||
cc.SetSourceRGBA(255, 255, 255, 0)
|
|
||||||
|
|
||||||
// Clip the image with the arc we drew.
|
|
||||||
cc.Clip()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paint the changes.
|
|
||||||
cc.Paint()
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
@ -103,3 +63,68 @@ func NewImage(radius float64) (*Image, error) {
|
||||||
func (i *Image) SetRadius(r float64) {
|
func (i *Image) SetRadius(r float64) {
|
||||||
i.Radius = r
|
i.Radius = r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Image) drawer(widget gtk.IWidget, cc *cairo.Context) bool {
|
||||||
|
var w = float64(i.GetAllocatedWidth())
|
||||||
|
var h = float64(i.GetAllocatedHeight())
|
||||||
|
|
||||||
|
var min = w
|
||||||
|
// Use the smallest side for radius calculation.
|
||||||
|
if h < w {
|
||||||
|
min = h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the variables in case we need to change them.
|
||||||
|
var r = i.Radius
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// If radius is less than 0, then don't round.
|
||||||
|
case r < 0:
|
||||||
|
return false
|
||||||
|
|
||||||
|
// If radius is 0, then we have to calculate our own radius.:This only
|
||||||
|
// works if the image is a square.
|
||||||
|
case r == 0:
|
||||||
|
// Calculate the radius by dividing a side by 2.
|
||||||
|
r = (min / 2)
|
||||||
|
|
||||||
|
// Draw an arc from 0deg to 360deg.
|
||||||
|
cc.Arc(w/2, h/2, r, 0, circle)
|
||||||
|
|
||||||
|
// We have to do this so the arc paint doesn't leave back a black
|
||||||
|
// background instead of the usual alpha.
|
||||||
|
cc.SetSourceRGBA(255, 255, 255, 0)
|
||||||
|
|
||||||
|
// Clip the image with the arc we drew.
|
||||||
|
cc.Clip()
|
||||||
|
|
||||||
|
// If radius is more than 0, then we have to calculate the radius from
|
||||||
|
// the edges.
|
||||||
|
case r > 0:
|
||||||
|
// StackOverflow is godly.
|
||||||
|
// https://stackoverflow.com/a/6959843.
|
||||||
|
|
||||||
|
// Radius should be largest a single side divided by 2.
|
||||||
|
if max := min / 2; r > max {
|
||||||
|
r = max
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw 4 arcs at 4 corners.
|
||||||
|
cc.Arc(0+r, 0+r, r, 2*(pi/2), 3*(pi/2)) // top left
|
||||||
|
cc.Arc(w-r, 0+r, r, 3*(pi/2), 4*(pi/2)) // top right
|
||||||
|
cc.Arc(w-r, h-r, r, 0*(pi/2), 1*(pi/2)) // bottom right
|
||||||
|
cc.Arc(0+r, h-r, r, 1*(pi/2), 2*(pi/2)) // bottom left
|
||||||
|
|
||||||
|
// Close the created path.
|
||||||
|
cc.ClosePath()
|
||||||
|
cc.SetSourceRGBA(255, 255, 255, 0)
|
||||||
|
|
||||||
|
// Clip the image with the arc we drew.
|
||||||
|
cc.Clip()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint the changes.
|
||||||
|
cc.Paint()
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -9,11 +9,15 @@ type Boxed struct {
|
||||||
|
|
||||||
func New() *Boxed {
|
func New() *Boxed {
|
||||||
spin, _ := gtk.SpinnerNew()
|
spin, _ := gtk.SpinnerNew()
|
||||||
|
spin.SetHAlign(gtk.ALIGN_CENTER)
|
||||||
|
spin.SetVAlign(gtk.ALIGN_CENTER)
|
||||||
spin.Show()
|
spin.Show()
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
box.SetHAlign(gtk.ALIGN_CENTER)
|
box.SetHAlign(gtk.ALIGN_CENTER)
|
||||||
box.SetVAlign(gtk.ALIGN_CENTER)
|
box.SetVAlign(gtk.ALIGN_CENTER)
|
||||||
|
box.SetHExpand(true)
|
||||||
|
box.SetVExpand(true)
|
||||||
box.Add(spin)
|
box.Add(spin)
|
||||||
|
|
||||||
return &Boxed{box, spin}
|
return &Boxed{box, spin}
|
||||||
|
|
|
@ -82,19 +82,8 @@ func BindRichLabel(label Labeler) {
|
||||||
var output = label.Output()
|
var output = label.Output()
|
||||||
|
|
||||||
if mention := output.IsMention(uri); mention != nil {
|
if mention := output.IsMention(uri); mention != nil {
|
||||||
if info := mention.MentionInfo(); !info.Empty() {
|
if p := popoverMentioner(label, mention); p != nil {
|
||||||
l, _ := gtk.LabelNew(markup.Render(info))
|
|
||||||
l.SetUseMarkup(true)
|
|
||||||
l.SetXAlign(0)
|
|
||||||
l.Show()
|
|
||||||
|
|
||||||
// Enable images???
|
|
||||||
BindActivator(l)
|
|
||||||
|
|
||||||
p, _ := gtk.PopoverNew(label)
|
|
||||||
p.SetPointingTo(ptr)
|
p.SetPointingTo(ptr)
|
||||||
p.Add(l)
|
|
||||||
p.Connect("destroy", l.Destroy)
|
|
||||||
p.Popup()
|
p.Popup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +94,32 @@ func BindRichLabel(label Labeler) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PopoverMentioner(rel gtk.IWidget, mention text.Mentioner) {
|
||||||
|
if p := popoverMentioner(rel, mention); p != nil {
|
||||||
|
p.Popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func popoverMentioner(rel gtk.IWidget, mention text.Mentioner) *gtk.Popover {
|
||||||
|
var info = mention.MentionInfo()
|
||||||
|
if info.Empty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l, _ := gtk.LabelNew(markup.Render(info))
|
||||||
|
l.SetUseMarkup(true)
|
||||||
|
l.SetXAlign(0)
|
||||||
|
l.Show()
|
||||||
|
|
||||||
|
// Enable images???
|
||||||
|
BindActivator(l)
|
||||||
|
|
||||||
|
p, _ := gtk.PopoverNew(rel)
|
||||||
|
p.Add(l)
|
||||||
|
p.Connect("destroy", l.Destroy)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
func BindActivator(connector WidgetConnector) {
|
func BindActivator(connector WidgetConnector) {
|
||||||
bind(connector, nil)
|
bind(connector, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,14 @@ func RenderCmplx(content text.Rich) RenderOutput {
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
buf.Grow(len(content.Content))
|
buf.Grow(len(content.Content))
|
||||||
|
|
||||||
|
// Sort so that all ending points are sorted decrementally. We probably
|
||||||
|
// don't need SliceStable here, as we're sorting again.
|
||||||
|
sort.Slice(content.Segments, func(i, j int) bool {
|
||||||
|
_, i = content.Segments[i].Bounds()
|
||||||
|
_, j = content.Segments[j].Bounds()
|
||||||
|
return i > j
|
||||||
|
})
|
||||||
|
|
||||||
// Sort so that all starting points are sorted incrementally.
|
// Sort so that all starting points are sorted incrementally.
|
||||||
sort.SliceStable(content.Segments, func(i, j int) bool {
|
sort.SliceStable(content.Segments, func(i, j int) bool {
|
||||||
i, _ = content.Segments[i].Bounds()
|
i, _ = content.Segments[i].Bounds()
|
||||||
|
@ -93,6 +101,10 @@ func RenderCmplx(content text.Rich) RenderOutput {
|
||||||
appended.Open(start, composeAvatarMarkup(segment))
|
appended.Open(start, composeAvatarMarkup(segment))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if segment, ok := segment.(text.Colorer); ok {
|
||||||
|
appended.Span(start, end, fmt.Sprintf("color=\"#%06X\"", segment.Color()))
|
||||||
|
}
|
||||||
|
|
||||||
// Mentioner needs to be before colorer, as we'd want the below color
|
// Mentioner needs to be before colorer, as we'd want the below color
|
||||||
// segment to also highlight the full mention as well as make the
|
// segment to also highlight the full mention as well as make the
|
||||||
// padding part of the hyperlink.
|
// padding part of the hyperlink.
|
||||||
|
@ -101,12 +113,15 @@ func RenderCmplx(content text.Rich) RenderOutput {
|
||||||
// components will take care of showing the information.
|
// components will take care of showing the information.
|
||||||
appended.AnchorNU(start, end, fmt.Sprintf(f_Mention, len(mentions)))
|
appended.AnchorNU(start, end, fmt.Sprintf(f_Mention, len(mentions)))
|
||||||
mentions = append(mentions, segment)
|
mentions = append(mentions, segment)
|
||||||
}
|
|
||||||
|
|
||||||
if segment, ok := segment.(text.Colorer); ok {
|
if segment, ok := segment.(text.Colorer); ok {
|
||||||
var covered = attrmap.CoverAll(content, start, end)
|
// Add a dimmed background highlight and pad the button-like
|
||||||
appended.Span(start, end, color(segment.Color(), !covered)...)
|
// link.
|
||||||
if !covered { // add padding if doesn't cover all
|
appended.Span(
|
||||||
|
start, end,
|
||||||
|
"bgalpha=\"10%\"",
|
||||||
|
fmt.Sprintf("bgcolor=\"#%06X\"", segment.Color()),
|
||||||
|
)
|
||||||
appended.Pad(start, end)
|
appended.Pad(start, end)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/drag"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
@ -43,6 +44,8 @@ func NewList(vctl ViewController) *List {
|
||||||
// List box of buttons.
|
// List box of buttons.
|
||||||
svlist.ListBox, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
svlist.ListBox, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
svlist.ListBox.Show()
|
svlist.ListBox.Show()
|
||||||
|
svlist.ListBox.SetHAlign(gtk.ALIGN_START)
|
||||||
|
svlist.ListBox.SetHExpand(false)
|
||||||
listCSS(svlist.ListBox)
|
listCSS(svlist.ListBox)
|
||||||
|
|
||||||
svlist.ScrolledWindow, _ = gtk.ScrolledWindowNew(nil, nil)
|
svlist.ScrolledWindow, _ = gtk.ScrolledWindowNew(nil, nil)
|
||||||
|
@ -82,230 +85,23 @@ func (sl *List) AddService(svc cchat.Service) {
|
||||||
// TODO: drag-and-drop?
|
// TODO: drag-and-drop?
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
func (sl *List) MoveService(targetID, movingID string) {
|
||||||
type View struct {
|
// Find the widgets.
|
||||||
*gtk.ScrolledWindow
|
var movingsv *Service
|
||||||
Box *gtk.Box
|
for _, svc := range sl.Services {
|
||||||
Services []*Container
|
if svc.ID() == movingID {
|
||||||
}
|
movingsv = svc
|
||||||
|
|
||||||
var servicesCSS = primitives.PrepareCSS(`
|
|
||||||
.services {
|
|
||||||
background-color: @theme_base_color;
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
func NewView() *View {
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
||||||
box.Show()
|
|
||||||
|
|
||||||
primitives.AddClass(box, "services")
|
|
||||||
primitives.AttachCSS(box, servicesCSS)
|
|
||||||
|
|
||||||
sw, _ := gtk.ScrolledWindowNew(nil, nil)
|
|
||||||
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
|
||||||
sw.Add(box)
|
|
||||||
|
|
||||||
return &View{
|
|
||||||
sw,
|
|
||||||
box,
|
|
||||||
nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) AddService(svc cchat.Service, ctrl Controller) *Container {
|
|
||||||
s := NewContainer(svc, ctrl)
|
|
||||||
v.Services = append(v.Services, s)
|
|
||||||
v.Box.Add(s)
|
|
||||||
|
|
||||||
// Try and restore all sessions.
|
|
||||||
s.restoreAllSessions()
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
type Controller interface {
|
|
||||||
// RowSelected is wrapped around session's MessageRowSelected.
|
|
||||||
RowSelected(*session.Row, *server.ServerRow, cchat.ServerMessage)
|
|
||||||
// AuthenticateSession is called to spawn the authentication dialog.
|
|
||||||
AuthenticateSession(*Container, cchat.Service)
|
|
||||||
// OnSessionRemove is called to remove a session. This should also clear out
|
|
||||||
// the message view in the parent package.
|
|
||||||
OnSessionRemove(id string)
|
|
||||||
// OnSessionDisconnect is here to satisfy session's controller.
|
|
||||||
OnSessionDisconnect(id string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Container represents a single service, including the button header and the
|
|
||||||
// child containers.
|
|
||||||
type Container struct {
|
|
||||||
*gtk.Box
|
|
||||||
Service cchat.Service
|
|
||||||
|
|
||||||
header *header
|
|
||||||
revealer *gtk.Revealer
|
|
||||||
children *children
|
|
||||||
|
|
||||||
// Embed controller and extend it to override RestoreSession.
|
|
||||||
Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guarantee that our interface is up-to-date with session's controller.
|
|
||||||
var _ session.Controller = (*Container)(nil)
|
|
||||||
|
|
||||||
func NewContainer(svc cchat.Service, ctrl Controller) *Container {
|
|
||||||
children := newChildren()
|
|
||||||
|
|
||||||
chrev, _ := gtk.RevealerNew()
|
|
||||||
chrev.SetRevealChild(true)
|
|
||||||
chrev.Add(children)
|
|
||||||
chrev.Show()
|
|
||||||
|
|
||||||
header := newHeader(svc)
|
|
||||||
header.SetActive(chrev.GetRevealChild())
|
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
||||||
box.Show()
|
|
||||||
box.PackStart(header, false, false, 0)
|
|
||||||
box.PackStart(chrev, false, false, 0)
|
|
||||||
|
|
||||||
primitives.AddClass(box, "service")
|
|
||||||
|
|
||||||
container := &Container{
|
|
||||||
Box: box,
|
|
||||||
Service: svc,
|
|
||||||
header: header,
|
|
||||||
revealer: chrev,
|
|
||||||
children: children,
|
|
||||||
Controller: ctrl,
|
|
||||||
}
|
|
||||||
|
|
||||||
// On click, toggle reveal.
|
|
||||||
header.Connect("clicked", func() {
|
|
||||||
revealed := !chrev.GetRevealChild()
|
|
||||||
chrev.SetRevealChild(revealed)
|
|
||||||
header.SetActive(revealed)
|
|
||||||
})
|
|
||||||
|
|
||||||
// On click, show the auth dialog.
|
|
||||||
header.Add.Connect("clicked", func() {
|
|
||||||
ctrl.AuthenticateSession(container, svc)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add more menu item(s).
|
|
||||||
header.Menu.AddSimpleItem("Save Sessions", container.SaveAllSessions)
|
|
||||||
|
|
||||||
return container
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) GetService() cchat.Service {
|
|
||||||
return c.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Sessions() []*session.Row {
|
|
||||||
return c.children.Sessions()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) AddSession(ses cchat.Session) *session.Row {
|
|
||||||
srow := session.New(c, ses, c)
|
|
||||||
c.children.AddSessionRow(ses.ID(), srow)
|
|
||||||
c.SaveAllSessions()
|
|
||||||
return srow
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) AddLoadingSession(id, name string) *session.Row {
|
|
||||||
srow := session.NewLoading(c, id, name, c)
|
|
||||||
c.children.AddSessionRow(id, srow)
|
|
||||||
return srow
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) RemoveSession(row *session.Row) {
|
|
||||||
var id = row.Session.ID()
|
|
||||||
c.children.RemoveSessionRow(id)
|
|
||||||
c.SaveAllSessions()
|
|
||||||
// Call the parent's method.
|
|
||||||
c.Controller.OnSessionRemove(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) MoveSession(rowID, beneathRowID string) {
|
|
||||||
c.children.MoveSession(rowID, beneathRowID)
|
|
||||||
c.SaveAllSessions()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) OnSessionDisconnect(ses *session.Row) {
|
|
||||||
c.Controller.OnSessionDisconnect(ses.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestoreSession tries to restore sessions asynchronously. This satisfies
|
|
||||||
// session.Controller.
|
|
||||||
func (c *Container) RestoreSession(row *session.Row, id string) {
|
|
||||||
// Can this session be restored? If not, exit.
|
|
||||||
restorer, ok := c.Service.(cchat.SessionRestorer)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we even have a session stored?
|
|
||||||
krs := keyring.RestoreSession(c.Service.Name(), id)
|
|
||||||
if krs == nil {
|
|
||||||
log.Error(fmt.Errorf(
|
|
||||||
"Missing keyring for service %s, session ID %s",
|
|
||||||
c.Service.Name().Content, id,
|
|
||||||
))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.restoreSession(row, restorer, *krs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal method called on AddService.
|
|
||||||
func (c *Container) restoreAllSessions() {
|
|
||||||
// Can this session be restored? If not, exit.
|
|
||||||
restorer, ok := c.Service.(cchat.SessionRestorer)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var sessions = keyring.RestoreSessions(c.Service.Name())
|
|
||||||
|
|
||||||
for _, krs := range sessions {
|
|
||||||
// Copy the session to avoid race conditions.
|
|
||||||
krs := krs
|
|
||||||
row := c.AddLoadingSession(krs.ID, krs.Name)
|
|
||||||
|
|
||||||
c.restoreSession(row, restorer, krs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) restoreSession(r *session.Row, res cchat.SessionRestorer, k keyring.Session) {
|
|
||||||
go func() {
|
|
||||||
s, err := res.RestoreSession(k.Data)
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Wrapf(err, "Failed to restore session %s (%s)", k.ID, k.Name)
|
|
||||||
log.Error(err)
|
|
||||||
|
|
||||||
gts.ExecAsync(func() { r.SetFailed(err) })
|
|
||||||
} else {
|
|
||||||
gts.ExecAsync(func() { r.SetSession(s) })
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) SaveAllSessions() {
|
|
||||||
var sessions = c.children.Sessions()
|
|
||||||
var ksessions = make([]keyring.Session, 0, len(sessions))
|
|
||||||
|
|
||||||
for _, s := range sessions {
|
|
||||||
if k := s.KeyringSession(); k != nil {
|
|
||||||
ksessions = append(ksessions, *k)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyring.SaveSessions(c.Service.Name(), ksessions)
|
// Not found, return.
|
||||||
}
|
if movingsv == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Container) Breadcrumb() breadcrumb.Breadcrumb {
|
// Get the location of where to move the widget to.
|
||||||
return breadcrumb.Try(nil, c.header.GetText())
|
var targetix = drag.Find(sl.ListBox, targetID)
|
||||||
|
|
||||||
|
// Actually move the child.
|
||||||
|
sl.ListBox.ReorderChild(movingsv, targetix)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/keyring"
|
"github.com/diamondburned/cchat-gtk/internal/keyring"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/drag"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
|
@ -24,6 +25,8 @@ type ListController interface {
|
||||||
SessionSelected(*Service, *session.Row)
|
SessionSelected(*Service, *session.Row)
|
||||||
// AuthenticateSession tells View to call to the parent's authenticator.
|
// AuthenticateSession tells View to call to the parent's authenticator.
|
||||||
AuthenticateSession(*Service)
|
AuthenticateSession(*Service)
|
||||||
|
// MoveService tells the view to shift the service to before the target.
|
||||||
|
MoveService(id, targetID string)
|
||||||
|
|
||||||
OnSessionRemove(*Service, *session.Row)
|
OnSessionRemove(*Service, *session.Row)
|
||||||
OnSessionDisconnect(*Service, *session.Row)
|
OnSessionDisconnect(*Service, *session.Row)
|
||||||
|
@ -125,6 +128,9 @@ func NewService(svc cchat.Service, svclctrl ListController) *Service {
|
||||||
service.Box.Show()
|
service.Box.Show()
|
||||||
serviceCSS(service.Box)
|
serviceCSS(service.Box)
|
||||||
|
|
||||||
|
// Bind a drag and drop on the button instead of the entire box.
|
||||||
|
drag.BindDraggable(service, "network-workgroup", svclctrl.MoveService, service.Button)
|
||||||
|
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +169,10 @@ func (s *Service) AddSession(ses cchat.Session) *session.Row {
|
||||||
return srow
|
return srow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) ID() string {
|
||||||
|
return s.service.Name().Content
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) Service() cchat.Service {
|
func (s *Service) Service() cchat.Service {
|
||||||
return s.service
|
return s.service
|
||||||
}
|
}
|
||||||
|
@ -247,38 +257,3 @@ func (s *Service) restoreAll() {
|
||||||
func restoreAsync(r *session.Row, res cchat.SessionRestorer, k keyring.Session) {
|
func restoreAsync(r *session.Row, res cchat.SessionRestorer, k keyring.Session) {
|
||||||
r.RestoreSession(res, k)
|
r.RestoreSession(res, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
type header struct {
|
|
||||||
*rich.ToggleButtonImage
|
|
||||||
Add *gtk.Button
|
|
||||||
|
|
||||||
Menu *menu.LazyMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHeader(svc cchat.Service) *header {
|
|
||||||
b := rich.NewToggleButtonImage(svc.Name())
|
|
||||||
b.Image.SetPlaceholderIcon("folder-remote-symbolic", IconSize)
|
|
||||||
b.SetRelief(gtk.RELIEF_NONE)
|
|
||||||
b.SetMode(true)
|
|
||||||
b.Show()
|
|
||||||
|
|
||||||
if iconer, ok := svc.(cchat.Icon); ok {
|
|
||||||
b.Image.AsyncSetIconer(iconer, "Error getting session logo")
|
|
||||||
}
|
|
||||||
|
|
||||||
add, _ := gtk.ButtonNewFromIconName("list-add-symbolic", gtk.ICON_SIZE_BUTTON)
|
|
||||||
add.Show()
|
|
||||||
|
|
||||||
// Add the button overlay into the main button.
|
|
||||||
buttonoverlay.Take(b, add, IconSize)
|
|
||||||
|
|
||||||
// Construct a menu and its items.
|
|
||||||
var menu = menu.NewLazyMenu(b)
|
|
||||||
if configurator, ok := svc.(config.Configurator); ok {
|
|
||||||
menu.AddItems(config.MenuItem(configurator))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &header{b, add, menu}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/drag"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,9 +90,6 @@ func (sl *List) AddSessionRow(id string, row *Row) {
|
||||||
// Set the map, which increases the length by 1.
|
// Set the map, which increases the length by 1.
|
||||||
sl.sessions[id] = row
|
sl.sessions[id] = row
|
||||||
|
|
||||||
// Bind the mover.
|
|
||||||
row.BindMover(id)
|
|
||||||
|
|
||||||
// Assert that a name can be obtained.
|
// Assert that a name can be obtained.
|
||||||
namer := primitives.Namer(row)
|
namer := primitives.Namer(row)
|
||||||
namer.SetName(id) // set ID here, get it in Move
|
namer.SetName(id) // set ID here, get it in Move
|
||||||
|
@ -108,23 +106,16 @@ func (sl *List) RemoveSessionRow(sessionID string) bool {
|
||||||
|
|
||||||
// MoveSession moves sessions around. This function must not touch the add
|
// MoveSession moves sessions around. This function must not touch the add
|
||||||
// button.
|
// button.
|
||||||
func (sl *List) MoveSession(id, movingID string) {
|
func (sl *List) MoveSession(targetID, movingID string) {
|
||||||
// Get the widget of the row that is moving.
|
// Get the widget of the row that is moving.
|
||||||
var moving = sl.sessions[movingID]
|
var moving, ok = sl.sessions[movingID]
|
||||||
|
if !ok {
|
||||||
|
return // sometimes movingID might come from other services
|
||||||
|
}
|
||||||
|
|
||||||
// Find the current position of the row that we're moving the other one
|
// Find the current position of the row that we're moving the other one
|
||||||
// underneath of.
|
// underneath of.
|
||||||
var rowix = -1
|
var rowix = drag.Find(sl.ListBox, targetID)
|
||||||
|
|
||||||
primitives.EachChildren(sl.ListBox, func(i int, v interface{}) bool {
|
|
||||||
// The obtained name will be the ID set in AddSessionRow.
|
|
||||||
if primitives.GetName(v.(primitives.Namer)) == id {
|
|
||||||
rowix = i
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
// Reorder the box.
|
// Reorder the box.
|
||||||
sl.ListBox.Remove(moving)
|
sl.ListBox.Remove(moving)
|
||||||
|
|
|
@ -26,7 +26,11 @@ type Children struct {
|
||||||
|
|
||||||
// reserved
|
// reserved
|
||||||
var childrenCSS = primitives.PrepareClassCSS("server-children", `
|
var childrenCSS = primitives.PrepareClassCSS("server-children", `
|
||||||
.server-children {}
|
.server-children {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 3px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
func NewChildren(p breadcrumb.Breadcrumber, ctrl Controller) *Children {
|
func NewChildren(p breadcrumb.Breadcrumber, ctrl Controller) *Children {
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"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-gtk/internal/ui/rich"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
"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/button"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -22,7 +22,8 @@ type ServerRow struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverCSS = primitives.PrepareClassCSS("server", `
|
var serverCSS = primitives.PrepareClassCSS("server", `
|
||||||
.server {
|
/* Ignore first child because .server-children already covers this */
|
||||||
|
.server:not(:first-child) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
@ -130,8 +131,8 @@ func (r *Row) Reset() {
|
||||||
|
|
||||||
// SetLoading is called by the parent struct.
|
// SetLoading is called by the parent struct.
|
||||||
func (r *Row) SetLoading() {
|
func (r *Row) SetLoading() {
|
||||||
r.Button.SetLoading()
|
|
||||||
r.SetSensitive(false)
|
r.SetSensitive(false)
|
||||||
|
r.Button.SetLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFailed is shared between the parent struct and the children list. This is
|
// SetFailed is shared between the parent struct and the children list. This is
|
||||||
|
@ -216,6 +217,7 @@ func (r *Row) Load() {
|
||||||
|
|
||||||
// Set that we're now loading.
|
// Set that we're now loading.
|
||||||
r.children.setLoading()
|
r.children.setLoading()
|
||||||
|
r.SetLoading()
|
||||||
r.SetSensitive(false)
|
r.SetSensitive(false)
|
||||||
|
|
||||||
// Load the list of servers if we're still in loading mode.
|
// Load the list of servers if we're still in loading mode.
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/actions"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/actions"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/drag"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/spinner"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/spinner"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
|
||||||
|
@ -132,6 +133,9 @@ func newRow(parent breadcrumb.Breadcrumber, name text.Rich, ctrl Servicer) *Row
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Bind drag-and-drop events.
|
||||||
|
drag.BindDraggable(row, "face-smile", ctrl.MoveSession)
|
||||||
|
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,10 +167,6 @@ func (r *Row) Reset() {
|
||||||
r.cmder = nil
|
r.cmder = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Row) SessionID() string {
|
|
||||||
return r.sessionID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
|
func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
|
||||||
return breadcrumb.Try(r.parentcrumb, r.Session.Name().Content)
|
return breadcrumb.Try(r.parentcrumb, r.Session.Name().Content)
|
||||||
}
|
}
|
||||||
|
@ -204,6 +204,7 @@ func (r *Row) SetLoading() {
|
||||||
spin.SetSizeRequest(IconSize, IconSize)
|
spin.SetSizeRequest(IconSize, IconSize)
|
||||||
spin.Start()
|
spin.Start()
|
||||||
spin.Show()
|
spin.Show()
|
||||||
|
rowIconCSS(spin)
|
||||||
|
|
||||||
r.Add(spin)
|
r.Add(spin)
|
||||||
r.SetSensitive(false) // no activate
|
r.SetSensitive(false) // no activate
|
||||||
|
@ -283,13 +284,6 @@ func (r *Row) SetSession(ses cchat.Session) {
|
||||||
r.Servers.SetList(ses)
|
r.Servers.SetList(ses)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindMover binds with the ID stored in the parent container to be used in the
|
|
||||||
// method itself. The ID may or may not have to do with session.
|
|
||||||
func (r *Row) BindMover(id string) {
|
|
||||||
// TODO: rows can be highlighted.
|
|
||||||
// primitives.BindDragSortable(r.Button, "GTK_TOGGLE_BUTTON", id, r.ctrl.MoveSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Row) RowSelected(sr *server.ServerRow, smsg cchat.ServerMessage) {
|
func (r *Row) RowSelected(sr *server.ServerRow, smsg cchat.ServerMessage) {
|
||||||
r.svcctrl.RowSelected(r, sr, smsg)
|
r.svcctrl.RowSelected(r, sr, smsg)
|
||||||
}
|
}
|
||||||
|
@ -371,196 +365,3 @@ func (r *Row) ShowCommander() {
|
||||||
}
|
}
|
||||||
r.cmder.ShowDialog()
|
r.cmder.ShowDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecate server.Row inheritance since the structure is entirely different
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Row represents a single session, including the button header and the
|
|
||||||
// children servers.
|
|
||||||
type Row struct {
|
|
||||||
*server.Row
|
|
||||||
Session cchat.Session
|
|
||||||
sessionID string // used for reconnection
|
|
||||||
|
|
||||||
ctrl Servicer
|
|
||||||
|
|
||||||
cmder *commander.Buffer
|
|
||||||
cmdbtn *gtk.Button
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Servicer) *Row {
|
|
||||||
row := newRow(parent, text.Rich{}, ctrl)
|
|
||||||
row.SetSession(ses)
|
|
||||||
return row
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLoading(parent breadcrumb.Breadcrumber, id, name string, ctrl Servicer) *Row {
|
|
||||||
row := newRow(parent, text.Rich{Content: name}, ctrl)
|
|
||||||
row.sessionID = id
|
|
||||||
row.Row.SetLoading()
|
|
||||||
return row
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRow(parent breadcrumb.Breadcrumber, name text.Rich, ctrl Servicer) *Row {
|
|
||||||
srow := server.NewRow(parent, name)
|
|
||||||
srow.Button.SetPlaceholderIcon(IconName, IconSize)
|
|
||||||
srow.Show()
|
|
||||||
|
|
||||||
// Bind the row to .session in CSS.
|
|
||||||
primitives.AddClass(srow, "session")
|
|
||||||
primitives.AddClass(srow, "server-list")
|
|
||||||
|
|
||||||
// Make a commander button that's hidden by default in case.
|
|
||||||
cmdbtn, _ := gtk.ButtonNewFromIconName("utilities-terminal-symbolic", gtk.ICON_SIZE_BUTTON)
|
|
||||||
buttonoverlay.Take(srow.Button, cmdbtn, server.IconSize)
|
|
||||||
primitives.AddClass(cmdbtn, "command-button")
|
|
||||||
|
|
||||||
row := &Row{
|
|
||||||
Row: srow,
|
|
||||||
ctrl: ctrl,
|
|
||||||
cmdbtn: cmdbtn,
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdbtn.Connect("clicked", row.ShowCommander)
|
|
||||||
|
|
||||||
return row
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset extends the server row's Reset function and resets additional states.
|
|
||||||
// It resets all states back to nil, but the session ID stays.
|
|
||||||
func (r *Row) Reset() {
|
|
||||||
r.Row.Reset()
|
|
||||||
r.Session = nil
|
|
||||||
r.cmder = nil
|
|
||||||
r.cmdbtn.Hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveSession removes itself from the session list.
|
|
||||||
func (r *Row) RemoveSession() {
|
|
||||||
// Remove the session off the list.
|
|
||||||
r.ctrl.RemoveSession(r)
|
|
||||||
|
|
||||||
// Asynchrously disconnect.
|
|
||||||
go func() {
|
|
||||||
if err := r.Session.Disconnect(); err != nil {
|
|
||||||
log.Error(errors.Wrap(err, "Non-fatal, failed to disconnect removed session"))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReconnectSession tries to reconnect with the keyring data. This is a slow
|
|
||||||
// method but it's also a very cold path.
|
|
||||||
func (r *Row) ReconnectSession() {
|
|
||||||
// If we haven't ever connected, then don't run. In a legitimate case, this
|
|
||||||
// shouldn't happen.
|
|
||||||
if r.sessionID == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the row as loading.
|
|
||||||
r.Row.SetLoading()
|
|
||||||
// Try to restore the session.
|
|
||||||
r.ctrl.RestoreSession(r, r.sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisconnectSession disconnects the current session. It does nothing if the row
|
|
||||||
// does not have a session active.
|
|
||||||
func (r *Row) DisconnectSession() {
|
|
||||||
// No-op if no session.
|
|
||||||
if r.Session == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the disconnect function from the controller first.
|
|
||||||
r.ctrl.OnSessionDisconnect(r)
|
|
||||||
|
|
||||||
// Show visually that we're disconnected first by wiping all servers.
|
|
||||||
r.Reset()
|
|
||||||
|
|
||||||
// Set the offline icon to the button.
|
|
||||||
r.Button.Image.SetPlaceholderIcon(IconName, IconSize)
|
|
||||||
// Also unselect the button.
|
|
||||||
r.Button.SetActive(false)
|
|
||||||
|
|
||||||
// Disable the button because we're busy disconnecting. We'll re-enable them
|
|
||||||
// once we're done reconnecting.
|
|
||||||
r.SetSensitive(false)
|
|
||||||
|
|
||||||
// Try and disconnect asynchronously.
|
|
||||||
gts.Async(func() (func(), error) {
|
|
||||||
// Disconnect and wrap the error if any. Wrap works with a nil error.
|
|
||||||
err := errors.Wrap(r.Session.Disconnect(), "Failed to disconnect.")
|
|
||||||
return func() {
|
|
||||||
// Allow access to the menu
|
|
||||||
r.SetSensitive(true)
|
|
||||||
|
|
||||||
// Set the menu to allow disconnection.
|
|
||||||
r.Button.SetNormalExtraMenu([]menu.Item{
|
|
||||||
menu.SimpleItem("Connect", r.ReconnectSession),
|
|
||||||
menu.SimpleItem("Remove", r.RemoveSession),
|
|
||||||
})
|
|
||||||
}, err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringSession returns a keyring session, or nil if the session cannot be
|
|
||||||
// saved.
|
|
||||||
func (r *Row) KeyringSession() *keyring.Session {
|
|
||||||
return keyring.ConvertSession(r.Session, r.Button.GetText())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the session ID.
|
|
||||||
func (r *Row) ID() string {
|
|
||||||
return r.sessionID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFailed sets the initial connect status to failed. Do note that session can
|
|
||||||
// have 2 types of loading: loading the session and loading the server list.
|
|
||||||
// This one sets the former.
|
|
||||||
func (r *Row) SetFailed(err error) {
|
|
||||||
// SetFailed, but also add the callback to retry.
|
|
||||||
r.Row.SetFailed(err, r.ReconnectSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSession binds the session and marks the row as ready. It extends SetDone.
|
|
||||||
func (r *Row) SetSession(ses cchat.Session) {
|
|
||||||
r.Session = ses
|
|
||||||
r.sessionID = ses.ID()
|
|
||||||
r.SetLabelUnsafe(ses.Name())
|
|
||||||
r.SetIconer(ses)
|
|
||||||
|
|
||||||
// Set the commander, if any. The function will return nil if the assertion
|
|
||||||
// returns nil. As such, we assert with an ignored ok bool, allowing cmd to
|
|
||||||
// be nil.
|
|
||||||
cmd, _ := ses.(commander.SessionCommander)
|
|
||||||
r.cmder = commander.NewBuffer(r.ctrl.GetService(), cmd)
|
|
||||||
// Show the command button if the session actually supports the commander.
|
|
||||||
if r.cmder != nil {
|
|
||||||
r.cmdbtn.Show()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind extra menu items before loading. These items won't be clickable
|
|
||||||
// during loading.
|
|
||||||
r.SetNormalExtraMenu([]menu.Item{
|
|
||||||
menu.SimpleItem("Disconnect", r.DisconnectSession),
|
|
||||||
menu.SimpleItem("Remove", r.RemoveSession),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Preload now.
|
|
||||||
r.SetServerList(ses, r)
|
|
||||||
r.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Row) RowSelected(server *server.ServerRow, smsg cchat.ServerMessage) {
|
|
||||||
r.ctrl.RowSelected(r, server, smsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommander shows the commander dialog, or it does nothing if session does
|
|
||||||
// not implement commander.
|
|
||||||
func (r *Row) ShowCommander() {
|
|
||||||
if r.cmder == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.cmder.ShowDialog()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
@ -8,22 +8,6 @@ import (
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Design:
|
|
||||||
|
|
||||||
____________________________
|
|
||||||
| # | | |
|
|
||||||
|-----|-----------|--------|
|
|
||||||
| D | nixhub | |
|
|
||||||
| --- | #home | | <- shaded revealer
|
|
||||||
| O | #dev... | | <- user accounts collapsed
|
|
||||||
| --- | astolf... | |
|
|
||||||
| | asdada... | |
|
|
||||||
| M | | |
|
|
||||||
|_____|___________|________|
|
|
||||||
*/
|
|
||||||
|
|
||||||
type Controller interface {
|
type Controller interface {
|
||||||
// SessionSelected is called when
|
// SessionSelected is called when
|
||||||
SessionSelected(svc *Service, srow *session.Row)
|
SessionSelected(svc *Service, srow *session.Row)
|
||||||
|
@ -66,16 +50,17 @@ func NewView(ctrller Controller) *View {
|
||||||
view.ServerStack.SetTransitionDuration(50)
|
view.ServerStack.SetTransitionDuration(50)
|
||||||
view.ServerStack.SetTransitionType(gtk.STACK_TRANSITION_TYPE_CROSSFADE)
|
view.ServerStack.SetTransitionType(gtk.STACK_TRANSITION_TYPE_CROSSFADE)
|
||||||
view.ServerStack.SetHomogeneous(true)
|
view.ServerStack.SetHomogeneous(true)
|
||||||
|
view.ServerStack.SetHExpand(true)
|
||||||
view.ServerStack.Show()
|
view.ServerStack.Show()
|
||||||
|
|
||||||
view.ServerView, _ = gtk.ScrolledWindowNew(nil, nil)
|
view.ServerView, _ = gtk.ScrolledWindowNew(nil, nil)
|
||||||
view.ServerView.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
view.ServerView.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||||
|
view.ServerView.SetHExpand(true)
|
||||||
view.ServerView.Add(view.ServerStack)
|
view.ServerView.Add(view.ServerStack)
|
||||||
view.ServerView.Show()
|
view.ServerView.Show()
|
||||||
|
|
||||||
view.Box, _ = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
view.Box, _ = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
view.Box.PackStart(view.Services, false, false, 0)
|
view.Box.PackStart(view.Services, false, false, 0)
|
||||||
// view.Box.PackStart(sep, false, false, 0)
|
|
||||||
view.Box.PackStart(view.ServerView, true, true, 0)
|
view.Box.PackStart(view.ServerView, true, true, 0)
|
||||||
view.Box.Show()
|
view.Box.Show()
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ func init() {
|
||||||
/* Make CSS more consistent across themes */
|
/* Make CSS more consistent across themes */
|
||||||
headerbar { padding-left: 0 }
|
headerbar { padding-left: 0 }
|
||||||
|
|
||||||
.appmenu { margin: 0 18px }
|
/* .appmenu { margin: 0 20px } */
|
||||||
|
|
||||||
popover > *:not(stack):not(button) { margin: 6px }
|
popover > *:not(stack):not(button) { margin: 6px }
|
||||||
|
|
||||||
|
@ -68,10 +68,16 @@ func NewApplication() *App {
|
||||||
app.window = newWindow(app)
|
app.window = newWindow(app)
|
||||||
app.header = newHeader()
|
app.header = newHeader()
|
||||||
|
|
||||||
|
// Resize the app icon with the left-most sidebar.
|
||||||
|
services := app.window.Services.Services
|
||||||
|
services.Connect("size-allocate", func() {
|
||||||
|
app.header.left.appmenu.SetSizeRequest(services.GetAllocatedWidth(), -1)
|
||||||
|
})
|
||||||
|
|
||||||
// Resize the left-side header w/ the left-side pane.
|
// Resize the left-side header w/ the left-side pane.
|
||||||
app.window.Services.Connect("size-allocate", func(wv gtk.IWidget) {
|
app.window.Services.ServerView.Connect("size-allocate", func() {
|
||||||
// Get the current width of the left sidebar.
|
// Get the current width of the left sidebar.
|
||||||
var width = app.window.GetPosition()
|
width := app.window.GetPosition()
|
||||||
// Set the left-side header's size.
|
// Set the left-side header's size.
|
||||||
app.header.left.SetSizeRequest(width, -1)
|
app.header.left.SetSizeRequest(width, -1)
|
||||||
})
|
})
|
||||||
|
@ -90,7 +96,7 @@ func (app *App) AddService(svc cchat.Service) {
|
||||||
// OnSessionRemove resets things before the session is removed.
|
// OnSessionRemove resets things before the session is removed.
|
||||||
func (app *App) OnSessionRemove(s *service.Service, r *session.Row) {
|
func (app *App) OnSessionRemove(s *service.Service, r *session.Row) {
|
||||||
// Reset the message view if it's what we're showing.
|
// Reset the message view if it's what we're showing.
|
||||||
if app.window.MessageView.SessionID() == r.SessionID() {
|
if app.window.MessageView.SessionID() == r.ID() {
|
||||||
app.window.MessageView.Reset()
|
app.window.MessageView.Reset()
|
||||||
app.header.SetBreadcrumber(nil)
|
app.header.SetBreadcrumber(nil)
|
||||||
}
|
}
|
||||||
|
@ -170,13 +176,9 @@ func (app *App) Window() gtk.IWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) Icon() *gdk.Pixbuf {
|
func (app *App) Icon() *gdk.Pixbuf {
|
||||||
return icons.Logo256()
|
return icons.Logo256(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) Menu() *glib.MenuModel {
|
func (app *App) Menu() *glib.MenuModel {
|
||||||
menu := glib.MenuNew()
|
return &app.header.menu.MenuModel
|
||||||
menu.Append("Preferences", "app.preferences")
|
|
||||||
menu.Append("Quit", "app.quit")
|
|
||||||
|
|
||||||
return &menu.MenuModel
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue