sampler-fork/component/layout/layout.go

367 lines
9.9 KiB
Go
Raw Normal View History

2019-03-08 04:04:46 +00:00
package layout
2019-01-31 00:02:38 +00:00
import (
2019-03-14 03:01:44 +00:00
ui "github.com/gizak/termui/v3"
2019-03-08 04:04:46 +00:00
"github.com/sqshq/sampler/component"
2019-02-24 04:49:09 +00:00
"github.com/sqshq/sampler/component/runchart"
"github.com/sqshq/sampler/component/util"
"github.com/sqshq/sampler/config"
2019-02-11 02:51:55 +00:00
"github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/data"
"image"
2019-03-01 03:44:01 +00:00
"math"
"time"
2019-01-31 00:02:38 +00:00
)
2019-07-27 04:15:35 +00:00
// Layout represents component arrangement on the screen
2019-01-31 00:02:38 +00:00
type Layout struct {
2019-02-11 02:51:55 +00:00
ui.Block
Components []*component.Component
2019-03-08 04:04:46 +00:00
statusbar *component.StatusBar
menu *component.Menu
ChangeModeEvents chan Mode
2019-02-25 00:08:36 +00:00
mode Mode
selection int
positionsChanged bool
startupTime time.Time
2019-01-31 00:02:38 +00:00
}
2019-02-11 02:51:55 +00:00
type Mode rune
const (
ModeDefault Mode = 0
ModeIntro Mode = 1
2019-12-22 02:05:01 +00:00
ModePause Mode = 2
ModeComponentSelect Mode = 3
ModeMenuOptionSelect Mode = 4
ModeComponentMove Mode = 5
ModeComponentResize Mode = 6
ModeChartPinpoint Mode = 7
2019-02-11 02:51:55 +00:00
)
2019-01-31 00:02:38 +00:00
const (
2019-12-22 02:05:01 +00:00
minDimension = 3
2021-05-31 17:54:15 +00:00
statusbarHeight = 0
2019-01-31 00:02:38 +00:00
)
2019-12-22 02:05:01 +00:00
func NewLayout(statusline *component.StatusBar, menu *component.Menu) *Layout {
2019-01-31 00:02:38 +00:00
2019-05-24 02:58:46 +00:00
width, height := ui.TerminalDimensions()
2019-02-11 02:51:55 +00:00
block := *ui.NewBlock()
2019-01-31 00:02:38 +00:00
block.SetRect(0, 0, width, height)
2019-02-25 00:37:57 +00:00
statusline.SetRect(0, height-statusbarHeight, width, height)
2019-01-31 00:02:38 +00:00
return &Layout{
Block: block,
Components: make([]*component.Component, 0),
2019-02-25 00:08:36 +00:00
statusbar: statusline,
menu: menu,
mode: ModeDefault,
selection: 0,
ChangeModeEvents: make(chan Mode, 10),
startupTime: time.Now(),
2019-01-31 00:02:38 +00:00
}
}
2019-03-16 23:59:28 +00:00
func (l *Layout) AddComponent(cpt *component.Component) {
l.Components = append(l.Components, cpt)
2019-01-31 00:02:38 +00:00
}
func (l *Layout) StartWithIntro() {
l.mode = ModeIntro
}
func (l *Layout) changeMode(m Mode) {
if m == ModeComponentResize || m == ModeComponentMove {
l.positionsChanged = true
}
l.mode = m
l.ChangeModeEvents <- m
}
2019-04-14 00:24:24 +00:00
func (l *Layout) HandleMouseClick(x int, y int) {
2019-12-22 02:05:01 +00:00
if l.mode == ModeIntro {
return
}
2019-04-14 00:24:24 +00:00
l.menu.Idle()
selected, i := l.findComponentAtPoint(image.Point{X: x, Y: y})
if selected == nil {
l.changeMode(ModeDefault)
} else {
l.selection = i
l.menu.Highlight(selected)
l.changeMode(ModeComponentSelect)
}
}
func (l *Layout) HandleKeyboardEvent(e string) {
selected := l.getSelection()
2019-02-11 02:51:55 +00:00
switch e {
case console.KeyPause1, console.KeyPause2:
2019-03-03 01:04:02 +00:00
if l.mode == ModePause {
l.changeMode(ModeDefault)
l.statusbar.TogglePause()
2019-03-03 01:04:02 +00:00
} else {
if selected.Type == config.TypeRunChart {
2019-03-16 23:59:28 +00:00
selected.CommandChannel <- &data.Command{Type: runchart.CommandDisableSelection}
2019-03-03 01:04:02 +00:00
}
2019-03-08 04:04:46 +00:00
l.menu.Idle()
2019-03-03 01:04:02 +00:00
l.changeMode(ModePause)
l.statusbar.TogglePause()
2019-03-03 01:04:02 +00:00
}
2019-02-11 02:51:55 +00:00
case console.KeyEnter:
switch l.mode {
case ModeComponentSelect:
2019-03-08 04:04:46 +00:00
l.menu.Choose()
l.changeMode(ModeMenuOptionSelect)
2019-02-11 02:51:55 +00:00
case ModeMenuOptionSelect:
2019-03-08 04:04:46 +00:00
option := l.menu.GetSelectedOption()
2019-02-11 02:51:55 +00:00
switch option {
2019-03-08 04:04:46 +00:00
case component.MenuOptionMove:
l.changeMode(ModeComponentMove)
2019-03-08 04:04:46 +00:00
l.menu.MoveOrResize()
case component.MenuOptionResize:
l.changeMode(ModeComponentResize)
2019-03-08 04:04:46 +00:00
l.menu.MoveOrResize()
case component.MenuOptionPinpoint:
l.changeMode(ModeChartPinpoint)
2019-03-08 04:04:46 +00:00
l.menu.Idle()
2019-03-16 23:59:28 +00:00
selected.CommandChannel <- &data.Command{Type: runchart.CommandMoveSelection, Value: 0}
2019-03-08 04:04:46 +00:00
case component.MenuOptionResume:
l.changeMode(ModeDefault)
2019-03-08 04:04:46 +00:00
l.menu.Idle()
2019-02-11 02:51:55 +00:00
}
case ModeComponentMove:
fallthrough
case ModeComponentResize:
2019-03-08 04:04:46 +00:00
l.menu.Idle()
l.changeMode(ModeDefault)
break
2019-02-11 02:51:55 +00:00
}
case console.KeyEsc:
2019-03-17 00:54:24 +00:00
l.resetAlerts()
2019-02-11 02:51:55 +00:00
switch l.mode {
case ModeChartPinpoint:
2019-03-16 23:59:28 +00:00
selected.CommandChannel <- &data.Command{Type: runchart.CommandDisableSelection}
2019-02-11 02:51:55 +00:00
fallthrough
case ModeComponentSelect:
fallthrough
case ModeMenuOptionSelect:
2019-03-08 04:04:46 +00:00
l.menu.Idle()
l.changeMode(ModeDefault)
case ModeComponentMove:
fallthrough
case ModeComponentResize:
l.menu.Idle()
l.changeMode(ModeDefault)
2019-02-11 02:51:55 +00:00
}
case console.KeyLeft:
switch l.mode {
case ModeDefault:
l.changeMode(ModeComponentSelect)
2019-03-08 04:04:46 +00:00
l.menu.Highlight(l.getComponent(l.selection))
2019-02-11 02:51:55 +00:00
case ModeChartPinpoint:
2019-03-16 23:59:28 +00:00
selected.CommandChannel <- &data.Command{Type: runchart.CommandMoveSelection, Value: -1}
2019-02-11 02:51:55 +00:00
case ModeComponentSelect:
l.moveSelection(e)
2019-03-08 04:04:46 +00:00
l.menu.Highlight(l.getComponent(l.selection))
2019-02-11 02:51:55 +00:00
case ModeComponentMove:
selected.Move(-1, 0)
2019-02-11 02:51:55 +00:00
case ModeComponentResize:
selected.Resize(-1, 0)
2019-02-11 02:51:55 +00:00
}
case console.KeyRight:
switch l.mode {
case ModeDefault:
l.changeMode(ModeComponentSelect)
2019-03-08 04:04:46 +00:00
l.menu.Highlight(l.getComponent(l.selection))
2019-02-11 02:51:55 +00:00
case ModeChartPinpoint:
2019-03-16 23:59:28 +00:00
selected.CommandChannel <- &data.Command{Type: runchart.CommandMoveSelection, Value: 1}
2019-02-11 02:51:55 +00:00
case ModeComponentSelect:
l.moveSelection(e)
2019-03-08 04:04:46 +00:00
l.menu.Highlight(l.getComponent(l.selection))
2019-02-11 02:51:55 +00:00
case ModeComponentMove:
selected.Move(1, 0)
2019-02-11 02:51:55 +00:00
case ModeComponentResize:
selected.Resize(1, 0)
2019-02-11 02:51:55 +00:00
}
case console.KeyUp:
switch l.mode {
2019-02-16 03:46:03 +00:00
case ModeDefault:
l.changeMode(ModeComponentSelect)
2019-03-08 04:04:46 +00:00
l.menu.Highlight(l.getComponent(l.selection))
2019-02-16 03:46:03 +00:00
case ModeComponentSelect:
l.moveSelection(e)
2019-03-08 04:04:46 +00:00
l.menu.Highlight(l.getComponent(l.selection))
2019-02-11 02:51:55 +00:00
case ModeMenuOptionSelect:
2019-03-08 04:04:46 +00:00
l.menu.Up()
2019-02-11 02:51:55 +00:00
case ModeComponentMove:
selected.Move(0, -1)
2019-02-11 02:51:55 +00:00
case ModeComponentResize:
selected.Resize(0, -1)
2019-02-11 02:51:55 +00:00
}
case console.KeyDown:
switch l.mode {
2019-02-16 03:46:03 +00:00
case ModeDefault:
l.changeMode(ModeComponentSelect)
2019-03-08 04:04:46 +00:00
l.menu.Highlight(l.getComponent(l.selection))
2019-02-16 03:46:03 +00:00
case ModeComponentSelect:
l.moveSelection(e)
2019-03-08 04:04:46 +00:00
l.menu.Highlight(l.getComponent(l.selection))
2019-02-11 02:51:55 +00:00
case ModeMenuOptionSelect:
2019-03-08 04:04:46 +00:00
l.menu.Down()
2019-02-11 02:51:55 +00:00
case ModeComponentMove:
selected.Move(0, 1)
2019-02-11 02:51:55 +00:00
case ModeComponentResize:
selected.Resize(0, 1)
2019-02-11 02:51:55 +00:00
}
}
}
2019-02-11 02:51:55 +00:00
func (l *Layout) ChangeDimensions(width, height int) {
l.SetRect(0, 0, width, height)
2019-01-31 00:02:38 +00:00
}
2019-03-08 04:04:46 +00:00
func (l *Layout) getComponent(i int) *component.Component {
return l.Components[i]
}
func (l *Layout) getSelection() *component.Component {
return l.Components[l.selection]
}
func (l *Layout) moveSelection(direction string) {
previouslySelected := l.getSelection()
2019-03-22 03:47:28 +00:00
newlySelectedIndex := l.selection + 1
for i, current := range l.Components {
if current == previouslySelected {
continue
}
2019-03-22 03:47:28 +00:00
if newlySelectedIndex >= len(l.Components) {
newlySelectedIndex = i
}
var previouslySelectedCornerPoint image.Point
var newlySelectedCornerPoint image.Point
var currentCornerPoint image.Point
switch direction {
case console.KeyLeft:
previouslySelectedCornerPoint = util.GetRectLeftSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = util.GetRectRightSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = util.GetRectRightSideCenter(current.GetRect())
case console.KeyRight:
previouslySelectedCornerPoint = util.GetRectRightSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = util.GetRectLeftSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = util.GetRectLeftSideCenter(current.GetRect())
case console.KeyUp:
previouslySelectedCornerPoint = util.GetRectTopSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = util.GetRectBottomSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = util.GetRectBottomSideCenter(current.GetRect())
case console.KeyDown:
previouslySelectedCornerPoint = util.GetRectBottomSideCenter(previouslySelected.GetRect())
newlySelectedCornerPoint = util.GetRectTopSideCenter(l.getComponent(newlySelectedIndex).GetRect())
currentCornerPoint = util.GetRectTopSideCenter(current.GetRect())
}
2019-04-17 03:55:23 +00:00
switch direction {
case console.KeyLeft:
fallthrough
case console.KeyRight:
if ui.AbsInt(currentCornerPoint.X-previouslySelectedCornerPoint.X) <= ui.AbsInt(newlySelectedCornerPoint.X-previouslySelectedCornerPoint.X) {
if ui.AbsInt(currentCornerPoint.Y-previouslySelectedCornerPoint.Y) <= ui.AbsInt(newlySelectedCornerPoint.Y-previouslySelectedCornerPoint.Y) {
newlySelectedIndex = i
}
}
case console.KeyUp:
fallthrough
case console.KeyDown:
if ui.AbsInt(currentCornerPoint.Y-previouslySelectedCornerPoint.Y) <= ui.AbsInt(newlySelectedCornerPoint.Y-previouslySelectedCornerPoint.Y) {
if ui.AbsInt(currentCornerPoint.X-previouslySelectedCornerPoint.X) <= ui.AbsInt(newlySelectedCornerPoint.X-previouslySelectedCornerPoint.X) {
newlySelectedIndex = i
}
}
}
}
2019-03-22 03:47:28 +00:00
if newlySelectedIndex < len(l.Components) {
l.selection = newlySelectedIndex
}
}
2019-02-11 02:51:55 +00:00
func (l *Layout) Draw(buffer *ui.Buffer) {
2019-01-31 00:02:38 +00:00
columnWidth := float64(l.GetRect().Dx()) / float64(console.ColumnsCount)
rowHeight := float64(l.GetRect().Dy()-statusbarHeight) / float64(console.RowsCount)
2019-01-31 00:02:38 +00:00
for _, c := range l.Components {
rectangle := calculateComponentCoordinates(c, columnWidth, rowHeight)
c.SetRect(rectangle.Min.X, rectangle.Min.Y, rectangle.Max.X, rectangle.Max.Y)
}
2019-03-08 04:04:46 +00:00
for _, c := range l.Components {
c.Draw(buffer)
2019-01-31 00:02:38 +00:00
}
2019-02-11 02:51:55 +00:00
2019-02-25 00:37:57 +00:00
l.statusbar.SetRect(
0, l.GetRect().Dy()-statusbarHeight,
l.GetRect().Dx(), l.GetRect().Dy())
2019-02-25 00:08:36 +00:00
l.statusbar.Draw(buffer)
2019-03-03 01:04:02 +00:00
l.menu.Draw(buffer)
2019-01-31 00:02:38 +00:00
}
2019-03-17 00:54:24 +00:00
2019-04-14 00:24:24 +00:00
func (l *Layout) findComponentAtPoint(point image.Point) (*component.Component, int) {
columnWidth := float64(l.GetRect().Dx()) / float64(console.ColumnsCount)
rowHeight := float64(l.GetRect().Dy()-statusbarHeight) / float64(console.RowsCount)
2019-04-14 00:24:24 +00:00
for i, c := range l.Components {
rectangle := calculateComponentCoordinates(c, columnWidth, rowHeight)
2019-04-14 00:24:24 +00:00
if point.In(rectangle) {
return c, i
}
}
return nil, -1
}
func calculateComponentCoordinates(c *component.Component, columnWidth float64, rowHeight float64) image.Rectangle {
x1 := math.Floor(float64(c.Location.X) * columnWidth)
y1 := math.Floor(float64(c.Location.Y) * rowHeight)
x2 := x1 + math.Floor(float64(c.Size.X))*columnWidth
y2 := y1 + math.Floor(float64(c.Size.Y))*rowHeight
if x2-x1 < minDimension {
x2 = x1 + minDimension
}
if y2-y1 < minDimension {
y2 = y1 + minDimension
}
return image.Rectangle{Min: image.Point{
X: int(x1), Y: int(y1)},
Max: image.Point{X: int(x2), Y: int(y2)},
}
}
2019-03-17 00:54:24 +00:00
func (l *Layout) resetAlerts() {
for _, c := range l.Components {
c.AlertChannel <- nil
}
}
func (l *Layout) WerePositionsChanged() bool {
return l.positionsChanged
}