mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-12-22 20:27:07 +00:00
Added menu buttons and several tweaks
This commit is contained in:
parent
e65dbb20ed
commit
cebc5f58b4
2
go.mod
2
go.mod
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/Xuanwo/go-locale v0.2.0
|
||||
github.com/alecthomas/chroma v0.7.3
|
||||
github.com/diamondburned/cchat v0.0.43
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200714063838-0a590627268b
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200715034853-d1e516c919c4
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b
|
||||
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
|
|
56
go.sum
56
go.sum
|
@ -44,64 +44,20 @@ 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/aqs v0.0.0-20200704043812-99b676ee44eb h1:Ja/niwykeFoSkYxdRRzM8QUAuCswfLmaiBTd2UIU+54=
|
||||
github.com/diamondburned/aqs v0.0.0-20200704043812-99b676ee44eb/go.mod h1:q1MbMBfZrv7xqV8n7LgMwhHs3oBbNwWJes8exs2AmDs=
|
||||
github.com/diamondburned/arikawa v0.9.5 h1:P1ffsp+NHT22wWKYFVC8CdlGRLzPuUV9FcCBKOCJpCI=
|
||||
github.com/diamondburned/arikawa v0.9.5/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
|
||||
github.com/diamondburned/arikawa v0.9.6 h1:6TpfTKa2btoVQGxojNqv8g2YC0tIc/tX5w/OCVZPF5Q=
|
||||
github.com/diamondburned/arikawa v0.9.6/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
|
||||
github.com/diamondburned/arikawa v0.10.2 h1:xTsFWlWwGzFr8HD7tyv2jMRyserOR4yV5dhq/PZMPAA=
|
||||
github.com/diamondburned/arikawa v0.10.2/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
|
||||
github.com/diamondburned/cchat v0.0.40 h1:38gPyJnnDoNDHrXcV8Qchfv3y6jlS3Fzz/6FY0BPH6I=
|
||||
github.com/diamondburned/cchat v0.0.40/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||
github.com/diamondburned/cchat v0.0.41 h1:6y32s2wWTiDw4hWN/Gna6ay3uUrRAW5V8Cj0/xLKovw=
|
||||
github.com/diamondburned/cchat v0.0.41/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||
github.com/diamondburned/cchat v0.0.42 h1:FVMLy9hOTxKju8OWDBIStrekbgTHCaH8+GVnV4LOByg=
|
||||
github.com/diamondburned/cchat v0.0.42/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||
github.com/diamondburned/arikawa v0.10.5 h1:o5lBopooA+8cXlKZdct5qF0xztuZZ35phvQrwGS5vYM=
|
||||
github.com/diamondburned/arikawa v0.10.5/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
|
||||
github.com/diamondburned/cchat v0.0.43 h1:HetAujSaUSdnQgAUZgprNLARjf/MSWXpCfWdvX2wOCU=
|
||||
github.com/diamondburned/cchat v0.0.43/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200703190659-fbf95b9b6c03 h1:F5TL7GPRU/D4ldVkS0haY3SiHPtf1Kby/4nbYpm//MQ=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200703190659-fbf95b9b6c03/go.mod h1:p0X6QUH0mxK8yEW0+a4QA77ClAmoxz8CvgbnobMtWQA=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708083530-d0e43cc63b03 h1:Xx4ioFTurT6qTxzTL8QlsH3E5VskLxHPJ8RwmaKhObA=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708083530-d0e43cc63b03/go.mod h1:hjaCeQfhSqANH+ZtxXPMWzpgobb4syy2VcqDK4PXcPU=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b h1:c01OHXPE/HXcZdaPDgNNzmz3xN7MC0yIyL5i+6qILAc=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b/go.mod h1:IDBm0RqdQUASjPGP/RQkhmC1nGhGTTFoaHn9NbD/l7I=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708212314-e08c31b895a6 h1:Nc9B6i7siInzh/uu3bTu7mcw7ckcFDTu4TtSNoxUMis=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200708212314-e08c31b895a6/go.mod h1:QHPtnxNrnMFCYB/b9kUP93D30Kf3AuGmkM91tScIpB8=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c h1:4F7IJqMfpF02/j6ZbVO9x29rWExQncLHFbxZrsAhhvM=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c/go.mod h1:QHPtnxNrnMFCYB/b9kUP93D30Kf3AuGmkM91tScIpB8=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200711215912-d1f5376e30b3 h1:uMrWIWdI3qGY8IdH8LUyAM9pflcPvgpLTREOI0Qn7rw=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200711215912-d1f5376e30b3/go.mod h1:TP5/aE708Ae2quG1yAvCCoDJjKuBcjFZ1LYVw60FbTw=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200711221358-e50f72a01d0a h1:0/j7J0HTR3OuU0qaQ9HWLK0DlJdXPL72ziFVD1f6d8E=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200711221358-e50f72a01d0a/go.mod h1:TP5/aE708Ae2quG1yAvCCoDJjKuBcjFZ1LYVw60FbTw=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200712063736-b8c92a56d89b h1:W5Mmf6GagIctMH5n2TYORSENMZdJm8th4JrEJubLb60=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200712063736-b8c92a56d89b/go.mod h1:KRdCNnJHsHIcQP6/fE90MKTIZJuGyTKW/aTx18eGuuI=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200714012913-3c1d6f1d3f17 h1:dUF7+pmzfAKS1gBr5SySg5xkSrAmzRGcghDRjXl5TDg=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200714012913-3c1d6f1d3f17/go.mod h1:KRdCNnJHsHIcQP6/fE90MKTIZJuGyTKW/aTx18eGuuI=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200714014557-cc97c2a69cc2 h1:2+XIy4DzLymP/X7LFH03WBdHpkVavb0MC0fQrPvRGxs=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200714014557-cc97c2a69cc2/go.mod h1:KRdCNnJHsHIcQP6/fE90MKTIZJuGyTKW/aTx18eGuuI=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200714063838-0a590627268b h1:dZ7fntEpmeh2xvwZ6jqyWjzk9Ikvx0o/O7pEDMR1PzU=
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200714063838-0a590627268b/go.mod h1:KRdCNnJHsHIcQP6/fE90MKTIZJuGyTKW/aTx18eGuuI=
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3 h1:xr07/2cwINyrMqh92pQQJVDfQqG0u6gHAK+ZcGfpSew=
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3/go.mod h1:SRu3OOeggELFr2Wd3/+SpYV1eNcvSk2LBhM70NOZSG8=
|
||||
github.com/diamondburned/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-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/gotk3 v0.0.0-20200630065217-97aeb06d705d h1:Ha/I6PMKi+B4hpWclwlXj0tUMehR7Q0TNxPczzBwzPI=
|
||||
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
||||
github.com/diamondburned/imgutil v0.0.0-20200704034004-40dbfc732516 h1:6j4oZahbNdVhSEInRfeYbgDpx1FXDfJy6CcUVyWOuVY=
|
||||
github.com/diamondburned/imgutil v0.0.0-20200704034004-40dbfc732516/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
|
||||
github.com/diamondburned/imgutil v0.0.0-20200708012333-53c9e45dd28b h1:iYKHGvWzNFBIRTSY8Pd5g301YDGWMfs3fh1VS0iBSj0=
|
||||
github.com/diamondburned/imgutil v0.0.0-20200708012333-53c9e45dd28b/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
|
||||
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972 h1:OWxllHbUptXzDias6YI4MM0R3o50q8MfhkkwVIlfiNo=
|
||||
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249 h1:yP7kJ+xCGpDz6XbcfACJcju4SH1XDPwlrvbofz3lP8I=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249/go.mod h1:xW9hpBZsGi8KpAh10TyP+YQlYBo+Xc+2w4TR6N0951A=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200708090333-227e90d19851 h1:xf1aLPnwK/Yn2z7dBIgQROSVOEc2wtivgnnwBItdEVM=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200708090333-227e90d19851/go.mod h1:FNezDLQIhoDS+RkXLSQ7dJNrt6BW/nVl1krzDgWMQwg=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200708211706-57c712372ede h1:qRmfQCOS+ZnH4G0+8O09PUx3HQTdQwzsDoo1ucTgm2E=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200708211706-57c712372ede/go.mod h1:FNezDLQIhoDS+RkXLSQ7dJNrt6BW/nVl1krzDgWMQwg=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200711215126-d4b8a17e818d h1:XgG/KRbAwu8v2/YZWimjXo0dgdD69E38ollCfbFtU7s=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200711215126-d4b8a17e818d/go.mod h1:NVneOJDUDEIC3cnyeh2vpeAPVtBdC2Kcy+uwDy4o2qk=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200712031630-349ee2c3f01c h1:CpYhIGiRzee7Jm0H4c0fLvRe/08QitDNo8KHYtrOmFE=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200712031630-349ee2c3f01c/go.mod h1:NVneOJDUDEIC3cnyeh2vpeAPVtBdC2Kcy+uwDy4o2qk=
|
||||
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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||
|
|
15
icons/byte
Executable file
15
icons/byte
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
# Script to embed a file to Go.
|
||||
|
||||
for file in "$@"; {
|
||||
name="${file%.*}"
|
||||
name="${name//[^[:alnum:]]/_}" # sanitize
|
||||
|
||||
file2byteslice \
|
||||
-input "$file" \
|
||||
-output "${name}.go" \
|
||||
-package icons \
|
||||
-var "__${name}"
|
||||
|
||||
echo "Written $file to \`var __$name'"
|
||||
}
|
6
icons/cchat_256.go
Normal file
6
icons/cchat_256.go
Normal file
File diff suppressed because one or more lines are too long
6
icons/cchat_variant2_256.go
Normal file
6
icons/cchat_variant2_256.go
Normal file
File diff suppressed because one or more lines are too long
|
@ -9,22 +9,23 @@ import (
|
|||
)
|
||||
|
||||
// static assets
|
||||
var logo256 *gdk.Pixbuf
|
||||
var assets = map[string]*gdk.Pixbuf{}
|
||||
|
||||
func Logo256() *gdk.Pixbuf {
|
||||
if logo256 == nil {
|
||||
logo256 = loadPixbuf(pkger.Include("/icons/cchat-variant2_256.png"))
|
||||
}
|
||||
return logo256
|
||||
func Logo256Variant2() *gdk.Pixbuf {
|
||||
return loadPixbuf(__cchat_variant2_256)
|
||||
}
|
||||
|
||||
func loadPixbuf(name string) *gdk.Pixbuf {
|
||||
func Logo256() *gdk.Pixbuf {
|
||||
return loadPixbuf(__cchat_256)
|
||||
}
|
||||
|
||||
func loadPixbuf(data []byte) *gdk.Pixbuf {
|
||||
l, err := gdk.PixbufLoaderNew()
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to create a pixbuf loader for icons:", err)
|
||||
}
|
||||
|
||||
p, err := l.WriteAndReturnPixbuf(readFile(name))
|
||||
p, err := l.WriteAndReturnPixbuf(data)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to write and return pixbuf:", err)
|
||||
}
|
||||
|
|
|
@ -29,24 +29,14 @@ func loadProviders(screen *gdk.Screen) {
|
|||
}
|
||||
}
|
||||
|
||||
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 LoadCSS(name, css string) {
|
||||
prov, _ := gtk.CssProviderNew()
|
||||
if err := prov.LoadFromData(css); err != nil {
|
||||
log.Error(errors.Wrap(err, "Failed to parse CSS in "+name))
|
||||
return
|
||||
}
|
||||
|
||||
cssRepos[name] = prov
|
||||
}
|
||||
|
||||
func readFile(buf *bytes.Buffer, file string) error {
|
||||
|
|
|
@ -23,6 +23,8 @@ var App struct {
|
|||
*gtk.Application
|
||||
Window *gtk.ApplicationWindow
|
||||
Header *gtk.HeaderBar
|
||||
|
||||
Throttler *throttler.State
|
||||
}
|
||||
|
||||
// Clipboard is initialized on init().
|
||||
|
@ -37,6 +39,7 @@ func NewModalDialog() (*gtk.Dialog, error) {
|
|||
}
|
||||
d.SetModal(true)
|
||||
d.SetTransientFor(App.Window)
|
||||
App.Throttler.Connect(d)
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
@ -63,48 +66,48 @@ func AddAppAction(name string, call func()) {
|
|||
App.AddAction(action)
|
||||
}
|
||||
|
||||
// Commented because this is not a good function to use. Components should use
|
||||
// AddAppAction instead.
|
||||
|
||||
// func AddWindowAction(name string, call func()) {
|
||||
// action := glib.SimpleActionNew(name, nil)
|
||||
// action.Connect("activate", call)
|
||||
// App.Window.AddAction(action)
|
||||
// }
|
||||
|
||||
func init() {
|
||||
gtk.Init(&Args)
|
||||
App.Application, _ = gtk.ApplicationNew(AppID, 0)
|
||||
Clipboard, _ = gtk.ClipboardGet(gdk.SELECTION_CLIPBOARD)
|
||||
|
||||
// Limit the TPS of the main loop on window unfocus.
|
||||
App.Throttler = throttler.Bind(App.Application)
|
||||
}
|
||||
|
||||
type WindowHeaderer interface {
|
||||
type Window interface {
|
||||
Window() gtk.IWidget
|
||||
Header() gtk.IWidget
|
||||
Menu() *glib.MenuModel
|
||||
Icon() *gdk.Pixbuf
|
||||
Close()
|
||||
}
|
||||
|
||||
func Main(wfn func() WindowHeaderer) {
|
||||
func Main(wfn func() Window) {
|
||||
App.Application.Connect("activate", func() {
|
||||
// Load all CSS onto the default screen.
|
||||
loadProviders(getDefaultScreen())
|
||||
|
||||
// Execute the function later, because we need it to run after
|
||||
// initialization.
|
||||
w := wfn()
|
||||
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)
|
||||
// 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.Show()
|
||||
|
||||
// Execute the function later, because we need it to run after
|
||||
// initialization.
|
||||
w := wfn()
|
||||
App.Window.Add(w.Window())
|
||||
App.Header.Add(w.Header())
|
||||
|
||||
|
@ -127,8 +130,6 @@ func Main(wfn func() WindowHeaderer) {
|
|||
})
|
||||
})
|
||||
|
||||
// Limit the TPS of the main loop on unfocus.
|
||||
throttler.Bind(App.Window)
|
||||
})
|
||||
|
||||
// Use a special function to run the application. Exit with the appropriate
|
||||
|
@ -223,6 +224,7 @@ func SpawnUploader(dirpath string, callback func(absolutePaths []string)) {
|
|||
"Upload", gtk.RESPONSE_ACCEPT,
|
||||
)
|
||||
|
||||
App.Throttler.Connect(dialog)
|
||||
BindPreviewer(dialog)
|
||||
|
||||
if dirpath == "" {
|
||||
|
|
|
@ -20,19 +20,25 @@ type Connector interface {
|
|||
Connect(string, interface{}, ...interface{}) (glib.SignalHandle, error)
|
||||
}
|
||||
|
||||
func Bind(evc Connector) *State {
|
||||
func Bind(app *gtk.Application) *State {
|
||||
var settings, _ = gtk.SettingsGetDefault()
|
||||
var s = State{
|
||||
settings: settings,
|
||||
ticker: time.Tick(time.Second / TPS),
|
||||
}
|
||||
|
||||
evc.Connect("focus-out-event", s.Start)
|
||||
evc.Connect("focus-in-event", s.Stop)
|
||||
app.Connect("window-added", func(app *gtk.Application, w *gtk.Window) {
|
||||
s.Connect(w)
|
||||
})
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *State) Connect(c Connector) {
|
||||
c.Connect("focus-out-event", s.Start)
|
||||
c.Connect("focus-in-event", s.Stop)
|
||||
}
|
||||
|
||||
func (s *State) Start() {
|
||||
if s.throttling {
|
||||
return
|
||||
|
|
|
@ -4,14 +4,15 @@ import (
|
|||
"html"
|
||||
"strings"
|
||||
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
"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/session"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
type header struct {
|
||||
*gtk.Box
|
||||
left *headerLeft // TODO
|
||||
left *headerLeft // middle-ish
|
||||
right *headerRight
|
||||
}
|
||||
|
||||
|
@ -41,26 +42,33 @@ func newHeader() *header {
|
|||
|
||||
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])
|
||||
func (h *header) SetBreadcrumber(b breadcrumb.Breadcrumber) {
|
||||
if b == nil {
|
||||
h.right.breadcrumb.SetText("")
|
||||
return
|
||||
}
|
||||
|
||||
var crumb = b.Breadcrumb()
|
||||
for i := range crumb {
|
||||
crumb[i] = html.EscapeString(crumb[i])
|
||||
}
|
||||
|
||||
h.right.breadcrumb.SetMarkup(
|
||||
BreadcrumbSlash + " " + strings.Join(b, " "+BreadcrumbSlash+" "),
|
||||
BreadcrumbSlash + " " + strings.Join(crumb, " "+BreadcrumbSlash+" "),
|
||||
)
|
||||
}
|
||||
|
||||
func (h *header) SetSessionMenu(s *session.Row) {
|
||||
h.left.openmenu.Bind(s.ActionsMenu)
|
||||
}
|
||||
|
||||
type headerLeft struct {
|
||||
*gtk.Box
|
||||
openmenu *gtk.MenuButton
|
||||
openmenu *actions.MenuButton
|
||||
}
|
||||
|
||||
func newHeaderLeft() *headerLeft {
|
||||
openmenu := primitives.NewMenuActionButton([][2]string{
|
||||
{"Preferences", "app.preferences"},
|
||||
{"Quit", "app.quit"},
|
||||
})
|
||||
openmenu := actions.NewMenuButton()
|
||||
openmenu.Show()
|
||||
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"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/service/menu"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"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/roundimage"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/labeluri"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/gotk3/gotk3/pango"
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/typing"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/autoscroll"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -85,7 +85,7 @@ func NewView() *View {
|
|||
primitives.AddClass(view.Box, "message-view")
|
||||
|
||||
// placeholder logo
|
||||
logo, _ := gtk.ImageNewFromPixbuf(icons.Logo256())
|
||||
logo, _ := gtk.ImageNewFromPixbuf(icons.Logo256Variant2())
|
||||
logo.Show()
|
||||
|
||||
view.FaceView = sadface.New(view.Box, logo)
|
||||
|
|
75
internal/ui/primitives/actions/menu.go
Normal file
75
internal/ui/primitives/actions/menu.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
type ActionGroupInserter interface {
|
||||
InsertActionGroup(prefix string, action glib.IActionGroup)
|
||||
}
|
||||
|
||||
var _ ActionGroupInserter = (*gtk.Widget)(nil)
|
||||
|
||||
type Menu struct {
|
||||
*Stateful
|
||||
menu *glib.Menu
|
||||
prefix string
|
||||
}
|
||||
|
||||
func NewMenu(prefix string) *Menu {
|
||||
return &Menu{
|
||||
Stateful: NewStateful(), // actiongroup and menu not linked
|
||||
menu: glib.MenuNew(),
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Menu) Prefix() string {
|
||||
return m.prefix
|
||||
}
|
||||
|
||||
func (m *Menu) MenuModel() (string, *glib.MenuModel) {
|
||||
return m.prefix, &m.menu.MenuModel
|
||||
}
|
||||
|
||||
func (m *Menu) InsertActionGroup(w ActionGroupInserter) {
|
||||
w.InsertActionGroup(m.prefix, m)
|
||||
}
|
||||
|
||||
func (m *Menu) Popover(relative gtk.IWidget) *gtk.Popover {
|
||||
_, model := m.MenuModel()
|
||||
|
||||
p, _ := gtk.PopoverNewFromModel(relative, model)
|
||||
p.SetPosition(gtk.POS_RIGHT)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (m *Menu) Reset() {
|
||||
m.menu.RemoveAll()
|
||||
m.Stateful.Reset()
|
||||
}
|
||||
|
||||
func (m *Menu) AddAction(label string, call func()) {
|
||||
m.Stateful.AddAction(label, call)
|
||||
m.menu.Append(label, fmt.Sprintf("%s.%s", m.prefix, ActionName(label)))
|
||||
}
|
||||
|
||||
func (m *Menu) RemoveAction(label string) {
|
||||
var labels = m.Stateful.labels
|
||||
|
||||
for i, l := range labels {
|
||||
if l == label {
|
||||
labels = append(labels[:i], labels[:i+1]...)
|
||||
m.menu.Remove(i)
|
||||
|
||||
m.Stateful.labels = labels
|
||||
m.Stateful.group.RemoveAction(ActionName(label))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
55
internal/ui/primitives/actions/menubutton.go
Normal file
55
internal/ui/primitives/actions/menubutton.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
type MenuButton struct {
|
||||
*gtk.MenuButton
|
||||
|
||||
lastsig glib.SignalHandle
|
||||
lastmod *glib.MenuModel
|
||||
}
|
||||
|
||||
func NewMenuButton() *MenuButton {
|
||||
b, _ := gtk.MenuButtonNew()
|
||||
b.SetSensitive(false)
|
||||
|
||||
return &MenuButton{
|
||||
MenuButton: b,
|
||||
}
|
||||
}
|
||||
|
||||
// Bind binds the given menu. The menu's prefix MUST be a constant for this
|
||||
// instance of the MenuButton.
|
||||
func (m *MenuButton) Bind(menu *Menu) {
|
||||
prefix, model := menu.MenuModel()
|
||||
|
||||
// Insert the action group into the menu. This will only override the old
|
||||
// action group, as the prefix is a constant for this instance.
|
||||
m.MenuButton.InsertActionGroup(prefix, menu)
|
||||
// Only after we have inserted the action group can we set the model that
|
||||
// menu has. This tells Gtk to look for the menu actions inside the inserted
|
||||
// group.
|
||||
m.MenuButton.SetMenuModel(model)
|
||||
|
||||
// Unbind the last handler if we have one.
|
||||
if m.lastmod != nil {
|
||||
m.lastmod.HandlerDisconnect(m.lastsig)
|
||||
}
|
||||
|
||||
// Set the current model as the last one for future calls.
|
||||
if m.lastmod = model; m.lastmod != nil {
|
||||
// If we have a model, then only activate the button when we have any
|
||||
// menu items.
|
||||
m.SetSensitive(model.GetNItems() > 0)
|
||||
// Subscribe the button to menu update events.
|
||||
m.lastsig, _ = model.Connect("items-changed", func() {
|
||||
m.SetSensitive(model.GetNItems() > 0)
|
||||
})
|
||||
} else {
|
||||
// Else, don't allow the button to be clicked at all.
|
||||
m.SetSensitive(false)
|
||||
}
|
||||
}
|
69
internal/ui/primitives/actions/stateful.go
Normal file
69
internal/ui/primitives/actions/stateful.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
)
|
||||
|
||||
// Stateful is a stateful action group, which would allow additional methods
|
||||
// that would otherwise be impossible to do with a simple Action Map.
|
||||
type Stateful struct {
|
||||
glib.IActionGroup
|
||||
group *glib.SimpleActionGroup
|
||||
labels []string // labels
|
||||
}
|
||||
|
||||
func NewStateful() *Stateful {
|
||||
group := glib.SimpleActionGroupNew()
|
||||
return &Stateful{
|
||||
IActionGroup: group,
|
||||
group: group,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stateful) Reset() {
|
||||
for _, label := range s.labels {
|
||||
s.group.RemoveAction(ActionName(label))
|
||||
}
|
||||
s.labels = nil
|
||||
}
|
||||
|
||||
func (s *Stateful) AddAction(label string, call func()) {
|
||||
sa := glib.SimpleActionNew(ActionName(label), nil)
|
||||
sa.Connect("activate", call)
|
||||
|
||||
s.labels = append(s.labels, label)
|
||||
s.group.AddAction(sa)
|
||||
}
|
||||
|
||||
func (s *Stateful) LookupAction(label string) *glib.Action {
|
||||
for _, l := range s.labels {
|
||||
if l == label {
|
||||
return s.group.LookupAction(ActionName(label))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Stateful) RemoveAction(label string) {
|
||||
for i, l := range s.labels {
|
||||
if l == label {
|
||||
s.labels = append(s.labels[:i], s.labels[:i+1]...)
|
||||
s.group.RemoveAction(ActionName(label))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ActionName converts the label name into the action name.
|
||||
func ActionName(label string) (actionName string) {
|
||||
actionName = strings.Replace(label, " ", "-", -1)
|
||||
|
||||
if !glib.ActionNameIsValid(actionName) {
|
||||
log.Panicf("Label makes for invalid action name %q\n", actionName)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -20,6 +20,13 @@ func NewLazyMenu(bindTo primitives.Connector) *LazyMenu {
|
|||
return l
|
||||
}
|
||||
|
||||
func (m *LazyMenu) popup(w gtk.IWidget, ev *gdk.Event) {
|
||||
// Is this a right click? Run the menu if yes.
|
||||
if gts.EventIsRightClick(ev) {
|
||||
m.PopupAtPointer(ev)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LazyMenu) SetItems(items []Item) {
|
||||
m.items = items
|
||||
}
|
||||
|
@ -47,13 +54,6 @@ func (m *LazyMenu) PopupAtPointer(ev *gdk.Event) {
|
|||
menu.PopupAtPointer(ev)
|
||||
}
|
||||
|
||||
func (m *LazyMenu) popup(w gtk.IWidget, ev *gdk.Event) {
|
||||
// Is this a right click? Run the menu if yes.
|
||||
if gts.EventIsRightClick(ev) {
|
||||
m.PopupAtPointer(ev)
|
||||
}
|
||||
}
|
||||
|
||||
type MenuAppender interface {
|
||||
Append(gtk.IMenuItem)
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ func ToolbarItems(toolbar ToolbarInserter, items []Item) {
|
|||
}
|
||||
|
||||
type Item struct {
|
||||
Icon string
|
||||
Name string
|
||||
Func func()
|
||||
Extra func(*gtk.MenuItem)
|
||||
|
@ -101,6 +102,19 @@ func SimpleItem(name string, fn func()) Item {
|
|||
return Item{Name: name, Func: fn}
|
||||
}
|
||||
|
||||
// func (item Item) ToModelButton() *gtk.ModelButton {
|
||||
// b, _ := gtk.ModelButtonNew()
|
||||
// b.SetLabel(action[0])
|
||||
// b.SetActionName(action[1])
|
||||
// b.Show()
|
||||
|
||||
// // Set the label's alignment in a hacky way.
|
||||
// c, _ := b.GetChild()
|
||||
// l := c.(LabelTweaker)
|
||||
// l.SetUseMarkup(true)
|
||||
// l.SetHAlign(gtk.ALIGN_START)
|
||||
// }
|
||||
|
||||
func (item Item) ToMenuItem() *gtk.MenuItem {
|
||||
mb, _ := gtk.MenuItemNewWithLabel(item.Name)
|
||||
mb.Connect("activate", item.Func)
|
|
@ -40,7 +40,6 @@ func NewIcon(sizepx int, procs ...imgutil.Processor) *Icon {
|
|||
|
||||
img, _ := roundimage.NewImage(0)
|
||||
img.Show()
|
||||
img.SetSizeRequest(sizepx, sizepx)
|
||||
|
||||
rev, _ := gtk.RevealerNew()
|
||||
rev.Add(img)
|
||||
|
@ -93,7 +92,6 @@ func (i *Icon) CopyPixbuf(dst httputil.ImageContainer) {
|
|||
func (i *Icon) SetPlaceholderIcon(iconName string, iconSzPx int) {
|
||||
i.Image.SetRadius(-1) // square
|
||||
i.SetRevealChild(true)
|
||||
i.SetSize(iconSzPx)
|
||||
|
||||
if iconName != "" {
|
||||
primitives.SetImageIcon(i.Image.Image, iconName, iconSzPx)
|
||||
|
@ -136,6 +134,24 @@ func (i *Icon) updateAsync() {
|
|||
httputil.AsyncImageSized(i.Image, i.url, i.size, i.size, i.procs...)
|
||||
}
|
||||
|
||||
type EventIcon struct {
|
||||
*gtk.EventBox
|
||||
Icon *Icon
|
||||
}
|
||||
|
||||
func NewEventIcon(sizepx int, pp ...imgutil.Processor) *EventIcon {
|
||||
icn := NewIcon(sizepx, pp...)
|
||||
icn.Show()
|
||||
|
||||
evb, _ := gtk.EventBoxNew()
|
||||
evb.Add(icn)
|
||||
|
||||
return &EventIcon{
|
||||
EventBox: evb,
|
||||
Icon: icn,
|
||||
}
|
||||
}
|
||||
|
||||
type ToggleButtonImage struct {
|
||||
gtk.ToggleButton
|
||||
Labeler
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package config
|
|||
import (
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ type ViewController interface {
|
|||
RowSelected(*session.Row, *server.ServerRow, cchat.ServerMessage)
|
||||
SessionSelected(*Service, *session.Row)
|
||||
AuthenticateSession(*List, *Service)
|
||||
OnSessionRemove(*Service, *session.Row)
|
||||
OnSessionDisconnect(*Service, *session.Row)
|
||||
}
|
||||
|
||||
// List is a list of services. Each service is a revealer that contains
|
||||
|
@ -59,6 +61,14 @@ func (sl *List) AuthenticateSession(svc *Service) {
|
|||
sl.ViewController.AuthenticateSession(sl, svc)
|
||||
}
|
||||
|
||||
func (sl *List) OnSessionRemove(svc *Service, row *session.Row) {
|
||||
sl.ViewController.OnSessionRemove(svc, row)
|
||||
}
|
||||
|
||||
func (sl *List) OnSessionDisconnect(svc *Service, row *session.Row) {
|
||||
sl.ViewController.OnSessionDisconnect(svc, row)
|
||||
}
|
||||
|
||||
func (sl *List) AddService(svc cchat.Service) {
|
||||
row := NewService(svc, sl)
|
||||
row.Show()
|
||||
|
|
|
@ -24,6 +24,9 @@ type ListController interface {
|
|||
SessionSelected(*Service, *session.Row)
|
||||
// AuthenticateSession tells View to call to the parent's authenticator.
|
||||
AuthenticateSession(*Service)
|
||||
|
||||
OnSessionRemove(*Service, *session.Row)
|
||||
OnSessionDisconnect(*Service, *session.Row)
|
||||
}
|
||||
|
||||
// Service holds everything that a single service has.
|
||||
|
@ -165,8 +168,16 @@ func (s *Service) Service() cchat.Service {
|
|||
}
|
||||
|
||||
func (s *Service) OnSessionDisconnect(row *session.Row) {
|
||||
s.BodyList.RemoveSessionRow(row.Session.ID())
|
||||
s.SaveAllSessions()
|
||||
// Unselect if selected.
|
||||
if cur := s.BodyList.GetSelectedRow(); cur.GetIndex() == row.GetIndex() {
|
||||
s.BodyList.UnselectAll()
|
||||
}
|
||||
|
||||
s.svclctrl.OnSessionDisconnect(s, row)
|
||||
|
||||
// WHY WAS THIS HERE?!?!?!
|
||||
// s.BodyList.RemoveSessionRow(row.Session.ID())
|
||||
// s.SaveAllSessions()
|
||||
}
|
||||
|
||||
func (s *Service) RowSelected(r *session.Row, sv *server.ServerRow, m cchat.ServerMessage) {
|
||||
|
@ -174,6 +185,7 @@ func (s *Service) RowSelected(r *session.Row, sv *server.ServerRow, m cchat.Serv
|
|||
}
|
||||
|
||||
func (s *Service) RemoveSession(row *session.Row) {
|
||||
s.svclctrl.OnSessionRemove(s, row)
|
||||
s.BodyList.RemoveSessionRow(row.Session.ID())
|
||||
s.SaveAllSessions()
|
||||
}
|
||||
|
|
|
@ -5,23 +5,6 @@ import (
|
|||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
// TODO rename file
|
||||
|
||||
// TODO: round buttons
|
||||
func NewAddButton() *gtk.ListBoxRow {
|
||||
img, _ := gtk.ImageNew()
|
||||
img.Show()
|
||||
primitives.SetImageIcon(img, "list-add-symbolic", IconSize/2)
|
||||
|
||||
row, _ := gtk.ListBoxRowNew()
|
||||
row.SetSizeRequest(IconSize, IconSize)
|
||||
row.SetSelectable(false) // activatable though
|
||||
row.Add(img)
|
||||
row.Show()
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
type ServiceController interface {
|
||||
SessionSelected(*Row)
|
||||
AuthenticateSession()
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"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/button"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/diamondburned/cchat-gtk/internal/keyring"
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"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/spinner"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/commander"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/gotk3/gotk3/gdk"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -47,7 +49,9 @@ type Servicer interface {
|
|||
// Row represents a session row entry in the session List.
|
||||
type Row struct {
|
||||
*gtk.ListBoxRow
|
||||
icon *rich.Icon // nilable
|
||||
icon *rich.EventIcon // nilable
|
||||
|
||||
parentcrumb breadcrumb.Breadcrumber
|
||||
|
||||
Session cchat.Session // state; nilable
|
||||
sessionID string
|
||||
|
@ -55,6 +59,8 @@ type Row struct {
|
|||
Servers *Servers // accessed by View for the right view
|
||||
svcctrl Servicer
|
||||
|
||||
ActionsMenu *actions.Menu // session.*
|
||||
|
||||
// TODO: enum class? having the button be red on fail would be good
|
||||
|
||||
// put commander in either a hover menu or a right click menu. maybe in the
|
||||
|
@ -78,6 +84,9 @@ var rowIconCSS = primitives.PrepareClassCSS("session-icon", `
|
|||
padding: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
.session-icon.failed {
|
||||
background-color: alpha(red, 0.45);
|
||||
}
|
||||
`)
|
||||
|
||||
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Servicer) *Row {
|
||||
|
@ -94,34 +103,74 @@ func NewLoading(parent breadcrumb.Breadcrumber, id, name string, ctrl Servicer)
|
|||
}
|
||||
|
||||
func newRow(parent breadcrumb.Breadcrumber, name text.Rich, ctrl Servicer) *Row {
|
||||
row := &Row{svcctrl: ctrl}
|
||||
row := &Row{
|
||||
svcctrl: ctrl,
|
||||
parentcrumb: parent,
|
||||
}
|
||||
|
||||
row.icon = rich.NewIcon(IconSize)
|
||||
row.icon.SetPlaceholderIcon(IconName, IconSize)
|
||||
row.icon = rich.NewEventIcon(IconSize)
|
||||
row.icon.Icon.SetPlaceholderIcon(IconName, IconSize)
|
||||
row.icon.Show()
|
||||
rowIconCSS(row.icon)
|
||||
rowIconCSS(row.icon.Icon)
|
||||
|
||||
row.ListBoxRow, _ = gtk.ListBoxRowNew()
|
||||
rowCSS(row.ListBoxRow)
|
||||
|
||||
// TODO: commander button
|
||||
|
||||
row.Servers = NewServers(parent, row)
|
||||
row.Servers = NewServers(row, row)
|
||||
row.Servers.Show()
|
||||
|
||||
// Bind session.* actions into row.
|
||||
row.ActionsMenu = actions.NewMenu("session")
|
||||
row.ActionsMenu.InsertActionGroup(row)
|
||||
|
||||
// Bind right clicks and show a popover menu on such event.
|
||||
row.icon.Connect("button-press-event", func(_ gtk.IWidget, ev *gdk.Event) {
|
||||
if gts.EventIsRightClick(ev) {
|
||||
row.ActionsMenu.Popover(row).Popup()
|
||||
}
|
||||
})
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
func NewAddButton() *gtk.ListBoxRow {
|
||||
img, _ := gtk.ImageNew()
|
||||
img.Show()
|
||||
primitives.SetImageIcon(img, "list-add-symbolic", IconSize/2)
|
||||
|
||||
row, _ := gtk.ListBoxRowNew()
|
||||
row.SetSizeRequest(IconSize, IconSize)
|
||||
row.SetSelectable(false) // activatable though
|
||||
row.Add(img)
|
||||
row.Show()
|
||||
rowCSS(row)
|
||||
|
||||
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.Servers.Reset() // wipe servers
|
||||
// TODO: better visual clue
|
||||
r.icon.Image.SetFromPixbuf(nil) // wipe image
|
||||
r.Servers.Reset() // wipe servers
|
||||
r.ActionsMenu.Reset() // wipe menu items
|
||||
|
||||
// Set a lame placeholder icon.
|
||||
r.icon.Icon.SetPlaceholderIcon("folder-remote-symbolic", IconSize)
|
||||
|
||||
r.Session = nil
|
||||
r.cmder = nil
|
||||
}
|
||||
|
||||
func (r *Row) SessionID() string {
|
||||
return r.sessionID
|
||||
}
|
||||
|
||||
func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
|
||||
return breadcrumb.Try(r.parentcrumb, r.Session.Name().Content)
|
||||
}
|
||||
|
||||
// Activate executes whatever needs to be done. If the row has failed, then this
|
||||
// method will reconnect. If the row is already loaded, then SessionSelected
|
||||
// will be called.
|
||||
|
@ -142,11 +191,14 @@ func (r *Row) SetLoading() {
|
|||
r.Session = nil
|
||||
|
||||
// Reset the icon.
|
||||
r.icon.Image.SetFromPixbuf(nil)
|
||||
r.icon.Icon.Reset()
|
||||
|
||||
// Remove everything from the row, including the icon.
|
||||
primitives.RemoveChildren(r)
|
||||
|
||||
// Remove the failed class.
|
||||
primitives.RemoveClass(r.icon.Icon, "failed")
|
||||
|
||||
// Add a loading circle.
|
||||
spin := spinner.New()
|
||||
spin.SetSizeRequest(IconSize, IconSize)
|
||||
|
@ -170,7 +222,9 @@ func (r *Row) SetFailed(err error) {
|
|||
// Add the icon.
|
||||
r.Add(r.icon)
|
||||
// Set the button to a retry icon.
|
||||
r.icon.SetPlaceholderIcon("view-refresh-symbolic", IconSize)
|
||||
r.icon.Icon.SetPlaceholderIcon("view-refresh-symbolic", IconSize)
|
||||
// Mark the icon as failed.
|
||||
primitives.AddClass(r.icon.Icon, "failed")
|
||||
|
||||
// SetFailed, but also add the callback to retry.
|
||||
// r.Row.SetFailed(err, r.ReconnectSession)
|
||||
|
@ -196,11 +250,11 @@ func (r *Row) SetSession(ses cchat.Session) {
|
|||
r.Session = ses
|
||||
r.sessionID = ses.ID()
|
||||
r.SetTooltipMarkup(markup.Render(ses.Name()))
|
||||
r.icon.SetPlaceholderIcon(IconName, IconSize)
|
||||
r.icon.Icon.SetPlaceholderIcon(IconName, IconSize)
|
||||
|
||||
// If the session has an icon, then use it.
|
||||
if iconer, ok := ses.(cchat.Icon); ok {
|
||||
r.icon.AsyncSetIconer(iconer, "Failed to set session icon")
|
||||
r.icon.Icon.AsyncSetIconer(iconer, "Failed to set session icon")
|
||||
}
|
||||
|
||||
// Update to indicate that we're done.
|
||||
|
@ -208,20 +262,22 @@ func (r *Row) SetSession(ses cchat.Session) {
|
|||
r.SetSensitive(true)
|
||||
r.Add(r.icon)
|
||||
|
||||
// Bind extra menu items before loading. These items won't be clickable
|
||||
// during loading.
|
||||
r.ActionsMenu.Reset()
|
||||
r.ActionsMenu.AddAction("Disconnect", r.DisconnectSession)
|
||||
r.ActionsMenu.AddAction("Remove", r.RemoveSession)
|
||||
|
||||
// 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.svcctrl.Service(), cmd)
|
||||
|
||||
// TODO commander button
|
||||
|
||||
// // 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),
|
||||
// })
|
||||
// Show the command button if the session actually supports the commander.
|
||||
if r.cmder != nil {
|
||||
r.ActionsMenu.AddAction("Command Prompt", r.ShowCommander)
|
||||
}
|
||||
|
||||
// Load all top-level servers now.
|
||||
r.Servers.SetList(ses)
|
||||
|
@ -277,6 +333,9 @@ func (r *Row) DisconnectSession() {
|
|||
// Call the disconnect function from the controller first.
|
||||
r.svcctrl.OnSessionDisconnect(r)
|
||||
|
||||
// Copy the session to avoid data race and allow us to reset.
|
||||
session := r.Session
|
||||
|
||||
// Show visually that we're disconnected first by wiping all servers.
|
||||
r.Reset()
|
||||
|
||||
|
@ -287,26 +346,18 @@ func (r *Row) DisconnectSession() {
|
|||
// 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.")
|
||||
err := errors.Wrap(session.Disconnect(), "Failed to disconnect.")
|
||||
return func() {
|
||||
// Re-enable 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),
|
||||
// })
|
||||
r.ActionsMenu.AddAction("Connect", r.ReconnectSession)
|
||||
r.ActionsMenu.AddAction("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)
|
||||
// }
|
||||
|
||||
// ID returns the session ID.
|
||||
func (r *Row) ID() string {
|
||||
return r.sessionID
|
||||
|
|
|
@ -33,14 +33,11 @@ type Controller interface {
|
|||
AuthenticateSession(*List, *Service)
|
||||
// OnSessionRemove is called to remove a session. This should also clear out
|
||||
// the message view in the parent package.
|
||||
OnSessionRemove(id string)
|
||||
OnSessionRemove(*Service, *session.Row)
|
||||
// OnSessionDisconnect is here to satisfy session's controller.
|
||||
OnSessionDisconnect(id string)
|
||||
OnSessionDisconnect(*Service, *session.Row)
|
||||
}
|
||||
|
||||
// LeftWidth is the width of the left-most services panel.
|
||||
const LeftWidth = IconSize
|
||||
|
||||
type View struct {
|
||||
*gtk.Box // 2 panes, but left-most hard-coded
|
||||
Controller // inherit main controller
|
||||
|
@ -57,7 +54,6 @@ func NewView(ctrller Controller) *View {
|
|||
view := &View{Controller: ctrller}
|
||||
|
||||
view.Services = NewList(view)
|
||||
view.Services.SetSizeRequest(LeftWidth, -1)
|
||||
view.Services.Show()
|
||||
|
||||
// Make a separator.
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
/* Make CSS more consistent across themes */
|
||||
headerbar { padding: 0; }
|
||||
|
||||
popover > *:not(stack):not(button) { margin: 6px; }
|
||||
|
||||
/* Hack to fix the input bar being high in Adwaita */
|
||||
.input-field * { min-height: 0; }
|
|
@ -2,6 +2,7 @@ package ui
|
|||
|
||||
import (
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/icons"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/config/preferences"
|
||||
|
@ -10,14 +11,25 @@ import (
|
|||
"github.com/diamondburned/cchat-gtk/internal/ui/service/auth"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||
"github.com/gotk3/gotk3/gdk"
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/markbates/pkger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Load the local CSS.
|
||||
gts.LoadCSS(pkger.Include("/internal/ui/style.css"))
|
||||
gts.LoadCSS("main", `
|
||||
/* Make CSS more consistent across themes */
|
||||
headerbar { padding-left: 0 }
|
||||
|
||||
.appmenu { margin: 0 18px }
|
||||
|
||||
popover > *:not(stack):not(button) { margin: 6px }
|
||||
|
||||
/* Hack to fix the input bar being high in Adwaita */
|
||||
.input-field * { min-height: 0 }
|
||||
`)
|
||||
}
|
||||
|
||||
// constraints for the left panel
|
||||
|
@ -47,7 +59,7 @@ type App struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ gts.WindowHeaderer = (*App)(nil)
|
||||
_ gts.Window = (*App)(nil)
|
||||
_ service.Controller = (*App)(nil)
|
||||
)
|
||||
|
||||
|
@ -76,24 +88,25 @@ func (app *App) AddService(svc cchat.Service) {
|
|||
}
|
||||
|
||||
// OnSessionRemove resets things before the session is removed.
|
||||
func (app *App) OnSessionRemove(id string) {
|
||||
func (app *App) OnSessionRemove(s *service.Service, r *session.Row) {
|
||||
// Reset the message view if it's what we're showing.
|
||||
if app.window.MessageView.SessionID() == id {
|
||||
if app.window.MessageView.SessionID() == r.SessionID() {
|
||||
app.window.MessageView.Reset()
|
||||
app.header.SetBreadcrumb(nil)
|
||||
app.header.SetBreadcrumber(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) OnSessionDisconnect(id string) {
|
||||
func (app *App) OnSessionDisconnect(s *service.Service, r *session.Row) {
|
||||
// We're basically doing the same thing as removing a session. Check
|
||||
// OnSessionRemove above.
|
||||
app.OnSessionRemove(id)
|
||||
app.OnSessionRemove(s, r)
|
||||
}
|
||||
|
||||
func (app *App) SessionSelected(svc *service.Service, ses *session.Row) {
|
||||
// TODO: restore last message box
|
||||
app.window.MessageView.Reset()
|
||||
app.header.SetBreadcrumb(nil)
|
||||
app.header.SetBreadcrumber(ses)
|
||||
app.header.SetSessionMenu(ses)
|
||||
}
|
||||
|
||||
func (app *App) RowSelected(ses *session.Row, srv *server.ServerRow, smsg cchat.ServerMessage) {
|
||||
|
@ -106,7 +119,7 @@ func (app *App) RowSelected(ses *session.Row, srv *server.ServerRow, smsg cchat.
|
|||
app.lastSelector = srv.SetSelected
|
||||
app.lastSelector(true)
|
||||
|
||||
app.header.SetBreadcrumb(srv.Breadcrumb())
|
||||
app.header.SetBreadcrumber(srv)
|
||||
|
||||
// Disable the server list because we don't want the user to switch around
|
||||
// while we're loading.
|
||||
|
@ -155,3 +168,15 @@ func (app *App) Header() gtk.IWidget {
|
|||
func (app *App) Window() gtk.IWidget {
|
||||
return app.window
|
||||
}
|
||||
|
||||
func (app *App) Icon() *gdk.Pixbuf {
|
||||
return icons.Logo256()
|
||||
}
|
||||
|
||||
func (app *App) Menu() *glib.MenuModel {
|
||||
menu := glib.MenuNew()
|
||||
menu.Append("Preferences", "app.preferences")
|
||||
menu.Append("Quit", "app.quit")
|
||||
|
||||
return &menu.MenuModel
|
||||
}
|
||||
|
|
2
main.go
2
main.go
|
@ -16,7 +16,7 @@ import (
|
|||
var destructor = func() {}
|
||||
|
||||
func main() {
|
||||
gts.Main(func() gts.WindowHeaderer {
|
||||
gts.Main(func() gts.Window {
|
||||
var app = ui.NewApplication()
|
||||
|
||||
// Load all cchat services.
|
||||
|
|
Loading…
Reference in a new issue