aboutsummaryrefslogtreecommitdiff
path: root/lib/ui
diff options
context:
space:
mode:
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 {