MouseEvent handling refactored

This commit is contained in:
Timon Ringwald 2022-05-04 14:16:08 +02:00
parent b1ad5ab081
commit b6b6668d0e
8 changed files with 118 additions and 43 deletions

View File

@ -6,7 +6,29 @@ type Events interface {
// If OnKeyPressed returns true, the event will not be passed onto child views // If OnKeyPressed returns true, the event will not be passed onto child views
OnKeyPressed(event *KeyEvent) (consumed bool) OnKeyPressed(event *KeyEvent) (consumed bool)
// OnMouseClicked is called every time a mouse button was pressed on the view. // OnMouseEvent is called every time the mouse was used in any way.
// That includes mouse movement, button presses and scroll wheel usage
// If OnMouseClicked returns true, the event will not be passed onto child views // If OnMouseClicked returns true, the event will not be passed onto child views
OnMouseClicked(event *MouseEvent) (consumed bool) OnMouseEvent(event *MouseEvent) (consumed bool)
}
type EventTmpl struct {
KeyPressed func(event *KeyEvent) (consumed bool)
MouseEvent func(event *MouseEvent) (consumed bool)
}
var _ Events = &EventTmpl{}
func (e *EventTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
if e.KeyPressed == nil {
return false
}
return e.KeyPressed(event)
}
func (e *EventTmpl) OnMouseEvent(event *MouseEvent) (consumed bool) {
if e.MouseEvent == nil {
return false
}
return e.MouseEvent(event)
} }

View File

@ -9,6 +9,8 @@ import (
) )
type Screen struct { type Screen struct {
EventTmpl
scr tcell.Screen scr tcell.Screen
buf *ViewBuffer buf *ViewBuffer
@ -17,12 +19,6 @@ type Screen struct {
// Root is the root view which is currently shown on screen // Root is the root view which is currently shown on screen
Root View Root View
// KeyPressed is called every time a key or key-combination is pressed.
KeyPressed func(event *KeyEvent) (consumed bool)
// MouseClicked is called every time a mouse button was pressed.
MouseClicked func(event *MouseEvent) (consumed bool)
} }
func NewScreen(root View) (*Screen, error) { func NewScreen(root View) (*Screen, error) {
@ -72,9 +68,10 @@ func (s *Screen) onKeyPressed(event *KeyEvent) {
s.Redraw() s.Redraw()
} }
func (s *Screen) onMouseClicked(event *MouseEvent) { func (s *Screen) onMouseEvent(event *MouseEvent) {
if s.MouseClicked == nil || !s.MouseClicked(event) { if s.MouseEvent == nil || !s.MouseEvent(event) {
go s.Root.OnMouseClicked(event) go s.Root.OnMouseEvent(event)
// TODO redraw in same thread
} }
if event.Button != MouseButtonNone { if event.Button != MouseButtonNone {
s.Redraw() s.Redraw()
@ -104,7 +101,7 @@ func (s *Screen) eventloop() {
case *tcell.EventKey: case *tcell.EventKey:
s.onKeyPressed(event) s.onKeyPressed(event)
case *tcell.EventMouse: case *tcell.EventMouse:
s.onMouseClicked(convertMouseEvent(event)) s.onMouseEvent(convertMouseEvent(event))
default: default:
s.StopWithError(fmt.Errorf("%#v", event)) s.StopWithError(fmt.Errorf("%#v", event))
} }

View File

@ -7,6 +7,7 @@ import (
"math/rand" "math/rand"
"os" "os"
"strconv" "strconv"
"strings"
"testing" "testing"
"git.tordarus.net/Tordarus/tui" "git.tordarus.net/Tordarus/tui"
@ -61,7 +62,7 @@ func TestScrollView(t *testing.T) {
return true return true
} }
screen.MouseClicked = func(event *tui.MouseEvent) (consumed bool) { screen.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
//textViews[0].(*views.TextView).Text = fmt.Sprintf("mouse position: %d | %d", event.X, event.Y) //textViews[0].(*views.TextView).Text = fmt.Sprintf("mouse position: %d | %d", event.X, event.Y)
//textViews[1].(*views.TextView).Text = fmt.Sprintf("mouse button: %d", event.Button) //textViews[1].(*views.TextView).Text = fmt.Sprintf("mouse button: %d", event.Button)
@ -123,7 +124,7 @@ func TestMousePosition(t *testing.T) {
return return
} }
screen.MouseClicked = func(event *tui.MouseEvent) (consumed bool) { screen.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
textView.Text = fmt.Sprintf("position: %s", event.Position.String()) textView.Text = fmt.Sprintf("position: %s", event.Position.String())
return true return true
} }
@ -217,20 +218,58 @@ func TestSeparatorLayout(t *testing.T) {
} }
func TestBorderLayout(t *testing.T) { func TestBorderLayout(t *testing.T) {
topView := views.NewConstrainView(nil, 10, 10) textView := views.NewTextView("")
textView.SetStyle(tui.StyleDefault.Background(tcell.ColorBlue))
textView.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
if event.Button == tui.MouseButtonLeft {
textView.SetStyle(tui.StyleDefault.Background(tcell.ColorRed))
return true
}
return false
}
topView := views.NewConstrainView(textView, -1, -1)
topView.SetStyle(tui.StyleDefault.Background(tcell.ColorBlue)) topView.SetStyle(tui.StyleDefault.Background(tcell.ColorBlue))
bottomView := views.NewConstrainView(nil, 10, 10) bottomView := views.NewConstrainView(nil, 10, 10)
bottomView.SetStyle(tui.StyleDefault.Background(tcell.ColorRed)) bottomView.SetStyle(tui.StyleDefault.Background(tcell.ColorRed))
bottomView.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
if event.Button == tui.MouseButtonLeft {
bottomView.SetStyle(tui.StyleDefault.Background(tcell.ColorBlue))
return true
}
return false
}
leftView := views.NewConstrainView(nil, 10, 10) leftView := views.NewConstrainView(nil, 10, 10)
leftView.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow)) leftView.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow))
leftView.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
if event.Button == tui.MouseButtonLeft {
leftView.SetStyle(tui.StyleDefault.Background(tcell.ColorGreen))
return true
}
return false
}
rightView := views.NewConstrainView(nil, 10, 10) rightView := views.NewConstrainView(nil, 10, 10)
rightView.SetStyle(tui.StyleDefault.Background(tcell.ColorGreen)) rightView.SetStyle(tui.StyleDefault.Background(tcell.ColorGreen))
rightView.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
if event.Button == tui.MouseButtonLeft {
rightView.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow))
return true
}
return false
}
centerView := views.NewConstrainView(nil, 10, 10) centerView := views.NewConstrainView(nil, 10, 10)
centerView.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple)) centerView.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple))
centerView.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
if event.Button == tui.MouseButtonLeft {
centerView.SetStyle(tui.StyleDefault.Background(tcell.ColorOrange))
return true
}
return false
}
borderLayout := views.NewBorderLayout() borderLayout := views.NewBorderLayout()
borderLayout.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple)) borderLayout.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple))
@ -253,6 +292,23 @@ func TestBorderLayout(t *testing.T) {
return true return true
} }
screen.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
if event.Button == tui.MouseButtonLeft {
b := new(strings.Builder)
b.WriteString(textView.Text)
for slot, dim := range borderLayout.ViewDimensions {
if event.Position.In(dim) {
b.WriteString(fmt.Sprintf("%s: %s\n", slot, dim))
}
}
textView.Text = b.String()
}
return false
}
err = screen.Start() err = screen.Start()
fmt.Println(err) fmt.Println(err)
} }

View File

@ -23,7 +23,7 @@ func P(x, y int) Point {
} }
func (p Point) In(d Dimension) bool { func (p Point) In(d Dimension) bool {
return p.X >= d.X && p.X < d.X+d.Width && p.Y >= d.Y && p.Y < d.Y+d.Height return p.X > d.X && p.X < d.X+d.Width && p.Y > d.Y && p.Y < d.Y+d.Height
} }
func (p Point) String() string { func (p Point) String() string {

View File

@ -11,7 +11,7 @@ type BorderLayout struct {
horizontalLayout *LayoutResult horizontalLayout *LayoutResult
verticalLayout *LayoutResult verticalLayout *LayoutResult
viewDimensions map[Slot]tui.Dimension ViewDimensions map[Slot]tui.Dimension
} }
var _ tui.Layout = &BorderLayout{} var _ tui.Layout = &BorderLayout{}
@ -19,7 +19,7 @@ var _ tui.Layout = &BorderLayout{}
func NewBorderLayout() *BorderLayout { func NewBorderLayout() *BorderLayout {
return &BorderLayout{ return &BorderLayout{
views: map[Slot]tui.View{}, views: map[Slot]tui.View{},
viewDimensions: map[Slot]tui.Dimension{}, ViewDimensions: map[Slot]tui.Dimension{},
} }
} }
@ -79,7 +79,7 @@ func (g *BorderLayout) Draw(buf *tui.ViewBuffer) {
topHeight = int(float64(buf.Height()) * float64(topHeight) / float64(verticalLayout.Sum.Height)) topHeight = int(float64(buf.Height()) * float64(topHeight) / float64(verticalLayout.Sum.Height))
} }
g.viewDimensions[Top] = tui.D(0, 0, buf.Width(), topHeight) g.ViewDimensions[Top] = tui.D(0, 0, buf.Width(), topHeight)
view.Draw(buf.Sub(0, 0, buf.Width(), topHeight)) view.Draw(buf.Sub(0, 0, buf.Width(), topHeight))
} }
@ -92,7 +92,7 @@ func (g *BorderLayout) Draw(buf *tui.ViewBuffer) {
bottomHeight = int(float64(buf.Height()) * float64(bottomHeight) / float64(verticalLayout.Sum.Height)) bottomHeight = int(float64(buf.Height()) * float64(bottomHeight) / float64(verticalLayout.Sum.Height))
} }
g.viewDimensions[Bottom] = tui.D(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight) g.ViewDimensions[Bottom] = tui.D(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight)
view.Draw(buf.Sub(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight)) view.Draw(buf.Sub(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight))
} }
@ -105,7 +105,7 @@ func (g *BorderLayout) Draw(buf *tui.ViewBuffer) {
leftWidth = int(float64(buf.Width()) * float64(leftWidth) / float64(horizontalLayout.Sum.Width)) leftWidth = int(float64(buf.Width()) * float64(leftWidth) / float64(horizontalLayout.Sum.Width))
} }
g.viewDimensions[Left] = tui.D(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight) g.ViewDimensions[Left] = tui.D(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight)
view.Draw(buf.Sub(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight)) view.Draw(buf.Sub(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight))
} }
@ -118,12 +118,12 @@ func (g *BorderLayout) Draw(buf *tui.ViewBuffer) {
rightWidth = int(float64(buf.Width()) * float64(rightWidth) / float64(horizontalLayout.Sum.Width)) rightWidth = int(float64(buf.Width()) * float64(rightWidth) / float64(horizontalLayout.Sum.Width))
} }
g.viewDimensions[Right] = tui.D(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight) g.ViewDimensions[Right] = tui.D(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight)
view.Draw(buf.Sub(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight)) view.Draw(buf.Sub(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight))
} }
if view, ok := g.views[Center]; ok { if view, ok := g.views[Center]; ok {
g.viewDimensions[Center] = tui.D(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight) g.ViewDimensions[Center] = tui.D(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight)
view.Draw(buf.Sub(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight)) view.Draw(buf.Sub(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight))
} }
@ -146,22 +146,22 @@ func (g *BorderLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
return false return false
} }
func (g *BorderLayout) OnMouseClicked(event *tui.MouseEvent) (consumed bool) { func (g *BorderLayout) OnMouseEvent(event *tui.MouseEvent) (consumed bool) {
for slot, dim := range g.viewDimensions { for slot, dim := range g.ViewDimensions {
if event.Position.In(dim) { if event.Position.In(dim) {
g.views[slot].OnMouseClicked(event) g.views[slot].OnMouseEvent(event)
return true return true
} }
} }
return false return false
} }
type Slot uint8 type Slot string
const ( const (
Top Slot = iota Top Slot = "top"
Bottom Bottom Slot = "bottom"
Left Left Slot = "left"
Right Right Slot = "right"
Center Center Slot = "center"
) )

View File

@ -4,7 +4,7 @@ import (
"git.tordarus.net/Tordarus/tui" "git.tordarus.net/Tordarus/tui"
) )
// SeperatorLayout ia a tui.Layout which separates // SeperatorLayout ia a tui.Layout which separates its view into gravity-based portions
type SeperatorLayout struct { type SeperatorLayout struct {
tui.ViewTmpl tui.ViewTmpl
views []tui.View views []tui.View
@ -68,8 +68,8 @@ func (g *SeperatorLayout) RemoveView(v tui.View) {
} }
} }
func (g *SeperatorLayout) View(slot Slot) tui.View { func (g *SeperatorLayout) View(index int) tui.View {
return g.views[slot] return g.views[index]
} }
func (g *SeperatorLayout) Draw(buf *tui.ViewBuffer) { func (g *SeperatorLayout) Draw(buf *tui.ViewBuffer) {

View File

@ -1,6 +1,7 @@
package tui package tui
type ViewTmpl struct { type ViewTmpl struct {
EventTmpl
style *Style style *Style
} }
@ -14,14 +15,6 @@ func (v *ViewTmpl) Layout() (prefWidth, prefHeight int) {
return -1, -1 return -1, -1
} }
func (v *ViewTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
return false
}
func (v *ViewTmpl) OnMouseClicked(event *MouseEvent) (consumed bool) {
return false
}
func (v *ViewTmpl) SetStyle(s Style) { func (v *ViewTmpl) SetStyle(s Style) {
v.style = &s v.style = &s
} }

View File

@ -29,6 +29,13 @@ func (v *WrapperTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
return v.ViewTmpl.OnKeyPressed(event) return v.ViewTmpl.OnKeyPressed(event)
} }
func (v *WrapperTmpl) OnMouseEvent(event *MouseEvent) (consumed bool) {
if v.view != nil {
return v.view.OnMouseEvent(event)
}
return v.ViewTmpl.OnMouseEvent(event)
}
func (v *WrapperTmpl) SetStyle(s Style) { func (v *WrapperTmpl) SetStyle(s Style) {
v.ViewTmpl.SetStyle(s) v.ViewTmpl.SetStyle(s)
} }