Added limiting backlog and more general improvements

This commit is contained in:
diamondburned (Forefront) 2020-06-13 14:51:03 -07:00
parent ce5c7a3016
commit adeffc7717
11 changed files with 142 additions and 34 deletions

View File

@ -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()

View File

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

View File

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

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -94,8 +94,6 @@ func NewView() *View {
logo.Show()
view.FaceView = sadface.New(view.Box, logo)
view.FaceView.Show()
return view
}

View File

@ -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}
}

View File

@ -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,

View File

@ -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
}

View File

@ -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}
}