msglist.go (4257B)
1 package widgets 2 3 import ( 4 "log" 5 6 "github.com/gdamore/tcell" 7 8 "git.sr.ht/~sircmpwn/aerc/config" 9 "git.sr.ht/~sircmpwn/aerc/lib" 10 "git.sr.ht/~sircmpwn/aerc/lib/ui" 11 "git.sr.ht/~sircmpwn/aerc/worker/types" 12 ) 13 14 type MessageList struct { 15 ui.Invalidatable 16 conf *config.AercConfig 17 logger *log.Logger 18 height int 19 scroll int 20 selected int 21 nmsgs int 22 spinner *Spinner 23 store *lib.MessageStore 24 } 25 26 func NewMessageList(conf *config.AercConfig, logger *log.Logger) *MessageList { 27 ml := &MessageList{ 28 conf: conf, 29 logger: logger, 30 selected: 0, 31 spinner: NewSpinner(), 32 } 33 ml.spinner.OnInvalidate(func(_ ui.Drawable) { 34 ml.Invalidate() 35 }) 36 // TODO: stop spinner, probably 37 ml.spinner.Start() 38 return ml 39 } 40 41 func (ml *MessageList) Invalidate() { 42 ml.DoInvalidate(ml) 43 } 44 45 func (ml *MessageList) Draw(ctx *ui.Context) { 46 ml.height = ctx.Height() 47 ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) 48 49 store := ml.Store() 50 if store == nil { 51 ml.spinner.Draw(ctx) 52 return 53 } 54 55 var ( 56 needsHeaders []uint32 57 row int = 0 58 ) 59 60 for i := len(store.Uids) - 1 - ml.scroll; i >= 0; i-- { 61 uid := store.Uids[i] 62 msg := store.Messages[uid] 63 64 if row >= ctx.Height() { 65 break 66 } 67 68 if msg == nil { 69 needsHeaders = append(needsHeaders, uid) 70 ml.spinner.Draw(ctx.Subcontext(0, row, ctx.Width(), 1)) 71 row += 1 72 continue 73 } 74 75 style := tcell.StyleDefault 76 if row == ml.selected-ml.scroll { 77 style = style.Foreground(tcell.ColorBlue).Reverse(true) 78 } 79 if _, ok := store.Deleted[msg.Uid]; ok { 80 style = style.Foreground(tcell.ColorGray) 81 } 82 ctx.Fill(0, row, ctx.Width(), 1, ' ', style) 83 ctx.Printf(0, row, style, "%s", msg.Envelope.Subject) 84 85 row += 1 86 } 87 88 if len(store.Uids) == 0 { 89 msg := ml.conf.Ui.EmptyMessage 90 ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0, 91 tcell.StyleDefault, "%s", msg) 92 } 93 94 if len(needsHeaders) != 0 { 95 store.FetchHeaders(needsHeaders, nil) 96 ml.spinner.Start() 97 } else { 98 ml.spinner.Stop() 99 } 100 } 101 102 func (ml *MessageList) Height() int { 103 return ml.height 104 } 105 106 func (ml *MessageList) storeUpdate(store *lib.MessageStore) { 107 if ml.Store() != store { 108 return 109 } 110 111 if len(store.Uids) > 0 { 112 // When new messages come in, advance the cursor accordingly 113 // Note that this assumes new messages are appended to the top, which 114 // isn't necessarily true once we implement SORT... ideally we'd look 115 // for the previously selected UID. 116 if len(store.Uids) > ml.nmsgs && ml.nmsgs != 0 { 117 for i := 0; i < len(store.Uids)-ml.nmsgs; i++ { 118 ml.Next() 119 } 120 } 121 if len(store.Uids) < ml.nmsgs && ml.nmsgs != 0 { 122 for i := 0; i < ml.nmsgs-len(store.Uids); i++ { 123 ml.Prev() 124 } 125 } 126 ml.nmsgs = len(store.Uids) 127 } 128 129 ml.Invalidate() 130 } 131 132 func (ml *MessageList) SetStore(store *lib.MessageStore) { 133 if ml.Store() != store { 134 ml.scroll = 0 135 ml.selected = 0 136 } 137 ml.store = store 138 if store != nil { 139 ml.spinner.Stop() 140 ml.nmsgs = len(store.Uids) 141 store.OnUpdate(ml.storeUpdate) 142 } else { 143 ml.spinner.Start() 144 } 145 ml.Invalidate() 146 } 147 148 func (ml *MessageList) Store() *lib.MessageStore { 149 return ml.store 150 } 151 152 func (ml *MessageList) Empty() bool { 153 store := ml.Store() 154 return store == nil || len(store.Uids) == 0 155 } 156 157 func (ml *MessageList) Selected() *types.MessageInfo { 158 store := ml.Store() 159 return store.Messages[store.Uids[len(store.Uids)-ml.selected-1]] 160 } 161 162 func (ml *MessageList) Select(index int) { 163 store := ml.Store() 164 165 ml.selected = index 166 for ; ml.selected < 0; ml.selected = len(store.Uids) + ml.selected { 167 } 168 if ml.selected > len(store.Uids) { 169 ml.selected = len(store.Uids) 170 } 171 // I'm too lazy to do the math right now 172 for ml.selected-ml.scroll >= ml.Height() { 173 ml.scroll += 1 174 } 175 for ml.selected-ml.scroll < 0 { 176 ml.scroll -= 1 177 } 178 } 179 180 func (ml *MessageList) nextPrev(delta int) { 181 store := ml.Store() 182 183 if store == nil || len(store.Uids) == 0 { 184 return 185 } 186 ml.selected += delta 187 if ml.selected < 0 { 188 ml.selected = 0 189 } 190 if ml.selected >= len(store.Uids) { 191 ml.selected = len(store.Uids) - 1 192 } 193 if ml.Height() != 0 { 194 if ml.selected-ml.scroll >= ml.Height() { 195 ml.scroll += 1 196 } else if ml.selected-ml.scroll < 0 { 197 ml.scroll -= 1 198 } 199 } 200 ml.Invalidate() 201 } 202 203 func (ml *MessageList) Next() { 204 ml.nextPrev(1) 205 } 206 207 func (ml *MessageList) Prev() { 208 ml.nextPrev(-1) 209 }