From bf98883e17149869821eb3438aab47822b9af203 Mon Sep 17 00:00:00 2001 From: aca Date: Tue, 21 Apr 2020 23:51:07 +0900 Subject: [PATCH] Initial commit --- README.md | 59 +++++++++ go.mod | 8 ++ go.sum | 14 ++ tdraw.go | 373 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 454 insertions(+) create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 tdraw.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..68f7514 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +tdraw +--- +Inspired by [Explaining Code using ASCII Art](https://blog.regehr.org/archives/1653).
+Supports box / line drawing, text input and eraser. + +#### Install +``` +go get -u github.com/aca/tdraw +``` +#### Usage +``` +tdraw > output +``` + +``` +tdraw has 3 mode. + +ESC: Box Mode(default) +L: Draw Line +I: Text +--- +MouseR: Eraser +CTRL-C/CTRL-D: exit +``` + +``` + ┌──────────────┐ + │ │ BOX + └──────────────┘ + <--------------- LINES + ---------------> + + TEXT + + ---- --- ----> ERASE WITH MouseR +``` + +``` + +----------+ + v | + ┌─────────────────┐ | + │ STATE A │ | + └─────────────────┘ | + | | + v | + ┌─────────────────┐ | + │ │ | ┌───────────────┐ + │ STATE B │ ---------> │ FAIL │ + │ │ | └───────────────┘ + └─────────────────┘ | + | | + v | + ┌─────────────────┐ | + │ │ | + │ STATE C │ | + │ │ ---+ + └─────────────────┘ + +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9e3cbe4 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/aca/tdraw + +go 1.14 + +require ( + github.com/gdamore/tcell v1.3.0 + github.com/mattn/go-runewidth v0.0.9 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6cb56c5 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM= +github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= +github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4= +github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/tdraw.go b/tdraw.go new file mode 100644 index 0000000..bc1ba03 --- /dev/null +++ b/tdraw.go @@ -0,0 +1,373 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/gdamore/tcell" + "github.com/gdamore/tcell/encoding" + + "github.com/mattn/go-runewidth" +) + +const ( + MODE_BOX = "BOX" + MODE_LINE = "LINE" + MODE_INSERT = "INSERT" + MODE_ERASOR = "ERASE" +) + +var defStyle tcell.Style + +func emitStr(s tcell.Screen, x, y int, style tcell.Style, str string) { + for _, c := range str { + var comb []rune + w := runewidth.RuneWidth(c) + if w == 0 { + comb = []rune{c} + c = ' ' + w = 1 + } + s.SetContent(x, y, c, comb, style) + x += w + } +} + +func drawLine(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, r rune) { + // if y2 < y1 { + // y1, y2 = y2, y1 + // } + // if x2 < x1 { + // x1, x2 = x2, x1 + // } + + // do not draw point + if x1 == x2 && y1 == y2 { + return + } + + cross := false + if c, _, _, _ := s.GetContent(x1, y1); c != ' ' { + cross = true + } + + if x1 == x2 { + if y1 < y2 { + for row := y1; row <= y2; row++ { + s.SetContent(x1, row, '|', nil, tcell.StyleDefault) + } + s.SetContent(x1, y2, 'v', nil, tcell.StyleDefault) + + } else { + for row := y2; row <= y1; row++ { + s.SetContent(x1, row, '|', nil, tcell.StyleDefault) + } + s.SetContent(x1, y2, '^', nil, tcell.StyleDefault) + } + } else if y1 == y2 { + if x1 < x2 { + for col := x1; col <= x2; col++ { + s.SetContent(col, y1, '-', nil, tcell.StyleDefault) + } + s.SetContent(x2, y1, '>', nil, tcell.StyleDefault) + } else { + for col := x2; col <= x1; col++ { + s.SetContent(col, y1, '-', nil, tcell.StyleDefault) + } + s.SetContent(x2, y1, '<', nil, tcell.StyleDefault) + } + } + + if cross { + s.SetContent(x1, y1, '+', nil, tcell.StyleDefault) + } + + return + // for col := x1; col <= x2; col++ { + // // RuneHLine = '─' + // s.SetContent(col, y1, tcell.RuneHLine, nil, tcell.StyleDefault) + // s.SetContent(col, y2, tcell.RuneHLine, nil, tcell.StyleDefault) + // } + // for row := y1 + 1; row <= y2; row++ { + // // RuneVLine = '│' + // s.SetContent(x1, row, tcell.RuneVLine, nil, tcell.StyleDefault) + // s.SetContent(x2, row, tcell.RuneVLine, nil, tcell.StyleDefault) + // } + // if y1 != y2 && x1 != x2 { + // // Only add corners if we need to + // s.SetContent(x1, y1, tcell.RuneULCorner, nil, tcell.StyleDefault) + // s.SetContent(x2, y1, tcell.RuneURCorner, nil, tcell.StyleDefault) + // s.SetContent(x1, y2, tcell.RuneLLCorner, nil, tcell.StyleDefault) + // s.SetContent(x2, y2, tcell.RuneLRCorner, nil, tcell.StyleDefault) + // } +} + +func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, r rune) { + if y2 < y1 { + y1, y2 = y2, y1 + } + if x2 < x1 { + x1, x2 = x2, x1 + } + + // do not draw point + if x1 == x2 && y1 == y2 { + return + } + + for col := x1; col <= x2; col++ { + // RuneHLine = '─' + s.SetContent(col, y1, tcell.RuneHLine, nil, tcell.StyleDefault) + s.SetContent(col, y2, tcell.RuneHLine, nil, tcell.StyleDefault) + } + for row := y1; row <= y2; row++ { + // RuneVLine = '│' + s.SetContent(x1, row, tcell.RuneVLine, nil, tcell.StyleDefault) + s.SetContent(x2, row, tcell.RuneVLine, nil, tcell.StyleDefault) + } + if y1 != y2 && x1 != x2 { + // Only add corners if we need to + s.SetContent(x1, y1, tcell.RuneULCorner, nil, tcell.StyleDefault) + s.SetContent(x2, y1, tcell.RuneURCorner, nil, tcell.StyleDefault) + s.SetContent(x1, y2, tcell.RuneLLCorner, nil, tcell.StyleDefault) + s.SetContent(x2, y2, tcell.RuneLRCorner, nil, tcell.StyleDefault) + } +} + +func drawSelect(s tcell.Screen, x1, y1, x2, y2 int, sel bool) { + if y2 < y1 { + y1, y2 = y2, y1 + } + if x2 < x1 { + x1, x2 = x2, x1 + } + for row := y1; row <= y2; row++ { + for col := x1; col <= x2; col++ { + mainc, combc, style, width := s.GetContent(col, row) + if style == tcell.StyleDefault { + style = defStyle + } + style = style.Reverse(sel) + s.SetContent(col, row, mainc, combc, style) + col += width - 1 + } + } +} + +func main() { + log.SetFlags(log.LstdFlags | log.Lshortfile) + log.SetOutput(os.Stdout) + + encoding.Register() + + s, e := tcell.NewScreen() + if e != nil { + fmt.Fprintf(os.Stderr, "%v\n", e) + os.Exit(1) + } + if e := s.Init(); e != nil { + fmt.Fprintf(os.Stderr, "%v\n", e) + os.Exit(1) + } + defStyle = tcell.StyleDefault + s.SetStyle(defStyle) + + s.EnableMouse() + s.Clear() + + white := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorBlack) + + // mouse + mx, my := -1, -1 + ox, oy := -1, -1 + bx, by := -1, -1 + // w, h := s.Size() + lchar := '*' + + mode := MODE_BOX + imodeCurX, imodeCurY := 0, 0 + imodeStartX, _ := 0, 0 + // imodeLastC := ' ' + + for { + r, _, _, _ := s.GetContent(mx, my) + emitStr(s, 1, 1, white, fmt.Sprintf("[%s][%v,%v][%d]"+" ", mode, mx, my, r)) + + s.Show() + ev := s.PollEvent() + st := tcell.StyleDefault + up := tcell.StyleDefault. + Background(tcell.ColorBlue). + Foreground(tcell.ColorBlack) + // w, h = s.Size() + + // always clear any old selection box + if ox >= 0 && oy >= 0 && bx >= 0 { + drawSelect(s, ox, oy, bx, by, false) + } + + switch ev := ev.(type) { + case *tcell.EventResize: + // s.Sync() + // s.SetContent(w-1, h-1, 'R', nil, st) + case *tcell.EventKey: + + if ev.Key() == tcell.KeyCtrlC || ev.Key() == tcell.KeyCtrlD { + s.Sync() + // s.Fini() + sizeX, sizeY := s.Size() + + // arr := make([][]rune, sizeX) + // for i := range arr { + // arr[i] = make([]rune, sizeY) + // } + arr := make([]string, sizeY) + + for y := 2; y < sizeY; y++ { + for x := 0; x < sizeX; x++ { + c, _, _, _ := s.GetContent(x, y) + // log.Println(x, y, c) + arr[y] = arr[y] + string(c) + } + } + s.Clear() + s.Sync() + + { + n := 0 + for y := 0; y < len(arr); y++ { + // if strings.TrimSpace(arr[y]) == "" || strings.TrimSpace(arr[y]) == "\n" { + if strings.TrimSpace(arr[y]) == "" { + n++ + } else { + break + } + } + arr = arr[n:len(arr)] + } + + { + n := 0 + for y := len(arr) - 1; y >= 0; y-- { + if strings.TrimSpace(arr[y]) == "" { + // if strings.TrimSpace(arr[y]) == "" || strings.TrimSpace(arr[y]) == "\n" { + n++ + } else { + break + } + } + arr = arr[0 : len(arr)-n] + } + + for y := 0; y < len(arr); y++ { + fmt.Println(arr[y]) + } + os.Exit(0) + } + + switch mode { + case MODE_BOX: + switch ev.Key() { + default: + switch ev.Rune() { + case 'i': + mode = MODE_INSERT + imodeCurX, imodeCurY = mx, my + imodeStartX, _ = mx, my + s.SetContent(imodeCurX, imodeCurY, '_', nil, st) + case 'l': + mode = MODE_LINE + } + } + case MODE_LINE: + switch ev.Key() { + case tcell.KeyEscape: + mode = MODE_BOX + } + + case MODE_INSERT: + switch ev.Key() { + case tcell.KeyEscape: + mode = MODE_BOX + s.SetContent(imodeCurX, imodeCurY, ' ', nil, st) + // s.Fini() + // os.Exit(0) + case tcell.KeyEnter: + // mode = MODE_NORMAL + s.SetContent(imodeCurX, imodeCurY, ' ', nil, st) + imodeCurX = imodeStartX + imodeCurY = imodeCurY + 1 + case tcell.KeyDEL: + if imodeCurX > imodeStartX { + s.SetContent(imodeCurX, imodeCurY, ' ', nil, st) + s.SetContent(imodeCurX-1, imodeCurY, '_', nil, st) + imodeCurX -= 1 + } else { + s.SetContent(imodeCurX, imodeCurY, '_', nil, st) + } + default: + s.SetContent(imodeCurX, imodeCurY, ev.Rune(), nil, st) + // imodeLastC, _, _, _ = s.GetContent(imodeCurX, imodeCurY) + imodeCurX += 1 + s.SetContent(imodeCurX, imodeCurY, '_', nil, st) + } + } + case *tcell.EventMouse: + x, y := ev.Position() + mx, my = x, y + button := ev.Buttons() + + if button != tcell.ButtonNone && ox < 0 { + ox, oy = x, y + } + + switch mode { + case MODE_BOX: + switch ev.Buttons() { + case tcell.ButtonNone: + if ox >= 0 { + bg := tcell.Color((lchar - '0') * 2) + drawBox(s, ox, oy, x, y, up.Background(bg), lchar) + ox, oy = -1, -1 + bx, by = -1, -1 + } + case tcell.Button1: + ch := ' ' + bx, by = x, y + lchar = rune(ch) + if ox >= 0 && bx >= 0 { + drawSelect(s, ox, oy, bx, by, true) + } + case tcell.Button3: + ox, oy = -1, -1 + bx, by = -1, -1 + s.SetContent(x, y, ' ', nil, tcell.StyleDefault) + } + case MODE_LINE: + switch ev.Buttons() { + case tcell.ButtonNone: + if ox >= 0 { + bg := tcell.Color((lchar - '0') * 2) + drawLine(s, ox, oy, x, y, up.Background(bg), lchar) + ox, oy = -1, -1 + bx, by = -1, -1 + } + case tcell.Button1: + ch := ' ' + bx, by = x, y + lchar = rune(ch) + if ox >= 0 && bx >= 0 { + drawSelect(s, ox, oy, bx, by, true) + } + case tcell.Button3: + ox, oy = -1, -1 + bx, by = -1, -1 + s.SetContent(x, y, ' ', nil, tcell.StyleDefault) + } + + } + } + } +}