summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-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 {