cchat-gtk/internal/ui/service/session/server/traverse/traverse.go

135 lines
4.0 KiB
Go

// Package traverse implements an extensible interface that allows children
// widgets to announce state changes to their parent container.
//
// The objective of this package is to allow for easier parent traversal without
// cluttering its structure with too much state. It also allows for proper
// encapsulation as well as a fallback mechanism without lengthy boolean checks.
package traverse
import (
"strings"
)
type Breadcrumb []string
func (b Breadcrumb) String() string {
return strings.Join([]string(b), "/")
}
// Breadcrumber is the base interface that other interfaces extend on. A child
// must at minimum implement this interface to use any other.
type Breadcrumber interface {
// Breadcrumb returns the parent's path before the children's breadcrumb.
// This method recursively joins the parent's crumb with the children's,
// then eventually make its way up to the root node.
Breadcrumb() Breadcrumb
}
// TryBreadcrumb accepts a nilable breadcrumber and handles it appropriately.
func TryBreadcrumb(i Breadcrumber, appended ...string) []string {
if i == nil {
return appended
}
return append(i.Breadcrumb(), appended...)
}
// Unreadabler extends Breadcrumber to add unread states to the parent node.
type Unreadabler interface {
SetState(id string, unread, mentioned bool)
}
// TrySetUnread tries to check if a breadcrumber parent node supports
// Unreadabler. If it does, then this function will set the state appropriately.
func TrySetUnread(parent Breadcrumber, selfID string, unread, mentioned bool) {
if u, ok := parent.(Unreadabler); ok {
u.SetState(selfID, unread, mentioned)
}
}
// UnreadSetter is an interface that a single row implements to set state. It
// does not have to do with Breadcrumber.
type UnreadSetter interface {
SetUnreadUnsafe(unread, mentioned bool)
}
// Unreadable is a struct that nodes could embed to implement unreadable
// capability, that is, the unread and mentioned states. A zero-value Unreadable
// is a valid Unreadable without an update handler.
//
// Typically, parent nodes would implement this as a way to count the number of
// unread and mentioned children nodes.
type Unreadable struct {
UnreadableState
unreadHandler func(unread, mentioned bool)
}
func NewUnreadable(unreadHandler UnreadSetter) *Unreadable {
u := &Unreadable{}
u.SetUnreadHandler(unreadHandler.SetUnreadUnsafe)
return u
}
// SetUpdateHandler sets the parent's update handler. This update handler must
// refer to the parent's breadcrumb.
func (u *Unreadable) SetUnreadHandler(updateHandler func(unread, mentioned bool)) {
// Update with the current state.
if u.unreadHandler = updateHandler; updateHandler != nil {
updateHandler(u.State())
}
}
// SetState updates the node ID's state in this parent unreadable state
// container.
func (u *Unreadable) SetState(id string, unread, mentioned bool) {
u.UnreadableState.SetState(id, unread, mentioned)
if u.unreadHandler != nil {
u.unreadHandler(u.UnreadableState.State())
}
}
// UnreadableState implements a map of unread children for indication. A
// zero-value UnreadableState is a valid value.
type UnreadableState struct {
// both maps represent sets of server IDs
unreads map[string]struct{}
mentions map[string]struct{}
}
func NewUnreadableState() *UnreadableState {
return &UnreadableState{}
}
func (s *UnreadableState) Reset() {
s.unreads = map[string]struct{}{}
s.mentions = map[string]struct{}{}
}
func (s *UnreadableState) State() (unread, mentioned bool) {
unread = len(s.unreads) > 0
mentioned = len(s.mentions) > 0
// Count mentioned as unread.
return unread || mentioned, mentioned
}
func (s *UnreadableState) SetState(id string, unread, mentioned bool) {
if s.unreads == nil && s.mentions == nil {
s.Reset()
}
setIf(unread, id, s.unreads)
setIf(mentioned, id, s.mentions)
}
// setIf sets the ID into the given map if the cond boolean is true, or deletes
// it if the boolean is false.
func setIf(cond bool, id string, m map[string]struct{}) {
if cond {
m[id] = struct{}{}
} else {
delete(m, id)
}
}