mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-11-01 20:14:16 +00:00
135 lines
4 KiB
Go
135 lines
4 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)
|
||
|
}
|
||
|
}
|