mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-11-17 03:32:56 +00:00
213 lines
5.8 KiB
Go
213 lines
5.8 KiB
Go
package container
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/diamondburned/cchat"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
|
"github.com/diamondburned/handy"
|
|
"github.com/gotk3/gotk3/gtk"
|
|
)
|
|
|
|
// BacklogLimit is the maximum number of messages to store in the container at
|
|
// once.
|
|
const BacklogLimit = 50
|
|
|
|
type MessageRow interface {
|
|
message.Container
|
|
GrabFocus()
|
|
}
|
|
|
|
type PresendMessageRow interface {
|
|
MessageRow
|
|
message.Presender
|
|
}
|
|
|
|
// Container is a generic messages container for children messages for children
|
|
// packages.
|
|
type Container interface {
|
|
gtk.IWidget
|
|
cchat.MessagesContainer
|
|
|
|
// Reset resets the message container to its original state.
|
|
Reset()
|
|
|
|
// SetSelf sets the author for the current user.
|
|
SetSelf(self *message.Author)
|
|
|
|
// NewPresendMessage creates and adds a presend message state into the list.
|
|
NewPresendMessage(state *message.PresendState) PresendMessageRow
|
|
|
|
// AddMessageAt adds a new message into the list at the given index.
|
|
AddMessageAt(row MessageRow, ix int)
|
|
|
|
// MessagesLen returns the current number of messages.
|
|
MessagesLen() int
|
|
// NthMessage returns the nth message in the buffer or nil if there's
|
|
// nothing.
|
|
NthMessage(ix int) MessageRow
|
|
|
|
// Message finds and returns the message, if any. It performs maximum 2
|
|
// constant-time lookups.
|
|
Message(id cchat.ID, nonce string) MessageRow
|
|
// FindMessage finds a message that satisfies the given callback. It
|
|
// iterates the message buffer from latest to earliest.
|
|
FindMessage(isMessage func(MessageRow) bool) (MessageRow, int)
|
|
|
|
// Highlight temporarily highlights the given message for a short while.
|
|
Highlight(msg MessageRow)
|
|
|
|
// UI methods.
|
|
|
|
SetFocusHAdjustment(*gtk.Adjustment)
|
|
SetFocusVAdjustment(*gtk.Adjustment)
|
|
}
|
|
|
|
// UpdateMessage is a convenient function to update a message in the container.
|
|
// It does nothing if the message is not found.
|
|
func UpdateMessage(ct Container, update cchat.MessageUpdate) {
|
|
if msg := ct.Message(update.ID(), ""); msg != nil {
|
|
msg.UpdateContent(update.Content(), true)
|
|
}
|
|
}
|
|
|
|
// LatestMessageFrom returns the latest message from the given author ID.
|
|
func LatestMessageFrom(ct Container, authorID cchat.ID) (MessageRow, int) {
|
|
finder, ok := ct.(messageFinder)
|
|
if !ok {
|
|
return ct.FindMessage(func(msg MessageRow) bool {
|
|
return msg.Unwrap().Author.ID == authorID
|
|
})
|
|
}
|
|
|
|
msg, ix := finder.findMessage(true, func(msg *messageRow) bool {
|
|
return msg.state.Author.ID == authorID
|
|
})
|
|
|
|
return unwrapRow(msg), ix
|
|
}
|
|
|
|
// FirstMessage returns the first message in the buffer. Nil is returned if
|
|
// there's nothing.
|
|
func FirstMessage(ct Container) MessageRow {
|
|
return ct.NthMessage(0)
|
|
}
|
|
|
|
// LastMessage returns the last message in the buffer or nil if there's nothing.
|
|
func LastMessage(ct Container) MessageRow {
|
|
return ct.NthMessage(ct.MessagesLen() - 1)
|
|
}
|
|
|
|
// InsertPosition returns the message that is before the given time (or nil) and
|
|
// the new index of the message with the given timestamp. If -1 is returned,
|
|
// then there is no message prior, and the message should be prepended on top.
|
|
func InsertPosition(ct Container, t time.Time) (MessageRow, int) {
|
|
var row MessageRow
|
|
var mIx int
|
|
|
|
finder, ok := ct.(messageFinder)
|
|
if !ok {
|
|
row, mIx = ct.FindMessage(func(msg MessageRow) bool {
|
|
return t.After(msg.Unwrap().Time)
|
|
})
|
|
} else {
|
|
// Iterate and compare timestamp to find where to insert a message. Note
|
|
// that "before" is the message that will go before the to-be-inserted
|
|
// method.
|
|
msg, ix := finder.findMessage(true, func(msg *messageRow) bool {
|
|
return t.After(msg.state.Time)
|
|
})
|
|
row = unwrapRow(msg)
|
|
mIx = ix
|
|
}
|
|
|
|
return row, mIx
|
|
}
|
|
|
|
// Controller is for menu actions.
|
|
type Controller interface {
|
|
// Connector is used for button press events to unselect messages.
|
|
primitives.Connector
|
|
// BindMenu expects the controller to add actioner into the message.
|
|
BindMenu(MessageRow)
|
|
// Bottomed returns whether or not the message scroller is at the bottom.
|
|
Bottomed() bool
|
|
// AuthorEvent is called on message create/update. This is used to update
|
|
// the typer state.
|
|
AuthorEvent(authorID cchat.ID)
|
|
// SelectMessage is called when a message is selected.
|
|
SelectMessage(list *ListStore, msg MessageRow)
|
|
// UnselectMessage is called when the message selection is cleared.
|
|
UnselectMessage()
|
|
}
|
|
|
|
const ColumnSpacing = 8
|
|
|
|
// ListContainer is an implementation of Container, which allows flexible
|
|
// message grids.
|
|
type ListContainer struct {
|
|
*handy.Clamp
|
|
|
|
*ListStore
|
|
|
|
Controller
|
|
}
|
|
|
|
// messageRow w/ required internals
|
|
type messageRow struct {
|
|
MessageRow
|
|
state *message.State
|
|
presend message.Presender // this shouldn't be here but i'm lazy
|
|
}
|
|
|
|
// unwrapRow is a helper that unwraps a messageRow if it's not nil. If it's nil,
|
|
// then a nil interface is returned.
|
|
func unwrapRow(msg *messageRow) MessageRow {
|
|
if msg == nil || msg.MessageRow == nil {
|
|
return nil
|
|
}
|
|
return msg.MessageRow
|
|
}
|
|
|
|
func NewListContainer(ctrl Controller) *ListContainer {
|
|
listStore := NewListStore(ctrl)
|
|
listStore.ListBox.Show()
|
|
|
|
clamp := handy.ClampNew()
|
|
clamp.SetMaximumSize(800)
|
|
clamp.SetTighteningThreshold(600)
|
|
clamp.SetHExpand(true)
|
|
clamp.SetVExpand(true)
|
|
clamp.Add(listStore.ListBox)
|
|
clamp.Show()
|
|
|
|
return &ListContainer{
|
|
Clamp: clamp,
|
|
ListStore: listStore,
|
|
Controller: ctrl,
|
|
}
|
|
}
|
|
|
|
// CleanMessages cleans up the oldest messages if the user is scrolled to the
|
|
// bottom. True is returned if there were changes.
|
|
func (c *ListContainer) CleanMessages() bool {
|
|
// Determine if the user is scrolled to the bottom for cleaning up.
|
|
if c.Bottomed() {
|
|
// Clean up the backlog.
|
|
if delta := c.MessagesLen() - BacklogLimit; delta > 0 {
|
|
c.DeleteEarliest(delta)
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (c *ListContainer) SetFocusHAdjustment(adj *gtk.Adjustment) {
|
|
c.ListBox.SetFocusHAdjustment(adj)
|
|
}
|
|
func (c *ListContainer) SetFocusVAdjustment(adj *gtk.Adjustment) {
|
|
c.ListBox.SetFocusVAdjustment(adj)
|
|
}
|