diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ui/borders.go | 7 | ||||
-rw-r--r-- | lib/ui/grid.go | 41 | ||||
-rw-r--r-- | lib/ui/interfaces.go | 15 | ||||
-rw-r--r-- | lib/ui/stack.go | 9 | ||||
-rw-r--r-- | lib/ui/tab.go | 102 | ||||
-rw-r--r-- | lib/ui/textinput.go | 14 |
6 files changed, 164 insertions, 24 deletions
diff --git a/lib/ui/borders.go b/lib/ui/borders.go index cffd3ca3..7a757595 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 7f131bdf..b47c6bd6 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 2f634248..9e795712 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 75cc7807..690a8699 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 90c7ce90..1fd2b80b 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 00e91eee..39351735 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 { |