Add Mouseable
This adds the Mouseable interface. When this is implemented for a component that item can accept and process mouseevents. At the top level when a mouse event is received it is passed to the grid's handler and then it trickles down until it reaches a component that can actually handle it, such as the tablist, dirlist or msglist. A mouse event is passed so that components can handle other things such as scrolling with the mousewheel. The components themselves then perform the necessary actions. Clicking emails in the messagelist opens them in a new tab. Textinputs can be clicked to position the cursor inside them. Mouseevents are not forwarded to the terminal at the moment. Elements which do not handle mouse events are not required to implement the Mouseable interface.
This commit is contained in:
parent
a441e3b3a5
commit
f6216bb621
14 changed files with 378 additions and 49 deletions
|
@ -66,3 +66,10 @@ func (bordered *Bordered) Draw(ctx *Context) {
|
|||
subctx := ctx.Subcontext(x, y, width, height)
|
||||
bordered.content.Draw(subctx)
|
||||
}
|
||||
|
||||
func (bordered *Bordered) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
switch content := bordered.content.(type) {
|
||||
case Mouseable:
|
||||
content.MouseEvent(localX, localY, event)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type Grid struct {
|
||||
|
@ -141,6 +143,45 @@ func (grid *Grid) Draw(ctx *Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (grid *Grid) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
invalid := grid.invalid
|
||||
|
||||
grid.mutex.RLock()
|
||||
defer grid.mutex.RUnlock()
|
||||
|
||||
for _, cell := range grid.cells {
|
||||
cellInvalid := cell.invalid.Load().(bool)
|
||||
if !cellInvalid && !invalid {
|
||||
continue
|
||||
}
|
||||
rows := grid.rowLayout[cell.Row : cell.Row+cell.RowSpan]
|
||||
cols := grid.columnLayout[cell.Column : cell.Column+cell.ColSpan]
|
||||
x := cols[0].Offset
|
||||
y := rows[0].Offset
|
||||
width := 0
|
||||
height := 0
|
||||
for _, col := range cols {
|
||||
width += col.Size
|
||||
}
|
||||
for _, row := range rows {
|
||||
height += row.Size
|
||||
}
|
||||
if x <= localX && localX < x+width && y <= localY && localY < y+height {
|
||||
switch content := cell.Content.(type) {
|
||||
case MouseableDrawableInteractive:
|
||||
content.MouseEvent(localX-x, localY-y, event)
|
||||
case Mouseable:
|
||||
content.MouseEvent(localX-x, localY-y, event)
|
||||
case MouseHandler:
|
||||
content.MouseEvent(localX-x, localY-y, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (grid *Grid) reflow(ctx *Context) {
|
||||
grid.rowLayout = nil
|
||||
grid.columnLayout = nil
|
||||
|
|
|
@ -50,9 +50,18 @@ type Container interface {
|
|||
Children() []Drawable
|
||||
}
|
||||
|
||||
// A drawable that can be clicked
|
||||
type Clickable interface {
|
||||
Drawable
|
||||
|
||||
MouseEvent(event tcell.Event)
|
||||
type MouseHandler interface {
|
||||
// Handle a mouse event which occurred at the local x and y positions
|
||||
MouseEvent(localX int, localY int, event tcell.Event)
|
||||
}
|
||||
|
||||
// A drawable that can be interacted with by the mouse
|
||||
type Mouseable interface {
|
||||
Drawable
|
||||
MouseHandler
|
||||
}
|
||||
|
||||
type MouseableDrawableInteractive interface {
|
||||
DrawableInteractive
|
||||
MouseHandler
|
||||
}
|
||||
|
|
|
@ -37,6 +37,15 @@ func (stack *Stack) Draw(ctx *Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (stack *Stack) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
if len(stack.children) > 0 {
|
||||
switch element := stack.Peek().(type) {
|
||||
case Mouseable:
|
||||
element.MouseEvent(localX, localY, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (stack *Stack) Push(d Drawable) {
|
||||
if len(stack.children) != 0 {
|
||||
stack.Peek().OnInvalidate(nil)
|
||||
|
|
102
lib/ui/tab.go
102
lib/ui/tab.go
|
@ -14,6 +14,9 @@ type Tabs struct {
|
|||
|
||||
onInvalidateStrip func(d Drawable)
|
||||
onInvalidateContent func(d Drawable)
|
||||
|
||||
parent *Tabs
|
||||
CloseTab func(index int)
|
||||
}
|
||||
|
||||
type Tab struct {
|
||||
|
@ -28,7 +31,9 @@ type TabContent Tabs
|
|||
func NewTabs() *Tabs {
|
||||
tabs := &Tabs{}
|
||||
tabs.TabStrip = (*TabStrip)(tabs)
|
||||
tabs.TabStrip.parent = tabs
|
||||
tabs.TabContent = (*TabContent)(tabs)
|
||||
tabs.TabContent.parent = tabs
|
||||
tabs.history = []int{}
|
||||
return tabs
|
||||
}
|
||||
|
@ -114,6 +119,22 @@ func (tabs *Tabs) SelectPrevious() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (tabs *Tabs) NextTab() {
|
||||
next := tabs.Selected + 1
|
||||
if next >= len(tabs.Tabs) {
|
||||
next = 0
|
||||
}
|
||||
tabs.Select(next)
|
||||
}
|
||||
|
||||
func (tabs *Tabs) PrevTab() {
|
||||
next := tabs.Selected - 1
|
||||
if next < 0 {
|
||||
next = len(tabs.Tabs) - 1
|
||||
}
|
||||
tabs.Select(next)
|
||||
}
|
||||
|
||||
func (tabs *Tabs) pushHistory(index int) {
|
||||
tabs.history = append(tabs.history, index)
|
||||
}
|
||||
|
@ -146,19 +167,6 @@ func (tabs *Tabs) removeHistory(index int) {
|
|||
tabs.history = newHist
|
||||
}
|
||||
|
||||
func (tabs *Tabs) MouseEvent(event tcell.Event) {
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
if event.Buttons()&tcell.Button1 != 0 {
|
||||
x, y := event.Position()
|
||||
selectedTab, ok := tabs.TabStrip.Clicked(x, y)
|
||||
if ok {
|
||||
tabs.Select(selectedTab)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Color repository
|
||||
func (strip *TabStrip) Draw(ctx *Context) {
|
||||
x := 0
|
||||
|
@ -187,21 +195,65 @@ func (strip *TabStrip) Invalidate() {
|
|||
}
|
||||
}
|
||||
|
||||
func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
changeFocus := func(focus bool) {
|
||||
interactive, ok := strip.parent.Tabs[strip.parent.Selected].Content.(Interactive)
|
||||
if ok {
|
||||
interactive.Focus(focus)
|
||||
}
|
||||
}
|
||||
unfocus := func() { changeFocus(false) }
|
||||
refocus := func() { changeFocus(true) }
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
switch event.Buttons() {
|
||||
case tcell.Button1:
|
||||
selectedTab, ok := strip.Clicked(localX, localY)
|
||||
if !ok || selectedTab == strip.parent.Selected {
|
||||
return
|
||||
}
|
||||
unfocus()
|
||||
strip.parent.Select(selectedTab)
|
||||
refocus()
|
||||
case tcell.WheelDown:
|
||||
unfocus()
|
||||
strip.parent.NextTab()
|
||||
refocus()
|
||||
case tcell.WheelUp:
|
||||
unfocus()
|
||||
strip.parent.PrevTab()
|
||||
refocus()
|
||||
case tcell.Button3:
|
||||
selectedTab, ok := strip.Clicked(localX, localY)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
unfocus()
|
||||
if selectedTab == strip.parent.Selected {
|
||||
strip.parent.CloseTab(selectedTab)
|
||||
} else {
|
||||
current := strip.parent.Selected
|
||||
strip.parent.CloseTab(selectedTab)
|
||||
strip.parent.Select(current)
|
||||
}
|
||||
refocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (strip *TabStrip) OnInvalidate(onInvalidate func(d Drawable)) {
|
||||
strip.onInvalidateStrip = onInvalidate
|
||||
}
|
||||
|
||||
func (strip *TabStrip) Clicked(mouseX int, mouseY int) (int, bool) {
|
||||
x := 0
|
||||
if mouseY == 0 {
|
||||
for i, tab := range strip.Tabs {
|
||||
trunc := runewidth.Truncate(tab.Name, 32, "…")
|
||||
length := len(trunc) + 2
|
||||
if x <= mouseX && mouseX < x+length {
|
||||
return i, true
|
||||
}
|
||||
x += length
|
||||
for i, tab := range strip.Tabs {
|
||||
trunc := runewidth.Truncate(tab.Name, 32, "…")
|
||||
length := len(trunc) + 2
|
||||
if x <= mouseX && mouseX < x+length {
|
||||
return i, true
|
||||
}
|
||||
x += length
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
@ -225,6 +277,14 @@ func (content *TabContent) Draw(ctx *Context) {
|
|||
tab.Content.Draw(ctx)
|
||||
}
|
||||
|
||||
func (content *TabContent) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
tab := content.Tabs[content.Selected]
|
||||
switch tabContent := tab.Content.(type) {
|
||||
case Mouseable:
|
||||
tabContent.MouseEvent(localX, localY, event)
|
||||
}
|
||||
}
|
||||
|
||||
func (content *TabContent) Invalidate() {
|
||||
if content.onInvalidateContent != nil {
|
||||
content.onInvalidateContent(content)
|
||||
|
|
|
@ -97,6 +97,20 @@ func (ti *TextInput) Draw(ctx *Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (ti *TextInput) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
switch event.Buttons() {
|
||||
case tcell.Button1:
|
||||
if localX >= len(ti.prompt)+1 && localX <= len(ti.text[ti.scroll:])+len(ti.prompt)+1 {
|
||||
ti.index = localX - len(ti.prompt) - 1
|
||||
ti.ensureScroll()
|
||||
ti.Invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ti *TextInput) Focus(focus bool) {
|
||||
ti.focus = focus
|
||||
if focus && ti.ctx != nil {
|
||||
|
|
|
@ -523,7 +523,7 @@ func (wizard *AccountWizard) finish(tutorial bool) {
|
|||
}
|
||||
wizard.conf.Accounts = append(wizard.conf.Accounts, account)
|
||||
|
||||
view := NewAccountView(wizard.conf, &account,
|
||||
view := NewAccountView(wizard.aerc, wizard.conf, &account,
|
||||
wizard.aerc.logger, wizard.aerc)
|
||||
wizard.aerc.accounts[account.Name] = view
|
||||
wizard.aerc.NewTab(view, account.Name)
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
|
||||
type AccountView struct {
|
||||
acct *config.AccountConfig
|
||||
aerc *Aerc
|
||||
conf *config.AercConfig
|
||||
dirlist *DirectoryList
|
||||
grid *ui.Grid
|
||||
|
@ -26,7 +27,7 @@ type AccountView struct {
|
|||
worker *types.Worker
|
||||
}
|
||||
|
||||
func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
|
||||
func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountConfig,
|
||||
logger *log.Logger, host TabHost) *AccountView {
|
||||
|
||||
grid := ui.NewGrid().Rows([]ui.GridSpec{
|
||||
|
@ -42,6 +43,7 @@ func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
|
|||
Color(tcell.ColorDefault, tcell.ColorRed)
|
||||
return &AccountView{
|
||||
acct: acct,
|
||||
aerc: aerc,
|
||||
grid: grid,
|
||||
host: host,
|
||||
logger: logger,
|
||||
|
@ -53,11 +55,12 @@ func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
|
|||
grid.AddChild(ui.NewBordered(dirlist, ui.BORDER_RIGHT))
|
||||
}
|
||||
|
||||
msglist := NewMessageList(conf, logger)
|
||||
msglist := NewMessageList(conf, logger, aerc)
|
||||
grid.AddChild(msglist).At(0, 1)
|
||||
|
||||
view := &AccountView{
|
||||
acct: acct,
|
||||
aerc: aerc,
|
||||
conf: conf,
|
||||
dirlist: dirlist,
|
||||
grid: grid,
|
||||
|
@ -124,6 +127,10 @@ func (acct *AccountView) Draw(ctx *ui.Context) {
|
|||
acct.grid.Draw(ctx)
|
||||
}
|
||||
|
||||
func (acct *AccountView) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
acct.grid.MouseEvent(localX, localY, event)
|
||||
}
|
||||
|
||||
func (acct *AccountView) Focus(focus bool) {
|
||||
// TODO: Unfocus children I guess
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
|
|||
conf.Triggers.ExecuteCommand = cmd
|
||||
|
||||
for i, acct := range conf.Accounts {
|
||||
view := NewAccountView(conf, &conf.Accounts[i], logger, aerc)
|
||||
view := NewAccountView(aerc, conf, &conf.Accounts[i], logger, aerc)
|
||||
aerc.accounts[acct.Name] = view
|
||||
tabs.Add(view, acct.Name)
|
||||
}
|
||||
|
@ -85,6 +85,22 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
|
|||
aerc.NewTab(wizard, "New account")
|
||||
}
|
||||
|
||||
tabs.CloseTab = func(index int) {
|
||||
switch content := aerc.tabs.Tabs[index].Content.(type) {
|
||||
case *AccountView:
|
||||
return
|
||||
case *AccountWizard:
|
||||
return
|
||||
case *Composer:
|
||||
aerc.RemoveTab(content)
|
||||
content.Close()
|
||||
case *Terminal:
|
||||
content.Close(nil)
|
||||
case *MessageViewer:
|
||||
aerc.RemoveTab(content)
|
||||
}
|
||||
}
|
||||
|
||||
return aerc
|
||||
}
|
||||
|
||||
|
@ -235,7 +251,12 @@ func (aerc *Aerc) Event(event tcell.Event) bool {
|
|||
return false
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
aerc.tabs.MouseEvent(event)
|
||||
if event.Buttons() == tcell.ButtonNone {
|
||||
return false
|
||||
}
|
||||
x, y := event.Position()
|
||||
aerc.grid.MouseEvent(x, y, event)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -260,8 +281,8 @@ func (aerc *Aerc) SelectedTab() ui.Drawable {
|
|||
return aerc.tabs.Tabs[aerc.tabs.Selected].Content
|
||||
}
|
||||
|
||||
func (aerc *Aerc) NewTab(drawable ui.Drawable, name string) *ui.Tab {
|
||||
tab := aerc.tabs.Add(drawable, name)
|
||||
func (aerc *Aerc) NewTab(clickable ui.Drawable, name string) *ui.Tab {
|
||||
tab := aerc.tabs.Add(clickable, name)
|
||||
aerc.tabs.Select(len(aerc.tabs.Tabs) - 1)
|
||||
return tab
|
||||
}
|
||||
|
@ -275,19 +296,11 @@ func (aerc *Aerc) ReplaceTab(tabSrc ui.Drawable, tabTarget ui.Drawable, name str
|
|||
}
|
||||
|
||||
func (aerc *Aerc) NextTab() {
|
||||
next := aerc.tabs.Selected + 1
|
||||
if next >= len(aerc.tabs.Tabs) {
|
||||
next = 0
|
||||
}
|
||||
aerc.tabs.Select(next)
|
||||
aerc.tabs.NextTab()
|
||||
}
|
||||
|
||||
func (aerc *Aerc) PrevTab() {
|
||||
next := aerc.tabs.Selected - 1
|
||||
if next < 0 {
|
||||
next = len(aerc.tabs.Tabs) - 1
|
||||
}
|
||||
aerc.tabs.Select(next)
|
||||
aerc.tabs.PrevTab()
|
||||
}
|
||||
|
||||
func (aerc *Aerc) SelectTab(name string) bool {
|
||||
|
|
|
@ -40,10 +40,12 @@ type Composer struct {
|
|||
worker *types.Worker
|
||||
|
||||
layout HeaderLayout
|
||||
focusable []ui.DrawableInteractive
|
||||
focusable []ui.MouseableDrawableInteractive
|
||||
focused int
|
||||
|
||||
onClose []func(ti *Composer)
|
||||
|
||||
width int
|
||||
}
|
||||
|
||||
func NewComposer(conf *config.AercConfig,
|
||||
|
@ -87,10 +89,10 @@ func NewComposer(conf *config.AercConfig,
|
|||
func buildComposeHeader(layout HeaderLayout, defaults map[string]string) (
|
||||
newLayout HeaderLayout,
|
||||
editors map[string]*headerEditor,
|
||||
focusable []ui.DrawableInteractive,
|
||||
focusable []ui.MouseableDrawableInteractive,
|
||||
) {
|
||||
editors = make(map[string]*headerEditor)
|
||||
focusable = make([]ui.DrawableInteractive, 0)
|
||||
focusable = make([]ui.MouseableDrawableInteractive, 0)
|
||||
|
||||
for _, row := range layout {
|
||||
for _, h := range row {
|
||||
|
@ -99,7 +101,7 @@ func buildComposeHeader(layout HeaderLayout, defaults map[string]string) (
|
|||
switch h {
|
||||
case "From":
|
||||
// Prepend From to support backtab
|
||||
focusable = append([]ui.DrawableInteractive{e}, focusable...)
|
||||
focusable = append([]ui.MouseableDrawableInteractive{e}, focusable...)
|
||||
default:
|
||||
focusable = append(focusable, e)
|
||||
}
|
||||
|
@ -176,6 +178,7 @@ func (c *Composer) OnClose(fn func(composer *Composer)) {
|
|||
}
|
||||
|
||||
func (c *Composer) Draw(ctx *ui.Context) {
|
||||
c.width = ctx.Width()
|
||||
c.grid.Draw(ctx)
|
||||
}
|
||||
|
||||
|
@ -617,6 +620,16 @@ func (he *headerEditor) Draw(ctx *ui.Context) {
|
|||
he.input.Draw(ctx.Subcontext(size, 0, ctx.Width()-size, 1))
|
||||
}
|
||||
|
||||
func (he *headerEditor) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
width := runewidth.StringWidth(he.name + " ")
|
||||
if localX >= width {
|
||||
he.input.MouseEvent(localX-width, localY, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (he *headerEditor) Invalidate() {
|
||||
he.input.Invalidate()
|
||||
}
|
||||
|
|
|
@ -137,6 +137,35 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (dirlist *DirectoryList) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
switch event.Buttons() {
|
||||
case tcell.Button1:
|
||||
clickedDir, ok := dirlist.Clicked(localX, localY)
|
||||
if ok {
|
||||
dirlist.Select(clickedDir)
|
||||
}
|
||||
case tcell.WheelDown:
|
||||
dirlist.Next()
|
||||
case tcell.WheelUp:
|
||||
dirlist.Prev()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dirlist *DirectoryList) Clicked(x int, y int) (string, bool) {
|
||||
if dirlist.dirs == nil || len(dirlist.dirs) == 0 {
|
||||
return "", false
|
||||
}
|
||||
for i, name := range dirlist.dirs {
|
||||
if i == y {
|
||||
return name, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (dirlist *DirectoryList) NextPrev(delta int) {
|
||||
curIdx := sort.SearchStrings(dirlist.dirs, dirlist.selected)
|
||||
if curIdx == len(dirlist.dirs) {
|
||||
|
|
|
@ -25,6 +25,7 @@ type MessageList struct {
|
|||
spinner *Spinner
|
||||
store *lib.MessageStore
|
||||
isInitalizing bool
|
||||
aerc *Aerc
|
||||
}
|
||||
|
||||
type msgSorter struct {
|
||||
|
@ -55,12 +56,13 @@ func (s *msgSorter) Swap(i, j int) {
|
|||
s.uids[j] = tmp
|
||||
}
|
||||
|
||||
func NewMessageList(conf *config.AercConfig, logger *log.Logger) *MessageList {
|
||||
func NewMessageList(conf *config.AercConfig, logger *log.Logger, aerc *Aerc) *MessageList {
|
||||
ml := &MessageList{
|
||||
conf: conf,
|
||||
logger: logger,
|
||||
spinner: NewSpinner(&conf.Ui),
|
||||
isInitalizing: true,
|
||||
aerc: aerc,
|
||||
}
|
||||
ml.spinner.OnInvalidate(func(_ ui.Drawable) {
|
||||
ml.Invalidate()
|
||||
|
@ -161,6 +163,47 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (ml *MessageList) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
switch event.Buttons() {
|
||||
case tcell.Button1:
|
||||
if ml.aerc == nil {
|
||||
return
|
||||
}
|
||||
selectedMsg, ok := ml.Clicked(localX, localY)
|
||||
if ok {
|
||||
ml.Select(selectedMsg)
|
||||
acct := ml.aerc.SelectedAccount()
|
||||
if acct.Messages().Empty() {
|
||||
return
|
||||
}
|
||||
store := acct.Messages().Store()
|
||||
msg := acct.Messages().Selected()
|
||||
if msg == nil {
|
||||
return
|
||||
}
|
||||
viewer := NewMessageViewer(acct, ml.aerc.Config(), store, msg)
|
||||
ml.aerc.NewTab(viewer, msg.Envelope.Subject)
|
||||
}
|
||||
case tcell.WheelDown:
|
||||
ml.store.Next()
|
||||
ml.Scroll()
|
||||
case tcell.WheelUp:
|
||||
ml.store.Prev()
|
||||
ml.Scroll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ml *MessageList) Clicked(x, y int) (int, bool) {
|
||||
store := ml.Store()
|
||||
if store == nil || ml.nmsgs == 0 || y >= ml.nmsgs {
|
||||
return 0, false
|
||||
}
|
||||
return y + ml.scroll, true
|
||||
}
|
||||
|
||||
func (ml *MessageList) Height() int {
|
||||
return ml.height
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ type PartSwitcher struct {
|
|||
selected int
|
||||
showHeaders bool
|
||||
alwaysShowMime bool
|
||||
|
||||
height int
|
||||
mv *MessageViewer
|
||||
}
|
||||
|
||||
func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
|
||||
|
@ -77,7 +80,7 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
|
|||
grid.AddChild(header).At(0, 0)
|
||||
grid.AddChild(switcher).At(1, 0)
|
||||
|
||||
return &MessageViewer{
|
||||
mv := &MessageViewer{
|
||||
acct: acct,
|
||||
conf: conf,
|
||||
grid: grid,
|
||||
|
@ -85,6 +88,9 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
|
|||
store: store,
|
||||
switcher: switcher,
|
||||
}
|
||||
switcher.mv = mv
|
||||
|
||||
return mv
|
||||
}
|
||||
|
||||
func fmtHeader(msg *models.MessageInfo, header string) string {
|
||||
|
@ -194,6 +200,13 @@ func (mv *MessageViewer) Draw(ctx *ui.Context) {
|
|||
mv.grid.Draw(ctx)
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
if mv.err != nil {
|
||||
return
|
||||
}
|
||||
mv.grid.MouseEvent(localX, localY, event)
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) Invalidate() {
|
||||
mv.grid.Invalidate()
|
||||
}
|
||||
|
@ -295,6 +308,7 @@ func (ps *PartSwitcher) Draw(ctx *ui.Context) {
|
|||
return
|
||||
}
|
||||
// TODO: cap height and add scrolling for messages with many parts
|
||||
ps.height = ctx.Height()
|
||||
y := ctx.Height() - height
|
||||
for i, part := range ps.parts {
|
||||
style := tcell.StyleDefault.Reverse(ps.selected == i)
|
||||
|
@ -311,6 +325,62 @@ func (ps *PartSwitcher) Draw(ctx *ui.Context) {
|
|||
0, 0, ctx.Width(), ctx.Height()-height))
|
||||
}
|
||||
|
||||
func (ps *PartSwitcher) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
switch event.Buttons() {
|
||||
case tcell.Button1:
|
||||
height := len(ps.parts)
|
||||
y := ps.height - height
|
||||
if localY < y {
|
||||
ps.parts[ps.selected].term.MouseEvent(localX, localY, event)
|
||||
}
|
||||
for i, _ := range ps.parts {
|
||||
if localY != y+i {
|
||||
continue
|
||||
}
|
||||
if ps.parts[i].part.MIMEType == "multipart" {
|
||||
continue
|
||||
}
|
||||
if ps.parts[ps.selected].term != nil {
|
||||
ps.parts[ps.selected].term.Focus(false)
|
||||
}
|
||||
ps.selected = i
|
||||
ps.Invalidate()
|
||||
if ps.parts[ps.selected].term != nil {
|
||||
ps.parts[ps.selected].term.Focus(true)
|
||||
}
|
||||
}
|
||||
case tcell.WheelDown:
|
||||
height := len(ps.parts)
|
||||
y := ps.height - height
|
||||
if localY < y {
|
||||
ps.parts[ps.selected].term.MouseEvent(localX, localY, event)
|
||||
}
|
||||
if ps.parts[ps.selected].term != nil {
|
||||
ps.parts[ps.selected].term.Focus(false)
|
||||
}
|
||||
ps.mv.NextPart()
|
||||
if ps.parts[ps.selected].term != nil {
|
||||
ps.parts[ps.selected].term.Focus(true)
|
||||
}
|
||||
case tcell.WheelUp:
|
||||
height := len(ps.parts)
|
||||
y := ps.height - height
|
||||
if localY < y {
|
||||
ps.parts[ps.selected].term.MouseEvent(localX, localY, event)
|
||||
}
|
||||
if ps.parts[ps.selected].term != nil {
|
||||
ps.parts[ps.selected].term.Focus(false)
|
||||
}
|
||||
ps.mv.PreviousPart()
|
||||
if ps.parts[ps.selected].term != nil {
|
||||
ps.parts[ps.selected].term.Focus(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mv *MessageViewer) Event(event tcell.Event) bool {
|
||||
return mv.switcher.Event(event)
|
||||
}
|
||||
|
|
|
@ -311,6 +311,20 @@ func (term *Terminal) Draw(ctx *ui.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (term *Terminal) MouseEvent(localX int, localY int, event tcell.Event) {
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
if term.OnEvent != nil {
|
||||
if term.OnEvent(event) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if term.closed {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (term *Terminal) Focus(focus bool) {
|
||||
if term.closed {
|
||||
return
|
||||
|
|
Loading…
Reference in a new issue