mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2025-01-22 01:46:47 +00:00
WIP libhandy support
This commit is contained in:
parent
c2fc4d7512
commit
ba105295eb
2
go.mod
2
go.mod
|
@ -10,7 +10,7 @@ require (
|
|||
github.com/diamondburned/cchat v0.0.49
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20200821041521-647c854d7b5e
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b
|
||||
github.com/diamondburned/handy v0.0.0-20200827040421-5b4a15843526 // indirect
|
||||
github.com/diamondburned/handy v0.0.0-20200827040421-5b4a15843526
|
||||
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/goodsign/monday v1.0.0
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts/throttler"
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"github.com/diamondburned/handy"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gotk3/gotk3/gdk"
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
|
@ -21,12 +22,22 @@ var Args = append([]string{}, os.Args...)
|
|||
|
||||
var App struct {
|
||||
*gtk.Application
|
||||
Window *gtk.ApplicationWindow
|
||||
Header *gtk.HeaderBar
|
||||
|
||||
Window *handy.ApplicationWindow
|
||||
Throttler *throttler.State
|
||||
}
|
||||
|
||||
// Windower is the interface for a window.
|
||||
type Windower interface {
|
||||
gtk.IWidget
|
||||
gtk.IWindow
|
||||
throttler.Connector
|
||||
}
|
||||
|
||||
func AddWindow(w Windower) {
|
||||
App.AddWindow(w)
|
||||
App.Throttler.Connect(w)
|
||||
}
|
||||
|
||||
// Clipboard is initialized on init().
|
||||
var Clipboard *gtk.Clipboard
|
||||
|
||||
|
@ -39,7 +50,8 @@ func NewModalDialog() (*gtk.Dialog, error) {
|
|||
}
|
||||
d.SetModal(true)
|
||||
d.SetTransientFor(App.Window)
|
||||
App.Throttler.Connect(d)
|
||||
|
||||
AddWindow(d)
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
@ -75,72 +87,42 @@ func init() {
|
|||
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 {
|
||||
Window() gtk.IWidget
|
||||
Header() gtk.IWidget
|
||||
type MainApplication interface {
|
||||
gtk.IWidget
|
||||
Menu() *glib.MenuModel
|
||||
Icon() *gdk.Pixbuf
|
||||
Close()
|
||||
}
|
||||
|
||||
func Main(wfn func() Window) {
|
||||
func Main(wfn func() MainApplication) {
|
||||
App.Application.Connect("activate", func() {
|
||||
handy.Init()
|
||||
|
||||
// Load all CSS onto the default screen.
|
||||
loadProviders(getDefaultScreen())
|
||||
|
||||
App.Header, _ = gtk.HeaderBarNew()
|
||||
// Right buttons only.
|
||||
App.Header.SetDecorationLayout(":minimize,close")
|
||||
App.Header.SetShowCloseButton(true)
|
||||
App.Header.SetProperty("spacing", 0)
|
||||
// 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 = handy.ApplicationWindowNew()
|
||||
App.Window.SetDefaultSize(1000, 500)
|
||||
App.Window.SetTitlebar(App.Header)
|
||||
App.Window.Show()
|
||||
AddWindow(&App.Window.Window)
|
||||
|
||||
App.Throttler.Connect(&App.Window.Window)
|
||||
|
||||
// Execute the function later, because we need it to run after
|
||||
// initialization.
|
||||
w := wfn()
|
||||
App.Application.SetAppMenu(w.Menu())
|
||||
|
||||
App.Window.Add(w)
|
||||
App.Window.SetIcon(w.Icon())
|
||||
App.Window.Add(w.Window())
|
||||
App.Window.Show()
|
||||
|
||||
App.Header.Add(w.Header())
|
||||
App.Header.Show()
|
||||
|
||||
// Connect extra actions.
|
||||
AddAppAction("quit", App.Window.Destroy)
|
||||
// App.Application.SetAppMenu(w.Menu())
|
||||
|
||||
// Connect the destructor.
|
||||
App.Window.Connect("destroy", func() {
|
||||
App.Window.Window.Connect("destroy", func() {
|
||||
// Hide the application window.
|
||||
App.Window.Hide()
|
||||
|
||||
|
@ -154,6 +136,9 @@ func Main(wfn func() Window) {
|
|||
w.Close()
|
||||
})
|
||||
})
|
||||
|
||||
// Connect extra actions.
|
||||
AddAppAction("quit", App.Window.Destroy)
|
||||
})
|
||||
|
||||
// Use a special function to run the application. Exit with the appropriate
|
||||
|
|
|
@ -9,43 +9,33 @@ import (
|
|||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/actions"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
|
||||
"github.com/diamondburned/handy"
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/gotk3/gotk3/pango"
|
||||
)
|
||||
|
||||
type header struct {
|
||||
*gtk.Box
|
||||
left *headerLeft // middle-ish
|
||||
right *headerRight
|
||||
menu *glib.Menu
|
||||
}
|
||||
|
||||
func newHeader() *header {
|
||||
left := newHeaderLeft()
|
||||
left.Show()
|
||||
|
||||
right := newHeaderRight()
|
||||
right.Show()
|
||||
|
||||
separator, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
||||
separator.Show()
|
||||
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
box.PackStart(left, false, false, 0)
|
||||
box.PackStart(separator, false, false, 0)
|
||||
box.PackStart(right, true, true, 0)
|
||||
box.Show()
|
||||
|
||||
menu := glib.MenuNew()
|
||||
menu.Append("Preferences", "app.preferences")
|
||||
menu.Append("Quit", "app.quit")
|
||||
|
||||
left := newHeaderLeft()
|
||||
left.appmenu.SetMenuModel(&menu.MenuModel)
|
||||
|
||||
// TODO
|
||||
right := newHeaderRight()
|
||||
|
||||
group := handy.HeaderGroupNew()
|
||||
group.AddHeaderBar(&left.HeaderBar)
|
||||
group.AddHeaderBar(&right.HeaderBar)
|
||||
|
||||
return &header{
|
||||
box,
|
||||
left,
|
||||
right,
|
||||
menu,
|
||||
|
@ -111,60 +101,84 @@ func (a *appMenu) SetSizeRequest(w, h int) {
|
|||
}
|
||||
|
||||
type headerLeft struct {
|
||||
*gtk.Box
|
||||
handy.HeaderBar
|
||||
|
||||
appmenu *appMenu
|
||||
svcname *gtk.Label
|
||||
sesmenu *actions.MenuButton
|
||||
}
|
||||
|
||||
var serviceNameCSS = primitives.PrepareClassCSS("service-name", `
|
||||
.service-name {
|
||||
margin-left: 14px;
|
||||
}
|
||||
`)
|
||||
|
||||
var sessionMenuCSS = primitives.PrepareClassCSS("session-menu", `
|
||||
.session-menu {
|
||||
margin: 0 5px;
|
||||
}
|
||||
`)
|
||||
|
||||
func newHeaderLeft() *headerLeft {
|
||||
appmenu := newAppMenu()
|
||||
appmenu.Show()
|
||||
|
||||
sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
||||
sep.Show()
|
||||
primitives.AddClass(sep, "titlebutton")
|
||||
// sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
||||
// sep.Show()
|
||||
// primitives.AddClass(sep, "titlebutton")
|
||||
|
||||
svcname, _ := gtk.LabelNew("")
|
||||
svcname, _ := gtk.LabelNew("cchat-gtk")
|
||||
svcname.SetXAlign(0)
|
||||
svcname.SetEllipsize(pango.ELLIPSIZE_END)
|
||||
svcname.Show()
|
||||
svcname.SetMarginStart(14)
|
||||
serviceNameCSS(svcname)
|
||||
|
||||
sesmenu := actions.NewMenuButton()
|
||||
sesmenu.Show()
|
||||
sessionMenuCSS(sesmenu)
|
||||
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
box.PackStart(appmenu, false, false, 0)
|
||||
box.PackStart(sep, false, false, 0)
|
||||
box.PackStart(svcname, true, true, 0)
|
||||
box.PackStart(sesmenu, false, false, 5)
|
||||
header := handy.HeaderBarNew()
|
||||
header.SetShowCloseButton(true)
|
||||
header.PackStart(appmenu)
|
||||
// box.PackStart(sep, false, false, 0)
|
||||
header.PackStart(svcname)
|
||||
header.PackStart(sesmenu)
|
||||
|
||||
return &headerLeft{
|
||||
Box: box,
|
||||
appmenu: appmenu,
|
||||
svcname: svcname,
|
||||
sesmenu: sesmenu,
|
||||
HeaderBar: *header,
|
||||
appmenu: appmenu,
|
||||
svcname: svcname,
|
||||
sesmenu: sesmenu,
|
||||
}
|
||||
}
|
||||
|
||||
type headerRight struct {
|
||||
*gtk.Box
|
||||
handy.HeaderBar
|
||||
|
||||
breadcrumb *gtk.Label
|
||||
}
|
||||
|
||||
var rightBreadcrumbCSS = primitives.PrepareClassCSS("right-breadcrumb", `
|
||||
.right-breadcrumb {
|
||||
margin: 0 14px;
|
||||
}
|
||||
`)
|
||||
|
||||
func newHeaderRight() *headerRight {
|
||||
bc, _ := gtk.LabelNew(BreadcrumbSlash)
|
||||
bc.SetUseMarkup(true)
|
||||
bc.SetXAlign(0.0)
|
||||
bc.Show()
|
||||
rightBreadcrumbCSS(bc)
|
||||
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
box.PackStart(bc, true, true, 14)
|
||||
box.Show()
|
||||
header := handy.HeaderBarNew()
|
||||
header.SetShowCloseButton(true)
|
||||
header.PackStart(bc)
|
||||
header.Show()
|
||||
|
||||
return &headerRight{
|
||||
Box: box,
|
||||
HeaderBar: *header,
|
||||
breadcrumb: bc,
|
||||
}
|
||||
}
|
||||
|
|
64
internal/ui/messages/header.go
Normal file
64
internal/ui/messages/header.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package messages
|
||||
|
||||
import (
|
||||
"html"
|
||||
"strings"
|
||||
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
|
||||
"github.com/diamondburned/handy"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
// const BreadcrumbSlash = `<span rise="-1024" size="x-large">❭</span>`
|
||||
const BreadcrumbSlash = " 〉"
|
||||
|
||||
type Header struct {
|
||||
handy.HeaderBar
|
||||
|
||||
Breadcrumb *gtk.Label
|
||||
}
|
||||
|
||||
var rightBreadcrumbCSS = primitives.PrepareClassCSS("right-breadcrumb", `
|
||||
.right-breadcrumb {
|
||||
margin: 0 14px;
|
||||
}
|
||||
`)
|
||||
|
||||
func NewHeader() *Header {
|
||||
bc, _ := gtk.LabelNew(BreadcrumbSlash)
|
||||
bc.SetUseMarkup(true)
|
||||
bc.SetXAlign(0.0)
|
||||
bc.Show()
|
||||
rightBreadcrumbCSS(bc)
|
||||
|
||||
header := handy.HeaderBarNew()
|
||||
header.SetShowCloseButton(true)
|
||||
header.PackStart(bc)
|
||||
header.Show()
|
||||
|
||||
return &Header{
|
||||
HeaderBar: *header,
|
||||
Breadcrumb: bc,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Header) Reset() {
|
||||
h.SetBreadcrumber(nil)
|
||||
}
|
||||
|
||||
func (h *Header) SetBreadcrumber(b traverse.Breadcrumber) {
|
||||
if b == nil {
|
||||
h.Breadcrumb.SetText("")
|
||||
return
|
||||
}
|
||||
|
||||
var crumb = b.Breadcrumb()
|
||||
for i := range crumb {
|
||||
crumb[i] = html.EscapeString(crumb[i])
|
||||
}
|
||||
|
||||
h.Breadcrumb.SetMarkup(
|
||||
BreadcrumbSlash + " " + strings.Join(crumb, " "+BreadcrumbSlash+" "),
|
||||
)
|
||||
}
|
|
@ -49,8 +49,12 @@ type Controller interface {
|
|||
}
|
||||
|
||||
type View struct {
|
||||
*sadface.FaceView
|
||||
Grid *gtk.Grid
|
||||
*gtk.Box
|
||||
|
||||
Header *Header
|
||||
|
||||
FaceView *sadface.FaceView
|
||||
Grid *gtk.Grid
|
||||
|
||||
Scroller *autoscroll.ScrolledWindow
|
||||
InputView *input.InputView
|
||||
|
@ -126,6 +130,15 @@ func NewView(c Controller) *View {
|
|||
logo.Show()
|
||||
|
||||
view.FaceView = sadface.New(view.Grid, logo)
|
||||
view.FaceView.Show()
|
||||
|
||||
view.Header = NewHeader()
|
||||
view.Header.Show()
|
||||
|
||||
view.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
view.Box.PackStart(view.Header, false, false, 0)
|
||||
view.Box.PackStart(view.FaceView, true, true, 0)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
|
@ -153,6 +166,7 @@ func (v *View) createMessageContainer() {
|
|||
func (v *View) Bottomed() bool { return v.Scroller.Bottomed }
|
||||
|
||||
func (v *View) Reset() {
|
||||
v.Header.Reset() // Reset the header.
|
||||
v.state.Reset() // Reset the state variables.
|
||||
v.Typing.Reset() // Reset the typing state.
|
||||
v.InputView.Reset() // Reset the input.
|
||||
|
@ -194,7 +208,7 @@ func (v *View) JoinServer(session cchat.Session, server ServerMessage) {
|
|||
err = errors.Wrap(err, "Failed to join server")
|
||||
// Even if we're erroring out, we're running the done() callback
|
||||
// anyway.
|
||||
return func() { v.ctrl.OnMessageDone(); v.SetError(err) }, err
|
||||
return func() { v.ctrl.OnMessageDone(); v.FaceView.SetError(err) }, err
|
||||
}
|
||||
|
||||
return func() {
|
||||
|
|
|
@ -14,6 +14,7 @@ type MenuButton struct {
|
|||
|
||||
func NewMenuButton() *MenuButton {
|
||||
b, _ := gtk.MenuButtonNew()
|
||||
b.SetVAlign(gtk.ALIGN_CENTER)
|
||||
b.SetSensitive(false)
|
||||
|
||||
return &MenuButton{
|
||||
|
|
137
internal/ui/service/header.go
Normal file
137
internal/ui/service/header.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"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/service/session"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
|
||||
"github.com/diamondburned/handy"
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/gotk3/gotk3/pango"
|
||||
)
|
||||
|
||||
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 Header struct {
|
||||
handy.HeaderBar
|
||||
|
||||
MenuModel *glib.MenuModel
|
||||
|
||||
AppMenu *AppMenu
|
||||
SvcName *gtk.Label
|
||||
SesMenu *actions.MenuButton
|
||||
}
|
||||
|
||||
var serviceNameCSS = primitives.PrepareClassCSS("service-name", `
|
||||
.service-name {
|
||||
margin-left: 14px;
|
||||
}
|
||||
`)
|
||||
|
||||
var sessionMenuCSS = primitives.PrepareClassCSS("session-menu", `
|
||||
.session-menu {
|
||||
margin: 0 5px;
|
||||
}
|
||||
`)
|
||||
|
||||
func NewHeader() *Header {
|
||||
menu := glib.MenuNew()
|
||||
menu.Append("Preferences", "app.preferences")
|
||||
menu.Append("Quit", "app.quit")
|
||||
|
||||
appmenu := NewAppMenu()
|
||||
appmenu.Show()
|
||||
appmenu.SetMenuModel(&menu.MenuModel)
|
||||
|
||||
sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
||||
sep.Show()
|
||||
primitives.AddClass(sep, "titlebutton")
|
||||
|
||||
svcname, _ := gtk.LabelNew("cchat-gtk")
|
||||
svcname.SetXAlign(0)
|
||||
svcname.SetEllipsize(pango.ELLIPSIZE_END)
|
||||
svcname.Show()
|
||||
serviceNameCSS(svcname)
|
||||
|
||||
sesmenu := actions.NewMenuButton()
|
||||
sesmenu.Show()
|
||||
sessionMenuCSS(sesmenu)
|
||||
|
||||
header := handy.HeaderBarNew()
|
||||
header.SetProperty("spacing", 0)
|
||||
header.SetShowCloseButton(true)
|
||||
header.PackStart(appmenu)
|
||||
header.PackStart(sep)
|
||||
header.PackStart(svcname)
|
||||
header.PackStart(sesmenu)
|
||||
|
||||
// Hack to hide the title.
|
||||
b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
header.SetCustomTitle(b)
|
||||
|
||||
return &Header{
|
||||
HeaderBar: *header,
|
||||
MenuModel: &menu.MenuModel,
|
||||
AppMenu: appmenu,
|
||||
SvcName: svcname,
|
||||
SesMenu: sesmenu,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Header) SetBreadcrumber(b traverse.Breadcrumber) {
|
||||
if b == nil {
|
||||
h.SvcName.SetText("cchat-gtk")
|
||||
return
|
||||
}
|
||||
|
||||
if crumb := b.Breadcrumb(); len(crumb) > 0 {
|
||||
h.SvcName.SetText(crumb[0])
|
||||
} else {
|
||||
h.SvcName.SetText("")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Header) SetSessionMenu(s *session.Row) {
|
||||
h.SesMenu.Bind(s.ActionsMenu)
|
||||
}
|
||||
|
||||
type sizeBinder interface {
|
||||
primitives.Connector
|
||||
GetAllocatedWidth() int
|
||||
}
|
||||
|
||||
var _ sizeBinder = (*List)(nil)
|
||||
|
||||
func (h *Header) AppMenuBindSize(c sizeBinder) {
|
||||
c.Connect("size-allocate", func() {
|
||||
h.AppMenu.SetSizeRequest(c.GetAllocatedWidth(), -1)
|
||||
})
|
||||
}
|
|
@ -45,7 +45,6 @@ func NewList(vctl ViewController) *List {
|
|||
svlist.ListBox, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
svlist.ListBox.Show()
|
||||
svlist.ListBox.SetHAlign(gtk.ALIGN_START)
|
||||
svlist.ListBox.SetHExpand(false)
|
||||
listCSS(svlist.ListBox)
|
||||
|
||||
svlist.ScrolledWindow, _ = gtk.ScrolledWindowNew(nil, nil)
|
||||
|
|
|
@ -8,13 +8,14 @@ import (
|
|||
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/spinner"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/gotk3/gotk3/pango"
|
||||
)
|
||||
|
||||
const FaceSize = 48 // gtk.ICON_SIZE_DIALOG
|
||||
const ListWidth = 200
|
||||
|
||||
// Servers wraps around a list of servers inherited from Children. It's the
|
||||
// container that's displayed on the right of the service sidebar.
|
||||
|
@ -34,7 +35,7 @@ var toplevelCSS = primitives.PrepareClassCSS("top-level", `
|
|||
func NewServers(p traverse.Breadcrumber, ctrl server.Controller) *Servers {
|
||||
c := server.NewChildren(p, ctrl)
|
||||
c.SetMarginStart(0) // children is top level; there is no main row
|
||||
c.SetHExpand(true) // fill
|
||||
c.SetVExpand(true)
|
||||
c.Show()
|
||||
toplevelCSS(c)
|
||||
|
||||
|
|
|
@ -23,8 +23,12 @@ type Controller interface {
|
|||
}
|
||||
|
||||
type View struct {
|
||||
*gtk.Box // 2 panes, but left-most hard-coded
|
||||
Controller // inherit main controller
|
||||
*gtk.Box
|
||||
|
||||
Header *Header
|
||||
|
||||
BottomPane *gtk.Box // 2 panes, but left-most hard-coded
|
||||
Controller // inherit main controller
|
||||
|
||||
Services *List
|
||||
ServerView *gtk.ScrolledWindow
|
||||
|
@ -40,9 +44,9 @@ func NewView(ctrller Controller) *View {
|
|||
view.Services = NewList(view)
|
||||
view.Services.Show()
|
||||
|
||||
// Make a separator.
|
||||
// sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
||||
// sep.Show()
|
||||
view.Header = NewHeader()
|
||||
view.Header.AppMenuBindSize(view.Services)
|
||||
view.Header.Show()
|
||||
|
||||
// Make a stack for the middle panel.
|
||||
view.ServerStack = singlestack.NewStack()
|
||||
|
@ -50,18 +54,21 @@ func NewView(ctrller Controller) *View {
|
|||
view.ServerStack.SetTransitionDuration(50)
|
||||
view.ServerStack.SetTransitionType(gtk.STACK_TRANSITION_TYPE_CROSSFADE)
|
||||
view.ServerStack.SetHomogeneous(true)
|
||||
view.ServerStack.SetHExpand(true)
|
||||
view.ServerStack.Show()
|
||||
|
||||
view.ServerView, _ = gtk.ScrolledWindowNew(nil, nil)
|
||||
view.ServerView.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
view.ServerView.SetHExpand(true)
|
||||
view.ServerView.Add(view.ServerStack)
|
||||
view.ServerView.Show()
|
||||
|
||||
view.Box, _ = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
view.Box.PackStart(view.Services, false, false, 0)
|
||||
view.Box.PackStart(view.ServerView, true, true, 0)
|
||||
view.BottomPane, _ = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||
view.BottomPane.PackStart(view.Services, false, false, 0)
|
||||
view.BottomPane.PackStart(view.ServerView, true, true, 0)
|
||||
view.BottomPane.Show()
|
||||
|
||||
view.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
view.Box.PackStart(view.Header, false, false, 0)
|
||||
view.Box.PackStart(view.BottomPane, true, true, 0)
|
||||
view.Box.Show()
|
||||
|
||||
return view
|
||||
|
@ -88,6 +95,24 @@ func (v *View) SessionSelected(svc *Service, srow *session.Row) {
|
|||
// reference anyway. In fact, cchat REQUIRES us to do so.
|
||||
v.ServerStack.SetVisibleChild(srow.Servers)
|
||||
|
||||
// Call the controller's method.
|
||||
v.Header.SetSessionMenu(srow)
|
||||
v.Header.SetBreadcrumber(srow)
|
||||
v.Controller.SessionSelected(svc, srow)
|
||||
}
|
||||
|
||||
// RowSelected is called when a row is selected. It updates the header then
|
||||
// calls the application's RowSelected method.
|
||||
func (v *View) RowSelected(srow *session.Row, srv *server.ServerRow, smsg cchat.ServerMessage) {
|
||||
v.Header.SetBreadcrumber(srv)
|
||||
v.Controller.RowSelected(srow, srv, smsg)
|
||||
}
|
||||
|
||||
func (v *View) OnSessionRemove(s *Service, r *session.Row) {
|
||||
v.Header.SetBreadcrumber(nil)
|
||||
v.Controller.OnSessionRemove(s, r)
|
||||
}
|
||||
|
||||
func (v *View) OnSessionDisconnect(s *Service, r *session.Row) {
|
||||
v.Header.SetBreadcrumber(nil)
|
||||
v.Controller.OnSessionDisconnect(s, r)
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ 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/diamondburned/handy"
|
||||
"github.com/gotk3/gotk3/gdk"
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -51,36 +51,40 @@ func clamp(n, min, max int) int {
|
|||
}
|
||||
|
||||
type App struct {
|
||||
window *window
|
||||
header *header
|
||||
handy.Leaflet
|
||||
HeaderGroup *handy.HeaderGroup
|
||||
|
||||
Services *service.View
|
||||
MessageView *messages.View
|
||||
|
||||
// used to keep track of what row to disconnect before switching
|
||||
lastSelector func(bool)
|
||||
}
|
||||
|
||||
var (
|
||||
_ gts.Window = (*App)(nil)
|
||||
_ service.Controller = (*App)(nil)
|
||||
_ gts.MainApplication = (*App)(nil)
|
||||
_ service.Controller = (*App)(nil)
|
||||
_ messages.Controller = (*App)(nil)
|
||||
)
|
||||
|
||||
func NewApplication() *App {
|
||||
app := &App{}
|
||||
app.window = newWindow(app)
|
||||
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)
|
||||
})
|
||||
app.Services = service.NewView(app)
|
||||
app.Services.SetSizeRequest(leftMinWidth, -1)
|
||||
app.Services.Show()
|
||||
|
||||
// Resize the left-side header w/ the left-side pane.
|
||||
app.window.Services.ServerView.Connect("size-allocate", func() {
|
||||
// Get the current width of the left sidebar.
|
||||
width := app.window.GetPosition()
|
||||
// Set the left-side header's size.
|
||||
app.header.left.SetSizeRequest(width, -1)
|
||||
})
|
||||
app.MessageView = messages.NewView(app)
|
||||
app.MessageView.Show()
|
||||
|
||||
app.HeaderGroup = handy.HeaderGroupNew()
|
||||
app.HeaderGroup.AddHeaderBar(&app.Services.Header.HeaderBar)
|
||||
app.HeaderGroup.AddHeaderBar(&app.MessageView.Header.HeaderBar)
|
||||
|
||||
app.Leaflet = *handy.LeafletNew()
|
||||
app.Leaflet.Add(app.Services)
|
||||
app.Leaflet.Add(app.MessageView)
|
||||
app.Leaflet.Show()
|
||||
|
||||
// Bind the preferences action for our GAction button in the header popover.
|
||||
// The action name for this is "app.preferences".
|
||||
|
@ -89,16 +93,17 @@ func NewApplication() *App {
|
|||
return app
|
||||
}
|
||||
|
||||
// Services methods.
|
||||
|
||||
func (app *App) AddService(svc cchat.Service) {
|
||||
app.window.Services.AddService(svc)
|
||||
app.Services.AddService(svc)
|
||||
}
|
||||
|
||||
// OnSessionRemove resets things before the session is removed.
|
||||
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() == r.ID() {
|
||||
app.window.MessageView.Reset()
|
||||
app.header.SetBreadcrumber(nil)
|
||||
if app.MessageView.SessionID() == r.ID() {
|
||||
app.MessageView.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,9 +124,7 @@ func (app *App) SessionSelected(svc *service.Service, ses *session.Row) {
|
|||
// reset view when setservers top level called
|
||||
|
||||
// TODO: restore last message box
|
||||
app.window.MessageView.Reset()
|
||||
app.header.SetBreadcrumber(ses)
|
||||
app.header.SetSessionMenu(ses)
|
||||
app.MessageView.Reset()
|
||||
}
|
||||
|
||||
func (app *App) RowSelected(ses *session.Row, srv *server.ServerRow, smsg cchat.ServerMessage) {
|
||||
|
@ -134,12 +137,12 @@ func (app *App) RowSelected(ses *session.Row, srv *server.ServerRow, smsg cchat.
|
|||
app.lastSelector = srv.SetSelected
|
||||
app.lastSelector(true)
|
||||
|
||||
app.header.SetBreadcrumber(srv)
|
||||
|
||||
// Assert that server is also a list, then join the server.
|
||||
app.window.MessageView.JoinServer(ses.Session, smsg.(messages.ServerMessage))
|
||||
app.MessageView.JoinServer(ses.Session, smsg.(messages.ServerMessage))
|
||||
}
|
||||
|
||||
// MessageView methods.
|
||||
|
||||
func (app *App) OnMessageBusy() {
|
||||
// Disable the server list because we don't want the user to switch around
|
||||
// while we're loading.
|
||||
|
@ -163,7 +166,7 @@ func (app *App) Close() {
|
|||
// Disconnect everything. This blocks the main thread, so by the time we're
|
||||
// done, the application would exit immediately. There's no need to update
|
||||
// the GUI.
|
||||
for _, s := range app.window.AllServices() {
|
||||
for _, s := range app.Services.Services.Services {
|
||||
var service = s.Service().Name()
|
||||
|
||||
for _, session := range s.BodyList.Sessions() {
|
||||
|
@ -180,18 +183,10 @@ func (app *App) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
func (app *App) Header() gtk.IWidget {
|
||||
return app.header
|
||||
}
|
||||
|
||||
func (app *App) Window() gtk.IWidget {
|
||||
return app.window
|
||||
}
|
||||
|
||||
func (app *App) Icon() *gdk.Pixbuf {
|
||||
return icons.Logo256(0)
|
||||
}
|
||||
|
||||
func (app *App) Menu() *glib.MenuModel {
|
||||
return &app.header.menu.MenuModel
|
||||
return app.Services.Header.MenuModel
|
||||
}
|
||||
|
|
|
@ -1,39 +1 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
type window struct {
|
||||
*gtk.Paned
|
||||
Services *service.View
|
||||
MessageView *messages.View
|
||||
}
|
||||
|
||||
type Controller interface {
|
||||
service.Controller
|
||||
messages.Controller
|
||||
}
|
||||
|
||||
func newWindow(mainctl Controller) *window {
|
||||
services := service.NewView(mainctl)
|
||||
services.SetSizeRequest(leftMinWidth, -1)
|
||||
services.Show()
|
||||
|
||||
mesgview := messages.NewView(mainctl)
|
||||
mesgview.Show()
|
||||
|
||||
pane, _ := gtk.PanedNew(gtk.ORIENTATION_HORIZONTAL)
|
||||
pane.Pack1(services, false, false)
|
||||
pane.Pack2(mesgview, true, false)
|
||||
pane.SetPosition(leftCurrentWidth)
|
||||
pane.Show()
|
||||
|
||||
return &window{pane, services, mesgview}
|
||||
}
|
||||
|
||||
func (w *window) AllServices() []*service.Service {
|
||||
return w.Services.Services.Services
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue