move a bunch of code into view, which holds onto a bunch of the drawing logic and settings now
This commit is contained in:
parent
f68bb4d8a2
commit
7c7502b42f
@ -17,6 +17,14 @@ var (
|
|||||||
Right = XY{1, 0}
|
Right = XY{1, 0}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Units is the set of unit vectors
|
||||||
|
var Units = []XY{
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
// Add returns the result of adding the two XYs' fields individually
|
// Add returns the result of adding the two XYs' fields individually
|
||||||
func (xy XY) Add(xy2 XY) XY {
|
func (xy XY) Add(xy2 XY) XY {
|
||||||
xy[0] += xy2[0]
|
xy[0] += xy2[0]
|
||||||
|
@ -49,6 +49,27 @@ func (r Rect) Corner(xDir, yDir XY) XY {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EdgeMidpoint returns the point which is the midpoint of the edge dientified by the
|
||||||
|
// direction (Up/Down/Left/Right)
|
||||||
|
func (r Rect) EdgeMidpoint(dir XY, rounder Rounder) XY {
|
||||||
|
var a, b XY
|
||||||
|
switch dir {
|
||||||
|
case Up:
|
||||||
|
a, b = r.Corner(Left, Up), r.Corner(Right, Up)
|
||||||
|
case Down:
|
||||||
|
a, b = r.Corner(Left, Down), r.Corner(Right, Down)
|
||||||
|
case Left:
|
||||||
|
a, b = r.Corner(Left, Up), r.Corner(Left, Down)
|
||||||
|
case Right:
|
||||||
|
a, b = r.Corner(Right, Up), r.Corner(Right, Down)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported direction: %#v", dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
mid := a.Midpoint(b, rounder)
|
||||||
|
return mid
|
||||||
|
}
|
||||||
|
|
||||||
func (r Rect) halfSize(rounder Rounder) XY {
|
func (r Rect) halfSize(rounder Rounder) XY {
|
||||||
return r.Size.Div(XY{2, 2}, rounder)
|
return r.Size.Div(XY{2, 2}, rounder)
|
||||||
}
|
}
|
||||||
|
140
gim/line.go
140
gim/line.go
@ -1,101 +1,18 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/gim/geo"
|
"github.com/mediocregopher/ginger/gim/geo"
|
||||||
"github.com/mediocregopher/ginger/gim/terminal"
|
"github.com/mediocregopher/ginger/gim/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// boxEdgeAdj returns the midpoint of a box's edge, using the given direction
|
|
||||||
// (single-dimension unit-vector) to know which edge to look at.
|
|
||||||
func boxEdgeAdj(box box, dir geo.XY) geo.XY {
|
|
||||||
boxRect := box.rect()
|
|
||||||
var a, b geo.XY
|
|
||||||
switch dir {
|
|
||||||
case geo.Up:
|
|
||||||
a, b = boxRect.Corner(geo.Left, geo.Up), boxRect.Corner(geo.Right, geo.Up)
|
|
||||||
case geo.Down:
|
|
||||||
a, b = boxRect.Corner(geo.Left, geo.Down), boxRect.Corner(geo.Right, geo.Down)
|
|
||||||
case geo.Left:
|
|
||||||
a, b = boxRect.Corner(geo.Left, geo.Up), boxRect.Corner(geo.Left, geo.Down)
|
|
||||||
case geo.Right:
|
|
||||||
a, b = boxRect.Corner(geo.Right, geo.Up), boxRect.Corner(geo.Right, geo.Down)
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unsupported direction: %#v", dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
mid := a.Midpoint(b, rounder)
|
|
||||||
return mid
|
|
||||||
}
|
|
||||||
|
|
||||||
var dirs = []geo.XY{
|
|
||||||
geo.Up,
|
|
||||||
geo.Down,
|
|
||||||
geo.Left,
|
|
||||||
geo.Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
// boxesRelDir returns the "best" direction between from and to. Returns
|
|
||||||
// geo.Zero if they overlap. It also returns the secondary direction. E.g. Down
|
|
||||||
// and Left. The secondary direction will never be zero if primary is given,
|
|
||||||
// even if the two boxes are in-line
|
|
||||||
func boxesRelDir(from, to box) (geo.XY, geo.XY) {
|
|
||||||
fromRect, toRect := from.rect(), to.rect()
|
|
||||||
rels := make([]int, len(dirs))
|
|
||||||
for i, dir := range dirs {
|
|
||||||
rels[i] = toRect.Edge(dir.Inv()) - fromRect.Edge(dir)
|
|
||||||
if dir == geo.Up || dir == geo.Left {
|
|
||||||
rels[i] *= -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// find primary
|
|
||||||
var primary geo.XY
|
|
||||||
var primaryMax int
|
|
||||||
for i, rel := range rels {
|
|
||||||
if rel < 0 {
|
|
||||||
continue
|
|
||||||
} else if rel > primaryMax || i == 0 {
|
|
||||||
primary = dirs[i]
|
|
||||||
primaryMax = rel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if all rels were negative the boxes are overlapping, return zeros
|
|
||||||
if primary == geo.Zero {
|
|
||||||
return geo.Zero, geo.Zero
|
|
||||||
}
|
|
||||||
|
|
||||||
// now find secondary, which must be perpendicular to primary
|
|
||||||
var secondary geo.XY
|
|
||||||
var secondaryMax int
|
|
||||||
var secondarySet bool
|
|
||||||
for i, rel := range rels {
|
|
||||||
if dirs[i] == primary {
|
|
||||||
continue
|
|
||||||
} else if dirs[i][0] == 0 && primary[0] == 0 {
|
|
||||||
continue
|
|
||||||
} else if dirs[i][1] == 0 && primary[1] == 0 {
|
|
||||||
continue
|
|
||||||
} else if !secondarySet || rel > secondaryMax {
|
|
||||||
secondary = dirs[i]
|
|
||||||
secondaryMax = rel
|
|
||||||
secondarySet = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return primary, secondary
|
|
||||||
}
|
|
||||||
|
|
||||||
var lineSegments = func() map[[2]geo.XY]string {
|
var lineSegments = func() map[[2]geo.XY]string {
|
||||||
m := map[[2]geo.XY]string{
|
m := map[[2]geo.XY]string{
|
||||||
{{-1, 0}, {1, 0}}: "─",
|
{geo.Left, geo.Right}: "─",
|
||||||
{{0, 1}, {0, -1}}: "│",
|
{geo.Down, geo.Up}: "│",
|
||||||
{{1, 0}, {0, 1}}: "┌",
|
{geo.Right, geo.Down}: "┌",
|
||||||
{{-1, 0}, {0, 1}}: "┐",
|
{geo.Left, geo.Down}: "┐",
|
||||||
{{1, 0}, {0, -1}}: "└",
|
{geo.Right, geo.Up}: "└",
|
||||||
{{-1, 0}, {0, -1}}: "┘",
|
{geo.Left, geo.Up}: "┘",
|
||||||
}
|
}
|
||||||
|
|
||||||
// the inverse segments use the same characters
|
// the inverse segments use the same characters
|
||||||
@ -122,17 +39,48 @@ var arrows = map[geo.XY]string{
|
|||||||
geo.Right: ">",
|
geo.Right: ">",
|
||||||
}
|
}
|
||||||
|
|
||||||
func basicLine(term *terminal.Terminal, from, to box) {
|
type line [2]*box
|
||||||
dir, dirSec := boxesRelDir(from, to)
|
|
||||||
|
|
||||||
// if the boxes overlap then don't draw anything
|
// given the "primary" direction the line should be headed, picks a possible
|
||||||
if dir == geo.Zero {
|
// secondary one which may be used to detour along the path in order to reach
|
||||||
return
|
// the destination (in the case that the two boxes are diagonal from each other)
|
||||||
|
func (l line) secondaryDir(primary geo.XY) geo.XY {
|
||||||
|
fromRect, toRect := l[0].rect(), l[1].rect()
|
||||||
|
rels := make([]int, len(geo.Units))
|
||||||
|
for i, dir := range geo.Units {
|
||||||
|
rels[i] = toRect.Edge(dir.Inv()) - fromRect.Edge(dir)
|
||||||
|
if dir == geo.Up || dir == geo.Left {
|
||||||
|
rels[i] *= -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var secondary geo.XY
|
||||||
|
var secondaryMax int
|
||||||
|
var secondarySet bool
|
||||||
|
for i, rel := range rels {
|
||||||
|
if geo.Units[i] == primary {
|
||||||
|
continue
|
||||||
|
} else if geo.Units[i][0] == 0 && primary[0] == 0 {
|
||||||
|
continue
|
||||||
|
} else if geo.Units[i][1] == 0 && primary[1] == 0 {
|
||||||
|
continue
|
||||||
|
} else if !secondarySet || rel > secondaryMax {
|
||||||
|
secondary = geo.Units[i]
|
||||||
|
secondaryMax = rel
|
||||||
|
secondarySet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secondary
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l line) draw(term *terminal.Terminal, dir geo.XY) {
|
||||||
|
from, to := *l[0], *l[1]
|
||||||
|
dirSec := l.secondaryDir(dir)
|
||||||
|
|
||||||
dirInv := dir.Inv()
|
dirInv := dir.Inv()
|
||||||
start := boxEdgeAdj(from, dir)
|
start := from.rect().EdgeMidpoint(dir, rounder)
|
||||||
end := boxEdgeAdj(to, dirInv)
|
end := to.rect().EdgeMidpoint(dirInv, rounder)
|
||||||
mid := start.Midpoint(end, rounder)
|
mid := start.Midpoint(end, rounder)
|
||||||
|
|
||||||
along := func(xy, dir geo.XY) int {
|
along := func(xy, dir geo.XY) int {
|
||||||
|
98
gim/main.go
98
gim/main.go
@ -18,9 +18,9 @@ import (
|
|||||||
// - Absolute positioning of some/all vertices
|
// - Absolute positioning of some/all vertices
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// - actually use flowDir
|
|
||||||
// - assign edges to "slots" on boxes
|
// - assign edges to "slots" on boxes
|
||||||
// - figure out how to keep boxes sorted on their levels (e.g. the "b" nodes)
|
// - figure out how to keep boxes sorted on their levels (e.g. the "b" nodes)
|
||||||
|
// - be able to draw circular graphs
|
||||||
|
|
||||||
const (
|
const (
|
||||||
framerate = 10
|
framerate = 10
|
||||||
@ -72,100 +72,18 @@ func main() {
|
|||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
term := terminal.New()
|
term := terminal.New()
|
||||||
term.Reset()
|
term.Reset()
|
||||||
termSize := term.WindowSize()
|
term.HideCursor()
|
||||||
g := mkGraph()
|
|
||||||
|
|
||||||
// level 0 is at the bottom of the screen, cause life is easier that way
|
v := view{
|
||||||
levels := map[*gg.Vertex]int{}
|
g: mkGraph(),
|
||||||
getLevel := func(v *gg.Vertex) int {
|
flowDir: geo.Down,
|
||||||
// if any of the tos have a level, this will be greater than the max
|
start: str("c"),
|
||||||
toMax := -1
|
center: geo.Zero.Midpoint(term.WindowSize(), rounder),
|
||||||
for _, e := range v.Out {
|
|
||||||
lvl, ok := levels[e.To]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
} else if lvl > toMax {
|
|
||||||
toMax = lvl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if toMax >= 0 {
|
|
||||||
return toMax + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise level is 0
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Walk(g.Value(str("c")), func(v *gg.Vertex) bool {
|
|
||||||
levels[v] = getLevel(v)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
// consolidate by level
|
|
||||||
byLevel := map[int][]*gg.Vertex{}
|
|
||||||
maxLvl := -1
|
|
||||||
for v, lvl := range levels {
|
|
||||||
byLevel[lvl] = append(byLevel[lvl], v)
|
|
||||||
if lvl > maxLvl {
|
|
||||||
maxLvl = lvl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create boxes
|
|
||||||
boxes := map[*gg.Vertex]box{}
|
|
||||||
for lvl := 0; lvl <= maxLvl; lvl++ {
|
|
||||||
vv := byLevel[lvl]
|
|
||||||
for i, v := range vv {
|
|
||||||
b := boxFromVertex(v, geo.Right)
|
|
||||||
bSize := b.rect().Size
|
|
||||||
b.topLeft = geo.XY{
|
|
||||||
10*(i-(len(vv)/2)) - (bSize[0] / 2),
|
|
||||||
lvl * -10,
|
|
||||||
}
|
|
||||||
boxes[v] = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// center boxes. first find overall dimensions, use that to create delta
|
|
||||||
// vector which would move that to the center
|
|
||||||
var graphRect geo.Rect
|
|
||||||
for _, b := range boxes {
|
|
||||||
graphRect = graphRect.Union(b.rect())
|
|
||||||
}
|
|
||||||
|
|
||||||
graphMid := graphRect.Center(rounder)
|
|
||||||
screenMid := geo.Zero.Midpoint(termSize, rounder)
|
|
||||||
delta := screenMid.Sub(graphMid)
|
|
||||||
|
|
||||||
// translate all boxes by delta
|
|
||||||
for v, b := range boxes {
|
|
||||||
b.topLeft = b.topLeft.Add(delta)
|
|
||||||
boxes[v] = b
|
|
||||||
}
|
|
||||||
|
|
||||||
// create lines
|
|
||||||
var lines [][2]box
|
|
||||||
for v := range levels {
|
|
||||||
b := boxes[v]
|
|
||||||
for _, e := range v.In {
|
|
||||||
bFrom := boxes[e.From]
|
|
||||||
lines = append(lines, [2]box{bFrom, b})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for range time.Tick(frameperiod) {
|
for range time.Tick(frameperiod) {
|
||||||
// update phase
|
|
||||||
// nufin
|
|
||||||
|
|
||||||
// draw phase
|
|
||||||
term.Reset()
|
term.Reset()
|
||||||
for v := range boxes {
|
v.draw(term)
|
||||||
boxes[v].draw(term)
|
|
||||||
}
|
|
||||||
for _, line := range lines {
|
|
||||||
basicLine(term, line[0], line[1])
|
|
||||||
}
|
|
||||||
term.Flush()
|
term.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,16 @@ func (t *Terminal) MoveCursor(by geo.XY) {
|
|||||||
t.MoveCursorTo(t.pos.Add(by))
|
t.MoveCursorTo(t.pos.Add(by))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HideCursor causes the cursor to not actually be shown
|
||||||
|
func (t *Terminal) HideCursor() {
|
||||||
|
fmt.Fprintf(t.buf, "\033[?25l")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowCursor causes the cursor to be shown, if it was previously hidden
|
||||||
|
func (t *Terminal) ShowCursor() {
|
||||||
|
fmt.Fprintf(t.buf, "\033[?25h")
|
||||||
|
}
|
||||||
|
|
||||||
// Reset completely clears all drawn characters on the screen and returns the
|
// Reset completely clears all drawn characters on the screen and returns the
|
||||||
// cursor to the origin
|
// cursor to the origin
|
||||||
func (t *Terminal) Reset() {
|
func (t *Terminal) Reset() {
|
||||||
|
99
gim/view.go
Normal file
99
gim/view.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mediocregopher/ginger/gg"
|
||||||
|
"github.com/mediocregopher/ginger/gim/geo"
|
||||||
|
"github.com/mediocregopher/ginger/gim/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type view struct {
|
||||||
|
g *gg.Graph
|
||||||
|
flowDir geo.XY
|
||||||
|
start str
|
||||||
|
center geo.XY
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *view) draw(term *terminal.Terminal) {
|
||||||
|
// level 0 is at the bottom of the screen, cause life is easier that way
|
||||||
|
levels := map[*gg.Vertex]int{}
|
||||||
|
getLevel := func(v *gg.Vertex) int {
|
||||||
|
// if any of the tos have a level, this will be greater than the max
|
||||||
|
toMax := -1
|
||||||
|
for _, e := range v.Out {
|
||||||
|
lvl, ok := levels[e.To]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
} else if lvl > toMax {
|
||||||
|
toMax = lvl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if toMax >= 0 {
|
||||||
|
return toMax + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise level is 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
v.g.Walk(v.g.Value(v.start), func(v *gg.Vertex) bool {
|
||||||
|
levels[v] = getLevel(v)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// consolidate by level
|
||||||
|
byLevel := map[int][]*gg.Vertex{}
|
||||||
|
maxLvl := -1
|
||||||
|
for v, lvl := range levels {
|
||||||
|
byLevel[lvl] = append(byLevel[lvl], v)
|
||||||
|
if lvl > maxLvl {
|
||||||
|
maxLvl = lvl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create boxes
|
||||||
|
boxes := map[*gg.Vertex]*box{}
|
||||||
|
for lvl := 0; lvl <= maxLvl; lvl++ {
|
||||||
|
vv := byLevel[lvl]
|
||||||
|
for i, v := range vv {
|
||||||
|
b := boxFromVertex(v, geo.Right)
|
||||||
|
bSize := b.rect().Size
|
||||||
|
// TODO make this dependent on flowDir
|
||||||
|
b.topLeft = geo.XY{
|
||||||
|
10*(i-(len(vv)/2)) - (bSize[0] / 2),
|
||||||
|
lvl * -10,
|
||||||
|
}
|
||||||
|
boxes[v] = &b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create lines
|
||||||
|
var lines []line
|
||||||
|
for v := range levels {
|
||||||
|
b := boxes[v]
|
||||||
|
for _, e := range v.In {
|
||||||
|
bFrom := boxes[e.From]
|
||||||
|
lines = append(lines, line{bFrom, b})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// translate all boxes so the graph is centered around v.center. Since the
|
||||||
|
// lines use pointers to the boxes this will update them as well
|
||||||
|
var graphRect geo.Rect
|
||||||
|
for _, b := range boxes {
|
||||||
|
graphRect = graphRect.Union(b.rect())
|
||||||
|
}
|
||||||
|
graphMid := graphRect.Center(rounder)
|
||||||
|
delta := v.center.Sub(graphMid)
|
||||||
|
for _, b := range boxes {
|
||||||
|
b.topLeft = b.topLeft.Add(delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// actually draw the boxes and lines
|
||||||
|
for _, box := range boxes {
|
||||||
|
box.draw(term)
|
||||||
|
}
|
||||||
|
for _, line := range lines {
|
||||||
|
line.draw(term, v.flowDir)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user