From 642e71636a10496845f67f6d3ff53f2d2434ddad Mon Sep 17 00:00:00 2001 From: seternate Date: Wed, 18 Sep 2024 20:53:02 +0200 Subject: [PATCH] Add connection bar At the bottom a bar was added, which shows the connection status to the server and the application version. --- cmd/client.go | 1 + go.mod | 2 +- go.sum | 4 +- pkg/controller/connectioncontroller.go | 88 ++++++++++++++++++ pkg/controller/controller.go | 18 ++-- pkg/widget/connectionbar.go | 121 +++++++++++++++++++++++++ pkg/widget/lanty.go | 18 ++-- pkg/widget/sidebar.go | 10 -- 8 files changed, 237 insertions(+), 25 deletions(-) create mode 100644 pkg/controller/connectioncontroller.go create mode 100644 pkg/widget/connectionbar.go diff --git a/cmd/client.go b/cmd/client.go index 04ec1ac..d3d5b40 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -41,6 +41,7 @@ func main() { } controller := controller.NewController(signalCtx). + WithConnectionController(). WithSettingsController(). WithStatusController(). WithGameController(). diff --git a/go.mod b/go.mod index e058872..cd294e8 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/rs/zerolog v1.31.0 - github.com/seternate/go-lanty v0.2.0 + github.com/seternate/go-lanty v0.2.1-0.20240918184806-7684fbfb8ee5 golang.design/x/clipboard v0.7.0 ) diff --git a/go.sum b/go.sum index 28cad31..90c9114 100644 --- a/go.sum +++ b/go.sum @@ -256,8 +256,8 @@ github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seternate/go-lanty v0.2.0 h1:Oj/ubPDZOvJerYcsCZXkiHQpXyzAZkcvpS+keiYrTTU= -github.com/seternate/go-lanty v0.2.0/go.mod h1:QL66bV5xXhUysHVUI8VaX773dsvXJp0bDzrXWWq3IOs= +github.com/seternate/go-lanty v0.2.1-0.20240918184806-7684fbfb8ee5 h1:zlp8eARrysYzkl34w3WdbKfS8QPSTa3LI92fH7KcceA= +github.com/seternate/go-lanty v0.2.1-0.20240918184806-7684fbfb8ee5/go.mod h1:QL66bV5xXhUysHVUI8VaX773dsvXJp0bDzrXWWq3IOs= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= diff --git a/pkg/controller/connectioncontroller.go b/pkg/controller/connectioncontroller.go new file mode 100644 index 0000000..47de5b3 --- /dev/null +++ b/pkg/controller/connectioncontroller.go @@ -0,0 +1,88 @@ +package controller + +import ( + "slices" + "sync" + "time" + + "github.com/rs/zerolog/log" + "github.com/seternate/go-lanty/pkg/util" +) + +type ConnectionStatus int + +const ( + Connected ConnectionStatus = iota + Disconnected +) + +type ConnectionController struct { + parent *Controller + subscriber []chan struct{} + refreshinterval time.Duration + mutex sync.RWMutex + Status ConnectionStatus +} + +func NewConnectionController(parent *Controller, refreshinterval time.Duration) (controller *ConnectionController) { + controller = &ConnectionController{ + parent: parent, + subscriber: make([]chan struct{}, 0, 50), + refreshinterval: refreshinterval, + Status: Disconnected, + } + parent.WaitGroup().Add(1) + go controller.run() + return +} + +func (controller *ConnectionController) run() { + defer controller.parent.WaitGroup().Done() + ticker := time.NewTicker(controller.refreshinterval) + controller.updateStatus() + for { + select { + case <-controller.parent.Context().Done(): + log.Trace().Err(controller.parent.Context().Err()).Msg("exiting connectioncontroller run()") + return + case <-ticker.C: + controller.updateStatus() + } + } +} + +func (controller *ConnectionController) updateStatus() { + controller.mutex.Lock() + oldstatus := controller.Status + if controller.parent.client.Health.Health() != nil { + controller.Status = Disconnected + } else { + controller.Status = Connected + } + newstatus := controller.Status + controller.mutex.Unlock() + if newstatus != oldstatus { + controller.notifySubcriber() + } +} + +func (controller *ConnectionController) Subscribe(subscriber chan struct{}) { + defer controller.mutex.Unlock() + controller.mutex.Lock() + controller.subscriber = append(controller.subscriber, subscriber) +} + +func (controller *ConnectionController) Unsubscribe(subscriber chan struct{}) { + defer controller.mutex.Unlock() + controller.mutex.Lock() + index := slices.Index(controller.subscriber, subscriber) + _ = slices.Delete(controller.subscriber, index, index+1) +} + +func (controller *ConnectionController) notifySubcriber() { + defer controller.mutex.RUnlock() + controller.mutex.RLock() + for _, subscriber := range controller.subscriber { + util.ChannelWriteNonBlocking(subscriber, struct{}{}) + } +} diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index fff3073..34cffa3 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -12,12 +12,13 @@ import ( ) type Controller struct { - Settings *SettingsController - Status *StatusController - Download *DownloadController - Game *GameController - User *UserController - Chat *ChatController + Settings *SettingsController + Status *StatusController + Download *DownloadController + Game *GameController + User *UserController + Chat *ChatController + Connection *ConnectionController settings *setting.Settings client *api.Client @@ -96,3 +97,8 @@ func (controller *Controller) WithChatController() *Controller { controller.Chat = NewChatController(controller) return controller } + +func (controller *Controller) WithConnectionController() *Controller { + controller.Connection = NewConnectionController(controller, 1*time.Second) + return controller +} diff --git a/pkg/widget/connectionbar.go b/pkg/widget/connectionbar.go new file mode 100644 index 0000000..44230c5 --- /dev/null +++ b/pkg/widget/connectionbar.go @@ -0,0 +1,121 @@ +package widget + +import ( + "fmt" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + fynetheme "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/rs/zerolog/log" + "github.com/seternate/go-lanty-client/pkg/controller" + "github.com/seternate/go-lanty-client/pkg/setting" + "github.com/seternate/go-lanty-client/pkg/theme" +) + +type Connectionbar struct { + widget.BaseWidget + + controller *controller.Controller + statusupdated chan struct{} + statustext string +} + +func NewConnectionbar(controller *controller.Controller) *Connectionbar { + connectionbar := &Connectionbar{ + controller: controller, + statusupdated: make(chan struct{}, 50), + statustext: "UNKNOWN", + } + connectionbar.ExtendBaseWidget(connectionbar) + + controller.Connection.Subscribe(connectionbar.statusupdated) + controller.WaitGroup().Add(1) + go connectionbar.statusUpdater() + + return connectionbar +} + +func (widget *Connectionbar) statusUpdater() { + defer widget.controller.WaitGroup().Done() + widget.updateStatus() + for { + select { + case <-widget.controller.Context().Done(): + log.Trace().Msg("exiting connectionbar statusUpdater()") + return + case <-widget.statusupdated: + widget.updateStatus() + widget.Refresh() + } + } +} + +func (widget *Connectionbar) updateStatus() { + status := widget.controller.Connection.Status + if status == controller.Connected { + widget.statustext = fmt.Sprintf("Connected to server: %s", widget.controller.Settings.Settings().ServerURL) + } else if status == controller.Disconnected { + widget.statustext = fmt.Sprintf("Error connecting to server: %s", widget.controller.Settings.Settings().ServerURL) + } +} + +func (widget *Connectionbar) CreateRenderer() fyne.WidgetRenderer { + return newConnectionbarRenderer(widget) +} + +type connectionbarRenderer struct { + widget *Connectionbar + line *canvas.Line + status *canvas.Text + version *canvas.Text +} + +func newConnectionbarRenderer(widget *Connectionbar) *connectionbarRenderer { + renderer := &connectionbarRenderer{ + widget: widget, + line: canvas.NewLine(fynetheme.InputBorderColor()), + status: canvas.NewText(widget.statustext, theme.ForegroundColor()), + version: canvas.NewText(setting.VERSION, theme.ForegroundColor()), + } + renderer.line.StrokeWidth = 2.0 + return renderer +} + +func (renderer *connectionbarRenderer) Objects() []fyne.CanvasObject { + objects := []fyne.CanvasObject{ + renderer.line, + renderer.status, + renderer.version, + } + return objects +} + +func (renderer *connectionbarRenderer) Layout(size fyne.Size) { + renderer.line.Resize(fyne.NewSize(size.Width, 0)) + + versiontextsize := fyne.MeasureText(renderer.version.Text, renderer.version.TextSize, renderer.version.TextStyle) + statustextsize := fyne.MeasureText(renderer.status.Text, renderer.status.TextSize, renderer.status.TextStyle) + + renderer.version.Move(fyne.NewPos(theme.InnerPadding(), (size.Height-versiontextsize.Height)/2)) + renderer.status.Move(fyne.NewPos(size.Width-statustextsize.Width-theme.InnerPadding(), (size.Height-statustextsize.Height)/2)) +} + +func (renderer *connectionbarRenderer) MinSize() fyne.Size { + versiontextsize := fyne.MeasureText(renderer.version.Text, renderer.version.TextSize, renderer.version.TextStyle) + statustextsize := fyne.MeasureText(renderer.status.Text, renderer.status.TextSize, renderer.status.TextStyle) + + minHeight := fyne.Max(versiontextsize.Height, statustextsize.Height) + theme.InnerPadding() + minWidth := versiontextsize.Width + statustextsize.Width + 3*theme.InnerPadding() + + return fyne.NewSize(minWidth, minHeight) +} + +func (renderer *connectionbarRenderer) Refresh() { + renderer.line.Refresh() + renderer.status.Text = renderer.widget.statustext + renderer.status.Refresh() + renderer.version.Refresh() +} + +func (renderer *connectionbarRenderer) Destroy() {} diff --git a/pkg/widget/lanty.go b/pkg/widget/lanty.go index 2f359a0..b8f067d 100644 --- a/pkg/widget/lanty.go +++ b/pkg/widget/lanty.go @@ -19,6 +19,7 @@ type Lanty struct { widget.BaseWidget controller *controller.Controller + connectionbar *Connectionbar sidebar *Sidebar gamebrowser *ScrollWithState startserver *ScrollWithState @@ -46,6 +47,7 @@ func NewLanty(controller *controller.Controller, window fyne.Window) *Lanty { lanty := &Lanty{ controller: controller, + connectionbar: NewConnectionbar(controller), sidebar: NewSidebar(setting.APPLICATION_NAME), gamebrowser: NewVScrollWithState(gamebrowser), startserver: NewVScrollWithState(startserver), @@ -206,7 +208,7 @@ func (widget *Lanty) showSettingsBrowser() { } func (widget *Lanty) showStartServer() { - widget.sidebar.Show() + widget.sidebar.Hide() widget.gamebrowser.Hide() widget.downloadbrowser.Hide() widget.chatbrowser.Hide() @@ -218,7 +220,7 @@ func (widget *Lanty) showStartServer() { } func (widget *Lanty) showJoinServer() { - widget.sidebar.Show() + widget.sidebar.Hide() widget.gamebrowser.Hide() widget.downloadbrowser.Hide() widget.chatbrowser.Hide() @@ -274,15 +276,18 @@ func (renderer *lantyRenderer) Objects() []fyne.CanvasObject { renderer.widget.sidebar, renderer.main, renderer.widget.statusbar, + renderer.widget.connectionbar, } } func (renderer *lantyRenderer) Layout(size fyne.Size) { renderer.widget.defaultusernamebrowser.Move(fyne.NewPos(0, 0)) - renderer.widget.defaultusernamebrowser.Resize(size) + renderer.widget.defaultusernamebrowser.Resize(size.SubtractWidthHeight(0, renderer.widget.connectionbar.MinSize().Height)) + renderer.widget.connectionbar.Move(fyne.NewPos(0, size.Height-renderer.widget.connectionbar.MinSize().Height)) + renderer.widget.connectionbar.Resize(fyne.NewSize(size.Width, renderer.widget.connectionbar.MinSize().Height)) renderer.widget.sidebar.Move(fyne.NewPos(0, 0)) - renderer.widget.sidebar.Resize(fyne.NewSize(fyne.Max(size.Width/7, renderer.widget.sidebar.MinSize().Width), size.Height)) - renderer.main.Resize(fyne.NewSize(size.Width-fyne.Max(size.Width/7, renderer.widget.sidebar.MinSize().Width), size.Height)) + renderer.widget.sidebar.Resize(fyne.NewSize(fyne.Max(size.Width/7, renderer.widget.sidebar.MinSize().Width), size.Height-renderer.widget.connectionbar.MinSize().Height)) + renderer.main.Resize(fyne.NewSize(size.Width-fyne.Max(size.Width/7, renderer.widget.sidebar.MinSize().Width), size.Height-renderer.widget.connectionbar.MinSize().Height)) renderer.main.Move(fyne.NewPos(fyne.Max(size.Width/7, renderer.widget.sidebar.MinSize().Width), 0)) renderer.widget.statusbar.Resize(fyne.NewSize(renderer.main.Size().Width/2, 40)) renderer.widget.statusbar.Move(fyne.NewPos(size.Width-theme.InnerPadding()-renderer.widget.statusbar.Size().Width, size.Height-theme.InnerPadding()-renderer.widget.statusbar.Size().Height)) @@ -290,7 +295,7 @@ func (renderer *lantyRenderer) Layout(size fyne.Size) { func (renderer *lantyRenderer) MinSize() fyne.Size { size := fyne.NewSize(0, 0) - size.Height = fyne.Max(renderer.widget.sidebar.MinSize().Height, renderer.main.MinSize().Height) + size.Height = fyne.Max(renderer.widget.sidebar.MinSize().Height, renderer.main.MinSize().Height) + renderer.widget.connectionbar.MinSize().Height size.Width = renderer.widget.gamebrowser.MinSize().Width + fyne.Max(renderer.widget.gamebrowser.MinSize().Width/6, renderer.widget.sidebar.MinSize().Width) return size } @@ -301,6 +306,7 @@ func (renderer *lantyRenderer) Refresh() { //Needed for statusbar to be drawn correctly //renderer.Layout(renderer.widget.Size()) renderer.main.Refresh() + renderer.widget.connectionbar.Refresh() renderer.widget.sidebar.Refresh() renderer.widget.statusbar.Refresh() renderer.widget.defaultusernamebrowser.Refresh() diff --git a/pkg/widget/sidebar.go b/pkg/widget/sidebar.go index 14453a4..9944327 100644 --- a/pkg/widget/sidebar.go +++ b/pkg/widget/sidebar.go @@ -5,7 +5,6 @@ import ( "fyne.io/fyne/v2/canvas" fynetheme "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - "github.com/seternate/go-lanty-client/pkg/setting" "github.com/seternate/go-lanty-client/pkg/theme" ) @@ -75,7 +74,6 @@ type sidebarRenderer struct { widget *Sidebar background *canvas.Rectangle text *canvas.Text - version *canvas.Text objects []fyne.CanvasObject } @@ -84,12 +82,10 @@ func newSidebarRenderer(widget *Sidebar) *sidebarRenderer { widget: widget, background: canvas.NewRectangle(fynetheme.InputBorderColor()), text: canvas.NewText(widget.text, theme.ForegroundColor()), - version: canvas.NewText(setting.VERSION, theme.ForegroundColor()), } renderer.objects = []fyne.CanvasObject{ renderer.background, renderer.text, - renderer.version, } for _, button := range renderer.widget.buttons { renderer.objects = append(renderer.objects, button) @@ -122,9 +118,6 @@ func (renderer *sidebarRenderer) Layout(size fyne.Size) { button.Move(fyne.NewPos(theme.InnerPadding(), textsize.Height*1.5+(button.MinSize().Height+theme.InnerPadding())*float32(index))) index++ } - - textsize = fyne.MeasureText(renderer.version.Text, renderer.version.TextSize, renderer.version.TextStyle) - renderer.version.Move(fyne.NewPos(theme.InnerPadding(), size.Height-theme.InnerPadding()-textsize.Height)) } func (renderer *sidebarRenderer) MinSize() fyne.Size { @@ -136,8 +129,6 @@ func (renderer *sidebarRenderer) MinSize() fyne.Size { minHeight += button.MinSize().Height + theme.InnerPadding() minWidth = fyne.Max(minWidth, button.MinSize().Width+2*theme.InnerPadding()) } - textsize = fyne.MeasureText(renderer.version.Text, renderer.version.TextSize, renderer.version.TextStyle) - minHeight += textsize.Height + theme.InnerPadding() return fyne.NewSize(minWidth, minHeight) } @@ -146,7 +137,6 @@ func (renderer *sidebarRenderer) Refresh() { renderer.text.Text = renderer.widget.text renderer.background.Refresh() renderer.text.Refresh() - renderer.version.Refresh() for _, button := range renderer.widget.buttons { button.Refresh() }