WIP libhandy support

This commit is contained in:
diamondburned 2020-08-28 00:16:03 -07:00
parent c2fc4d7512
commit ba105295eb
13 changed files with 383 additions and 186 deletions

2
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/diamondburned/cchat v0.0.49 github.com/diamondburned/cchat v0.0.49
github.com/diamondburned/cchat-discord v0.0.0-20200821041521-647c854d7b5e github.com/diamondburned/cchat-discord v0.0.0-20200821041521-647c854d7b5e
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b 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/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/goodsign/monday v1.0.0 github.com/goodsign/monday v1.0.0

View File

@ -8,6 +8,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/gts/throttler" "github.com/diamondburned/cchat-gtk/internal/gts/throttler"
"github.com/diamondburned/cchat-gtk/internal/log" "github.com/diamondburned/cchat-gtk/internal/log"
"github.com/diamondburned/handy"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/gotk3/gotk3/gdk" "github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/glib"
@ -21,12 +22,22 @@ var Args = append([]string{}, os.Args...)
var App struct { var App struct {
*gtk.Application *gtk.Application
Window *gtk.ApplicationWindow Window *handy.ApplicationWindow
Header *gtk.HeaderBar
Throttler *throttler.State 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(). // Clipboard is initialized on init().
var Clipboard *gtk.Clipboard var Clipboard *gtk.Clipboard
@ -39,7 +50,8 @@ func NewModalDialog() (*gtk.Dialog, error) {
} }
d.SetModal(true) d.SetModal(true)
d.SetTransientFor(App.Window) d.SetTransientFor(App.Window)
App.Throttler.Connect(d)
AddWindow(d)
return d, nil return d, nil
} }
@ -75,72 +87,42 @@ func init() {
App.Throttler = throttler.Bind(App.Application) App.Throttler = throttler.Bind(App.Application)
} }
// // AppMenuWidget returns the box that holds the app menu. type MainApplication interface {
// func AppMenuWidget() (widget *gtk.Widget) { gtk.IWidget
// 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
Menu() *glib.MenuModel Menu() *glib.MenuModel
Icon() *gdk.Pixbuf Icon() *gdk.Pixbuf
Close() Close()
} }
func Main(wfn func() Window) { func Main(wfn func() MainApplication) {
App.Application.Connect("activate", func() { App.Application.Connect("activate", func() {
handy.Init()
// Load all CSS onto the default screen. // Load all CSS onto the default screen.
loadProviders(getDefaultScreen()) loadProviders(getDefaultScreen())
App.Header, _ = gtk.HeaderBarNew() // App.Header, _ = gtk.HeaderBarNew()
// Right buttons only. // // Right buttons only.
App.Header.SetDecorationLayout(":minimize,close") // App.Header.SetDecorationLayout(":minimize,close")
App.Header.SetShowCloseButton(true) // App.Header.SetShowCloseButton(true)
App.Header.SetProperty("spacing", 0) // App.Header.SetProperty("spacing", 0)
b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) App.Window = handy.ApplicationWindowNew()
App.Header.SetCustomTitle(b)
App.Window, _ = gtk.ApplicationWindowNew(App.Application)
App.Window.SetDefaultSize(1000, 500) 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 // Execute the function later, because we need it to run after
// initialization. // initialization.
w := wfn() w := wfn()
App.Application.SetAppMenu(w.Menu()) App.Window.Add(w)
App.Window.SetIcon(w.Icon()) App.Window.SetIcon(w.Icon())
App.Window.Add(w.Window()) // App.Application.SetAppMenu(w.Menu())
App.Window.Show()
App.Header.Add(w.Header())
App.Header.Show()
// Connect extra actions.
AddAppAction("quit", App.Window.Destroy)
// Connect the destructor. // Connect the destructor.
App.Window.Connect("destroy", func() { App.Window.Window.Connect("destroy", func() {
// Hide the application window. // Hide the application window.
App.Window.Hide() App.Window.Hide()
@ -154,6 +136,9 @@ func Main(wfn func() Window) {
w.Close() w.Close()
}) })
}) })
// Connect extra actions.
AddAppAction("quit", App.Window.Destroy)
}) })
// Use a special function to run the application. Exit with the appropriate // Use a special function to run the application. Exit with the appropriate

View File

@ -9,43 +9,33 @@ import (
"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/session" "github.com/diamondburned/cchat-gtk/internal/ui/service/session"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse" "github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
"github.com/diamondburned/handy"
"github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk" "github.com/gotk3/gotk3/gtk"
"github.com/gotk3/gotk3/pango" "github.com/gotk3/gotk3/pango"
) )
type header struct { type header struct {
*gtk.Box
left *headerLeft // middle-ish left *headerLeft // middle-ish
right *headerRight right *headerRight
menu *glib.Menu menu *glib.Menu
} }
func newHeader() *header { 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 := glib.MenuNew()
menu.Append("Preferences", "app.preferences") menu.Append("Preferences", "app.preferences")
menu.Append("Quit", "app.quit") menu.Append("Quit", "app.quit")
left := newHeaderLeft()
left.appmenu.SetMenuModel(&menu.MenuModel) left.appmenu.SetMenuModel(&menu.MenuModel)
// TODO right := newHeaderRight()
group := handy.HeaderGroupNew()
group.AddHeaderBar(&left.HeaderBar)
group.AddHeaderBar(&right.HeaderBar)
return &header{ return &header{
box,
left, left,
right, right,
menu, menu,
@ -111,60 +101,84 @@ func (a *appMenu) SetSizeRequest(w, h int) {
} }
type headerLeft struct { type headerLeft struct {
*gtk.Box handy.HeaderBar
appmenu *appMenu appmenu *appMenu
svcname *gtk.Label svcname *gtk.Label
sesmenu *actions.MenuButton 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 { func newHeaderLeft() *headerLeft {
appmenu := newAppMenu() appmenu := newAppMenu()
appmenu.Show() appmenu.Show()
sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL) // sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
sep.Show() // sep.Show()
primitives.AddClass(sep, "titlebutton") // primitives.AddClass(sep, "titlebutton")
svcname, _ := gtk.LabelNew("") svcname, _ := gtk.LabelNew("cchat-gtk")
svcname.SetXAlign(0) svcname.SetXAlign(0)
svcname.SetEllipsize(pango.ELLIPSIZE_END) svcname.SetEllipsize(pango.ELLIPSIZE_END)
svcname.Show() svcname.Show()
svcname.SetMarginStart(14) serviceNameCSS(svcname)
sesmenu := actions.NewMenuButton() sesmenu := actions.NewMenuButton()
sesmenu.Show() sesmenu.Show()
sessionMenuCSS(sesmenu)
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) header := handy.HeaderBarNew()
box.PackStart(appmenu, false, false, 0) header.SetShowCloseButton(true)
box.PackStart(sep, false, false, 0) header.PackStart(appmenu)
box.PackStart(svcname, true, true, 0) // box.PackStart(sep, false, false, 0)
box.PackStart(sesmenu, false, false, 5) header.PackStart(svcname)
header.PackStart(sesmenu)
return &headerLeft{ return &headerLeft{
Box: box, HeaderBar: *header,
appmenu: appmenu, appmenu: appmenu,
svcname: svcname, svcname: svcname,
sesmenu: sesmenu, sesmenu: sesmenu,
} }
} }
type headerRight struct { type headerRight struct {
*gtk.Box handy.HeaderBar
breadcrumb *gtk.Label breadcrumb *gtk.Label
} }
var rightBreadcrumbCSS = primitives.PrepareClassCSS("right-breadcrumb", `
.right-breadcrumb {
margin: 0 14px;
}
`)
func newHeaderRight() *headerRight { func newHeaderRight() *headerRight {
bc, _ := gtk.LabelNew(BreadcrumbSlash) bc, _ := gtk.LabelNew(BreadcrumbSlash)
bc.SetUseMarkup(true) bc.SetUseMarkup(true)
bc.SetXAlign(0.0) bc.SetXAlign(0.0)
bc.Show() bc.Show()
rightBreadcrumbCSS(bc)
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) header := handy.HeaderBarNew()
box.PackStart(bc, true, true, 14) header.SetShowCloseButton(true)
box.Show() header.PackStart(bc)
header.Show()
return &headerRight{ return &headerRight{
Box: box, HeaderBar: *header,
breadcrumb: bc, breadcrumb: bc,
} }
} }

View 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+" "),
)
}

View File

@ -49,8 +49,12 @@ type Controller interface {
} }
type View struct { type View struct {
*sadface.FaceView *gtk.Box
Grid *gtk.Grid
Header *Header
FaceView *sadface.FaceView
Grid *gtk.Grid
Scroller *autoscroll.ScrolledWindow Scroller *autoscroll.ScrolledWindow
InputView *input.InputView InputView *input.InputView
@ -126,6 +130,15 @@ func NewView(c Controller) *View {
logo.Show() logo.Show()
view.FaceView = sadface.New(view.Grid, logo) 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 return view
} }
@ -153,6 +166,7 @@ func (v *View) createMessageContainer() {
func (v *View) Bottomed() bool { return v.Scroller.Bottomed } func (v *View) Bottomed() bool { return v.Scroller.Bottomed }
func (v *View) Reset() { func (v *View) Reset() {
v.Header.Reset() // Reset the header.
v.state.Reset() // Reset the state variables. v.state.Reset() // Reset the state variables.
v.Typing.Reset() // Reset the typing state. v.Typing.Reset() // Reset the typing state.
v.InputView.Reset() // Reset the input. 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") err = errors.Wrap(err, "Failed to join server")
// Even if we're erroring out, we're running the done() callback // Even if we're erroring out, we're running the done() callback
// anyway. // anyway.
return func() { v.ctrl.OnMessageDone(); v.SetError(err) }, err return func() { v.ctrl.OnMessageDone(); v.FaceView.SetError(err) }, err
} }
return func() { return func() {

View File

@ -14,6 +14,7 @@ type MenuButton struct {
func NewMenuButton() *MenuButton { func NewMenuButton() *MenuButton {
b, _ := gtk.MenuButtonNew() b, _ := gtk.MenuButtonNew()
b.SetVAlign(gtk.ALIGN_CENTER)
b.SetSensitive(false) b.SetSensitive(false)
return &MenuButton{ return &MenuButton{

View 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)
})
}

View File

@ -45,7 +45,6 @@ func NewList(vctl ViewController) *List {
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.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)

View File

@ -8,13 +8,14 @@ import (
"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/spinner" "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"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
"github.com/gotk3/gotk3/gtk" "github.com/gotk3/gotk3/gtk"
"github.com/gotk3/gotk3/pango" "github.com/gotk3/gotk3/pango"
) )
const FaceSize = 48 // gtk.ICON_SIZE_DIALOG const FaceSize = 48 // gtk.ICON_SIZE_DIALOG
const ListWidth = 200
// Servers wraps around a list of servers inherited from Children. It's the // Servers wraps around a list of servers inherited from Children. It's the
// container that's displayed on the right of the service sidebar. // 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 { func NewServers(p traverse.Breadcrumber, ctrl server.Controller) *Servers {
c := server.NewChildren(p, ctrl) c := server.NewChildren(p, ctrl)
c.SetMarginStart(0) // children is top level; there is no main row c.SetMarginStart(0) // children is top level; there is no main row
c.SetHExpand(true) // fill c.SetVExpand(true)
c.Show() c.Show()
toplevelCSS(c) toplevelCSS(c)

View File

@ -23,8 +23,12 @@ type Controller interface {
} }
type View struct { type View struct {
*gtk.Box // 2 panes, but left-most hard-coded *gtk.Box
Controller // inherit main controller
Header *Header
BottomPane *gtk.Box // 2 panes, but left-most hard-coded
Controller // inherit main controller
Services *List Services *List
ServerView *gtk.ScrolledWindow ServerView *gtk.ScrolledWindow
@ -40,9 +44,9 @@ func NewView(ctrller Controller) *View {
view.Services = NewList(view) view.Services = NewList(view)
view.Services.Show() view.Services.Show()
// Make a separator. view.Header = NewHeader()
// sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL) view.Header.AppMenuBindSize(view.Services)
// sep.Show() view.Header.Show()
// Make a stack for the middle panel. // Make a stack for the middle panel.
view.ServerStack = singlestack.NewStack() view.ServerStack = singlestack.NewStack()
@ -50,18 +54,21 @@ 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.BottomPane, _ = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
view.Box.PackStart(view.Services, false, false, 0) view.BottomPane.PackStart(view.Services, false, false, 0)
view.Box.PackStart(view.ServerView, true, true, 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() view.Box.Show()
return view 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. // reference anyway. In fact, cchat REQUIRES us to do so.
v.ServerStack.SetVisibleChild(srow.Servers) v.ServerStack.SetVisibleChild(srow.Servers)
// Call the controller's method. v.Header.SetSessionMenu(srow)
v.Header.SetBreadcrumber(srow)
v.Controller.SessionSelected(svc, 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)
}

View File

@ -11,9 +11,9 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/service/auth" "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"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server" "github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
"github.com/diamondburned/handy"
"github.com/gotk3/gotk3/gdk" "github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -51,36 +51,40 @@ func clamp(n, min, max int) int {
} }
type App struct { type App struct {
window *window handy.Leaflet
header *header HeaderGroup *handy.HeaderGroup
Services *service.View
MessageView *messages.View
// used to keep track of what row to disconnect before switching // used to keep track of what row to disconnect before switching
lastSelector func(bool) lastSelector func(bool)
} }
var ( var (
_ gts.Window = (*App)(nil) _ gts.MainApplication = (*App)(nil)
_ service.Controller = (*App)(nil) _ service.Controller = (*App)(nil)
_ messages.Controller = (*App)(nil)
) )
func NewApplication() *App { func NewApplication() *App {
app := &App{} app := &App{}
app.window = newWindow(app)
app.header = newHeader()
// Resize the app icon with the left-most sidebar. app.Services = service.NewView(app)
services := app.window.Services.Services app.Services.SetSizeRequest(leftMinWidth, -1)
services.Connect("size-allocate", func() { app.Services.Show()
app.header.left.appmenu.SetSizeRequest(services.GetAllocatedWidth(), -1)
})
// Resize the left-side header w/ the left-side pane. app.MessageView = messages.NewView(app)
app.window.Services.ServerView.Connect("size-allocate", func() { app.MessageView.Show()
// Get the current width of the left sidebar.
width := app.window.GetPosition() app.HeaderGroup = handy.HeaderGroupNew()
// Set the left-side header's size. app.HeaderGroup.AddHeaderBar(&app.Services.Header.HeaderBar)
app.header.left.SetSizeRequest(width, -1) 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. // Bind the preferences action for our GAction button in the header popover.
// The action name for this is "app.preferences". // The action name for this is "app.preferences".
@ -89,16 +93,17 @@ func NewApplication() *App {
return app return app
} }
// Services methods.
func (app *App) AddService(svc cchat.Service) { func (app *App) AddService(svc cchat.Service) {
app.window.Services.AddService(svc) app.Services.AddService(svc)
} }
// 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.ID() { if app.MessageView.SessionID() == r.ID() {
app.window.MessageView.Reset() app.MessageView.Reset()
app.header.SetBreadcrumber(nil)
} }
} }
@ -119,9 +124,7 @@ func (app *App) SessionSelected(svc *service.Service, ses *session.Row) {
// reset view when setservers top level called // reset view when setservers top level called
// TODO: restore last message box // TODO: restore last message box
app.window.MessageView.Reset() app.MessageView.Reset()
app.header.SetBreadcrumber(ses)
app.header.SetSessionMenu(ses)
} }
func (app *App) RowSelected(ses *session.Row, srv *server.ServerRow, smsg cchat.ServerMessage) { 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 = srv.SetSelected
app.lastSelector(true) app.lastSelector(true)
app.header.SetBreadcrumber(srv)
// Assert that server is also a list, then join the server. // 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() { func (app *App) OnMessageBusy() {
// Disable the server list because we don't want the user to switch around // Disable the server list because we don't want the user to switch around
// while we're loading. // 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 // 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 // done, the application would exit immediately. There's no need to update
// the GUI. // the GUI.
for _, s := range app.window.AllServices() { for _, s := range app.Services.Services.Services {
var service = s.Service().Name() var service = s.Service().Name()
for _, session := range s.BodyList.Sessions() { 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 { func (app *App) Icon() *gdk.Pixbuf {
return icons.Logo256(0) return icons.Logo256(0)
} }
func (app *App) Menu() *glib.MenuModel { func (app *App) Menu() *glib.MenuModel {
return &app.header.menu.MenuModel return app.Services.Header.MenuModel
} }

View File

@ -1,39 +1 @@
package ui 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
}

View File

@ -16,7 +16,7 @@ import (
var destructor = func() {} var destructor = func() {}
func main() { func main() {
gts.Main(func() gts.Window { gts.Main(func() gts.MainApplication {
var app = ui.NewApplication() var app = ui.NewApplication()
// Load all cchat services. // Load all cchat services.