aboutsummaryrefslogtreecommitdiff
path: root/lib/ui
diff options
context:
space:
mode:
authorJeffas <dev@jeffas.io>2019-09-05 23:32:36 +0100
committerDrew DeVault <sir@cmpwn.com>2019-09-11 11:41:34 -0400
commitf6216bb6213a15cdcdf50089f3c4a8e9a30d9337 (patch)
tree3075b68379ae7c13cb9eedc53b7cf033672478a9 /lib/ui
parenta441e3b3a55d11e7cc9444298051ff339d1b7519 (diff)
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.
Diffstat (limited to 'lib/ui')
-rw-r--r--lib/ui/borders.go7
-rw-r--r--lib/ui/grid.go41
-rw-r--r--lib/ui/interfaces.go15
-rw-r--r--lib/ui/stack.go9
-rw-r--r--lib/ui/tab.go102
-rw-r--r--lib/ui/textinput.go14
6 files changed, 164 insertions, 24 deletions
diff --git a/lib/ui/borders.go b/lib/ui/borders.go
index cffd3ca..7a75759 100644
--- a/lib/ui/borders.go
+++ b/lib/ui/borders.go
@@ -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)
+ }
+}
diff --git a/lib/ui/grid.go b/lib/ui/grid.go
index 7f131bd..b47c6bd 100644
--- a/lib/ui/grid.go
+++ b/lib/ui/grid.go
@@ -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
diff --git a/lib/ui/interfaces.go b/lib/ui/interfaces.go
index 2f63424..9e79571 100644
--- a/lib/ui/interfaces.go
+++ b/lib/ui/interfaces.go
@@ -50,9 +50,18 @@ type Container interface {
Children() []Drawable
}
-// A drawable that can be clicked
-type Clickable interface {
+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
+}
- MouseEvent(event tcell.Event)
+type MouseableDrawableInteractive interface {
+ DrawableInteractive
+ MouseHandler
}
diff --git a/lib/ui/stack.go b/lib/ui/stack.go
index 75cc780..690a869 100644
--- a/lib/ui/stack.go
+++ b/lib/ui/stack.go
@@ -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)
diff --git a/lib/ui/tab.go b/lib/ui/tab.go
index 90c7ce9..1fd2b80 100644
--- a/lib/ui/tab.go
+++ b/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)
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index 00e91ee..3935173 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -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 {