From adeffc7717f6ea02cb0da63c0921c53320091542 Mon Sep 17 00:00:00 2001 From: "diamondburned (Forefront)" Date: Sat, 13 Jun 2020 14:51:03 -0700 Subject: [PATCH] Added limiting backlog and more general improvements --- internal/ui/header.go | 2 +- internal/ui/messages/container/container.go | 30 ++++++++ internal/ui/messages/container/cozy/cozy.go | 75 +++++++++++++++---- ...essage_compact.go => message_collapsed.go} | 2 + .../messages/container/cozy/message_full.go | 2 + internal/ui/messages/container/grid.go | 10 ++- internal/ui/messages/view.go | 2 - internal/ui/rich/rich.go | 4 +- internal/ui/service/service.go | 1 - internal/ui/ui.go | 26 ++++++- internal/ui/window.go | 22 +++--- 11 files changed, 142 insertions(+), 34 deletions(-) rename internal/ui/messages/container/cozy/{message_compact.go => message_collapsed.go} (96%) diff --git a/internal/ui/header.go b/internal/ui/header.go index 915974a..360b77f 100644 --- a/internal/ui/header.go +++ b/internal/ui/header.go @@ -16,7 +16,7 @@ type header struct { func newHeader() *header { left, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) - left.SetSizeRequest(LeftWidth, -1) + left.SetSizeRequest(leftMinWidth, -1) left.Show() right := newHeaderRight() diff --git a/internal/ui/messages/container/container.go b/internal/ui/messages/container/container.go index dd4a79d..5d93494 100644 --- a/internal/ui/messages/container/container.go +++ b/internal/ui/messages/container/container.go @@ -9,6 +9,10 @@ import ( "github.com/gotk3/gotk3/gtk" ) +// BacklogLimit is the maximum number of messages to store in the container at +// once. +const BacklogLimit = 35 + type GridMessage interface { message.Container // Attach should only be called once. @@ -92,6 +96,32 @@ func NewGridContainer(constr Constructor, ctrl Controller) *GridContainer { } } +// CreateMessageUnsafe inserts a message as well as cleaning up the backlog if +// the user is scrolled to the bottom. +func (c *GridContainer) CreateMessageUnsafe(msg cchat.MessageCreate) { + // Insert the message first. + c.GridStore.CreateMessageUnsafe(msg) + + // Determine if the user is scrolled to the bottom for cleaning up. + if !c.ScrolledWindow.Bottomed { + return + } + + // Clean up the backlog. + if clean := len(c.messages) - BacklogLimit; clean > 0 { + // Remove them from the map and the container. + for _, id := range c.messageIDs[:clean] { + delete(c.messages, id) + // We can gradually pop the first item off here, as we're removing + // from 0th, and items are being shifted backwards. + c.Grid.RemoveRow(0) + } + + // Cut the message IDs away by shifting the slice. + c.messageIDs = append(c.messageIDs[:0], c.messageIDs[clean:]...) + } +} + func (c *GridContainer) CreateMessage(msg cchat.MessageCreate) { gts.ExecAsync(func() { c.CreateMessageUnsafe(msg) }) } diff --git a/internal/ui/messages/container/cozy/cozy.go b/internal/ui/messages/container/cozy/cozy.go index 554594a..f7444d5 100644 --- a/internal/ui/messages/container/cozy/cozy.go +++ b/internal/ui/messages/container/cozy/cozy.go @@ -23,6 +23,20 @@ var ( _ Unwrapper = (*FullSendingMessage)(nil) ) +// Collapsible is an interface for cozy messages to return whether or not +// they're full or collapsed. +type Collapsible interface { + // Compact returns true if the message is a compact one and not full. + Collapsed() bool +} + +var ( + _ Collapsible = (*CollapsedMessage)(nil) + _ Collapsible = (*CollapsedSendingMessage)(nil) + _ Collapsible = (*FullMessage)(nil) + _ Collapsible = (*FullSendingMessage)(nil) +) + const ( AvatarSize = 40 AvatarMargin = 10 @@ -106,6 +120,25 @@ func (c *Container) reuseAvatar(authorID, avatarURL string, full *FullMessage) { } } +func (c *Container) CreateMessage(msg cchat.MessageCreate) { + gts.ExecAsync(func() { + // Create the message in the parent's handler. This handler will also + // wipe old messages. + c.GridContainer.CreateMessageUnsafe(msg) + + // Did the handler wipe old messages? It will only do so if the user is + // scrolled to the bottom. + if !c.ScrolledWindow.Bottomed { + // If we're not at the bottom, then we exit. + return + } + + // We need to uncollapse the first (top) message. No length check is + // needed here, as we just inserted a message. + c.uncompact(c.FirstMessage()) + }) +} + func (c *Container) DeleteMessage(msg cchat.MessageDelete) { gts.ExecAsync(func() { // Get the previous and next message before deleting. We'll need them to @@ -138,21 +171,31 @@ func (c *Container) DeleteMessage(msg cchat.MessageDelete) { return } - // Get the unwrapper method, which allows us to get the - // *message.GenericContainer. - uw, ok := next.(Unwrapper) - if !ok { - return - } - - // Start the "lengthy" uncollapse process. - full := WrapFullMessage(uw.Unwrap(c.Grid)) - // Update the container to reformat everything including the timestamps. - message.RefreshContainer(full, full.GenericContainer) - // Update the avatar if needed be, since we're now showing it. - c.reuseAvatar(next.AuthorID(), next.AvatarURL(), full) - - // Swap the old next message out for a new one. - c.GridStore.SwapMessage(full) + // Uncompact or turn the message to a full one. + c.uncompact(next) }) } + +func (c *Container) uncompact(msg container.GridMessage) { + // We should only uncompact the message if it's compacted in the first + // place. + if collapse, ok := msg.(Collapsible); !ok || !collapse.Collapsed() { + return + } + + // We can't unwrap if the message doesn't implement Unwrapper. + uw, ok := msg.(Unwrapper) + if !ok { + return + } + + // Start the "lengthy" uncollapse process. + full := WrapFullMessage(uw.Unwrap(c.Grid)) + // Update the container to reformat everything including the timestamps. + message.RefreshContainer(full, full.GenericContainer) + // Update the avatar if needed be, since we're now showing it. + c.reuseAvatar(msg.AuthorID(), msg.AvatarURL(), full) + + // Swap the old next message out for a new one. + c.GridStore.SwapMessage(full) +} diff --git a/internal/ui/messages/container/cozy/message_compact.go b/internal/ui/messages/container/cozy/message_collapsed.go similarity index 96% rename from internal/ui/messages/container/cozy/message_compact.go rename to internal/ui/messages/container/cozy/message_collapsed.go index 405c9a2..75bd5e3 100644 --- a/internal/ui/messages/container/cozy/message_compact.go +++ b/internal/ui/messages/container/cozy/message_collapsed.go @@ -36,6 +36,8 @@ func WrapCollapsedMessage(gc *message.GenericContainer) *CollapsedMessage { } } +func (c *CollapsedMessage) Collapsed() bool { return true } + func (c *CollapsedMessage) Unwrap(grid *gtk.Grid) *message.GenericContainer { // Remove GenericContainer's widgets from the containers. grid.Remove(c.Timestamp) diff --git a/internal/ui/messages/container/cozy/message_full.go b/internal/ui/messages/container/cozy/message_full.go index 88693ad..13cd6a0 100644 --- a/internal/ui/messages/container/cozy/message_full.go +++ b/internal/ui/messages/container/cozy/message_full.go @@ -86,6 +86,8 @@ func WrapFullMessage(gc *message.GenericContainer) *FullMessage { } } +func (c *FullMessage) Collapsed() bool { return false } + func (m *FullMessage) Unwrap(grid *gtk.Grid) *message.GenericContainer { // Remove GenericContainer's widgets from the containers. m.HeaderBox.Remove(m.Username) diff --git a/internal/ui/messages/container/grid.go b/internal/ui/messages/container/grid.go index 3cd2fce..8e3a876 100644 --- a/internal/ui/messages/container/grid.go +++ b/internal/ui/messages/container/grid.go @@ -126,10 +126,18 @@ func (c *GridStore) FindMessage(isMessage func(msg GridMessage) bool) GridMessag return nil } +// FirstMessage returns the first message. +func (c *GridStore) FirstMessage() GridMessage { + if len(c.messageIDs) > 0 { + return c.messages[c.messageIDs[0]].GridMessage + } + return nil +} + // LastMessage returns the latest message. func (c *GridStore) LastMessage() GridMessage { if l := len(c.messageIDs); l > 0 { - return c.messages[c.messageIDs[l-1]] + return c.messages[c.messageIDs[l-1]].GridMessage } return nil } diff --git a/internal/ui/messages/view.go b/internal/ui/messages/view.go index 9a8d41b..2020e48 100644 --- a/internal/ui/messages/view.go +++ b/internal/ui/messages/view.go @@ -94,8 +94,6 @@ func NewView() *View { logo.Show() view.FaceView = sadface.New(view.Box, logo) - view.FaceView.Show() - return view } diff --git a/internal/ui/rich/rich.go b/internal/ui/rich/rich.go index e6342aa..1c01beb 100644 --- a/internal/ui/rich/rich.go +++ b/internal/ui/rich/rich.go @@ -10,6 +10,7 @@ import ( "github.com/diamondburned/cchat-gtk/internal/ui/rich/parser" "github.com/diamondburned/cchat/text" "github.com/gotk3/gotk3/gtk" + "github.com/gotk3/gotk3/pango" "github.com/pkg/errors" ) @@ -44,7 +45,8 @@ var ( func NewLabel(content text.Rich) *Label { label, _ := gtk.LabelNew("") label.SetMarkup(parser.RenderMarkup(content)) - label.SetHAlign(gtk.ALIGN_START) + label.SetXAlign(0) // left align + label.SetEllipsize(pango.ELLIPSIZE_END) return &Label{*label, content} } diff --git a/internal/ui/service/service.go b/internal/ui/service/service.go index 2568396..36687f7 100644 --- a/internal/ui/service/service.go +++ b/internal/ui/service/service.go @@ -28,7 +28,6 @@ func NewView() *View { sw, _ := gtk.ScrolledWindowNew(nil, nil) sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) sw.Add(box) - sw.Show() return &View{ sw, diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 7c0a848..20bf073 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -17,7 +17,23 @@ func init() { gts.LoadCSS(pkger.Include("/internal/ui/style.css")) } -const LeftWidth = 220 +// constraints for the left panel +const ( + leftMinWidth = 200 + leftCurrentWidth = 250 + leftMaxWidth = 400 +) + +func clamp(n, min, max int) int { + switch { + case n > max: + return max + case n < min: + return min + default: + return n + } +} type App struct { window *window @@ -39,6 +55,14 @@ func NewApplication() *App { header: newHeader(), } + // Resize the left-side header w/ the left-side pane. + app.window.Services.Connect("size-allocate", func(wv gtk.IWidget) { + // Get the current width of the left sidebar. + var width = app.window.GetPosition() + // Set the left-side header's size. + app.header.left.SetSizeRequest(width, -1) + }) + return app } diff --git a/internal/ui/window.go b/internal/ui/window.go index 798caa7..f07a686 100644 --- a/internal/ui/window.go +++ b/internal/ui/window.go @@ -7,24 +7,24 @@ import ( ) type window struct { - *gtk.Box + *gtk.Paned Services *service.View MessageView *messages.View } func newWindow() *window { services := service.NewView() - services.SetSizeRequest(LeftWidth, -1) + services.SetSizeRequest(leftMinWidth, -1) + services.Show() + mesgview := messages.NewView() + mesgview.Show() - separator, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL) - separator.Show() + pane, _ := gtk.PanedNew(gtk.ORIENTATION_HORIZONTAL) + pane.Pack1(services, false, false) + pane.Pack2(mesgview, true, false) + pane.SetPosition(leftCurrentWidth) + pane.Show() - box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0) - box.PackStart(services, false, false, 0) - box.PackStart(separator, false, false, 0) - box.PackStart(mesgview, true, true, 0) - box.Show() - - return &window{box, services, mesgview} + return &window{pane, services, mesgview} }