269 lines
6.4 KiB
Go
269 lines
6.4 KiB
Go
package session
|
|
|
|
import (
|
|
"github.com/diamondburned/cchat"
|
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
|
"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"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/serverpane"
|
|
"github.com/diamondburned/handy"
|
|
"github.com/gotk3/gotk3/gtk"
|
|
)
|
|
|
|
const FaceSize = 48 // gtk.ICON_SIZE_DIALOG
|
|
const ListWidth = 200
|
|
|
|
// SessionController extends server.Controller to add needed methods that the
|
|
// specific top-level servers container needs.
|
|
type SessionController interface {
|
|
ClearMessenger()
|
|
MessengerSelected(*server.ServerRow)
|
|
}
|
|
|
|
// Servers wraps around a list of servers inherited from Children to display a
|
|
// Lister in its own box instead of as a nested list. It's the container that's
|
|
// displayed on the right of the service sidebar.
|
|
type Servers struct {
|
|
gtk.Stack
|
|
SessionController
|
|
|
|
spinner *spinner.Boxed
|
|
// Main is the horizontal box containing the current struct's list of
|
|
// servers columnated with the same level. The second item in the box should
|
|
// be the selected server.
|
|
Main *serverpane.Paned
|
|
|
|
// Lister is the current lister belonging to this server.
|
|
Lister cchat.Lister
|
|
stopLs func()
|
|
|
|
// Children is main's lhs.
|
|
Children *server.Children
|
|
|
|
// NextColumn is main's rhs.
|
|
NextColumn *Servers // nil
|
|
detachNext func()
|
|
}
|
|
|
|
var toplevelCSS = primitives.PrepareClassCSS("top-level", `
|
|
.top-level {
|
|
}
|
|
`)
|
|
|
|
// NewServers creates a new Servers instance that holds only the given column
|
|
// number and its children. Any servers with a different columnate ID will be in
|
|
// the children pane.
|
|
func NewServers(p traverse.Breadcrumber, ctrl SessionController) *Servers {
|
|
servers := Servers{
|
|
SessionController: ctrl,
|
|
}
|
|
|
|
servers.Children = server.NewChildren(p, &servers)
|
|
servers.Children.SetVExpand(true)
|
|
servers.Children.Show()
|
|
toplevelCSS(servers.Children)
|
|
|
|
servers.Main = serverpane.NewPaned(servers.Children, gtk.ORIENTATION_VERTICAL)
|
|
servers.Main.Show()
|
|
|
|
stack, _ := gtk.StackNew()
|
|
servers.Stack = *stack
|
|
servers.Stack.SetVAlign(gtk.ALIGN_START)
|
|
servers.Stack.SetTransitionType(gtk.STACK_TRANSITION_TYPE_CROSSFADE)
|
|
servers.Stack.SetTransitionDuration(75)
|
|
servers.Stack.AddNamed(servers.Main, "main")
|
|
servers.Stack.Show()
|
|
|
|
return &servers
|
|
}
|
|
|
|
// Destroy destroys and invalidates this instance permanently.
|
|
func (s *Servers) Destroy() {
|
|
s.Reset()
|
|
s.Stack.Destroy()
|
|
}
|
|
|
|
func (s *Servers) Reset() {
|
|
// Reset isn't necessarily called while loading, so we do a check.
|
|
if s.spinner != nil {
|
|
s.spinner.Destroy()
|
|
s.spinner = nil
|
|
}
|
|
|
|
// Close the right server column if any.
|
|
if s.NextColumn != nil {
|
|
if s.detachNext != nil {
|
|
s.detachNext()
|
|
}
|
|
|
|
s.Main.Remove(s.NextColumn)
|
|
s.NextColumn.Destroy()
|
|
s.NextColumn = nil
|
|
}
|
|
|
|
// Call the destructor if any.
|
|
if s.stopLs != nil {
|
|
s.stopLs()
|
|
s.stopLs = nil
|
|
}
|
|
|
|
// Reset the state.
|
|
s.Lister = nil
|
|
// Reset the children container.
|
|
s.Children.Reset()
|
|
s.Stack.SetVisibleChild(s.Main)
|
|
}
|
|
|
|
// IsLoading returns true if the servers container is loading.
|
|
func (s *Servers) IsLoading() bool {
|
|
return s.spinner != nil
|
|
}
|
|
|
|
// SetList indicates that the server list has been loaded. Unlike
|
|
// server.Children, this method will load immediately.
|
|
func (s *Servers) SetList(slist cchat.Lister) {
|
|
if s.stopLs != nil {
|
|
s.stopLs()
|
|
s.stopLs = nil
|
|
}
|
|
|
|
s.Lister = slist
|
|
s.load()
|
|
}
|
|
|
|
func (s *Servers) load() {
|
|
if s.Lister == nil || s.IsLoading() {
|
|
return
|
|
}
|
|
|
|
// Mark the servers list as loading.
|
|
s.setLoading()
|
|
|
|
lister := s.Lister
|
|
|
|
go func() {
|
|
stop, err := lister.Servers(s)
|
|
gts.ExecAsync(func() {
|
|
if err != nil {
|
|
s.setFailed(err)
|
|
} else {
|
|
s.stopLs = stop
|
|
s.setDone()
|
|
}
|
|
})
|
|
}()
|
|
}
|
|
|
|
// SetServers is reserved for cchat.ServersContainer.
|
|
func (s *Servers) SetServers(servers []cchat.Server) {
|
|
gts.ExecAsync(func() {
|
|
s.Children.SetServersUnsafe(servers)
|
|
|
|
if servers == nil {
|
|
s.ClearMessenger()
|
|
return
|
|
}
|
|
|
|
// Reload all top-level nodes.
|
|
s.Children.LoadAll()
|
|
})
|
|
}
|
|
|
|
// SetServers is reserved for cchat.ServersContainer.
|
|
func (s *Servers) UpdateServer(update cchat.ServerUpdate) {
|
|
gts.ExecAsync(func() { s.Children.UpdateServerUnsafe(update) })
|
|
}
|
|
|
|
// setDone changes the view to show the servers.
|
|
func (s *Servers) setDone() {
|
|
s.SetVisibleChild(s.Main)
|
|
|
|
// stop the spinner.
|
|
if s.spinner != nil {
|
|
s.spinner.Destroy()
|
|
s.spinner = nil
|
|
}
|
|
}
|
|
|
|
// setLoading shows a loading spinner. Use this after the session row is
|
|
// connected.
|
|
func (s *Servers) setLoading() {
|
|
s.spinner = spinner.New()
|
|
s.spinner.SetSizeRequest(FaceSize, FaceSize)
|
|
s.spinner.Show()
|
|
s.spinner.Start()
|
|
|
|
s.AddNamed(s.spinner, "spinner")
|
|
s.SetVisibleChildName("spinner")
|
|
}
|
|
|
|
// setFailed shows a sad face with the error. Use this when the session row has
|
|
// failed to load.
|
|
func (s *Servers) setFailed(err error) {
|
|
// stop the spinner. Let this SEGFAULT if nil.
|
|
s.spinner.Destroy()
|
|
s.spinner = nil
|
|
|
|
// Remove existing error widgets.
|
|
w, err := s.Stack.GetChildByName("error")
|
|
if err == nil {
|
|
s.Stack.Remove(w)
|
|
}
|
|
|
|
// Create a retry button.
|
|
btn, _ := gtk.ButtonNewFromIconName("view-refresh-symbolic", gtk.ICON_SIZE_BUTTON)
|
|
btn.SetLabel("Retry")
|
|
btn.Connect("clicked", s.load)
|
|
btn.Show()
|
|
|
|
page := handy.StatusPageNew()
|
|
page.SetTitle("Error")
|
|
page.SetIconName("dialog-error")
|
|
page.SetTooltipText(err.Error())
|
|
page.SetDescription(humanize.Error(err))
|
|
page.Add(btn)
|
|
|
|
s.Stack.AddNamed(page, "error")
|
|
s.Stack.SetVisibleChildName("error")
|
|
}
|
|
|
|
// SelectColumnatedLister is called by children servers to open up a server list
|
|
// on the right.
|
|
func (s *Servers) SelectColumnatedLister(srv *server.ServerRow, lst cchat.Lister) {
|
|
if s.detachNext != nil {
|
|
s.Main.Remove(s.NextColumn) // run the deconstructor
|
|
s.detachNext()
|
|
s.NextColumn.Destroy()
|
|
}
|
|
|
|
if lst == nil {
|
|
return
|
|
}
|
|
|
|
s.NextColumn = NewServers(srv, s)
|
|
s.NextColumn.SetList(lst)
|
|
s.Main.AddSide(s.NextColumn)
|
|
|
|
update := func(box *gtk.Box) {
|
|
a := box.GetAllocation()
|
|
// Align the next column to the selected item.
|
|
s.NextColumn.SetMarginTop(a.GetY())
|
|
}
|
|
|
|
s.Children.SetExpand(false)
|
|
primitives.AddClass(srv, "active-column")
|
|
|
|
update(srv.Box)
|
|
sizeHandle := srv.Box.Connect("size-allocate", update)
|
|
|
|
s.detachNext = func() {
|
|
srv.Box.HandlerDisconnect(sizeHandle)
|
|
|
|
s.Children.SetExpand(true)
|
|
primitives.RemoveClass(srv, "active-column")
|
|
}
|
|
}
|