aboutsummaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
authorTulir Asokan <tulir@maunium.net>2018-05-22 18:30:25 +0300
committerTulir Asokan <tulir@maunium.net>2018-05-22 18:30:25 +0300
commitf5e07f40db87b221fafd081bda9801abd3c86376 (patch)
tree5821fd8dfc94a27a5e73d67d7402b767e0e60970 /ui
parentc87097627e89ffbe7dd92f3bc3ffe62f43ba2ca7 (diff)
Move TagRoomList stuff into new file and split RoomList.Draw()
Diffstat (limited to 'ui')
-rw-r--r--ui/room-list.go452
-rw-r--r--ui/tag-room-list.go314
2 files changed, 392 insertions, 374 deletions
diff --git a/ui/room-list.go b/ui/room-list.go
index 75d874f..5c40c44 100644
--- a/ui/room-list.go
+++ b/ui/room-list.go
@@ -17,210 +17,23 @@
package ui
import (
- "fmt"
"regexp"
- "strconv"
"strings"
"math"
"maunium.net/go/gomuks/debug"
"maunium.net/go/gomuks/matrix/rooms"
- "maunium.net/go/gomuks/ui/widget"
"maunium.net/go/tcell"
"maunium.net/go/tview"
)
-type orderedRoom struct {
- *rooms.Room
- order string
-}
-
-func newOrderedRoom(order string, room *rooms.Room) *orderedRoom {
- return &orderedRoom{
- Room: room,
- order: order,
- }
-}
-
-func convertRoom(room *rooms.Room) *orderedRoom {
- return newOrderedRoom("0.5", room)
-}
-
-type tagRoomList struct {
- rooms []*orderedRoom
- maxShown int
-}
-
-func newTagRoomList(rooms ...*orderedRoom) *tagRoomList {
- return &tagRoomList{
- maxShown: 10,
- rooms: rooms,
- }
-}
-
-func (trl *tagRoomList) Visible() []*orderedRoom {
- return trl.rooms[len(trl.rooms)-trl.Length():]
-}
-
-func (trl *tagRoomList) FirstVisible() *rooms.Room {
- visible := trl.Visible()
- if len(visible) > 0 {
- return visible[len(visible)-1].Room
- }
- return nil
-}
-
-func (trl *tagRoomList) LastVisible() *rooms.Room {
- visible := trl.Visible()
- if len(visible) > 0 {
- return visible[0].Room
- }
- return nil
-}
-
-func (trl *tagRoomList) All() []*orderedRoom {
- return trl.rooms
-}
-
-func (trl *tagRoomList) Length() int {
- if len(trl.rooms) < trl.maxShown {
- return len(trl.rooms)
- }
- return trl.maxShown
-}
-
-func (trl *tagRoomList) TotalLength() int {
- return len(trl.rooms)
-}
-
-func (trl *tagRoomList) IsEmpty() bool {
- return len(trl.rooms) == 0
-}
-
-func (trl *tagRoomList) IsCollapsed() bool {
- return trl.maxShown == 0
-}
-
-func (trl *tagRoomList) ToggleCollapse() {
- if trl.IsCollapsed() {
- trl.maxShown = 10
- } else {
- trl.maxShown = 0
- }
-}
-
-func (trl *tagRoomList) HasInvisibleRooms() bool {
- return trl.maxShown < trl.TotalLength()
-}
-
-func (trl *tagRoomList) HasVisibleRooms() bool {
- return !trl.IsEmpty() && trl.maxShown > 0
-}
-
-// ShouldBeBefore returns if the first room should be after the second room in the room list.
-// The manual order and last received message timestamp are considered.
-func (trl *tagRoomList) ShouldBeAfter(room1 *orderedRoom, room2 *orderedRoom) bool {
- orderComp := strings.Compare(room1.order, room2.order)
- return orderComp == 1 || (orderComp == 0 && room2.LastReceivedMessage.After(room1.LastReceivedMessage))
-}
-
-func (trl *tagRoomList) Insert(order string, mxRoom *rooms.Room) {
- room := newOrderedRoom(order, mxRoom)
- trl.rooms = append(trl.rooms, nil)
- // The default insert index is the newly added slot.
- // That index will be used if all other rooms in the list have the same LastReceivedMessage timestamp.
- insertAt := len(trl.rooms) - 1
- // Find the spot where the new room should be put according to the last received message timestamps.
- for i := 0; i < len(trl.rooms)-1; i++ {
- if trl.ShouldBeAfter(room, trl.rooms[i]) {
- insertAt = i
- break
- }
- }
- // Move newer rooms forward in the array.
- for i := len(trl.rooms) - 1; i > insertAt; i-- {
- trl.rooms[i] = trl.rooms[i-1]
- }
- // Insert room.
- trl.rooms[insertAt] = room
-}
-
-func (trl *tagRoomList) String() string {
- var str strings.Builder
- fmt.Fprintln(&str, "&tagRoomList{")
- fmt.Fprintf(&str, " maxShown: %d,\n", trl.maxShown)
- fmt.Fprint(&str, " rooms: {")
- for i, room := range trl.rooms {
- if room == nil {
- fmt.Fprintf(&str, "<<NIL>>")
- } else {
- fmt.Fprint(&str, room.ID)
- }
- if i != len(trl.rooms)-1 {
- fmt.Fprint(&str, ", ")
- }
- }
- fmt.Fprintln(&str, "},")
- fmt.Fprintln(&str, "}")
- return str.String()
-}
-
-func (trl *tagRoomList) Bump(mxRoom *rooms.Room) {
- var found *orderedRoom
- for i := 0; i < len(trl.rooms); i++ {
- currentRoom := trl.rooms[i]
- if found != nil {
- if trl.ShouldBeAfter(found, trl.rooms[i]) {
- // This room should be after the room being bumped, so insert the
- // room being bumped here and return
- trl.rooms[i-1] = found
- return
- }
- // Move older rooms back in the array
- trl.rooms[i-1] = currentRoom
- } else if currentRoom.Room == mxRoom {
- found = currentRoom
- }
- }
- // If the room being bumped should be first in the list, it won't be inserted during the loop.
- trl.rooms[len(trl.rooms)-1] = found
-}
-
-func (trl *tagRoomList) Remove(room *rooms.Room) {
- trl.RemoveIndex(trl.Index(room))
-}
-
-func (trl *tagRoomList) RemoveIndex(index int) {
- if index < 0 || index > len(trl.rooms) {
- return
- }
- trl.rooms = append(trl.rooms[0:index], trl.rooms[index+1:]...)
-}
-
-func (trl *tagRoomList) Index(room *rooms.Room) int {
- return trl.indexInList(trl.All(), room)
-}
-
-func (trl *tagRoomList) IndexVisible(room *rooms.Room) int {
- return trl.indexInList(trl.Visible(), room)
-}
-
-func (trl *tagRoomList) indexInList(list []*orderedRoom, room *rooms.Room) int {
- for index, entry := range list {
- if entry.Room == room {
- return index
- }
- }
- return -1
-}
-
type RoomList struct {
*tview.Box
// The list of tags in display order.
tags []string
// The list of rooms, in reverse order.
- items map[string]*tagRoomList
+ items map[string]*TagRoomList
// The selected room.
selected *rooms.Room
selectedTag string
@@ -238,7 +51,7 @@ type RoomList struct {
func NewRoomList() *RoomList {
list := &RoomList{
Box: tview.NewBox(),
- items: make(map[string]*tagRoomList),
+ items: make(map[string]*TagRoomList),
tags: []string{"m.favourite", "net.maunium.gomuks.fake.direct", "", "m.lowpriority"},
scrollOffset: 0,
@@ -248,14 +61,14 @@ func NewRoomList() *RoomList {
selectedBackgroundColor: tcell.ColorDarkGreen,
}
for _, tag := range list.tags {
- list.items[tag] = newTagRoomList()
+ list.items[tag] = NewTagRoomList(list, tag)
}
return list
}
func (list *RoomList) Contains(roomID string) bool {
- for _, tagRoomList := range list.items {
- for _, room := range tagRoomList.All() {
+ for _, trl := range list.items {
+ for _, room := range trl.All() {
if room.ID == roomID {
return true
}
@@ -274,9 +87,9 @@ func (list *RoomList) Add(room *rooms.Room) {
func (list *RoomList) CheckTag(tag string) {
index := list.IndexTag(tag)
- tagRoomList, ok := list.items[tag]
+ trl, ok := list.items[tag]
- if ok && tagRoomList.IsEmpty() {
+ if ok && trl.IsEmpty() {
//delete(list.items, tag)
ok = false
}
@@ -290,13 +103,13 @@ func (list *RoomList) CheckTag(tag string) {
}
func (list *RoomList) AddToTag(tag rooms.RoomTag, room *rooms.Room) {
- tagRoomList, ok := list.items[tag.Tag]
+ trl, ok := list.items[tag.Tag]
if !ok {
- list.items[tag.Tag] = newTagRoomList(convertRoom(room))
+ list.items[tag.Tag] = NewTagRoomList(list, tag.Tag, convertRoom(room))
return
}
- tagRoomList.Insert(tag.Order, room)
+ trl.Insert(tag.Order, room)
list.CheckTag(tag.Tag)
}
@@ -307,27 +120,27 @@ func (list *RoomList) Remove(room *rooms.Room) {
}
func (list *RoomList) RemoveFromTag(tag string, room *rooms.Room) {
- tagRoomList, ok := list.items[tag]
+ trl, ok := list.items[tag]
if !ok {
return
}
- index := tagRoomList.Index(room)
+ index := trl.Index(room)
if index == -1 {
return
}
- tagRoomList.RemoveIndex(index)
+ trl.RemoveIndex(index)
- if tagRoomList.IsEmpty() {
+ if trl.IsEmpty() {
// delete(list.items, tag)
}
if room == list.selected {
if index > 0 {
- list.selected = tagRoomList.All()[index-1].Room
- } else if tagRoomList.Length() > 0 {
- list.selected = tagRoomList.Visible()[0].Room
+ list.selected = trl.All()[index-1].Room
+ } else if trl.Length() > 0 {
+ list.selected = trl.Visible()[0].Room
} else if len(list.items) > 0 {
for _, tag := range list.tags {
moreItems := list.items[tag]
@@ -346,19 +159,19 @@ func (list *RoomList) RemoveFromTag(tag string, room *rooms.Room) {
func (list *RoomList) Bump(room *rooms.Room) {
for _, tag := range room.Tags() {
- tagRoomList, ok := list.items[tag.Tag]
+ trl, ok := list.items[tag.Tag]
if !ok {
return
}
- tagRoomList.Bump(room)
+ trl.Bump(room)
}
}
func (list *RoomList) Clear() {
- list.items = make(map[string]*tagRoomList)
+ list.items = make(map[string]*TagRoomList)
list.tags = []string{"m.favourite", "net.maunium.gomuks.fake.direct", "", "m.lowpriority"}
for _, tag := range list.tags {
- list.items[tag] = newTagRoomList()
+ list.items[tag] = NewTagRoomList(list, tag)
}
list.selected = nil
list.selectedTag = ""
@@ -406,9 +219,9 @@ func (list *RoomList) AddScrollOffset(offset int) {
func (list *RoomList) First() (string, *rooms.Room) {
for _, tag := range list.tags {
- tagRoomList := list.items[tag]
- if tagRoomList.HasVisibleRooms() {
- return tag, tagRoomList.FirstVisible()
+ trl := list.items[tag]
+ if trl.HasVisibleRooms() {
+ return tag, trl.FirstVisible()
}
}
return "", nil
@@ -417,9 +230,9 @@ func (list *RoomList) First() (string, *rooms.Room) {
func (list *RoomList) Last() (string, *rooms.Room) {
for tagIndex := len(list.tags) - 1; tagIndex >= 0; tagIndex-- {
tag := list.tags[tagIndex]
- tagRoomList := list.items[tag]
- if tagRoomList.HasVisibleRooms() {
- return tag, tagRoomList.LastVisible()
+ trl := list.items[tag]
+ if trl.HasVisibleRooms() {
+ return tag, trl.LastVisible()
}
}
return "", nil
@@ -441,28 +254,28 @@ func (list *RoomList) Previous() (string, *rooms.Room) {
return list.First()
}
- tagRoomList := list.items[list.selectedTag]
- index := tagRoomList.IndexVisible(list.selected)
- indexInvisible := tagRoomList.Index(list.selected)
+ trl := list.items[list.selectedTag]
+ index := trl.IndexVisible(list.selected)
+ indexInvisible := trl.Index(list.selected)
if index == -1 && indexInvisible >= 0 {
- num := tagRoomList.TotalLength() - indexInvisible
- tagRoomList.maxShown = int(math.Ceil(float64(num)/10.0) * 10.0)
- index = tagRoomList.IndexVisible(list.selected)
+ num := trl.TotalLength() - indexInvisible
+ trl.maxShown = int(math.Ceil(float64(num)/10.0) * 10.0)
+ index = trl.IndexVisible(list.selected)
}
- if index == tagRoomList.Length()-1 {
+ if index == trl.Length()-1 {
tagIndex := list.IndexTag(list.selectedTag)
tagIndex--
for ; tagIndex >= 0; tagIndex-- {
prevTag := list.tags[tagIndex]
- prevTagRoomList := list.items[prevTag]
- if prevTagRoomList.HasVisibleRooms() {
- return prevTag, prevTagRoomList.LastVisible()
+ prevTRL := list.items[prevTag]
+ if prevTRL.HasVisibleRooms() {
+ return prevTag, prevTRL.LastVisible()
}
}
return list.Last()
} else if index >= 0 {
- return list.selectedTag, tagRoomList.Visible()[index+1].Room
+ return list.selectedTag, trl.Visible()[index+1].Room
}
return list.First()
}
@@ -474,13 +287,13 @@ func (list *RoomList) Next() (string, *rooms.Room) {
return list.First()
}
- tagRoomList := list.items[list.selectedTag]
- index := tagRoomList.IndexVisible(list.selected)
- indexInvisible := tagRoomList.Index(list.selected)
+ trl := list.items[list.selectedTag]
+ index := trl.IndexVisible(list.selected)
+ indexInvisible := trl.Index(list.selected)
if index == -1 && indexInvisible >= 0 {
- num := tagRoomList.TotalLength() - indexInvisible + 1
- tagRoomList.maxShown = int(math.Ceil(float64(num)/10.0) * 10.0)
- index = tagRoomList.IndexVisible(list.selected)
+ num := trl.TotalLength() - indexInvisible + 1
+ trl.maxShown = int(math.Ceil(float64(num)/10.0) * 10.0)
+ index = trl.IndexVisible(list.selected)
}
if index == 0 {
@@ -488,14 +301,14 @@ func (list *RoomList) Next() (string, *rooms.Room) {
tagIndex++
for ; tagIndex < len(list.tags); tagIndex++ {
nextTag := list.tags[tagIndex]
- nextTagRoomList := list.items[nextTag]
- if nextTagRoomList.HasVisibleRooms() {
- return nextTag, nextTagRoomList.FirstVisible()
+ nextTRL := list.items[nextTag]
+ if nextTRL.HasVisibleRooms() {
+ return nextTag, nextTRL.FirstVisible()
}
}
return list.First()
} else if index > 0 {
- return list.selectedTag, tagRoomList.Visible()[index-1].Room
+ return list.selectedTag, trl.Visible()[index-1].Room
}
return list.Last()
}
@@ -506,39 +319,24 @@ func (list *RoomList) index(tag string, room *rooms.Room) int {
return -1
}
- tagRoomList, ok := list.items[tag]
+ trl, ok := list.items[tag]
localIndex := -1
if ok {
- localIndex = tagRoomList.IndexVisible(room)
+ localIndex = trl.IndexVisible(room)
}
if localIndex == -1 {
return -1
}
- localIndex = tagRoomList.Length() - 1 - localIndex
+ localIndex = trl.Length() - 1 - localIndex
// Tag header
localIndex += 1
if tagIndex > 0 {
for i := 0; i < tagIndex; i++ {
- previousTag := list.tags[i]
- previousTagRoomList := list.items[previousTag]
-
- tagDisplayName := list.GetTagDisplayName(previousTag)
- if len(tagDisplayName) > 0 {
- if previousTagRoomList.IsCollapsed() {
- localIndex++
- continue
- }
- // Previous tag header + space
- localIndex += 2
- if previousTagRoomList.HasInvisibleRooms() {
- // Previous tag "Show more" button
- localIndex++
- }
- // Previous tag items
- localIndex += previousTagRoomList.Length()
- }
+ prevTag := list.tags[i]
+ prevTRL := list.items[prevTag]
+ localIndex += prevTRL.RenderHeight()
}
}
@@ -547,19 +345,7 @@ func (list *RoomList) index(tag string, room *rooms.Room) int {
func (list *RoomList) ContentHeight() (height int) {
for _, tag := range list.tags {
- tagRoomList := list.items[tag]
- tagDisplayName := list.GetTagDisplayName(tag)
- if len(tagDisplayName) == 0 {
- continue
- }
- if tagRoomList.IsCollapsed() {
- height++
- continue
- }
- height += 2 + tagRoomList.Length()
- if tagRoomList.HasInvisibleRooms() {
- height++
- }
+ height += list.items[tag].RenderHeight()
}
return
}
@@ -570,27 +356,27 @@ func (list *RoomList) HandleClick(column, line int, mod bool) (string, *rooms.Ro
return "", nil
}
for _, tag := range list.tags {
- tagRoomList := list.items[tag]
+ trl := list.items[tag]
if line--; line == -1 {
- tagRoomList.ToggleCollapse()
+ trl.ToggleCollapse()
return "", nil
}
- if tagRoomList.IsCollapsed() {
+ if trl.IsCollapsed() {
continue
}
if line < 0 {
return "", nil
- } else if line < tagRoomList.Length() {
- return tag, tagRoomList.Visible()[tagRoomList.Length()-1-line].Room
+ } else if line < trl.Length() {
+ return tag, trl.Visible()[trl.Length()-1-line].Room
}
// Tag items
- line -= tagRoomList.Length()
+ line -= trl.Length()
- hasMore := tagRoomList.HasInvisibleRooms()
- hasLess := tagRoomList.maxShown > 10
+ hasMore := trl.HasInvisibleRooms()
+ hasLess := trl.maxShown > 10
if hasMore || hasLess {
if line--; line == -1 {
diff := 10
@@ -599,12 +385,12 @@ func (list *RoomList) HandleClick(column, line int, mod bool) (string, *rooms.Ro
}
_, _, width, _ := list.GetRect()
if column <= 6 && hasLess {
- tagRoomList.maxShown -= diff
+ trl.maxShown -= diff
} else if column >= width-6 && hasMore {
- tagRoomList.maxShown += diff
+ trl.maxShown += diff
}
- if tagRoomList.maxShown < 10 {
- tagRoomList.maxShown = 10
+ if trl.maxShown < 10 {
+ trl.maxShown = 10
}
return "", nil
}
@@ -641,108 +427,26 @@ func (list *RoomList) Draw(screen tcell.Screen) {
list.Box.Draw(screen)
x, y, width, height := list.GetInnerRect()
- bottomLimit := y + height
-
- handledOffset := 0
+ yLimit := y + height
+ y -= list.scrollOffset
// Draw the list items.
for _, tag := range list.tags {
- tagRoomList := list.items[tag]
+ trl := list.items[tag]
tagDisplayName := list.GetTagDisplayName(tag)
if len(tagDisplayName) == 0 {
continue
}
- localOffset := 0
-
- if handledOffset < list.scrollOffset {
- if handledOffset+tagRoomList.Length() < list.scrollOffset {
- if tagRoomList.IsCollapsed() {
- handledOffset++
- } else {
- handledOffset += tagRoomList.Length() + 2
- if tagRoomList.HasInvisibleRooms() || tagRoomList.maxShown > 10 {
- handledOffset++
- }
- }
- continue
- } else {
- localOffset = list.scrollOffset - handledOffset
- handledOffset += localOffset
- }
- }
-
- roomCount := strconv.Itoa(tagRoomList.TotalLength())
- widget.WriteLine(screen, tview.AlignLeft, tagDisplayName, x, y, width-1-len(roomCount), tcell.StyleDefault.Underline(true).Bold(true))
- widget.WriteLine(screen, tview.AlignLeft, roomCount, x+len(tagDisplayName)+1, y, width-2-len(tagDisplayName), tcell.StyleDefault.Italic(true))
-
- items := tagRoomList.Visible()
-
- if tagRoomList.IsCollapsed() {
- screen.SetCell(x+width-1, y, tcell.StyleDefault, '▶')
- y++
- continue
- }
- screen.SetCell(x+width-1, y, tcell.StyleDefault, '▼')
- y++
-
- for i := tagRoomList.Length() - 1; i >= 0; i-- {
- item := items[i]
- index := len(items) - 1 - i
-
- if y >= bottomLimit {
- break
- }
-
- if index < localOffset {
- continue
- }
-
- text := item.GetTitle()
-
- lineWidth := width
-
- style := tcell.StyleDefault.Foreground(list.mainTextColor)
- if tag == list.selectedTag && item.Room == list.selected {
- style = style.Foreground(list.selectedTextColor).Background(list.selectedBackgroundColor)
- }
- if item.HasNewMessages() {
- style = style.Bold(true)
- }
-
- unreadCount := item.UnreadCount()
- if unreadCount > 0 {
- unreadMessageCount := "99+"
- if unreadCount < 100 {
- unreadMessageCount = strconv.Itoa(unreadCount)
- }
- if item.Highlighted() {
- unreadMessageCount += "!"
- }
- unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
- widget.WriteLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-7, y, 7, style)
- lineWidth -= len(unreadMessageCount)
- }
-
- widget.WriteLinePadded(screen, tview.AlignLeft, text, x, y, lineWidth, style)
- y++
-
- if y >= bottomLimit {
- break
- }
+ renderHeight := trl.RenderHeight()
+ if y + renderHeight >= yLimit {
+ renderHeight = yLimit - y
}
- hasLess := tagRoomList.maxShown > 10
- hasMore := tagRoomList.HasInvisibleRooms()
- if hasLess || hasMore {
- if hasMore {
- widget.WriteLine(screen, tview.AlignRight, "More ↓", x, y, width, tcell.StyleDefault)
- }
- if hasLess {
- widget.WriteLine(screen, tview.AlignLeft, "↑ Less", x, y, width, tcell.StyleDefault)
- }
- y++
+ trl.SetRect(x, y, width, renderHeight)
+ trl.Draw(screen)
+ y += renderHeight
+ if y >= yLimit {
+ break
}
-
- y++
}
}
diff --git a/ui/tag-room-list.go b/ui/tag-room-list.go
new file mode 100644
index 0000000..fb61c54
--- /dev/null
+++ b/ui/tag-room-list.go
@@ -0,0 +1,314 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package ui
+
+import (
+ "maunium.net/go/gomuks/matrix/rooms"
+ "strings"
+ "fmt"
+ "maunium.net/go/tcell"
+ "strconv"
+ "maunium.net/go/gomuks/ui/widget"
+ "maunium.net/go/tview"
+)
+
+type orderedRoom struct {
+ *rooms.Room
+ order string
+}
+
+func newOrderedRoom(order string, room *rooms.Room) *orderedRoom {
+ return &orderedRoom{
+ Room: room,
+ order: order,
+ }
+}
+
+func convertRoom(room *rooms.Room) *orderedRoom {
+ return newOrderedRoom("0.5", room)
+}
+
+type TagRoomList struct {
+ *tview.Box
+ rooms []*orderedRoom
+ maxShown int
+ name string
+ displayname string
+ parent *RoomList
+}
+
+func NewTagRoomList(parent *RoomList, name string, rooms ...*orderedRoom) *TagRoomList {
+ return &TagRoomList{
+ Box: tview.NewBox(),
+ maxShown: 10,
+ rooms: rooms,
+ name: name,
+ displayname: parent.GetTagDisplayName(name),
+ parent: parent,
+ }
+}
+
+func (trl *TagRoomList) Visible() []*orderedRoom {
+ return trl.rooms[len(trl.rooms)-trl.Length():]
+}
+
+func (trl *TagRoomList) FirstVisible() *rooms.Room {
+ visible := trl.Visible()
+ if len(visible) > 0 {
+ return visible[len(visible)-1].Room
+ }
+ return nil
+}
+
+func (trl *TagRoomList) LastVisible() *rooms.Room {
+ visible := trl.Visible()
+ if len(visible) > 0 {
+ return visible[0].Room
+ }
+ return nil
+}
+
+func (trl *TagRoomList) All() []*orderedRoom {
+ return trl.rooms
+}
+
+func (trl *TagRoomList) Length() int {
+ if len(trl.rooms) < trl.maxShown {
+ return len(trl.rooms)
+ }
+ return trl.maxShown
+}
+
+func (trl *TagRoomList) TotalLength() int {
+ return len(trl.rooms)
+}
+
+func (trl *TagRoomList) IsEmpty() bool {
+ return len(trl.rooms) == 0
+}
+
+func (trl *TagRoomList) IsCollapsed() bool {
+ return trl.maxShown == 0
+}
+
+func (trl *TagRoomList) ToggleCollapse() {
+ if trl.IsCollapsed() {
+ trl.maxShown = 10
+ } else {
+ trl.maxShown = 0
+ }
+}
+
+func (trl *TagRoomList) HasInvisibleRooms() bool {
+ return trl.maxShown < trl.TotalLength()
+}
+
+func (trl *TagRoomList) HasVisibleRooms() bool {
+ return !trl.IsEmpty() && trl.maxShown > 0
+}
+
+// ShouldBeBefore returns if the first room should be after the second room in the room list.
+// The manual order and last received message timestamp are considered.
+func (trl *TagRoomList) ShouldBeAfter(room1 *orderedRoom, room2 *orderedRoom) bool {
+ orderComp := strings.Compare(room1.order, room2.order)
+ return orderComp == 1 || (orderComp == 0 && room2.LastReceivedMessage.After(room1.LastReceivedMessage))
+}
+
+func (trl *TagRoomList) Insert(order string, mxRoom *rooms.Room) {
+ room := newOrderedRoom(order, mxRoom)
+ trl.rooms = append(trl.rooms, nil)
+ // The default insert index is the newly added slot.
+ // That index will be used if all other rooms in the list have the same LastReceivedMessage timestamp.
+ insertAt := len(trl.rooms) - 1
+ // Find the spot where the new room should be put according to the last received message timestamps.
+ for i := 0; i < len(trl.rooms)-1; i++ {
+ if trl.ShouldBeAfter(room, trl.rooms[i]) {
+ insertAt = i
+ break
+ }
+ }
+ // Move newer rooms forward in the array.
+ for i := len(trl.rooms) - 1; i > insertAt; i-- {
+ trl.rooms[i] = trl.rooms[i-1]
+ }
+ // Insert room.
+ trl.rooms[insertAt] = room
+}
+
+func (trl *TagRoomList) String() string {
+ var str strings.Builder
+ fmt.Fprintln(&str, "&TagRoomList{")
+ fmt.Fprintf(&str, " maxShown: %d,\n", trl.maxShown)
+ fmt.Fprint(&str, " rooms: {")
+ for i, room := range trl.rooms {
+ if room == nil {
+ fmt.Fprintf(&str, "<<NIL>>")
+ } else {
+ fmt.Fprint(&str, room.ID)
+ }
+ if i != len(trl.rooms)-1 {
+ fmt.Fprint(&str, ", ")
+ }
+ }
+ fmt.Fprintln(&str, "},")
+ fmt.Fprintln(&str, "}")
+ return str.String()
+}
+
+func (trl *TagRoomList) Bump(mxRoom *rooms.Room) {
+ var found *orderedRoom
+ for i := 0; i < len(trl.rooms); i++ {
+ currentRoom := trl.rooms[i]
+ if found != nil {
+ if trl.ShouldBeAfter(found, trl.rooms[i]) {
+ // This room should be after the room being bumped, so insert the
+ // room being bumped here and return
+ trl.rooms[i-1] = found
+ return
+ }
+ // Move older rooms back in the array
+ trl.rooms[i-1] = currentRoom
+ } else if currentRoom.Room == mxRoom {
+ found = currentRoom
+ }
+ }
+ // If the room being bumped should be first in the list, it won't be inserted during the loop.
+ trl.rooms[len(trl.rooms)-1] = found
+}
+
+func (trl *TagRoomList) Remove(room *rooms.Room) {
+ trl.RemoveIndex(trl.Index(room))
+}
+
+func (trl *TagRoomList) RemoveIndex(index int) {
+ if index < 0 || index > len(trl.rooms) {
+ return
+ }
+ trl.rooms = append(trl.rooms[0:index], trl.rooms[index+1:]...)
+}
+
+func (trl *TagRoomList) Index(room *rooms.Room) int {
+ return trl.indexInList(trl.All(), room)
+}
+
+func (trl *TagRoomList) IndexVisible(room *rooms.Room) int {
+ return trl.indexInList(trl.Visible(), room)
+}
+
+func (trl *TagRoomList) indexInList(list []*orderedRoom, room *rooms.Room) int {
+ for index, entry := range list {
+ if entry.Room == room {
+ return index
+ }
+ }
+ return -1
+}
+
+var TagDisplayNameStyle = tcell.StyleDefault.Underline(true).Bold(true)
+var TagRoomCountStyle = tcell.StyleDefault.Italic(true)
+
+func (trl *TagRoomList) RenderHeight() int {
+ if len(trl.displayname) == 0 {
+ return 0
+ }
+
+ if trl.IsCollapsed() {
+ return 1
+ }
+ height := 2 + trl.Length()
+ if trl.HasInvisibleRooms() || trl.maxShown > 10 {
+ height++
+ }
+ return height
+}
+
+func (trl *TagRoomList) Draw(screen tcell.Screen) {
+ if len(trl.displayname) == 0 {
+ return
+ }
+
+ x, y, width, height := trl.GetRect()
+ yLimit := y + height
+
+ roomCount := strconv.Itoa(trl.TotalLength())
+
+ // Draw tag name
+ displayNameWidth := width - 1 - len(roomCount)
+ widget.WriteLine(screen, tview.AlignLeft, trl.displayname, x, y, displayNameWidth, TagDisplayNameStyle)
+
+ // Draw tag room count
+ roomCountX := x + len(trl.displayname) + 1
+ roomCountWidth := width - 2 - len(trl.displayname)
+ widget.WriteLine(screen, tview.AlignLeft, roomCount, roomCountX, y, roomCountWidth, TagRoomCountStyle)
+
+ items := trl.Visible()
+
+ if trl.IsCollapsed() {
+ screen.SetCell(x+width-1, y, tcell.StyleDefault, '▶')
+ return
+ }
+ screen.SetCell(x+width-1, y, tcell.StyleDefault, '▼')
+
+ offsetY := 1
+ for i := trl.Length() - 1; i >= 0; i-- {
+ if y+offsetY >= yLimit {
+ return
+ }
+
+ item := items[i]
+
+ text := item.GetTitle()
+
+ lineWidth := width
+
+ style := tcell.StyleDefault.Foreground(trl.parent.mainTextColor)
+ if trl.name == trl.parent.selectedTag && item.Room == trl.parent.selected {
+ style = style.Foreground(trl.parent.selectedTextColor).Background(trl.parent.selectedBackgroundColor)
+ }
+ if item.HasNewMessages() {
+ style = style.Bold(true)
+ }
+
+ unreadCount := item.UnreadCount()
+ if unreadCount > 0 {
+ unreadMessageCount := "99+"
+ if unreadCount < 100 {
+ unreadMessageCount = strconv.Itoa(unreadCount)
+ }
+ if item.Highlighted() {
+ unreadMessageCount += "!"
+ }
+ unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
+ widget.WriteLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-7, y+offsetY, 7, style)
+ lineWidth -= len(unreadMessageCount)
+ }
+
+ widget.WriteLinePadded(screen, tview.AlignLeft, text, x, y+offsetY, lineWidth, style)
+ offsetY++
+ }
+ hasLess := trl.maxShown > 10
+ hasMore := trl.HasInvisibleRooms()
+ if (hasLess || hasMore) && y+offsetY < yLimit {
+ if hasMore {
+ widget.WriteLine(screen, tview.AlignRight, "More ↓", x, y+offsetY, width, tcell.StyleDefault)
+ }
+ if hasLess {
+ widget.WriteLine(screen, tview.AlignLeft, "↑ Less", x, y+offsetY, width, tcell.StyleDefault)
+ }
+ offsetY++
+ }
+}