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, "<>") - } 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 - } + renderHeight := trl.RenderHeight() + if y + renderHeight >= yLimit { + renderHeight = yLimit - y } - - 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 + trl.SetRect(x, y, width, renderHeight) + trl.Draw(screen) + y += renderHeight + if y >= yLimit { + break } - 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 - } - } - 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++ - } - - 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 . + +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, "<>") + } 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++ + } +}