[
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.vscode/\n.mypy_cache/\n.idea\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n## [3.1.0] - 2019-07-15\n\n### Added\n\n- Added Tree widget [#237]\n\n## [3.0.0] - 2019-03-07\n\n### Changed\n\n- Added sync.Locker interface to Drawable interface\n\n## 2019-03-01\n\n### Changed\n\n- Change scroll method names in List widget\n\n### Fixed\n\n- Fix List widget scrolling\n\n## 2019-02-28\n\n### Added\n\n- Add `ColumnResizer` to table which allows for custom column sizing\n- Add widget padding\n\n### Changed\n\n- Change various widget field names\n- s/`TextParse`/`ParseStyles`\n- Remove `AddColorMap` in place of modifying `StyleParserColorMap` directly\n\n## 2019-01-31\n\n### Added\n\n- Add more scrolling options to List\n\n### Changed\n\n- Make list scroll automatically\n\n### Added\n\n## 2019-01-26\n\n### Added\n\n- Add scrolling to List widget\n- Add WrapText option to Paragraph\n  - controls if text should wrap automatically\n\n## 2019-01-24\n\n### Added\n\n- Add image widget [#126]\n\n### Changed\n\n- Change LineChart to Plot\n  - Added ScatterPlot mode which plots points instead of lines between points\n\n## 2019-01-23\n\n### Added\n\n- Add `Canvas` which allows for drawing braille lines to a `Buffer`\n\n### Changed\n\n- Set `termbox-go` backend to 256 colors by default\n- Moved widgets to `github.com/gizak/termui/widgets`\n- Rewrote widgets (check examples and code)\n- Rewrote grid\n  - grids are instantiated locally instead of through `termui.Body`\n  - grids can be nested\n  - change grid layout mechanism\n    - columns and rows can be arbitrarily nested\n    - column and row size is now specified as a ratio of the available space\n- `Cell`s now contain a `Style` which holds a `Fg`, `Bg`, and `Modifier`\n- Change `Bufferer` interface to `Drawable`\n  - Add `GetRect` and `SetRect` methods to control widget sizing\n  - Change `Buffer` method to `Draw`\n    - `Draw` takes a `Buffer` and draws to it instead of returning a new `Buffer`\n- Refactor `Theme`\n  - `Theme` is now a large struct which holds the default `Styles` of everything\n- Combine `TermWidth` and `TermHeight` functions into `TerminalDimensions`\n- Rework `Block`\n- Rework `Buffer` methods\n- Decremente color numbers by 1 to match xterm colors\n- Change text parsing\n  - change style items from `fg-color` to `fg:color`\n  - adde mod item like `mod:reverse`\n\n## 2018-11-29\n\n### Changed\n\n- Move Tabpane from termui/extra to termui and rename it to TabPane\n- Rename PollEvent to PollEvents\n\n## 2018-11-28\n\n### Changed\n\n- Migrate from Dep to vgo\n- Overhaul the event system\n  - check the wiki/examples for details\n- Rename Par widget to Paragraph\n- Rename MBarChart widget to StackedBarChart\n\n[#237]: https://github.com/gizak/termui/pull/237\n[#126]: https://github.com/gizak/termui/pull/126\n\n[Unreleased]: https://github.com/gizak/termui/compare/v3.1.0...HEAD\n[3.1.0]: https://github.com/gizak/termui/compare/v3.0.0...v3.1.0\n[3.0.0]: https://github.com/gizak/termui/compare/v2.3.0...v3.0.0\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Zack Guo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: run-examples\nrun-examples:\n\t@for file in _examples/*.go; do \\\n\t  go run $$file; \\\n\t  done;\n"
  },
  {
    "path": "README.md",
    "content": "# termui\n\n[<img src=\"./_assets/demo.gif\" alt=\"demo cast under osx 10.10; Terminal.app; Menlo Regular 12pt.)\" width=\"100%\">](./_examples/demo.go)\n\ntermui is a cross-platform and fully-customizable terminal dashboard and widget library built on top of [termbox-go](https://github.com/nsf/termbox-go). It is inspired by [blessed-contrib](https://github.com/yaronn/blessed-contrib) and [tui-rs](https://github.com/fdehau/tui-rs) and written purely in Go.\n\n## Note\n\nPlease be aware that due to my fluctuating availability, the frequency of updates to this project may not always follow a consistent schedule. I would like to invite potential maintainers to contribute to this project. If you are interested in becoming a maintainer, please do not hesitate to reach out to me.\n\n## Versions\n\ntermui is currently compatible with Go 1.15 (as in go.mod) and above (tracking the Debian's [oldstable](https://wiki.debian.org/DebianReleases)). Please use the version-numbered branch as stable release. The new changes will be pushed to master branch first and then merge to version branch.\n\n## Features\n\n- Several premade widgets for common use cases\n- Easily create custom widgets\n- Position widgets either in a relative grid or with absolute coordinates\n- Keyboard, mouse, and terminal resizing events\n- Colors and styling\n\n## Installation\n\n### Go modules\n\nIt is not necessary to `go get` termui, since Go will automatically manage any imported dependencies for you. Do note that you have to include `/v3` in the import statements as shown in the 'Hello World' example below.\n\n### Dep\n\nAdd with `dep ensure -add github.com/gizak/termui`. With Dep, `/v3` should *not* be included in the import statements.\n\n## Hello World\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tp := widgets.NewParagraph()\n\tp.Text = \"Hello World!\"\n\tp.SetRect(0, 0, 25, 5)\n\n\tui.Render(p)\n\n\tfor e := range ui.PollEvents() {\n\t\tif e.Type == ui.KeyboardEvent {\n\t\t\tbreak\n\t\t}\n\t}\n}\n```\n\n## Widgets\n\n- [BarChart](./_examples/barchart.go)\n- [Canvas](./_examples/canvas.go) (for drawing braille dots)\n- [Gauge](./_examples/gauge.go)\n- [Image](./_examples/image.go)\n- [List](./_examples/list.go)\n- [Tree](./_examples/tree.go)\n- [Paragraph](./_examples/paragraph.go)\n- [PieChart](./_examples/piechart.go)\n- [Plot](./_examples/plot.go) (for scatterplots and linecharts)\n- [Sparkline](./_examples/sparkline.go)\n- [StackedBarChart](./_examples/stacked_barchart.go)\n- [Table](./_examples/table.go)\n- [Tabs](./_examples/tabs.go)\n\nRun an example with `go run _examples/{example}.go` or run each example consecutively with `make run-examples`.\n\n## Documentation\n\n- [wiki](https://github.com/gizak/termui/wiki)\n\n## Uses\n\n- [dockdash](https://github.com/byrnedo/dockdash)\n- [expvarmon](https://github.com/divan/expvarmon)\n- [go-ethereum/monitorcmd](https://github.com/ethereum/go-ethereum/blob/master/cmd/geth/monitorcmd.go)\n- [go-jira-ui](https://github.com/mikepea/go-jira-ui)\n- [gotop](https://github.com/cjbassi/gotop)\n- [termeter](https://github.com/atsaki/termeter)\n- [updo](https://github.com/Owloops/updo)\n\n## Related Works\n\n- [blessed-contrib](https://github.com/yaronn/blessed-contrib)\n- [gocui](https://github.com/jroimartin/gocui)\n- [termdash](https://github.com/mum4k/termdash)\n- [tui-rs](https://github.com/fdehau/tui-rs)\n- [tview](https://github.com/rivo/tview)\n\n## License\n\n[MIT](http://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "_examples/barchart.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tbc := widgets.NewBarChart()\n\tbc.Data = []float64{3, 2, 5, 3, 9, 3}\n\tbc.Labels = []string{\"S0\", \"S1\", \"S2\", \"S3\", \"S4\", \"S5\"}\n\tbc.Title = \"Bar Chart\"\n\tbc.SetRect(5, 5, 100, 25)\n\tbc.BarWidth = 5\n\tbc.BarColors = []ui.Color{ui.ColorRed, ui.ColorGreen}\n\tbc.LabelStyles = []ui.Style{ui.NewStyle(ui.ColorBlue)}\n\tbc.NumStyles = []ui.Style{ui.NewStyle(ui.ColorYellow)}\n\n\tui.Render(bc)\n\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/canvas.go",
    "content": "// +build ignore\n\npackage main\n\nimport (\n\t\"image\"\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tc := ui.NewCanvas()\n\tc.SetRect(0, 0, 50, 50)\n\tc.SetLine(image.Pt(0, 0), image.Pt(10, 20), ui.ColorWhite)\n\n\tui.Render(c)\n\n\tfor e := range ui.PollEvents() {\n\t\tif e.Type == ui.KeyboardEvent {\n\t\t\tbreak\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/demo.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\t\"math\"\n\t\"time\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tp := widgets.NewParagraph()\n\tp.Title = \"Text Box\"\n\tp.Text = \"PRESS q TO QUIT DEMO\"\n\tp.SetRect(0, 0, 50, 5)\n\tp.TextStyle.Fg = ui.ColorWhite\n\tp.BorderStyle.Fg = ui.ColorCyan\n\n\tupdateParagraph := func(count int) {\n\t\tif count%2 == 0 {\n\t\t\tp.TextStyle.Fg = ui.ColorRed\n\t\t} else {\n\t\t\tp.TextStyle.Fg = ui.ColorWhite\n\t\t}\n\t}\n\n\tlistData := []string{\n\t\t\"[0] gizak/termui\",\n\t\t\"[1] editbox.go\",\n\t\t\"[2] interrupt.go\",\n\t\t\"[3] keyboard.go\",\n\t\t\"[4] output.go\",\n\t\t\"[5] random_out.go\",\n\t\t\"[6] dashboard.go\",\n\t\t\"[7] nsf/termbox-go\",\n\t}\n\n\tl := widgets.NewList()\n\tl.Title = \"List\"\n\tl.Rows = listData\n\tl.SetRect(0, 5, 25, 12)\n\tl.TextStyle.Fg = ui.ColorYellow\n\n\tg := widgets.NewGauge()\n\tg.Title = \"Gauge\"\n\tg.Percent = 50\n\tg.SetRect(0, 12, 50, 15)\n\tg.BarColor = ui.ColorRed\n\tg.BorderStyle.Fg = ui.ColorWhite\n\tg.TitleStyle.Fg = ui.ColorCyan\n\n\tsparklineData := []float64{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6}\n\n\tsl := widgets.NewSparkline()\n\tsl.Title = \"srv 0:\"\n\tsl.Data = sparklineData\n\tsl.LineColor = ui.ColorCyan\n\tsl.TitleStyle.Fg = ui.ColorWhite\n\n\tsl2 := widgets.NewSparkline()\n\tsl2.Title = \"srv 1:\"\n\tsl2.Data = sparklineData\n\tsl2.TitleStyle.Fg = ui.ColorWhite\n\tsl2.LineColor = ui.ColorRed\n\n\tslg := widgets.NewSparklineGroup(sl, sl2)\n\tslg.Title = \"Sparkline\"\n\tslg.SetRect(25, 5, 50, 12)\n\n\tsinData := (func() []float64 {\n\t\tn := 220\n\t\tps := make([]float64, n)\n\t\tfor i := range ps {\n\t\t\tps[i] = 1 + math.Sin(float64(i)/5)\n\t\t}\n\t\treturn ps\n\t})()\n\n\tlc := widgets.NewPlot()\n\tlc.Title = \"dot-marker Line Chart\"\n\tlc.Data = make([][]float64, 1)\n\tlc.Data[0] = sinData\n\tlc.SetRect(0, 15, 50, 25)\n\tlc.AxesColor = ui.ColorWhite\n\tlc.LineColors[0] = ui.ColorRed\n\tlc.Marker = widgets.MarkerDot\n\n\tbarchartData := []float64{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6}\n\n\tbc := widgets.NewBarChart()\n\tbc.Title = \"Bar Chart\"\n\tbc.SetRect(50, 0, 75, 10)\n\tbc.Labels = []string{\"S0\", \"S1\", \"S2\", \"S3\", \"S4\", \"S5\"}\n\tbc.BarColors[0] = ui.ColorGreen\n\tbc.NumStyles[0] = ui.NewStyle(ui.ColorBlack)\n\n\tlc2 := widgets.NewPlot()\n\tlc2.Title = \"braille-mode Line Chart\"\n\tlc2.Data = make([][]float64, 1)\n\tlc2.Data[0] = sinData\n\tlc2.SetRect(50, 15, 75, 25)\n\tlc2.AxesColor = ui.ColorWhite\n\tlc2.LineColors[0] = ui.ColorYellow\n\n\tp2 := widgets.NewParagraph()\n\tp2.Text = \"Hey!\\nI am a borderless block!\"\n\tp2.Border = false\n\tp2.SetRect(50, 10, 75, 10)\n\tp2.TextStyle.Fg = ui.ColorMagenta\n\n\tdraw := func(count int) {\n\t\tg.Percent = count % 101\n\t\tl.Rows = listData[count%9:]\n\t\tslg.Sparklines[0].Data = sparklineData[:30+count%50]\n\t\tslg.Sparklines[1].Data = sparklineData[:35+count%50]\n\t\tlc.Data[0] = sinData[count/2%220:]\n\t\tlc2.Data[0] = sinData[2*count%220:]\n\t\tbc.Data = barchartData[count/2%10:]\n\n\t\tui.Render(p, l, g, slg, lc, bc, lc2, p2)\n\t}\n\n\ttickerCount := 1\n\tdraw(tickerCount)\n\ttickerCount++\n\tuiEvents := ui.PollEvents()\n\tticker := time.NewTicker(time.Second).C\n\tfor {\n\t\tselect {\n\t\tcase e := <-uiEvents:\n\t\t\tswitch e.ID {\n\t\t\tcase \"q\", \"<C-c>\":\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-ticker:\n\t\t\tupdateParagraph(tickerCount)\n\t\t\tdraw(tickerCount)\n\t\t\ttickerCount++\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/gauge.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tg0 := widgets.NewGauge()\n\tg0.Title = \"Slim Gauge\"\n\tg0.SetRect(20, 20, 30, 30)\n\tg0.Percent = 75\n\tg0.BarColor = ui.ColorRed\n\tg0.BorderStyle.Fg = ui.ColorWhite\n\tg0.TitleStyle.Fg = ui.ColorCyan\n\n\tg2 := widgets.NewGauge()\n\tg2.Title = \"Slim Gauge\"\n\tg2.SetRect(0, 3, 50, 6)\n\tg2.Percent = 60\n\tg2.BarColor = ui.ColorYellow\n\tg2.LabelStyle = ui.NewStyle(ui.ColorBlue)\n\tg2.BorderStyle.Fg = ui.ColorWhite\n\n\tg1 := widgets.NewGauge()\n\tg1.Title = \"Big Gauge\"\n\tg1.SetRect(0, 6, 50, 11)\n\tg1.Percent = 30\n\tg1.BarColor = ui.ColorGreen\n\tg1.LabelStyle = ui.NewStyle(ui.ColorYellow)\n\tg1.TitleStyle.Fg = ui.ColorMagenta\n\tg1.BorderStyle.Fg = ui.ColorWhite\n\n\tg3 := widgets.NewGauge()\n\tg3.Title = \"Gauge with custom label\"\n\tg3.SetRect(0, 11, 50, 14)\n\tg3.Percent = 50\n\tg3.Label = fmt.Sprintf(\"%v%% (100MBs free)\", g3.Percent)\n\n\tg4 := widgets.NewGauge()\n\tg4.Title = \"Gauge\"\n\tg4.SetRect(0, 14, 50, 17)\n\tg4.Percent = 50\n\tg4.Label = \"Gauge with custom highlighted label\"\n\tg4.BarColor = ui.ColorGreen\n\tg4.LabelStyle = ui.NewStyle(ui.ColorYellow)\n\n\tui.Render(g0, g1, g2, g3, g4)\n\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/grid.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\t\"math\"\n\t\"time\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tsinFloat64 := (func() []float64 {\n\t\tn := 400\n\t\tdata := make([]float64, n)\n\t\tfor i := range data {\n\t\t\tdata[i] = 1 + math.Sin(float64(i)/5)\n\t\t}\n\t\treturn data\n\t})()\n\n\tsl := widgets.NewSparkline()\n\tsl.Data = sinFloat64[:100]\n\tsl.LineColor = ui.ColorCyan\n\tsl.TitleStyle.Fg = ui.ColorWhite\n\n\tslg := widgets.NewSparklineGroup(sl)\n\tslg.Title = \"Sparkline\"\n\n\tlc := widgets.NewPlot()\n\tlc.Title = \"braille-mode Line Chart\"\n\tlc.Data = append(lc.Data, sinFloat64)\n\tlc.AxesColor = ui.ColorWhite\n\tlc.LineColors[0] = ui.ColorYellow\n\n\tgs := make([]*widgets.Gauge, 3)\n\tfor i := range gs {\n\t\tgs[i] = widgets.NewGauge()\n\t\tgs[i].Percent = i * 10\n\t\tgs[i].BarColor = ui.ColorRed\n\t}\n\n\tls := widgets.NewList()\n\tls.Rows = []string{\n\t\t\"[1] Downloading File 1\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"[2] Downloading File 2\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"\",\n\t\t\"[3] Uploading File 3\",\n\t}\n\tls.Border = false\n\n\tp := widgets.NewParagraph()\n\tp.Text = \"<> This row has 3 columns\\n<- Widgets can be stacked up like left side\\n<- Stacked widgets are treated as a single widget\"\n\tp.Title = \"Demonstration\"\n\n\tgrid := ui.NewGrid()\n\ttermWidth, termHeight := ui.TerminalDimensions()\n\tgrid.SetRect(0, 0, termWidth, termHeight)\n\n\tgrid.Set(\n\t\tui.NewRow(1.0/2,\n\t\t\tui.NewCol(1.0/2, slg),\n\t\t\tui.NewCol(1.0/2, lc),\n\t\t),\n\t\tui.NewRow(1.0/2,\n\t\t\tui.NewCol(1.0/4, ls),\n\t\t\tui.NewCol(1.0/4,\n\t\t\t\tui.NewRow(.9/3, gs[0]),\n\t\t\t\tui.NewRow(.9/3, gs[1]),\n\t\t\t\tui.NewRow(1.2/3, gs[2]),\n\t\t\t),\n\t\t\tui.NewCol(1.0/2, p),\n\t\t),\n\t)\n\n\tui.Render(grid)\n\n\ttickerCount := 1\n\tuiEvents := ui.PollEvents()\n\tticker := time.NewTicker(time.Second).C\n\tfor {\n\t\tselect {\n\t\tcase e := <-uiEvents:\n\t\t\tswitch e.ID {\n\t\t\tcase \"q\", \"<C-c>\":\n\t\t\t\treturn\n\t\t\tcase \"<Resize>\":\n\t\t\t\tpayload := e.Payload.(ui.Resize)\n\t\t\t\tgrid.SetRect(0, 0, payload.Width, payload.Height)\n\t\t\t\tui.Clear()\n\t\t\t\tui.Render(grid)\n\t\t\t}\n\t\tcase <-ticker:\n\t\t\tif tickerCount == 100 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor _, g := range gs {\n\t\t\t\tg.Percent = (g.Percent + 3) % 100\n\t\t\t}\n\t\t\tslg.Sparklines[0].Data = sinFloat64[tickerCount : tickerCount+100]\n\t\t\tlc.Data[0] = sinFloat64[2*tickerCount:]\n\t\t\tui.Render(grid)\n\t\t\ttickerCount++\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/hello_world.go",
    "content": "// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tp := widgets.NewParagraph()\n\tp.Text = \"Hello World!\"\n\tp.SetRect(0, 0, 25, 5)\n\n\tui.Render(p)\n\n\tfor e := range ui.PollEvents() {\n\t\tif e.Type == ui.KeyboardEvent {\n\t\t\tbreak\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/image.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"image\"\n\t_ \"image/gif\"\n\t_ \"image/jpeg\"\n\t_ \"image/png\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tvar images []image.Image\n\tfor _, arg := range os.Args[1:] {\n\t\tresp, err := http.Get(arg)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"failed to fetch image: %v\", err)\n\t\t}\n\t\timage, _, err := image.Decode(resp.Body)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"failed to decode fetched image: %v\", err)\n\t\t}\n\t\timages = append(images, image)\n\t}\n\tif len(images) == 0 {\n\t\timage, _, err := image.Decode(base64.NewDecoder(base64.StdEncoding, strings.NewReader(GOPHER_IMAGE)))\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"failed to decode gopher image: %v\", err)\n\t\t}\n\t\timages = append(images, image)\n\t}\n\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\timg := widgets.NewImage(nil)\n\timg.SetRect(0, 0, 100, 50)\n\tindex := 0\n\trender := func() {\n\t\timg.Image = images[index]\n\t\tif !img.Monochrome {\n\t\t\timg.Title = fmt.Sprintf(\"Color %d/%d\", index+1, len(images))\n\t\t} else if !img.MonochromeInvert {\n\t\t\timg.Title = fmt.Sprintf(\"Monochrome(%d) %d/%d\", img.MonochromeThreshold, index+1, len(images))\n\t\t} else {\n\t\t\timg.Title = fmt.Sprintf(\"InverseMonochrome(%d) %d/%d\", img.MonochromeThreshold, index+1, len(images))\n\t\t}\n\t\tui.Render(img)\n\t}\n\trender()\n\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\tcase \"<Left>\", \"h\":\n\t\t\tindex = (index + len(images) - 1) % len(images)\n\t\tcase \"<Right>\", \"l\":\n\t\t\tindex = (index + 1) % len(images)\n\t\tcase \"<Up>\", \"k\":\n\t\t\timg.MonochromeThreshold++\n\t\tcase \"<Down>\", \"j\":\n\t\t\timg.MonochromeThreshold--\n\t\tcase \"<Enter>\":\n\t\t\timg.Monochrome = !img.Monochrome\n\t\tcase \"<Tab>\":\n\t\t\timg.MonochromeInvert = !img.MonochromeInvert\n\t\t}\n\t\trender()\n\t}\n}\n\nconst GOPHER_IMAGE = `iVBORw0KGgoAAAANSUhEUgAAAEsAAAA8CAAAAAALAhhPAAAFfUlEQVRYw62XeWwUVRzHf2+OPbo9d7tsWyiyaZti6eWGAhISoIGKECEKCAiJJkYTiUgTMYSIosYYBBIUIxoSPIINEBDi2VhwkQrVsj1ESgu9doHWdrul7ba73WNm3vOPtsseM9MdwvvrzTs+8/t95ze/33sI5BqiabU6m9En8oNjduLnAEDLUsQXFF8tQ5oxK3vmnNmDSMtrncks9Hhtt/qeWZapHb1ha3UqYSWVl2ZmpWgaXMXGohQAvmeop3bjTRtv6SgaK/Pb9/bFzUrYslbFAmHPp+3WhAYdr+7GN/YnpN46Opv55VDsJkoEpMrY/vO2BIYQ6LLvm0ThY3MzDzzeSJeeWNyTkgnIE5ePKsvKlcg/0T9QMzXalwXMlj54z4c0rh/mzEfr+FgWEz2w6uk8dkzFAgcARAgNp1ZYef8bH2AgvuStbc2/i6CiWGj98y2tw2l4FAXKkQBIf+exyRnteY83LfEwDQAYCoK+P6bxkZm/0966LxcAAILHB56kgD95PPxltuYcMtFTWw/FKkY/6Opf3GGd9ZF+Qp6mzJxzuRSractOmJrH1u8XTvWFHINNkLQLMR+XHXvfPPHw967raE1xxwtA36IMRfkAAG29/7mLuQcb2WOnsJReZGfpiHsSBX81cvMKywYZHhX5hFPtOqPGWZCXnhWGAu6lX91ElKXSalcLXu3UaOXVay57ZSe5f6Gpx7J2MXAsi7EqSp09b/MirKSyJfnfEEgeDjl8FgDAfvewP03zZ+AJ0m9aFRM8eEHBDRKjfcreDXnZdQuAxXpT2NRJ7xl3UkLBhuVGU16gZiGOgZmrSbRdqkILuL/yYoSXHHkl9KXgqNu3PB8oRg0geC5vFmLjad6mUyTKLmF3OtraWDIfACyXqmephaDABawfpi6tqqBZytfQMqOz6S09iWXhktrRaB8Xz4Yi/8gyABDm5NVe6qq/3VzPrcjELWrebVuyY2T7ar4zQyybUCtsQ5Es1FGaZVrRVQwAgHGW2ZCRZshI5bGQi7HesyE972pOSeMM0dSktlzxRdrlqb3Osa6CCS8IJoQQQgBAbTAa5l5epO34rJszibJI8rxLfGzcp1dRosutGeb2VDNgqYrwTiPNsLxXiPi3dz7LiS1WBRBDBOnqEjyy3aQb+/bLiJzz9dIkscVBBLxMfSEac7kO4Fpkngi0ruNBeSOal+u8jgOuqPz12nryMLCniEjtOOOmpt+KEIqsEdocJjYXwrh9OZqWJQyPCTo67LNS/TdxLAv6R5ZNK9npEjbYdT33gRo4o5oTqR34R+OmaSzDBWsAIPhuRcgyoteNi9gF0KzNYWVItPf2TLoXEg+7isNC7uJkgo1iQWOfRSP9NR11RtbZZ3OMG/VhL6jvx+J1m87+RCfJChAtEBQkSBX2PnSiihc/Twh3j0h7qdYQAoRVsRGmq7HU2QRbaxVGa1D6nIOqaIWRjyRZpHMQKWKpZM5feA+lzC4ZFultV8S6T0mzQGhQohi5I8iw+CsqBSxhFMuwyLgSwbghGb0AiIKkSDmGZVmJSiKihsiyOAUs70UkywooYP0bii9GdH4sfr1UNysd3fUyLLMQN+rsmo3grHl9VNJHbbwxoa47Vw5gupIqrZcjPh9R4Nye3nRDk199V+aetmvVtDRE8/+cbgAAgMIWGb3UA0MGLE9SCbWX670TDy1y98c3D27eppUjsZ6fql3jcd5rUe7+ZIlLNQny3Rd+E5Tct3WVhTM5RBCEdiEK0b6B+/ca2gYU393nFj/n1AygRQxPIUA043M42u85+z2SnssKrPl8Mx76NL3E6eXc3be7OD+H4WHbJkKI8AU8irbITQjZ+0hQcPEgId/Fn/pl9crKH02+5o2b9T/eMx7pKoskYgAAAABJRU5ErkJggg==`\n"
  },
  {
    "path": "_examples/list.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tl := widgets.NewList()\n\tl.Title = \"List\"\n\tl.Rows = []string{\n\t\t\"[0] github.com/gizak/termui/v3\",\n\t\t\"[1] [你好，世界](fg:blue)\",\n\t\t\"[2] [こんにちは世界](fg:red)\",\n\t\t\"[3] [color](fg:white,bg:green) output\",\n\t\t\"[4] output.go\",\n\t\t\"[5] random_out.go\",\n\t\t\"[6] dashboard.go\",\n\t\t\"[7] foo\",\n\t\t\"[8] bar\",\n\t\t\"[9] baz\",\n\t}\n\tl.TextStyle = ui.NewStyle(ui.ColorYellow)\n\tl.WrapText = false\n\tl.SetRect(0, 0, 25, 8)\n\n\tui.Render(l)\n\n\tpreviousKey := \"\"\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\tcase \"j\", \"<Down>\":\n\t\t\tl.ScrollDown()\n\t\tcase \"k\", \"<Up>\":\n\t\t\tl.ScrollUp()\n\t\tcase \"<C-d>\":\n\t\t\tl.ScrollHalfPageDown()\n\t\tcase \"<C-u>\":\n\t\t\tl.ScrollHalfPageUp()\n\t\tcase \"<C-f>\":\n\t\t\tl.ScrollPageDown()\n\t\tcase \"<C-b>\":\n\t\t\tl.ScrollPageUp()\n\t\tcase \"g\":\n\t\t\tif previousKey == \"g\" {\n\t\t\t\tl.ScrollTop()\n\t\t\t}\n\t\tcase \"<Home>\":\n\t\t\tl.ScrollTop()\n\t\tcase \"G\", \"<End>\":\n\t\t\tl.ScrollBottom()\n\t\t}\n\n\t\tif previousKey == \"g\" {\n\t\t\tpreviousKey = \"\"\n\t\t} else {\n\t\t\tpreviousKey = e.ID\n\t\t}\n\n\t\tui.Render(l)\n\t}\n}\n"
  },
  {
    "path": "_examples/paragraph.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tp0 := widgets.NewParagraph()\n\tp0.Text = \"Borderless Text\"\n\tp0.SetRect(0, 0, 20, 5)\n\tp0.Border = false\n\n\tp1 := widgets.NewParagraph()\n\tp1.Title = \"标签\"\n\tp1.Text = \"你好，世界。\"\n\tp1.SetRect(20, 0, 35, 5)\n\n\tp2 := widgets.NewParagraph()\n\tp2.Title = \"Multiline\"\n\tp2.Text = \"Simple colored text\\nwith label. It [can be](fg:red) multilined with \\\\n or [break automatically](fg:red,fg:bold)\"\n\tp2.SetRect(0, 5, 35, 10)\n\tp2.BorderStyle.Fg = ui.ColorYellow\n\n\tp3 := widgets.NewParagraph()\n\tp3.Title = \"Auto Trim\"\n\tp3.Text = \"Long text with label and it is auto trimmed.\"\n\tp3.SetRect(0, 10, 40, 15)\n\n\tp4 := widgets.NewParagraph()\n\tp4.Title = \"Text Box with Wrapping\"\n\tp4.Text = \"Press q to QUIT THE DEMO. [There](fg:blue,mod:bold) are other things [that](fg:red) are going to fit in here I think. What do you think? Now is the time for all good [men to](bg:blue) come to the aid of their country. [This is going to be one really really really long line](fg:green) that is going to go together and stuffs and things. Let's see how this thing renders out.\\n    Here is a new paragraph and stuffs and things. There should be a tab indent at the beginning of the paragraph. Let's see if that worked as well.\"\n\tp4.SetRect(40, 0, 70, 20)\n\tp4.BorderStyle.Fg = ui.ColorBlue\n\n\tui.Render(p0, p1, p2, p3, p4)\n\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/piechart.go",
    "content": "// +build ignore\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"math/rand\"\n\t\"time\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nvar run = true\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\trand.Seed(time.Now().UTC().UnixNano())\n\trandomDataAndOffset := func() (data []float64, offset float64) {\n\t\tnoSlices := 1 + rand.Intn(5)\n\t\tdata = make([]float64, noSlices)\n\t\tfor i := range data {\n\t\t\tdata[i] = rand.Float64()\n\t\t}\n\t\toffset = 2.0 * math.Pi * rand.Float64()\n\t\treturn\n\t}\n\n\tpc := widgets.NewPieChart()\n\tpc.Title = \"Pie Chart\"\n\tpc.SetRect(5, 5, 70, 36)\n\tpc.Data = []float64{.25, .25, .25, .25}\n\tpc.AngleOffset = -.5 * math.Pi\n\tpc.LabelFormatter = func(i int, v float64) string {\n\t\treturn fmt.Sprintf(\"%.02f\", v)\n\t}\n\n\tpause := func() {\n\t\trun = !run\n\t\tif run {\n\t\t\tpc.Title = \"Pie Chart\"\n\t\t} else {\n\t\t\tpc.Title = \"Pie Chart (Stopped)\"\n\t\t}\n\t\tui.Render(pc)\n\t}\n\n\tui.Render(pc)\n\n\tuiEvents := ui.PollEvents()\n\tticker := time.NewTicker(time.Second).C\n\tfor {\n\t\tselect {\n\t\tcase e := <-uiEvents:\n\t\t\tswitch e.ID {\n\t\t\tcase \"q\", \"<C-c>\":\n\t\t\t\treturn\n\t\t\tcase \"s\":\n\t\t\t\tpause()\n\t\t\t}\n\t\tcase <-ticker:\n\t\t\tif run {\n\t\t\t\tpc.Data, pc.AngleOffset = randomDataAndOffset()\n\t\t\t\tui.Render(pc)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/plot.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\t\"math\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tsinData := func() [][]float64 {\n\t\tn := 220\n\t\tdata := make([][]float64, 2)\n\t\tdata[0] = make([]float64, n)\n\t\tdata[1] = make([]float64, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tdata[0][i] = 1 + math.Sin(float64(i)/5)\n\t\t\tdata[1][i] = 1 + math.Cos(float64(i)/5)\n\t\t}\n\t\treturn data\n\t}()\n\n\tp0 := widgets.NewPlot()\n\tp0.Title = \"braille-mode Line Chart\"\n\tp0.Data = sinData\n\tp0.SetRect(0, 0, 50, 15)\n\tp0.AxesColor = ui.ColorWhite\n\tp0.LineColors[0] = ui.ColorGreen\n\n\tp1 := widgets.NewPlot()\n\tp1.Title = \"dot-mode line Chart\"\n\tp1.Marker = widgets.MarkerDot\n\tp1.Data = [][]float64{[]float64{1, 2, 3, 4, 5}}\n\tp1.SetRect(50, 0, 75, 10)\n\tp1.DotMarkerRune = '+'\n\tp1.AxesColor = ui.ColorWhite\n\tp1.LineColors[0] = ui.ColorYellow\n\tp1.DrawDirection = widgets.DrawLeft\n\n\tp2 := widgets.NewPlot()\n\tp2.Title = \"dot-mode Scatter Plot\"\n\tp2.Marker = widgets.MarkerDot\n\tp2.Data = make([][]float64, 2)\n\tp2.Data[0] = []float64{1, 2, 3, 4, 5}\n\tp2.Data[1] = sinData[1][4:]\n\tp2.SetRect(0, 15, 50, 30)\n\tp2.AxesColor = ui.ColorWhite\n\tp2.LineColors[0] = ui.ColorCyan\n\tp2.PlotType = widgets.ScatterPlot\n\n\tp3 := widgets.NewPlot()\n\tp3.Title = \"braille-mode Scatter Plot\"\n\tp3.Data = make([][]float64, 2)\n\tp3.Data[0] = []float64{1, 2, 3, 4, 5}\n\tp3.Data[1] = sinData[1][4:]\n\tp3.SetRect(45, 15, 80, 30)\n\tp3.AxesColor = ui.ColorWhite\n\tp3.LineColors[0] = ui.ColorCyan\n\tp3.Marker = widgets.MarkerBraille\n\tp3.PlotType = widgets.ScatterPlot\n\n\tui.Render(p0, p1, p2, p3)\n\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/sparkline.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tdata := []float64{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6}\n\n\tsl0 := widgets.NewSparkline()\n\tsl0.Data = data[3:]\n\tsl0.LineColor = ui.ColorGreen\n\n\t// single\n\tslg0 := widgets.NewSparklineGroup(sl0)\n\tslg0.Title = \"Sparkline 0\"\n\tslg0.SetRect(0, 0, 20, 10)\n\n\tsl1 := widgets.NewSparkline()\n\tsl1.Title = \"Sparkline 1\"\n\tsl1.Data = data\n\tsl1.LineColor = ui.ColorRed\n\n\tsl2 := widgets.NewSparkline()\n\tsl2.Title = \"Sparkline 2\"\n\tsl2.Data = data[5:]\n\tsl2.LineColor = ui.ColorMagenta\n\n\tslg1 := widgets.NewSparklineGroup(sl0, sl1, sl2)\n\tslg1.Title = \"Group Sparklines\"\n\tslg1.SetRect(0, 10, 25, 25)\n\n\tsl3 := widgets.NewSparkline()\n\tsl3.Title = \"Enlarged Sparkline\"\n\tsl3.Data = data\n\tsl3.LineColor = ui.ColorYellow\n\n\tslg2 := widgets.NewSparklineGroup(sl3)\n\tslg2.Title = \"Tweeked Sparkline\"\n\tslg2.SetRect(20, 0, 50, 10)\n\tslg2.BorderStyle.Fg = ui.ColorCyan\n\n\tui.Render(slg0, slg1, slg2)\n\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/stacked_barchart.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tsbc := widgets.NewStackedBarChart()\n\tsbc.Title = \"Student's Marks: X-Axis=Name, Y-Axis=Grade% (Math, English, Science, Computer Science)\"\n\tsbc.Labels = []string{\"Ken\", \"Rob\", \"Dennis\", \"Linus\"}\n\n\tsbc.Data = make([][]float64, 4)\n\tsbc.Data[0] = []float64{90, 85, 90, 80}\n\tsbc.Data[1] = []float64{70, 85, 75, 60}\n\tsbc.Data[2] = []float64{75, 60, 80, 85}\n\tsbc.Data[3] = []float64{100, 100, 100, 100}\n\tsbc.SetRect(5, 5, 100, 30)\n\tsbc.BarWidth = 5\n\n\tui.Render(sbc)\n\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/table.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\ttable1 := widgets.NewTable()\n\ttable1.Rows = [][]string{\n\t\t[]string{\"header1\", \"header2\", \"header3\"},\n\t\t[]string{\"你好吗\", \"Go-lang is so cool\", \"Im working on Ruby\"},\n\t\t[]string{\"2016\", \"10\", \"11\"},\n\t}\n\ttable1.TextStyle = ui.NewStyle(ui.ColorWhite)\n\ttable1.SetRect(0, 0, 60, 10)\n\n\tui.Render(table1)\n\n\ttable2 := widgets.NewTable()\n\ttable2.Rows = [][]string{\n\t\t[]string{\"header1\", \"header2\", \"header3\"},\n\t\t[]string{\"Foundations\", \"Go-lang is so cool\", \"Im working on Ruby\"},\n\t\t[]string{\"2016\", \"11\", \"11\"},\n\t}\n\ttable2.TextStyle = ui.NewStyle(ui.ColorWhite)\n\ttable2.TextAlignment = ui.AlignCenter\n\ttable2.RowSeparator = false\n\ttable2.SetRect(0, 10, 20, 20)\n\n\tui.Render(table2)\n\n\ttable3 := widgets.NewTable()\n\ttable3.Rows = [][]string{\n\t\t[]string{\"header1\", \"header2\", \"header3\"},\n\t\t[]string{\"AAA\", \"BBB\", \"CCC\"},\n\t\t[]string{\"DDD\", \"EEE\", \"FFF\"},\n\t\t[]string{\"GGG\", \"HHH\", \"III\"},\n\t}\n\ttable3.TextStyle = ui.NewStyle(ui.ColorWhite)\n\ttable3.RowSeparator = true\n\ttable3.BorderStyle = ui.NewStyle(ui.ColorGreen)\n\ttable3.SetRect(0, 30, 70, 20)\n\ttable3.FillRow = true\n\ttable3.RowStyles[0] = ui.NewStyle(ui.ColorWhite, ui.ColorBlack, ui.ModifierBold)\n\ttable3.RowStyles[2] = ui.NewStyle(ui.ColorWhite, ui.ColorRed, ui.ModifierBold)\n\ttable3.RowStyles[3] = ui.NewStyle(ui.ColorYellow)\n\n\tui.Render(table3)\n\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/tabs.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\theader := widgets.NewParagraph()\n\theader.Text = \"Press q to quit, Press h or l to switch tabs\"\n\theader.SetRect(0, 0, 50, 1)\n\theader.Border = false\n\theader.TextStyle.Bg = ui.ColorBlue\n\n\tp2 := widgets.NewParagraph()\n\tp2.Text = \"Press q to quit\\nPress h or l to switch tabs\\n\"\n\tp2.Title = \"Keys\"\n\tp2.SetRect(5, 5, 40, 15)\n\tp2.BorderStyle.Fg = ui.ColorYellow\n\n\tbc := widgets.NewBarChart()\n\tbc.Title = \"Bar Chart\"\n\tbc.Data = []float64{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6}\n\tbc.SetRect(5, 5, 35, 10)\n\tbc.Labels = []string{\"S0\", \"S1\", \"S2\", \"S3\", \"S4\", \"S5\"}\n\n\ttabpane := widgets.NewTabPane(\"pierwszy\", \"drugi\", \"trzeci\", \"żółw\", \"four\", \"five\")\n\ttabpane.SetRect(0, 1, 50, 4)\n\ttabpane.Border = true\n\n\trenderTab := func() {\n\t\tswitch tabpane.ActiveTabIndex {\n\t\tcase 0:\n\t\t\tui.Render(p2)\n\t\tcase 1:\n\t\t\tui.Render(bc)\n\t\t}\n\t}\n\n\tui.Render(header, tabpane, p2)\n\n\tuiEvents := ui.PollEvents()\n\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\tcase \"h\":\n\t\t\ttabpane.FocusLeft()\n\t\t\tui.Clear()\n\t\t\tui.Render(header, tabpane)\n\t\t\trenderTab()\n\t\tcase \"l\":\n\t\t\ttabpane.FocusRight()\n\t\t\tui.Clear()\n\t\t\tui.Render(header, tabpane)\n\t\t\trenderTab()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "_examples/tree.go",
    "content": "/// +build ignore\n\npackage main\n\nimport (\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n\t\"github.com/gizak/termui/v3/widgets\"\n)\n\ntype nodeValue string\n\nfunc (nv nodeValue) String() string {\n\treturn string(nv)\n}\n\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tnodes := []*widgets.TreeNode{\n\t\t{\n\t\t\tValue: nodeValue(\"Key 1\"),\n\t\t\tNodes: []*widgets.TreeNode{\n\t\t\t\t{\n\t\t\t\t\tValue: nodeValue(\"Key 1.1\"),\n\t\t\t\t\tNodes: []*widgets.TreeNode{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tValue: nodeValue(\"Key 1.1.1\"),\n\t\t\t\t\t\t\tNodes: nil,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tValue: nodeValue(\"Key 1.1.2\"),\n\t\t\t\t\t\t\tNodes: nil,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tValue: nodeValue(\"Key 1.2\"),\n\t\t\t\t\tNodes: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tValue: nodeValue(\"Key 2\"),\n\t\t\tNodes: []*widgets.TreeNode{\n\t\t\t\t{\n\t\t\t\t\tValue: nodeValue(\"Key 2.1\"),\n\t\t\t\t\tNodes: nil,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tValue: nodeValue(\"Key 2.2\"),\n\t\t\t\t\tNodes: nil,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tValue: nodeValue(\"Key 2.3\"),\n\t\t\t\t\tNodes: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tValue: nodeValue(\"Key 3\"),\n\t\t\tNodes: nil,\n\t\t},\n\t}\n\n\tl := widgets.NewTree()\n\tl.TextStyle = ui.NewStyle(ui.ColorYellow)\n\tl.WrapText = false\n\tl.SetNodes(nodes)\n\n\tx, y := ui.TerminalDimensions()\n\n\tl.SetRect(0, 0, x, y)\n\n\tui.Render(l)\n\n\tpreviousKey := \"\"\n\tuiEvents := ui.PollEvents()\n\tfor {\n\t\te := <-uiEvents\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\tcase \"j\", \"<Down>\":\n\t\t\tl.ScrollDown()\n\t\tcase \"k\", \"<Up>\":\n\t\t\tl.ScrollUp()\n\t\tcase \"<C-d>\":\n\t\t\tl.ScrollHalfPageDown()\n\t\tcase \"<C-u>\":\n\t\t\tl.ScrollHalfPageUp()\n\t\tcase \"<C-f>\":\n\t\t\tl.ScrollPageDown()\n\t\tcase \"<C-b>\":\n\t\t\tl.ScrollPageUp()\n\t\tcase \"g\":\n\t\t\tif previousKey == \"g\" {\n\t\t\t\tl.ScrollTop()\n\t\t\t}\n\t\tcase \"<Home>\":\n\t\t\tl.ScrollTop()\n\t\tcase \"<Enter>\":\n\t\t\tl.ToggleExpand()\n\t\tcase \"G\", \"<End>\":\n\t\t\tl.ScrollBottom()\n\t\tcase \"E\":\n\t\t\tl.ExpandAll()\n\t\tcase \"C\":\n\t\t\tl.CollapseAll()\n\t\tcase \"<Resize>\":\n\t\t\tx, y := ui.TerminalDimensions()\n\t\t\tl.SetRect(0, 0, x, y)\n\t\t}\n\n\t\tif previousKey == \"g\" {\n\t\t\tpreviousKey = \"\"\n\t\t} else {\n\t\t\tpreviousKey = e.ID\n\t\t}\n\n\t\tui.Render(l)\n\t}\n}\n"
  },
  {
    "path": "_scripts/copyright_header.py",
    "content": "#!/usr/bin/env python3\n\nimport re\nimport os\nimport io\n\ncopyright = \"\"\"// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n\"\"\"\n\nexclude_dirs = [\".git\", \"_docs\"]\nexclude_files = []\ninclude_dirs = [\".\", \"debug\", \"extra\", \"test\", \"_example\"]\n\n\ndef is_target(fpath):\n    if os.path.splitext(fpath)[-1] == \".go\":\n        return True\n    return False\n\n\ndef update_copyright(fpath):\n    print(\"processing \" + fpath)\n    f = io.open(fpath, 'r', encoding='utf-8')\n    fstr = f.read()\n    f.close()\n\n    # remove old\n    m = re.search('^// Copyright .+?\\r?\\n\\r?\\n', fstr, re.MULTILINE|re.DOTALL)\n    if m:\n        fstr = fstr[m.end():]\n\n    # add new\n    fstr = copyright + fstr\n    f = io.open(fpath, 'w',encoding='utf-8')\n    f.write(fstr)\n    f.close()\n\n\ndef main():\n    for d in include_dirs:\n        files = [\n            os.path.join(d, f) for f in os.listdir(d)\n            if os.path.isfile(os.path.join(d, f))\n        ]\n        for f in files:\n            if is_target(f):\n                update_copyright(f)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "_test/log_events.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build ignore\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\tui \"github.com/gizak/termui/v3\"\n)\n\n// logs all events to the termui window\n// stdout can also be redirected to a file and read with `tail -f`\nfunc main() {\n\tif err := ui.Init(); err != nil {\n\t\tlog.Fatalf(\"failed to initialize termui: %v\", err)\n\t}\n\tdefer ui.Close()\n\n\tevents := ui.PollEvents()\n\tfor {\n\t\te := <-events\n\t\tfmt.Printf(\"%v\", e)\n\t\tswitch e.ID {\n\t\tcase \"q\", \"<C-c>\":\n\t\t\treturn\n\t\tcase \"<MouseLeft>\":\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "alignment.go",
    "content": "package termui\n\ntype Alignment uint\n\nconst (\n\tAlignLeft Alignment = iota\n\tAlignCenter\n\tAlignRight\n)\n"
  },
  {
    "path": "backend.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage termui\n\nimport (\n\ttb \"github.com/nsf/termbox-go\"\n)\n\n// Init initializes termbox-go and is required to render anything.\n// After initialization, the library must be finalized with `Close`.\nfunc Init() error {\n\tif err := tb.Init(); err != nil {\n\t\treturn err\n\t}\n\ttb.SetInputMode(tb.InputEsc | tb.InputMouse)\n\ttb.SetOutputMode(tb.Output256)\n\treturn nil\n}\n\n// Close closes termbox-go.\nfunc Close() {\n\ttb.Close()\n}\n\nfunc TerminalDimensions() (int, int) {\n\ttb.Sync()\n\twidth, height := tb.Size()\n\treturn width, height\n}\n\nfunc Clear() {\n\ttb.Clear(tb.ColorDefault, tb.Attribute(Theme.Default.Bg+1))\n}\n"
  },
  {
    "path": "block.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage termui\n\nimport (\n\t\"image\"\n\t\"sync\"\n)\n\n// Block is the base struct inherited by most widgets.\n// Block manages size, position, border, and title.\n// It implements all 3 of the methods needed for the `Drawable` interface.\n// Custom widgets will override the Draw method.\ntype Block struct {\n\tBorder      bool\n\tBorderStyle Style\n\n\tBorderLeft, BorderRight, BorderTop, BorderBottom bool\n\n\tPaddingLeft, PaddingRight, PaddingTop, PaddingBottom int\n\n\timage.Rectangle\n\tInner image.Rectangle\n\n\tTitle      string\n\tTitleStyle Style\n\n\tsync.Mutex\n}\n\nfunc NewBlock() *Block {\n\treturn &Block{\n\t\tBorder:       true,\n\t\tBorderStyle:  Theme.Block.Border,\n\t\tBorderLeft:   true,\n\t\tBorderRight:  true,\n\t\tBorderTop:    true,\n\t\tBorderBottom: true,\n\n\t\tTitleStyle: Theme.Block.Title,\n\t}\n}\n\nfunc (self *Block) drawBorder(buf *Buffer) {\n\tverticalCell := Cell{VERTICAL_LINE, self.BorderStyle}\n\thorizontalCell := Cell{HORIZONTAL_LINE, self.BorderStyle}\n\n\t// draw lines\n\tif self.BorderTop {\n\t\tbuf.Fill(horizontalCell, image.Rect(self.Min.X, self.Min.Y, self.Max.X, self.Min.Y+1))\n\t}\n\tif self.BorderBottom {\n\t\tbuf.Fill(horizontalCell, image.Rect(self.Min.X, self.Max.Y-1, self.Max.X, self.Max.Y))\n\t}\n\tif self.BorderLeft {\n\t\tbuf.Fill(verticalCell, image.Rect(self.Min.X, self.Min.Y, self.Min.X+1, self.Max.Y))\n\t}\n\tif self.BorderRight {\n\t\tbuf.Fill(verticalCell, image.Rect(self.Max.X-1, self.Min.Y, self.Max.X, self.Max.Y))\n\t}\n\n\t// draw corners\n\tif self.BorderTop && self.BorderLeft {\n\t\tbuf.SetCell(Cell{TOP_LEFT, self.BorderStyle}, self.Min)\n\t}\n\tif self.BorderTop && self.BorderRight {\n\t\tbuf.SetCell(Cell{TOP_RIGHT, self.BorderStyle}, image.Pt(self.Max.X-1, self.Min.Y))\n\t}\n\tif self.BorderBottom && self.BorderLeft {\n\t\tbuf.SetCell(Cell{BOTTOM_LEFT, self.BorderStyle}, image.Pt(self.Min.X, self.Max.Y-1))\n\t}\n\tif self.BorderBottom && self.BorderRight {\n\t\tbuf.SetCell(Cell{BOTTOM_RIGHT, self.BorderStyle}, self.Max.Sub(image.Pt(1, 1)))\n\t}\n}\n\n// Draw implements the Drawable interface.\nfunc (self *Block) Draw(buf *Buffer) {\n\tif self.Border {\n\t\tself.drawBorder(buf)\n\t}\n\tbuf.SetString(\n\t\tself.Title,\n\t\tself.TitleStyle,\n\t\timage.Pt(self.Min.X+2, self.Min.Y),\n\t)\n}\n\n// SetRect implements the Drawable interface.\nfunc (self *Block) SetRect(x1, y1, x2, y2 int) {\n\tself.Rectangle = image.Rect(x1, y1, x2, y2)\n\tself.Inner = image.Rect(\n\t\tself.Min.X+1+self.PaddingLeft,\n\t\tself.Min.Y+1+self.PaddingTop,\n\t\tself.Max.X-1-self.PaddingRight,\n\t\tself.Max.Y-1-self.PaddingBottom,\n\t)\n}\n\n// GetRect implements the Drawable interface.\nfunc (self *Block) GetRect() image.Rectangle {\n\treturn self.Rectangle\n}\n"
  },
  {
    "path": "buffer.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage termui\n\nimport (\n\t\"image\"\n\n\trw \"github.com/mattn/go-runewidth\"\n)\n\n// Cell represents a viewable terminal cell\ntype Cell struct {\n\tRune  rune\n\tStyle Style\n}\n\nvar CellClear = Cell{\n\tRune:  ' ',\n\tStyle: StyleClear,\n}\n\n// NewCell takes 1 to 2 arguments\n// 1st argument = rune\n// 2nd argument = optional style\nfunc NewCell(rune rune, args ...interface{}) Cell {\n\tstyle := StyleClear\n\tif len(args) == 1 {\n\t\tstyle = args[0].(Style)\n\t}\n\treturn Cell{\n\t\tRune:  rune,\n\t\tStyle: style,\n\t}\n}\n\n// Buffer represents a section of a terminal and is a renderable rectangle of cells.\ntype Buffer struct {\n\timage.Rectangle\n\tCellMap map[image.Point]Cell\n}\n\nfunc NewBuffer(r image.Rectangle) *Buffer {\n\tbuf := &Buffer{\n\t\tRectangle: r,\n\t\tCellMap:   make(map[image.Point]Cell),\n\t}\n\tbuf.Fill(CellClear, r) // clears out area\n\treturn buf\n}\n\nfunc (self *Buffer) GetCell(p image.Point) Cell {\n\treturn self.CellMap[p]\n}\n\nfunc (self *Buffer) SetCell(c Cell, p image.Point) {\n\tself.CellMap[p] = c\n}\n\nfunc (self *Buffer) Fill(c Cell, rect image.Rectangle) {\n\tfor x := rect.Min.X; x < rect.Max.X; x++ {\n\t\tfor y := rect.Min.Y; y < rect.Max.Y; y++ {\n\t\t\tself.SetCell(c, image.Pt(x, y))\n\t\t}\n\t}\n}\n\nfunc (self *Buffer) SetString(s string, style Style, p image.Point) {\n\trunes := []rune(s)\n\tx := 0\n\tfor _, char := range runes {\n\t\tself.SetCell(Cell{char, style}, image.Pt(p.X+x, p.Y))\n\t\tx += rw.RuneWidth(char)\n\t}\n}\n"
  },
  {
    "path": "canvas.go",
    "content": "package termui\n\nimport (\n\t\"image\"\n\n\t\"github.com/gizak/termui/v3/drawille\"\n)\n\ntype Canvas struct {\n\tBlock\n\tdrawille.Canvas\n}\n\nfunc NewCanvas() *Canvas {\n\treturn &Canvas{\n\t\tBlock:  *NewBlock(),\n\t\tCanvas: *drawille.NewCanvas(),\n\t}\n}\n\nfunc (self *Canvas) SetPoint(p image.Point, color Color) {\n\tself.Canvas.SetPoint(p, drawille.Color(color))\n}\n\nfunc (self *Canvas) SetLine(p0, p1 image.Point, color Color) {\n\tself.Canvas.SetLine(p0, p1, drawille.Color(color))\n}\n\nfunc (self *Canvas) Draw(buf *Buffer) {\n\tfor point, cell := range self.Canvas.GetCells() {\n\t\tif point.In(self.Rectangle) {\n\t\t\tconvertedCell := Cell{\n\t\t\t\tcell.Rune,\n\t\t\t\tStyle{\n\t\t\t\t\tColor(cell.Color),\n\t\t\t\t\tColorClear,\n\t\t\t\t\tModifierClear,\n\t\t\t\t},\n\t\t\t}\n\t\t\tbuf.SetCell(convertedCell, point)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n/*\nPackage termui is a library for creating terminal user interfaces (TUIs) using widgets.\n*/\npackage termui\n"
  },
  {
    "path": "drawille/drawille.go",
    "content": "package drawille\n\nimport (\n\t\"image\"\n)\n\nconst BRAILLE_OFFSET = '\\u2800'\n\nvar BRAILLE = [4][2]rune{\n\t{'\\u0001', '\\u0008'},\n\t{'\\u0002', '\\u0010'},\n\t{'\\u0004', '\\u0020'},\n\t{'\\u0040', '\\u0080'},\n}\n\ntype Color int\n\ntype Cell struct {\n\tRune  rune\n\tColor Color\n}\n\ntype Canvas struct {\n\tCellMap map[image.Point]Cell\n}\n\nfunc NewCanvas() *Canvas {\n\treturn &Canvas{\n\t\tCellMap: make(map[image.Point]Cell),\n\t}\n}\n\nfunc (self *Canvas) SetPoint(p image.Point, color Color) {\n\tpoint := image.Pt(p.X/2, p.Y/4)\n\tself.CellMap[point] = Cell{\n\t\tself.CellMap[point].Rune | BRAILLE[p.Y%4][p.X%2],\n\t\tcolor,\n\t}\n}\n\nfunc (self *Canvas) SetLine(p0, p1 image.Point, color Color) {\n\tfor _, p := range line(p0, p1) {\n\t\tself.SetPoint(p, color)\n\t}\n}\n\nfunc (self *Canvas) GetCells() map[image.Point]Cell {\n\tcellMap := make(map[image.Point]Cell)\n\tfor point, cell := range self.CellMap {\n\t\tcellMap[point] = Cell{cell.Rune + BRAILLE_OFFSET, cell.Color}\n\t}\n\treturn cellMap\n}\n\nfunc line(p0, p1 image.Point) []image.Point {\n\tpoints := []image.Point{}\n\n\tleftPoint, rightPoint := p0, p1\n\tif leftPoint.X > rightPoint.X {\n\t\tleftPoint, rightPoint = rightPoint, leftPoint\n\t}\n\n\txDistance := absInt(leftPoint.X - rightPoint.X)\n\tyDistance := absInt(leftPoint.Y - rightPoint.Y)\n\tslope := float64(yDistance) / float64(xDistance)\n\tslopeSign := 1\n\tif rightPoint.Y < leftPoint.Y {\n\t\tslopeSign = -1\n\t}\n\n\ttargetYCoordinate := float64(leftPoint.Y)\n\tcurrentYCoordinate := leftPoint.Y\n\tfor i := leftPoint.X; i < rightPoint.X; i++ {\n\t\tpoints = append(points, image.Pt(i, currentYCoordinate))\n\t\ttargetYCoordinate += (slope * float64(slopeSign))\n\t\tfor currentYCoordinate != int(targetYCoordinate) {\n\t\t\tpoints = append(points, image.Pt(i, currentYCoordinate))\n\t\t\tcurrentYCoordinate += slopeSign\n\t\t}\n\t}\n\n\treturn points\n}\n\nfunc absInt(x int) int {\n\tif x >= 0 {\n\t\treturn x\n\t}\n\treturn -x\n}\n"
  },
  {
    "path": "events.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage termui\n\nimport (\n\t\"fmt\"\n\n\ttb \"github.com/nsf/termbox-go\"\n)\n\n/*\nList of events:\n\tmouse events:\n\t\t<MouseLeft> <MouseRight> <MouseMiddle>\n\t\t<MouseWheelUp> <MouseWheelDown>\n\tkeyboard events:\n\t\tany uppercase or lowercase letter like j or J\n\t\t<C-d> etc\n\t\t<M-d> etc\n\t\t<Up> <Down> <Left> <Right>\n\t\t<Insert> <Delete> <Home> <End> <Previous> <Next>\n\t\t<Backspace> <Tab> <Enter> <Escape> <Space>\n\t\t<C-<Space>> etc\n\tterminal events:\n        <Resize>\n\n    keyboard events that do not work:\n        <C-->\n        <C-2> <C-~>\n        <C-h>\n        <C-i>\n        <C-m>\n        <C-[> <C-3>\n        <C-\\\\>\n        <C-]>\n        <C-/> <C-_>\n        <C-8>\n*/\n\ntype EventType uint\n\nconst (\n\tKeyboardEvent EventType = iota\n\tMouseEvent\n\tResizeEvent\n)\n\ntype Event struct {\n\tType    EventType\n\tID      string\n\tPayload interface{}\n}\n\n// Mouse payload.\ntype Mouse struct {\n\tDrag bool\n\tX    int\n\tY    int\n}\n\n// Resize payload.\ntype Resize struct {\n\tWidth  int\n\tHeight int\n}\n\n// PollEvents gets events from termbox, converts them, then sends them to each of its channels.\nfunc PollEvents() <-chan Event {\n\tch := make(chan Event)\n\tgo func() {\n\t\tfor {\n\t\t\tch <- convertTermboxEvent(tb.PollEvent())\n\t\t}\n\t}()\n\treturn ch\n}\n\nvar keyboardMap = map[tb.Key]string{\n\ttb.KeyF1:         \"<F1>\",\n\ttb.KeyF2:         \"<F2>\",\n\ttb.KeyF3:         \"<F3>\",\n\ttb.KeyF4:         \"<F4>\",\n\ttb.KeyF5:         \"<F5>\",\n\ttb.KeyF6:         \"<F6>\",\n\ttb.KeyF7:         \"<F7>\",\n\ttb.KeyF8:         \"<F8>\",\n\ttb.KeyF9:         \"<F9>\",\n\ttb.KeyF10:        \"<F10>\",\n\ttb.KeyF11:        \"<F11>\",\n\ttb.KeyF12:        \"<F12>\",\n\ttb.KeyInsert:     \"<Insert>\",\n\ttb.KeyDelete:     \"<Delete>\",\n\ttb.KeyHome:       \"<Home>\",\n\ttb.KeyEnd:        \"<End>\",\n\ttb.KeyPgup:       \"<PageUp>\",\n\ttb.KeyPgdn:       \"<PageDown>\",\n\ttb.KeyArrowUp:    \"<Up>\",\n\ttb.KeyArrowDown:  \"<Down>\",\n\ttb.KeyArrowLeft:  \"<Left>\",\n\ttb.KeyArrowRight: \"<Right>\",\n\n\ttb.KeyCtrlSpace:  \"<C-<Space>>\", // tb.KeyCtrl2 tb.KeyCtrlTilde\n\ttb.KeyCtrlA:      \"<C-a>\",\n\ttb.KeyCtrlB:      \"<C-b>\",\n\ttb.KeyCtrlC:      \"<C-c>\",\n\ttb.KeyCtrlD:      \"<C-d>\",\n\ttb.KeyCtrlE:      \"<C-e>\",\n\ttb.KeyCtrlF:      \"<C-f>\",\n\ttb.KeyCtrlG:      \"<C-g>\",\n\ttb.KeyBackspace:  \"<C-<Backspace>>\", // tb.KeyCtrlH\n\ttb.KeyTab:        \"<Tab>\",           // tb.KeyCtrlI\n\ttb.KeyCtrlJ:      \"<C-j>\",\n\ttb.KeyCtrlK:      \"<C-k>\",\n\ttb.KeyCtrlL:      \"<C-l>\",\n\ttb.KeyEnter:      \"<Enter>\", // tb.KeyCtrlM\n\ttb.KeyCtrlN:      \"<C-n>\",\n\ttb.KeyCtrlO:      \"<C-o>\",\n\ttb.KeyCtrlP:      \"<C-p>\",\n\ttb.KeyCtrlQ:      \"<C-q>\",\n\ttb.KeyCtrlR:      \"<C-r>\",\n\ttb.KeyCtrlS:      \"<C-s>\",\n\ttb.KeyCtrlT:      \"<C-t>\",\n\ttb.KeyCtrlU:      \"<C-u>\",\n\ttb.KeyCtrlV:      \"<C-v>\",\n\ttb.KeyCtrlW:      \"<C-w>\",\n\ttb.KeyCtrlX:      \"<C-x>\",\n\ttb.KeyCtrlY:      \"<C-y>\",\n\ttb.KeyCtrlZ:      \"<C-z>\",\n\ttb.KeyEsc:        \"<Escape>\", // tb.KeyCtrlLsqBracket tb.KeyCtrl3\n\ttb.KeyCtrl4:      \"<C-4>\",    // tb.KeyCtrlBackslash\n\ttb.KeyCtrl5:      \"<C-5>\",    // tb.KeyCtrlRsqBracket\n\ttb.KeyCtrl6:      \"<C-6>\",\n\ttb.KeyCtrl7:      \"<C-7>\", // tb.KeyCtrlSlash tb.KeyCtrlUnderscore\n\ttb.KeySpace:      \"<Space>\",\n\ttb.KeyBackspace2: \"<Backspace>\", // tb.KeyCtrl8:\n}\n\n// convertTermboxKeyboardEvent converts a termbox keyboard event to a more friendly string format.\n// Combines modifiers into the string instead of having them as additional fields in an event.\nfunc convertTermboxKeyboardEvent(e tb.Event) Event {\n\tID := \"%s\"\n\tif e.Mod == tb.ModAlt {\n\t\tID = \"<M-%s>\"\n\t}\n\n\tif e.Ch != 0 {\n\t\tID = fmt.Sprintf(ID, string(e.Ch))\n\t} else {\n\t\tconverted, ok := keyboardMap[e.Key]\n\t\tif !ok {\n\t\t\tconverted = \"\"\n\t\t}\n\t\tID = fmt.Sprintf(ID, converted)\n\t}\n\n\treturn Event{\n\t\tType: KeyboardEvent,\n\t\tID:   ID,\n\t}\n}\n\nvar mouseButtonMap = map[tb.Key]string{\n\ttb.MouseLeft:      \"<MouseLeft>\",\n\ttb.MouseMiddle:    \"<MouseMiddle>\",\n\ttb.MouseRight:     \"<MouseRight>\",\n\ttb.MouseRelease:   \"<MouseRelease>\",\n\ttb.MouseWheelUp:   \"<MouseWheelUp>\",\n\ttb.MouseWheelDown: \"<MouseWheelDown>\",\n}\n\nfunc convertTermboxMouseEvent(e tb.Event) Event {\n\tconverted, ok := mouseButtonMap[e.Key]\n\tif !ok {\n\t\tconverted = \"Unknown_Mouse_Button\"\n\t}\n\tDrag := e.Mod == tb.ModMotion\n\treturn Event{\n\t\tType: MouseEvent,\n\t\tID:   converted,\n\t\tPayload: Mouse{\n\t\t\tX:    e.MouseX,\n\t\t\tY:    e.MouseY,\n\t\t\tDrag: Drag,\n\t\t},\n\t}\n}\n\n// convertTermboxEvent turns a termbox event into a termui event.\nfunc convertTermboxEvent(e tb.Event) Event {\n\tif e.Type == tb.EventError {\n\t\tpanic(e.Err)\n\t}\n\tswitch e.Type {\n\tcase tb.EventKey:\n\t\treturn convertTermboxKeyboardEvent(e)\n\tcase tb.EventMouse:\n\t\treturn convertTermboxMouseEvent(e)\n\tcase tb.EventResize:\n\t\treturn Event{\n\t\t\tType: ResizeEvent,\n\t\t\tID:   \"<Resize>\",\n\t\t\tPayload: Resize{\n\t\t\t\tWidth:  e.Width,\n\t\t\t\tHeight: e.Height,\n\t\t\t},\n\t\t}\n\t}\n\treturn Event{}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/gizak/termui/v3\n\ngo 1.15\n\nrequire (\n\tgithub.com/mattn/go-runewidth v0.0.15\n\tgithub.com/mitchellh/go-wordwrap v1.0.1\n\tgithub.com/nsf/termbox-go v1.1.1\n\tgithub.com/rivo/uniseg v0.4.4 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=\ngithub.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=\ngithub.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=\ngithub.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=\ngithub.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=\ngithub.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=\ngithub.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=\ngithub.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\n"
  },
  {
    "path": "grid.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage termui\n\ntype gridItemType uint\n\nconst (\n\tcol gridItemType = 0\n\trow gridItemType = 1\n)\n\ntype Grid struct {\n\tBlock\n\tItems []*GridItem\n}\n\n// GridItem represents either a Row or Column in a grid.\n// Holds sizing information and either an []GridItems or a widget.\ntype GridItem struct {\n\tType        gridItemType\n\tXRatio      float64\n\tYRatio      float64\n\tWidthRatio  float64\n\tHeightRatio float64\n\tEntry       interface{} // Entry.type == GridBufferer if IsLeaf else []GridItem\n\tIsLeaf      bool\n\tratio       float64\n}\n\nfunc NewGrid() *Grid {\n\tg := &Grid{\n\t\tBlock: *NewBlock(),\n\t}\n\tg.Border = false\n\treturn g\n}\n\n// NewCol takes a height percentage and either a widget or a Row or Column\nfunc NewCol(ratio float64, i ...interface{}) GridItem {\n\t_, ok := i[0].(Drawable)\n\tentry := i[0]\n\tif !ok {\n\t\tentry = i\n\t}\n\treturn GridItem{\n\t\tType:   col,\n\t\tEntry:  entry,\n\t\tIsLeaf: ok,\n\t\tratio:  ratio,\n\t}\n}\n\n// NewRow takes a width percentage and either a widget or a Row or Column\nfunc NewRow(ratio float64, i ...interface{}) GridItem {\n\t_, ok := i[0].(Drawable)\n\tentry := i[0]\n\tif !ok {\n\t\tentry = i\n\t}\n\treturn GridItem{\n\t\tType:   row,\n\t\tEntry:  entry,\n\t\tIsLeaf: ok,\n\t\tratio:  ratio,\n\t}\n}\n\n// Set is used to add Columns and Rows to the grid.\n// It recursively searches the GridItems, adding leaves to the grid and calculating the dimensions of the leaves.\nfunc (self *Grid) Set(entries ...interface{}) {\n\tentry := GridItem{\n\t\tType:   row,\n\t\tEntry:  entries,\n\t\tIsLeaf: false,\n\t\tratio:  1.0,\n\t}\n\tself.setHelper(entry, 1.0, 1.0)\n}\n\nfunc (self *Grid) setHelper(item GridItem, parentWidthRatio, parentHeightRatio float64) {\n\tvar HeightRatio float64\n\tvar WidthRatio float64\n\tswitch item.Type {\n\tcase col:\n\t\tHeightRatio = 1.0\n\t\tWidthRatio = item.ratio\n\tcase row:\n\t\tHeightRatio = item.ratio\n\t\tWidthRatio = 1.0\n\t}\n\titem.WidthRatio = parentWidthRatio * WidthRatio\n\titem.HeightRatio = parentHeightRatio * HeightRatio\n\n\tif item.IsLeaf {\n\t\tself.Items = append(self.Items, &item)\n\t} else {\n\t\tXRatio := 0.0\n\t\tYRatio := 0.0\n\t\tcols := false\n\t\trows := false\n\n\t\tchildren := InterfaceSlice(item.Entry)\n\n\t\tfor i := 0; i < len(children); i++ {\n\t\t\tif children[i] == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tchild, _ := children[i].(GridItem)\n\n\t\t\tchild.XRatio = item.XRatio + (item.WidthRatio * XRatio)\n\t\t\tchild.YRatio = item.YRatio + (item.HeightRatio * YRatio)\n\n\t\t\tswitch child.Type {\n\t\t\tcase col:\n\t\t\t\tcols = true\n\t\t\t\tXRatio += child.ratio\n\t\t\t\tif rows {\n\t\t\t\t\titem.HeightRatio /= 2\n\t\t\t\t}\n\t\t\tcase row:\n\t\t\t\trows = true\n\t\t\t\tYRatio += child.ratio\n\t\t\t\tif cols {\n\t\t\t\t\titem.WidthRatio /= 2\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tself.setHelper(child, item.WidthRatio, item.HeightRatio)\n\t\t}\n\t}\n}\n\nfunc (self *Grid) Draw(buf *Buffer) {\n\twidth := float64(self.Dx()) + 1\n\theight := float64(self.Dy()) + 1\n\n\tfor _, item := range self.Items {\n\t\tentry, _ := item.Entry.(Drawable)\n\n\t\tx := int(width*item.XRatio) + self.Min.X\n\t\ty := int(height*item.YRatio) + self.Min.Y\n\t\tw := int(width * item.WidthRatio)\n\t\th := int(height * item.HeightRatio)\n\n\t\tif x+w > self.Dx() {\n\t\t\tw--\n\t\t}\n\t\tif y+h > self.Dy() {\n\t\t\th--\n\t\t}\n\n\t\tentry.SetRect(x, y, x+w, y+h)\n\n\t\tentry.Lock()\n\t\tentry.Draw(buf)\n\t\tentry.Unlock()\n\t}\n}\n"
  },
  {
    "path": "render.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage termui\n\nimport (\n\t\"image\"\n\t\"sync\"\n\n\ttb \"github.com/nsf/termbox-go\"\n)\n\ntype Drawable interface {\n\tGetRect() image.Rectangle\n\tSetRect(int, int, int, int)\n\tDraw(*Buffer)\n\tsync.Locker\n}\n\nfunc Render(items ...Drawable) {\n\tfor _, item := range items {\n\t\tbuf := NewBuffer(item.GetRect())\n\t\titem.Lock()\n\t\titem.Draw(buf)\n\t\titem.Unlock()\n\t\tfor point, cell := range buf.CellMap {\n\t\t\tif point.In(buf.Rectangle) {\n\t\t\t\ttb.SetCell(\n\t\t\t\t\tpoint.X, point.Y,\n\t\t\t\t\tcell.Rune,\n\t\t\t\t\ttb.Attribute(cell.Style.Fg+1)|tb.Attribute(cell.Style.Modifier), tb.Attribute(cell.Style.Bg+1),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\ttb.Flush()\n}\n"
  },
  {
    "path": "style.go",
    "content": "package termui\n\n// Color is an integer from -1 to 255\n// -1 = ColorClear\n// 0-255 = Xterm colors\ntype Color int\n\n// ColorClear clears the Fg or Bg color of a Style\nconst ColorClear Color = -1\n\n// Basic terminal colors\nconst (\n\tColorBlack   Color = 0\n\tColorRed     Color = 1\n\tColorGreen   Color = 2\n\tColorYellow  Color = 3\n\tColorBlue    Color = 4\n\tColorMagenta Color = 5\n\tColorCyan    Color = 6\n\tColorWhite   Color = 7\n)\n\ntype Modifier uint\n\nconst (\n\t// ModifierClear clears any modifiers\n\tModifierClear     Modifier = 0\n\tModifierBold      Modifier = 1 << 9\n\tModifierUnderline Modifier = 1 << 10\n\tModifierReverse   Modifier = 1 << 11\n)\n\n// Style represents the style of one terminal cell\ntype Style struct {\n\tFg       Color\n\tBg       Color\n\tModifier Modifier\n}\n\n// StyleClear represents a default Style, with no colors or modifiers\nvar StyleClear = Style{\n\tFg:       ColorClear,\n\tBg:       ColorClear,\n\tModifier: ModifierClear,\n}\n\n// NewStyle takes 1 to 3 arguments\n// 1st argument = Fg\n// 2nd argument = optional Bg\n// 3rd argument = optional Modifier\nfunc NewStyle(fg Color, args ...interface{}) Style {\n\tbg := ColorClear\n\tmodifier := ModifierClear\n\tif len(args) >= 1 {\n\t\tbg = args[0].(Color)\n\t}\n\tif len(args) == 2 {\n\t\tmodifier = args[1].(Modifier)\n\t}\n\treturn Style{\n\t\tfg,\n\t\tbg,\n\t\tmodifier,\n\t}\n}\n"
  },
  {
    "path": "style_parser.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage termui\n\nimport (\n\t\"strings\"\n)\n\nconst (\n\ttokenFg       = \"fg\"\n\ttokenBg       = \"bg\"\n\ttokenModifier = \"mod\"\n\n\ttokenItemSeparator  = \",\"\n\ttokenValueSeparator = \":\"\n\n\ttokenBeginStyledText = '['\n\ttokenEndStyledText   = ']'\n\n\ttokenBeginStyle = '('\n\ttokenEndStyle   = ')'\n)\n\ntype parserState uint\n\nconst (\n\tparserStateDefault parserState = iota\n\tparserStateStyleItems\n\tparserStateStyledText\n)\n\n// StyleParserColorMap can be modified to add custom color parsing to text\nvar StyleParserColorMap = map[string]Color{\n\t\"red\":     ColorRed,\n\t\"blue\":    ColorBlue,\n\t\"black\":   ColorBlack,\n\t\"cyan\":    ColorCyan,\n\t\"yellow\":  ColorYellow,\n\t\"white\":   ColorWhite,\n\t\"clear\":   ColorClear,\n\t\"green\":   ColorGreen,\n\t\"magenta\": ColorMagenta,\n}\n\nvar modifierMap = map[string]Modifier{\n\t\"bold\":      ModifierBold,\n\t\"underline\": ModifierUnderline,\n\t\"reverse\":   ModifierReverse,\n}\n\n// readStyle translates an []rune like `fg:red,mod:bold,bg:white` to a style\nfunc readStyle(runes []rune, defaultStyle Style) Style {\n\tstyle := defaultStyle\n\tsplit := strings.Split(string(runes), tokenItemSeparator)\n\tfor _, item := range split {\n\t\tpair := strings.Split(item, tokenValueSeparator)\n\t\tif len(pair) == 2 {\n\t\t\tswitch pair[0] {\n\t\t\tcase tokenFg:\n\t\t\t\tstyle.Fg = StyleParserColorMap[pair[1]]\n\t\t\tcase tokenBg:\n\t\t\t\tstyle.Bg = StyleParserColorMap[pair[1]]\n\t\t\tcase tokenModifier:\n\t\t\t\tstyle.Modifier = modifierMap[pair[1]]\n\t\t\t}\n\t\t}\n\t}\n\treturn style\n}\n\n// ParseStyles parses a string for embedded Styles and returns []Cell with the correct styling.\n// Uses defaultStyle for any text without an embedded style.\n// Syntax is of the form [text](fg:<color>,mod:<attribute>,bg:<color>).\n// Ordering does not matter. All fields are optional.\nfunc ParseStyles(s string, defaultStyle Style) []Cell {\n\tcells := []Cell{}\n\trunes := []rune(s)\n\tstate := parserStateDefault\n\tstyledText := []rune{}\n\tstyleItems := []rune{}\n\tsquareCount := 0\n\n\treset := func() {\n\t\tstyledText = []rune{}\n\t\tstyleItems = []rune{}\n\t\tstate = parserStateDefault\n\t\tsquareCount = 0\n\t}\n\n\trollback := func() {\n\t\tcells = append(cells, RunesToStyledCells(styledText, defaultStyle)...)\n\t\tcells = append(cells, RunesToStyledCells(styleItems, defaultStyle)...)\n\t\treset()\n\t}\n\n\t// chop first and last runes\n\tchop := func(s []rune) []rune {\n\t\treturn s[1 : len(s)-1]\n\t}\n\n\tfor i, _rune := range runes {\n\t\tswitch state {\n\t\tcase parserStateDefault:\n\t\t\tif _rune == tokenBeginStyledText {\n\t\t\t\tstate = parserStateStyledText\n\t\t\t\tsquareCount = 1\n\t\t\t\tstyledText = append(styledText, _rune)\n\t\t\t} else {\n\t\t\t\tcells = append(cells, Cell{_rune, defaultStyle})\n\t\t\t}\n\t\tcase parserStateStyledText:\n\t\t\tswitch {\n\t\t\tcase squareCount == 0:\n\t\t\t\tswitch _rune {\n\t\t\t\tcase tokenBeginStyle:\n\t\t\t\t\tstate = parserStateStyleItems\n\t\t\t\t\tstyleItems = append(styleItems, _rune)\n\t\t\t\tdefault:\n\t\t\t\t\trollback()\n\t\t\t\t\tswitch _rune {\n\t\t\t\t\tcase tokenBeginStyledText:\n\t\t\t\t\t\tstate = parserStateStyledText\n\t\t\t\t\t\tsquareCount = 1\n\t\t\t\t\t\tstyleItems = append(styleItems, _rune)\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tcells = append(cells, Cell{_rune, defaultStyle})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase len(runes) == i+1:\n\t\t\t\trollback()\n\t\t\t\tstyledText = append(styledText, _rune)\n\t\t\tcase _rune == tokenBeginStyledText:\n\t\t\t\tsquareCount++\n\t\t\t\tstyledText = append(styledText, _rune)\n\t\t\tcase _rune == tokenEndStyledText:\n\t\t\t\tsquareCount--\n\t\t\t\tstyledText = append(styledText, _rune)\n\t\t\tdefault:\n\t\t\t\tstyledText = append(styledText, _rune)\n\t\t\t}\n\t\tcase parserStateStyleItems:\n\t\t\tstyleItems = append(styleItems, _rune)\n\t\t\tif _rune == tokenEndStyle {\n\t\t\t\tstyle := readStyle(chop(styleItems), defaultStyle)\n\t\t\t\tcells = append(cells, RunesToStyledCells(chop(styledText), style)...)\n\t\t\t\treset()\n\t\t\t} else if len(runes) == i+1 {\n\t\t\t\trollback()\n\t\t\t}\n\t\t}\n\t}\n\n\treturn cells\n}\n"
  },
  {
    "path": "symbols.go",
    "content": "package termui\n\nconst (\n\tDOT      = '•'\n\tELLIPSES = '…'\n\n\tUP_ARROW   = '▲'\n\tDOWN_ARROW = '▼'\n\n\tCOLLAPSED = '+'\n\tEXPANDED  = '−'\n)\n\nvar (\n\tBARS = [...]rune{' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}\n\n\tSHADED_BLOCKS = [...]rune{' ', '░', '▒', '▓', '█'}\n\n\tIRREGULAR_BLOCKS = [...]rune{\n\t\t' ', '▘', '▝', '▀', '▖', '▌', '▞', '▛',\n\t\t'▗', '▚', '▐', '▜', '▄', '▙', '▟', '█',\n\t}\n\n\tBRAILLE_OFFSET = '\\u2800'\n\tBRAILLE        = [4][2]rune{\n\t\t{'\\u0001', '\\u0008'},\n\t\t{'\\u0002', '\\u0010'},\n\t\t{'\\u0004', '\\u0020'},\n\t\t{'\\u0040', '\\u0080'},\n\t}\n\n\tDOUBLE_BRAILLE = map[[2]int]rune{\n\t\t[2]int{0, 0}: '⣀',\n\t\t[2]int{0, 1}: '⡠',\n\t\t[2]int{0, 2}: '⡐',\n\t\t[2]int{0, 3}: '⡈',\n\n\t\t[2]int{1, 0}: '⢄',\n\t\t[2]int{1, 1}: '⠤',\n\t\t[2]int{1, 2}: '⠔',\n\t\t[2]int{1, 3}: '⠌',\n\n\t\t[2]int{2, 0}: '⢂',\n\t\t[2]int{2, 1}: '⠢',\n\t\t[2]int{2, 2}: '⠒',\n\t\t[2]int{2, 3}: '⠊',\n\n\t\t[2]int{3, 0}: '⢁',\n\t\t[2]int{3, 1}: '⠡',\n\t\t[2]int{3, 2}: '⠑',\n\t\t[2]int{3, 3}: '⠉',\n\t}\n\n\tSINGLE_BRAILLE_LEFT  = [4]rune{'\\u2840', '⠄', '⠂', '⠁'}\n\tSINGLE_BRAILLE_RIGHT = [4]rune{'\\u2880', '⠠', '⠐', '⠈'}\n)\n"
  },
  {
    "path": "symbols_other.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build !windows\n\npackage termui\n\nconst (\n\tTOP_LEFT     = '┌'\n\tTOP_RIGHT    = '┐'\n\tBOTTOM_LEFT  = '└'\n\tBOTTOM_RIGHT = '┘'\n\n\tVERTICAL_LINE   = '│'\n\tHORIZONTAL_LINE = '─'\n\n\tVERTICAL_LEFT   = '┤'\n\tVERTICAL_RIGHT  = '├'\n\tHORIZONTAL_UP   = '┴'\n\tHORIZONTAL_DOWN = '┬'\n\n\tQUOTA_LEFT  = '«'\n\tQUOTA_RIGHT = '»'\n\n\tVERTICAL_DASH   = '┊'\n\tHORIZONTAL_DASH = '┈'\n)\n"
  },
  {
    "path": "symbols_windows.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\n// +build windows\n\npackage termui\n\nconst (\n\tTOP_LEFT     = '+'\n\tTOP_RIGHT    = '+'\n\tBOTTOM_LEFT  = '+'\n\tBOTTOM_RIGHT = '+'\n\n\tVERTICAL_LINE   = '|'\n\tHORIZONTAL_LINE = '-'\n\n\tVERTICAL_LEFT   = '+'\n\tVERTICAL_RIGHT  = '+'\n\tHORIZONTAL_UP   = '+'\n\tHORIZONTAL_DOWN = '+'\n\n\tQUOTA_LEFT  = '<'\n\tQUOTA_RIGHT = '>'\n\n\tVERTICAL_DASH   = '|'\n\tHORIZONTAL_DASH = '-'\n)\n"
  },
  {
    "path": "theme.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage termui\n\nvar StandardColors = []Color{\n\tColorRed,\n\tColorGreen,\n\tColorYellow,\n\tColorBlue,\n\tColorMagenta,\n\tColorCyan,\n\tColorWhite,\n}\n\nvar StandardStyles = []Style{\n\tNewStyle(ColorRed),\n\tNewStyle(ColorGreen),\n\tNewStyle(ColorYellow),\n\tNewStyle(ColorBlue),\n\tNewStyle(ColorMagenta),\n\tNewStyle(ColorCyan),\n\tNewStyle(ColorWhite),\n}\n\ntype RootTheme struct {\n\tDefault Style\n\n\tBlock BlockTheme\n\n\tBarChart        BarChartTheme\n\tGauge           GaugeTheme\n\tPlot            PlotTheme\n\tList            ListTheme\n\tTree            TreeTheme\n\tParagraph       ParagraphTheme\n\tPieChart        PieChartTheme\n\tSparkline       SparklineTheme\n\tStackedBarChart StackedBarChartTheme\n\tTab             TabTheme\n\tTable           TableTheme\n}\n\ntype BlockTheme struct {\n\tTitle  Style\n\tBorder Style\n}\n\ntype BarChartTheme struct {\n\tBars   []Color\n\tNums   []Style\n\tLabels []Style\n}\n\ntype GaugeTheme struct {\n\tBar   Color\n\tLabel Style\n}\n\ntype PlotTheme struct {\n\tLines []Color\n\tAxes  Color\n}\n\ntype ListTheme struct {\n\tText Style\n}\n\ntype TreeTheme struct {\n\tText      Style\n\tCollapsed rune\n\tExpanded  rune\n}\n\ntype ParagraphTheme struct {\n\tText Style\n}\n\ntype PieChartTheme struct {\n\tSlices []Color\n}\n\ntype SparklineTheme struct {\n\tTitle Style\n\tLine  Color\n}\n\ntype StackedBarChartTheme struct {\n\tBars   []Color\n\tNums   []Style\n\tLabels []Style\n}\n\ntype TabTheme struct {\n\tActive   Style\n\tInactive Style\n}\n\ntype TableTheme struct {\n\tText Style\n}\n\n// Theme holds the default Styles and Colors for all widgets.\n// You can set default widget Styles by modifying the Theme before creating the widgets.\nvar Theme = RootTheme{\n\tDefault: NewStyle(ColorWhite),\n\n\tBlock: BlockTheme{\n\t\tTitle:  NewStyle(ColorWhite),\n\t\tBorder: NewStyle(ColorWhite),\n\t},\n\n\tBarChart: BarChartTheme{\n\t\tBars:   StandardColors,\n\t\tNums:   StandardStyles,\n\t\tLabels: StandardStyles,\n\t},\n\n\tParagraph: ParagraphTheme{\n\t\tText: NewStyle(ColorWhite),\n\t},\n\n\tPieChart: PieChartTheme{\n\t\tSlices: StandardColors,\n\t},\n\n\tList: ListTheme{\n\t\tText: NewStyle(ColorWhite),\n\t},\n\n\tTree: TreeTheme{\n\t\tText:      NewStyle(ColorWhite),\n\t\tCollapsed: COLLAPSED,\n\t\tExpanded:  EXPANDED,\n\t},\n\n\tStackedBarChart: StackedBarChartTheme{\n\t\tBars:   StandardColors,\n\t\tNums:   StandardStyles,\n\t\tLabels: StandardStyles,\n\t},\n\n\tGauge: GaugeTheme{\n\t\tBar:   ColorWhite,\n\t\tLabel: NewStyle(ColorWhite),\n\t},\n\n\tSparkline: SparklineTheme{\n\t\tTitle: NewStyle(ColorWhite),\n\t\tLine:  ColorWhite,\n\t},\n\n\tPlot: PlotTheme{\n\t\tLines: StandardColors,\n\t\tAxes:  ColorWhite,\n\t},\n\n\tTable: TableTheme{\n\t\tText: NewStyle(ColorWhite),\n\t},\n\n\tTab: TabTheme{\n\t\tActive:   NewStyle(ColorRed),\n\t\tInactive: NewStyle(ColorWhite),\n\t},\n}\n"
  },
  {
    "path": "utils.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage termui\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"reflect\"\n\n\trw \"github.com/mattn/go-runewidth\"\n\twordwrap \"github.com/mitchellh/go-wordwrap\"\n)\n\n// InterfaceSlice takes an []interface{} represented as an interface{} and converts it\n// https://stackoverflow.com/questions/12753805/type-converting-slices-of-interfaces-in-go\nfunc InterfaceSlice(slice interface{}) []interface{} {\n\ts := reflect.ValueOf(slice)\n\tif s.Kind() != reflect.Slice {\n\t\tpanic(\"InterfaceSlice() given a non-slice type\")\n\t}\n\n\tret := make([]interface{}, s.Len())\n\n\tfor i := 0; i < s.Len(); i++ {\n\t\tret[i] = s.Index(i).Interface()\n\t}\n\n\treturn ret\n}\n\n// TrimString trims a string to a max length and adds '…' to the end if it was trimmed.\nfunc TrimString(s string, w int) string {\n\tif w <= 0 {\n\t\treturn \"\"\n\t}\n\tif rw.StringWidth(s) > w {\n\t\treturn rw.Truncate(s, w, string(ELLIPSES))\n\t}\n\treturn s\n}\n\nfunc SelectColor(colors []Color, index int) Color {\n\treturn colors[index%len(colors)]\n}\n\nfunc SelectStyle(styles []Style, index int) Style {\n\treturn styles[index%len(styles)]\n}\n\n// Math ------------------------------------------------------------------------\n\nfunc SumIntSlice(slice []int) int {\n\tsum := 0\n\tfor _, val := range slice {\n\t\tsum += val\n\t}\n\treturn sum\n}\n\nfunc SumFloat64Slice(data []float64) float64 {\n\tsum := 0.0\n\tfor _, v := range data {\n\t\tsum += v\n\t}\n\treturn sum\n}\n\nfunc GetMaxIntFromSlice(slice []int) (int, error) {\n\tif len(slice) == 0 {\n\t\treturn 0, fmt.Errorf(\"cannot get max value from empty slice\")\n\t}\n\tvar max int\n\tfor _, val := range slice {\n\t\tif val > max {\n\t\t\tmax = val\n\t\t}\n\t}\n\treturn max, nil\n}\n\nfunc GetMaxFloat64FromSlice(slice []float64) (float64, error) {\n\tif len(slice) == 0 {\n\t\treturn 0, fmt.Errorf(\"cannot get max value from empty slice\")\n\t}\n\tvar max float64\n\tfor _, val := range slice {\n\t\tif val > max {\n\t\t\tmax = val\n\t\t}\n\t}\n\treturn max, nil\n}\n\nfunc GetMaxFloat64From2dSlice(slices [][]float64) (float64, error) {\n\tif len(slices) == 0 {\n\t\treturn 0, fmt.Errorf(\"cannot get max value from empty slice\")\n\t}\n\tvar max float64\n\tfor _, slice := range slices {\n\t\tfor _, val := range slice {\n\t\t\tif val > max {\n\t\t\t\tmax = val\n\t\t\t}\n\t\t}\n\t}\n\treturn max, nil\n}\n\nfunc RoundFloat64(x float64) float64 {\n\treturn math.Floor(x + 0.5)\n}\n\nfunc FloorFloat64(x float64) float64 {\n\treturn math.Floor(x)\n}\n\nfunc AbsInt(x int) int {\n\tif x >= 0 {\n\t\treturn x\n\t}\n\treturn -x\n}\n\nfunc MinFloat64(x, y float64) float64 {\n\tif x < y {\n\t\treturn x\n\t}\n\treturn y\n}\n\nfunc MaxFloat64(x, y float64) float64 {\n\tif x > y {\n\t\treturn x\n\t}\n\treturn y\n}\n\nfunc MaxInt(x, y int) int {\n\tif x > y {\n\t\treturn x\n\t}\n\treturn y\n}\n\nfunc MinInt(x, y int) int {\n\tif x < y {\n\t\treturn x\n\t}\n\treturn y\n}\n\n// []Cell ----------------------------------------------------------------------\n\n// WrapCells takes []Cell and inserts Cells containing '\\n' wherever a linebreak should go.\nfunc WrapCells(cells []Cell, width uint) []Cell {\n\tstr := CellsToString(cells)\n\twrapped := wordwrap.WrapString(str, width)\n\twrappedCells := []Cell{}\n\ti := 0\n\tfor _, _rune := range wrapped {\n\t\tif _rune == '\\n' {\n\t\t\twrappedCells = append(wrappedCells, Cell{_rune, StyleClear})\n\t\t} else {\n\t\t\twrappedCells = append(wrappedCells, Cell{_rune, cells[i].Style})\n\t\t}\n\t\ti++\n\t}\n\treturn wrappedCells\n}\n\nfunc RunesToStyledCells(runes []rune, style Style) []Cell {\n\tcells := []Cell{}\n\tfor _, _rune := range runes {\n\t\tcells = append(cells, Cell{_rune, style})\n\t}\n\treturn cells\n}\n\nfunc CellsToString(cells []Cell) string {\n\trunes := make([]rune, len(cells))\n\tfor i, cell := range cells {\n\t\trunes[i] = cell.Rune\n\t}\n\treturn string(runes)\n}\n\nfunc TrimCells(cells []Cell, w int) []Cell {\n\ts := CellsToString(cells)\n\ts = TrimString(s, w)\n\trunes := []rune(s)\n\tnewCells := []Cell{}\n\tfor i, r := range runes {\n\t\tnewCells = append(newCells, Cell{r, cells[i].Style})\n\t}\n\treturn newCells\n}\n\nfunc SplitCells(cells []Cell, r rune) [][]Cell {\n\tsplitCells := [][]Cell{}\n\ttemp := []Cell{}\n\tfor _, cell := range cells {\n\t\tif cell.Rune == r {\n\t\t\tsplitCells = append(splitCells, temp)\n\t\t\ttemp = []Cell{}\n\t\t} else {\n\t\t\ttemp = append(temp, cell)\n\t\t}\n\t}\n\tif len(temp) > 0 {\n\t\tsplitCells = append(splitCells, temp)\n\t}\n\treturn splitCells\n}\n\ntype CellWithX struct {\n\tX    int\n\tCell Cell\n}\n\nfunc BuildCellWithXArray(cells []Cell) []CellWithX {\n\tcellWithXArray := make([]CellWithX, len(cells))\n\tindex := 0\n\tfor i, cell := range cells {\n\t\tcellWithXArray[i] = CellWithX{X: index, Cell: cell}\n\t\tindex += rw.RuneWidth(cell.Rune)\n\t}\n\treturn cellWithXArray\n}\n"
  },
  {
    "path": "widgets/barchart.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\n\trw \"github.com/mattn/go-runewidth\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\ntype BarChart struct {\n\tBlock\n\tBarColors    []Color\n\tLabelStyles  []Style\n\tNumStyles    []Style // only Fg and Modifier are used\n\tNumFormatter func(float64) string\n\tData         []float64\n\tLabels       []string\n\tBarWidth     int\n\tBarGap       int\n\tMaxVal       float64\n}\n\nfunc NewBarChart() *BarChart {\n\treturn &BarChart{\n\t\tBlock:        *NewBlock(),\n\t\tBarColors:    Theme.BarChart.Bars,\n\t\tNumStyles:    Theme.BarChart.Nums,\n\t\tLabelStyles:  Theme.BarChart.Labels,\n\t\tNumFormatter: func(n float64) string { return fmt.Sprint(n) },\n\t\tBarGap:       1,\n\t\tBarWidth:     3,\n\t}\n}\n\nfunc (self *BarChart) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tmaxVal := self.MaxVal\n\tif maxVal == 0 {\n\t\tmaxVal, _ = GetMaxFloat64FromSlice(self.Data)\n\t}\n\n\tbarXCoordinate := self.Inner.Min.X\n\n\tfor i, data := range self.Data {\n\t\tif data > 0 {\n\t\t\t// draw bar\n\t\t\theight := int((data / maxVal) * float64(self.Inner.Dy()-1))\n\t\t\tfor x := barXCoordinate; x < MinInt(barXCoordinate+self.BarWidth, self.Inner.Max.X); x++ {\n\t\t\t\tfor y := self.Inner.Max.Y - 2; y > (self.Inner.Max.Y-2)-height; y-- {\n\t\t\t\t\tc := NewCell(' ', NewStyle(ColorClear, SelectColor(self.BarColors, i)))\n\t\t\t\t\tbuf.SetCell(c, image.Pt(x, y))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// draw label\n\t\tif i < len(self.Labels) {\n\t\t\tlabelXCoordinate := barXCoordinate +\n\t\t\t\tint((float64(self.BarWidth) / 2)) -\n\t\t\t\tint((float64(rw.StringWidth(self.Labels[i])) / 2))\n\t\t\tbuf.SetString(\n\t\t\t\tself.Labels[i],\n\t\t\t\tSelectStyle(self.LabelStyles, i),\n\t\t\t\timage.Pt(labelXCoordinate, self.Inner.Max.Y-1),\n\t\t\t)\n\t\t}\n\n\t\t// draw number\n\t\tnumberXCoordinate := barXCoordinate + int((float64(self.BarWidth) / 2))\n\t\tif numberXCoordinate <= self.Inner.Max.X {\n\t\t\tbuf.SetString(\n\t\t\t\tself.NumFormatter(data),\n\t\t\t\tNewStyle(\n\t\t\t\t\tSelectStyle(self.NumStyles, i+1).Fg,\n\t\t\t\t\tSelectColor(self.BarColors, i),\n\t\t\t\t\tSelectStyle(self.NumStyles, i+1).Modifier,\n\t\t\t\t),\n\t\t\t\timage.Pt(numberXCoordinate, self.Inner.Max.Y-2),\n\t\t\t)\n\t\t}\n\n\t\tbarXCoordinate += (self.BarWidth + self.BarGap)\n\t}\n}\n"
  },
  {
    "path": "widgets/gauge.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\ntype Gauge struct {\n\tBlock\n\tPercent    int\n\tBarColor   Color\n\tLabel      string\n\tLabelStyle Style\n}\n\nfunc NewGauge() *Gauge {\n\treturn &Gauge{\n\t\tBlock:      *NewBlock(),\n\t\tBarColor:   Theme.Gauge.Bar,\n\t\tLabelStyle: Theme.Gauge.Label,\n\t}\n}\n\nfunc (self *Gauge) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tlabel := self.Label\n\tif label == \"\" {\n\t\tlabel = fmt.Sprintf(\"%d%%\", self.Percent)\n\t}\n\n\t// plot bar\n\tbarWidth := int((float64(self.Percent) / 100) * float64(self.Inner.Dx()))\n\tbuf.Fill(\n\t\tNewCell(' ', NewStyle(ColorClear, self.BarColor)),\n\t\timage.Rect(self.Inner.Min.X, self.Inner.Min.Y, self.Inner.Min.X+barWidth, self.Inner.Max.Y),\n\t)\n\n\t// plot label\n\tlabelXCoordinate := self.Inner.Min.X + (self.Inner.Dx() / 2) - int(float64(len(label))/2)\n\tlabelYCoordinate := self.Inner.Min.Y + ((self.Inner.Dy() - 1) / 2)\n\tif labelYCoordinate < self.Inner.Max.Y {\n\t\tfor i, char := range label {\n\t\t\tstyle := self.LabelStyle\n\t\t\tif labelXCoordinate+i+1 <= self.Inner.Min.X+barWidth {\n\t\t\t\tstyle = NewStyle(self.BarColor, ColorClear, ModifierReverse)\n\t\t\t}\n\t\t\tbuf.SetCell(NewCell(char, style), image.Pt(labelXCoordinate+i, labelYCoordinate))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "widgets/image.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"image\"\n\t\"image/color\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\ntype Image struct {\n\tBlock\n\tImage               image.Image\n\tMonochrome          bool\n\tMonochromeThreshold uint8\n\tMonochromeInvert    bool\n}\n\nfunc NewImage(img image.Image) *Image {\n\treturn &Image{\n\t\tBlock:               *NewBlock(),\n\t\tMonochromeThreshold: 128,\n\t\tImage:               img,\n\t}\n}\n\nfunc (self *Image) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tif self.Image == nil {\n\t\treturn\n\t}\n\n\tbufWidth := self.Inner.Dx()\n\tbufHeight := self.Inner.Dy()\n\timageWidth := self.Image.Bounds().Dx()\n\timageHeight := self.Image.Bounds().Dy()\n\n\tif self.Monochrome {\n\t\tif bufWidth > imageWidth/2 {\n\t\t\tbufWidth = imageWidth / 2\n\t\t}\n\t\tif bufHeight > imageHeight/2 {\n\t\t\tbufHeight = imageHeight / 2\n\t\t}\n\t\tfor bx := 0; bx < bufWidth; bx++ {\n\t\t\tfor by := 0; by < bufHeight; by++ {\n\t\t\t\tul := self.colorAverage(\n\t\t\t\t\t2*bx*imageWidth/bufWidth/2,\n\t\t\t\t\t(2*bx+1)*imageWidth/bufWidth/2,\n\t\t\t\t\t2*by*imageHeight/bufHeight/2,\n\t\t\t\t\t(2*by+1)*imageHeight/bufHeight/2,\n\t\t\t\t)\n\t\t\t\tur := self.colorAverage(\n\t\t\t\t\t(2*bx+1)*imageWidth/bufWidth/2,\n\t\t\t\t\t(2*bx+2)*imageWidth/bufWidth/2,\n\t\t\t\t\t2*by*imageHeight/bufHeight/2,\n\t\t\t\t\t(2*by+1)*imageHeight/bufHeight/2,\n\t\t\t\t)\n\t\t\t\tll := self.colorAverage(\n\t\t\t\t\t2*bx*imageWidth/bufWidth/2,\n\t\t\t\t\t(2*bx+1)*imageWidth/bufWidth/2,\n\t\t\t\t\t(2*by+1)*imageHeight/bufHeight/2,\n\t\t\t\t\t(2*by+2)*imageHeight/bufHeight/2,\n\t\t\t\t)\n\t\t\t\tlr := self.colorAverage(\n\t\t\t\t\t(2*bx+1)*imageWidth/bufWidth/2,\n\t\t\t\t\t(2*bx+2)*imageWidth/bufWidth/2,\n\t\t\t\t\t(2*by+1)*imageHeight/bufHeight/2,\n\t\t\t\t\t(2*by+2)*imageHeight/bufHeight/2,\n\t\t\t\t)\n\t\t\t\tbuf.SetCell(\n\t\t\t\t\tNewCell(blocksChar(ul, ur, ll, lr, self.MonochromeThreshold, self.MonochromeInvert)),\n\t\t\t\t\timage.Pt(self.Inner.Min.X+bx, self.Inner.Min.Y+by),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif bufWidth > imageWidth {\n\t\t\tbufWidth = imageWidth\n\t\t}\n\t\tif bufHeight > imageHeight {\n\t\t\tbufHeight = imageHeight\n\t\t}\n\t\tfor bx := 0; bx < bufWidth; bx++ {\n\t\t\tfor by := 0; by < bufHeight; by++ {\n\t\t\t\tc := self.colorAverage(\n\t\t\t\t\tbx*imageWidth/bufWidth,\n\t\t\t\t\t(bx+1)*imageWidth/bufWidth,\n\t\t\t\t\tby*imageHeight/bufHeight,\n\t\t\t\t\t(by+1)*imageHeight/bufHeight,\n\t\t\t\t)\n\t\t\t\tbuf.SetCell(\n\t\t\t\t\tNewCell(c.ch(), NewStyle(c.fgColor(), ColorBlack)),\n\t\t\t\t\timage.Pt(self.Inner.Min.X+bx, self.Inner.Min.Y+by),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (self *Image) colorAverage(x0, x1, y0, y1 int) colorAverager {\n\tvar c colorAverager\n\tfor x := x0; x < x1; x++ {\n\t\tfor y := y0; y < y1; y++ {\n\t\t\tc = c.add(\n\t\t\t\tself.Image.At(\n\t\t\t\t\tx+self.Image.Bounds().Min.X,\n\t\t\t\t\ty+self.Image.Bounds().Min.Y,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t}\n\treturn c\n}\n\ntype colorAverager struct {\n\trsum, gsum, bsum, asum, count uint64\n}\n\nfunc (self colorAverager) add(col color.Color) colorAverager {\n\tr, g, b, a := col.RGBA()\n\treturn colorAverager{\n\t\trsum:  self.rsum + uint64(r),\n\t\tgsum:  self.gsum + uint64(g),\n\t\tbsum:  self.bsum + uint64(b),\n\t\tasum:  self.asum + uint64(a),\n\t\tcount: self.count + 1,\n\t}\n}\n\nfunc (self colorAverager) RGBA() (uint32, uint32, uint32, uint32) {\n\tif self.count == 0 {\n\t\treturn 0, 0, 0, 0\n\t}\n\treturn uint32(self.rsum/self.count) & 0xffff,\n\t\tuint32(self.gsum/self.count) & 0xffff,\n\t\tuint32(self.bsum/self.count) & 0xffff,\n\t\tuint32(self.asum/self.count) & 0xffff\n}\n\nfunc (self colorAverager) fgColor() Color {\n\treturn palette.Convert(self).(paletteColor).attribute\n}\n\nfunc (self colorAverager) ch() rune {\n\tgray := color.GrayModel.Convert(self).(color.Gray).Y\n\tswitch {\n\tcase gray < 51:\n\t\treturn SHADED_BLOCKS[0]\n\tcase gray < 102:\n\t\treturn SHADED_BLOCKS[1]\n\tcase gray < 153:\n\t\treturn SHADED_BLOCKS[2]\n\tcase gray < 204:\n\t\treturn SHADED_BLOCKS[3]\n\tdefault:\n\t\treturn SHADED_BLOCKS[4]\n\t}\n}\n\nfunc (self colorAverager) monochrome(threshold uint8, invert bool) bool {\n\treturn self.count != 0 && (color.GrayModel.Convert(self).(color.Gray).Y < threshold != invert)\n}\n\ntype paletteColor struct {\n\trgba      color.RGBA\n\tattribute Color\n}\n\nfunc (self paletteColor) RGBA() (uint32, uint32, uint32, uint32) {\n\treturn self.rgba.RGBA()\n}\n\nvar palette = color.Palette([]color.Color{\n\tpaletteColor{color.RGBA{0, 0, 0, 255}, ColorBlack},\n\tpaletteColor{color.RGBA{255, 0, 0, 255}, ColorRed},\n\tpaletteColor{color.RGBA{0, 255, 0, 255}, ColorGreen},\n\tpaletteColor{color.RGBA{255, 255, 0, 255}, ColorYellow},\n\tpaletteColor{color.RGBA{0, 0, 255, 255}, ColorBlue},\n\tpaletteColor{color.RGBA{255, 0, 255, 255}, ColorMagenta},\n\tpaletteColor{color.RGBA{0, 255, 255, 255}, ColorCyan},\n\tpaletteColor{color.RGBA{255, 255, 255, 255}, ColorWhite},\n})\n\nfunc blocksChar(ul, ur, ll, lr colorAverager, threshold uint8, invert bool) rune {\n\tindex := 0\n\tif ul.monochrome(threshold, invert) {\n\t\tindex |= 1\n\t}\n\tif ur.monochrome(threshold, invert) {\n\t\tindex |= 2\n\t}\n\tif ll.monochrome(threshold, invert) {\n\t\tindex |= 4\n\t}\n\tif lr.monochrome(threshold, invert) {\n\t\tindex |= 8\n\t}\n\treturn IRREGULAR_BLOCKS[index]\n}\n"
  },
  {
    "path": "widgets/list.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"image\"\n\n\trw \"github.com/mattn/go-runewidth\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\ntype List struct {\n\tBlock\n\tRows             []string\n\tWrapText         bool\n\tTextStyle        Style\n\tSelectedRow      int\n\ttopRow           int\n\tSelectedRowStyle Style\n}\n\nfunc NewList() *List {\n\treturn &List{\n\t\tBlock:            *NewBlock(),\n\t\tTextStyle:        Theme.List.Text,\n\t\tSelectedRowStyle: Theme.List.Text,\n\t}\n}\n\nfunc (self *List) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tpoint := self.Inner.Min\n\n\t// adjusts view into widget\n\tif self.SelectedRow >= self.Inner.Dy()+self.topRow {\n\t\tself.topRow = self.SelectedRow - self.Inner.Dy() + 1\n\t} else if self.SelectedRow < self.topRow {\n\t\tself.topRow = self.SelectedRow\n\t}\n\n\t// draw rows\n\tfor row := self.topRow; row < len(self.Rows) && point.Y < self.Inner.Max.Y; row++ {\n\t\tcells := ParseStyles(self.Rows[row], self.TextStyle)\n\t\tif self.WrapText {\n\t\t\tcells = WrapCells(cells, uint(self.Inner.Dx()))\n\t\t}\n\t\tfor j := 0; j < len(cells) && point.Y < self.Inner.Max.Y; j++ {\n\t\t\tstyle := cells[j].Style\n\t\t\tif row == self.SelectedRow {\n\t\t\t\tstyle = self.SelectedRowStyle\n\t\t\t}\n\t\t\tif cells[j].Rune == '\\n' {\n\t\t\t\tpoint = image.Pt(self.Inner.Min.X, point.Y+1)\n\t\t\t} else {\n\t\t\t\tif point.X+1 == self.Inner.Max.X+1 && len(cells) > self.Inner.Dx() {\n\t\t\t\t\tbuf.SetCell(NewCell(ELLIPSES, style), point.Add(image.Pt(-1, 0)))\n\t\t\t\t\tbreak\n\t\t\t\t} else {\n\t\t\t\t\tbuf.SetCell(NewCell(cells[j].Rune, style), point)\n\t\t\t\t\tpoint = point.Add(image.Pt(rw.RuneWidth(cells[j].Rune), 0))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpoint = image.Pt(self.Inner.Min.X, point.Y+1)\n\t}\n\n\t// draw UP_ARROW if needed\n\tif self.topRow > 0 {\n\t\tbuf.SetCell(\n\t\t\tNewCell(UP_ARROW, NewStyle(ColorWhite)),\n\t\t\timage.Pt(self.Inner.Max.X-1, self.Inner.Min.Y),\n\t\t)\n\t}\n\n\t// draw DOWN_ARROW if needed\n\tif len(self.Rows) > int(self.topRow)+self.Inner.Dy() {\n\t\tbuf.SetCell(\n\t\t\tNewCell(DOWN_ARROW, NewStyle(ColorWhite)),\n\t\t\timage.Pt(self.Inner.Max.X-1, self.Inner.Max.Y-1),\n\t\t)\n\t}\n}\n\n// ScrollAmount scrolls by amount given. If amount is < 0, then scroll up.\n// There is no need to set self.topRow, as this will be set automatically when drawn,\n// since if the selected item is off screen then the topRow variable will change accordingly.\nfunc (self *List) ScrollAmount(amount int) {\n\tif len(self.Rows)-int(self.SelectedRow) <= amount {\n\t\tself.SelectedRow = len(self.Rows) - 1\n\t} else if int(self.SelectedRow)+amount < 0 {\n\t\tself.SelectedRow = 0\n\t} else {\n\t\tself.SelectedRow += amount\n\t}\n}\n\nfunc (self *List) ScrollUp() {\n\tself.ScrollAmount(-1)\n}\n\nfunc (self *List) ScrollDown() {\n\tself.ScrollAmount(1)\n}\n\nfunc (self *List) ScrollPageUp() {\n\t// If an item is selected below top row, then go to the top row.\n\tif self.SelectedRow > self.topRow {\n\t\tself.SelectedRow = self.topRow\n\t} else {\n\t\tself.ScrollAmount(-self.Inner.Dy())\n\t}\n}\n\nfunc (self *List) ScrollPageDown() {\n\tself.ScrollAmount(self.Inner.Dy())\n}\n\nfunc (self *List) ScrollHalfPageUp() {\n\tself.ScrollAmount(-int(FloorFloat64(float64(self.Inner.Dy()) / 2)))\n}\n\nfunc (self *List) ScrollHalfPageDown() {\n\tself.ScrollAmount(int(FloorFloat64(float64(self.Inner.Dy()) / 2)))\n}\n\nfunc (self *List) ScrollTop() {\n\tself.SelectedRow = 0\n}\n\nfunc (self *List) ScrollBottom() {\n\tself.SelectedRow = len(self.Rows) - 1\n}\n"
  },
  {
    "path": "widgets/paragraph.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"image\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\ntype Paragraph struct {\n\tBlock\n\tText      string\n\tTextStyle Style\n\tWrapText  bool\n}\n\nfunc NewParagraph() *Paragraph {\n\treturn &Paragraph{\n\t\tBlock:     *NewBlock(),\n\t\tTextStyle: Theme.Paragraph.Text,\n\t\tWrapText:  true,\n\t}\n}\n\nfunc (self *Paragraph) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tcells := ParseStyles(self.Text, self.TextStyle)\n\tif self.WrapText {\n\t\tcells = WrapCells(cells, uint(self.Inner.Dx()))\n\t}\n\n\trows := SplitCells(cells, '\\n')\n\n\tfor y, row := range rows {\n\t\tif y+self.Inner.Min.Y >= self.Inner.Max.Y {\n\t\t\tbreak\n\t\t}\n\t\trow = TrimCells(row, self.Inner.Dx())\n\t\tfor _, cx := range BuildCellWithXArray(row) {\n\t\t\tx, cell := cx.X, cx.Cell\n\t\t\tbuf.SetCell(cell, image.Pt(x, y).Add(self.Inner.Min))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "widgets/piechart.go",
    "content": "package widgets\n\nimport (\n\t\"image\"\n\t\"math\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\nconst (\n\tpiechartOffsetUp = -.5 * math.Pi // the northward angle\n\tresolutionFactor = .0001         // circle resolution: precision vs. performance\n\tfullCircle       = 2.0 * math.Pi // the full circle angle\n\txStretch         = 2.0           // horizontal adjustment\n)\n\n// PieChartLabel callback\ntype PieChartLabel func(dataIndex int, currentValue float64) string\n\ntype PieChart struct {\n\tBlock\n\tData           []float64     // list of data items\n\tColors         []Color       // colors to by cycled through\n\tLabelFormatter PieChartLabel // callback function for labels\n\tAngleOffset    float64       // which angle to start drawing at? (see piechartOffsetUp)\n}\n\n// NewPieChart Creates a new pie chart with reasonable defaults and no labels.\nfunc NewPieChart() *PieChart {\n\treturn &PieChart{\n\t\tBlock:       *NewBlock(),\n\t\tColors:      Theme.PieChart.Slices,\n\t\tAngleOffset: piechartOffsetUp,\n\t}\n}\n\nfunc (self *PieChart) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tcenter := self.Inner.Min.Add(self.Inner.Size().Div(2))\n\tradius := MinFloat64(float64(self.Inner.Dx()/2/xStretch), float64(self.Inner.Dy()/2))\n\n\t// compute slice sizes\n\tsum := SumFloat64Slice(self.Data)\n\tsliceSizes := make([]float64, len(self.Data))\n\tfor i, v := range self.Data {\n\t\tsliceSizes[i] = v / sum * fullCircle\n\t}\n\n\tborderCircle := &circle{center, radius}\n\tmiddleCircle := circle{Point: center, radius: radius / 2.0}\n\n\t// draw sectors\n\tphi := self.AngleOffset\n\tfor i, size := range sliceSizes {\n\t\tfor j := 0.0; j < size; j += resolutionFactor {\n\t\t\tborderPoint := borderCircle.at(phi + j)\n\t\t\tline := line{P1: center, P2: borderPoint}\n\t\t\tline.draw(NewCell(SHADED_BLOCKS[1], NewStyle(SelectColor(self.Colors, i))), buf)\n\t\t}\n\t\tphi += size\n\t}\n\n\t// draw labels\n\tif self.LabelFormatter != nil {\n\t\tphi = self.AngleOffset\n\t\tfor i, size := range sliceSizes {\n\t\t\tlabelPoint := middleCircle.at(phi + size/2.0)\n\t\t\tif len(self.Data) == 1 {\n\t\t\t\tlabelPoint = center\n\t\t\t}\n\t\t\tbuf.SetString(\n\t\t\t\tself.LabelFormatter(i, self.Data[i]),\n\t\t\t\tNewStyle(SelectColor(self.Colors, i)),\n\t\t\t\timage.Pt(labelPoint.X, labelPoint.Y),\n\t\t\t)\n\t\t\tphi += size\n\t\t}\n\t}\n}\n\ntype circle struct {\n\timage.Point\n\tradius float64\n}\n\n// computes the point at a given angle phi\nfunc (self circle) at(phi float64) image.Point {\n\tx := self.X + int(RoundFloat64(xStretch*self.radius*math.Cos(phi)))\n\ty := self.Y + int(RoundFloat64(self.radius*math.Sin(phi)))\n\treturn image.Point{X: x, Y: y}\n}\n\n// computes the perimeter of a circle\nfunc (self circle) perimeter() float64 {\n\treturn 2.0 * math.Pi * self.radius\n}\n\n// a line between two points\ntype line struct {\n\tP1, P2 image.Point\n}\n\n// draws the line\nfunc (self line) draw(cell Cell, buf *Buffer) {\n\tisLeftOf := func(p1, p2 image.Point) bool {\n\t\treturn p1.X <= p2.X\n\t}\n\tisTopOf := func(p1, p2 image.Point) bool {\n\t\treturn p1.Y <= p2.Y\n\t}\n\tp1, p2 := self.P1, self.P2\n\tbuf.SetCell(NewCell('*', cell.Style), self.P2)\n\twidth, height := self.size()\n\tif width > height { // paint left to right\n\t\tif !isLeftOf(p1, p2) {\n\t\t\tp1, p2 = p2, p1\n\t\t}\n\t\tflip := 1.0\n\t\tif !isTopOf(p1, p2) {\n\t\t\tflip = -1.0\n\t\t}\n\t\tfor x := p1.X; x <= p2.X; x++ {\n\t\t\tratio := float64(height) / float64(width)\n\t\t\tfactor := float64(x - p1.X)\n\t\t\ty := ratio * factor * flip\n\t\t\tbuf.SetCell(cell, image.Pt(x, int(RoundFloat64(y))+p1.Y))\n\t\t}\n\t} else { // paint top to bottom\n\t\tif !isTopOf(p1, p2) {\n\t\t\tp1, p2 = p2, p1\n\t\t}\n\t\tflip := 1.0\n\t\tif !isLeftOf(p1, p2) {\n\t\t\tflip = -1.0\n\t\t}\n\t\tfor y := p1.Y; y <= p2.Y; y++ {\n\t\t\tratio := float64(width) / float64(height)\n\t\t\tfactor := float64(y - p1.Y)\n\t\t\tx := ratio * factor * flip\n\t\t\tbuf.SetCell(cell, image.Pt(int(RoundFloat64(x))+p1.X, y))\n\t\t}\n\t}\n}\n\n// width and height of a line\nfunc (self line) size() (w, h int) {\n\treturn AbsInt(self.P2.X - self.P1.X), AbsInt(self.P2.Y - self.P1.Y)\n}\n"
  },
  {
    "path": "widgets/plot.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\n// Plot has two modes: line(default) and scatter.\n// Plot also has two marker types: braille(default) and dot.\n// A single braille character is a 2x4 grid of dots, so using braille\n// gives 2x X resolution and 4x Y resolution over dot mode.\ntype Plot struct {\n\tBlock\n\n\tData       [][]float64\n\tDataLabels []string\n\tMaxVal     float64\n\n\tLineColors []Color\n\tAxesColor  Color // TODO\n\tShowAxes   bool\n\n\tMarker          PlotMarker\n\tDotMarkerRune   rune\n\tPlotType        PlotType\n\tHorizontalScale int\n\tDrawDirection   DrawDirection // TODO\n}\n\nconst (\n\txAxisLabelsHeight = 1\n\tyAxisLabelsWidth  = 4\n\txAxisLabelsGap    = 2\n\tyAxisLabelsGap    = 1\n)\n\ntype PlotType uint\n\nconst (\n\tLineChart PlotType = iota\n\tScatterPlot\n)\n\ntype PlotMarker uint\n\nconst (\n\tMarkerBraille PlotMarker = iota\n\tMarkerDot\n)\n\ntype DrawDirection uint\n\nconst (\n\tDrawLeft DrawDirection = iota\n\tDrawRight\n)\n\nfunc NewPlot() *Plot {\n\treturn &Plot{\n\t\tBlock:           *NewBlock(),\n\t\tLineColors:      Theme.Plot.Lines,\n\t\tAxesColor:       Theme.Plot.Axes,\n\t\tMarker:          MarkerBraille,\n\t\tDotMarkerRune:   DOT,\n\t\tData:            [][]float64{},\n\t\tHorizontalScale: 1,\n\t\tDrawDirection:   DrawRight,\n\t\tShowAxes:        true,\n\t\tPlotType:        LineChart,\n\t}\n}\n\nfunc (self *Plot) renderBraille(buf *Buffer, drawArea image.Rectangle, maxVal float64) {\n\tcanvas := NewCanvas()\n\tcanvas.Rectangle = drawArea\n\n\tswitch self.PlotType {\n\tcase ScatterPlot:\n\t\tfor i, line := range self.Data {\n\t\t\tfor j, val := range line {\n\t\t\t\theight := int((val / maxVal) * float64(drawArea.Dy()-1))\n\t\t\t\tcanvas.SetPoint(\n\t\t\t\t\timage.Pt(\n\t\t\t\t\t\t(drawArea.Min.X+(j*self.HorizontalScale))*2,\n\t\t\t\t\t\t(drawArea.Max.Y-height-1)*4,\n\t\t\t\t\t),\n\t\t\t\t\tSelectColor(self.LineColors, i),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\tcase LineChart:\n\t\tfor i, line := range self.Data {\n\t\t\tpreviousHeight := int((line[1] / maxVal) * float64(drawArea.Dy()-1))\n\t\t\tfor j, val := range line[1:] {\n\t\t\t\theight := int((val / maxVal) * float64(drawArea.Dy()-1))\n\t\t\t\tcanvas.SetLine(\n\t\t\t\t\timage.Pt(\n\t\t\t\t\t\t(drawArea.Min.X+(j*self.HorizontalScale))*2,\n\t\t\t\t\t\t(drawArea.Max.Y-previousHeight-1)*4,\n\t\t\t\t\t),\n\t\t\t\t\timage.Pt(\n\t\t\t\t\t\t(drawArea.Min.X+((j+1)*self.HorizontalScale))*2,\n\t\t\t\t\t\t(drawArea.Max.Y-height-1)*4,\n\t\t\t\t\t),\n\t\t\t\t\tSelectColor(self.LineColors, i),\n\t\t\t\t)\n\t\t\t\tpreviousHeight = height\n\t\t\t}\n\t\t}\n\t}\n\n\tcanvas.Draw(buf)\n}\n\nfunc (self *Plot) renderDot(buf *Buffer, drawArea image.Rectangle, maxVal float64) {\n\tswitch self.PlotType {\n\tcase ScatterPlot:\n\t\tfor i, line := range self.Data {\n\t\t\tfor j, val := range line {\n\t\t\t\theight := int((val / maxVal) * float64(drawArea.Dy()-1))\n\t\t\t\tpoint := image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height)\n\t\t\t\tif point.In(drawArea) {\n\t\t\t\t\tbuf.SetCell(\n\t\t\t\t\t\tNewCell(self.DotMarkerRune, NewStyle(SelectColor(self.LineColors, i))),\n\t\t\t\t\t\tpoint,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase LineChart:\n\t\tfor i, line := range self.Data {\n\t\t\tfor j := 0; j < len(line) && j*self.HorizontalScale < drawArea.Dx(); j++ {\n\t\t\t\tval := line[j]\n\t\t\t\theight := int((val / maxVal) * float64(drawArea.Dy()-1))\n\t\t\t\tbuf.SetCell(\n\t\t\t\t\tNewCell(self.DotMarkerRune, NewStyle(SelectColor(self.LineColors, i))),\n\t\t\t\t\timage.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (self *Plot) plotAxes(buf *Buffer, maxVal float64) {\n\t// draw origin cell\n\tbuf.SetCell(\n\t\tNewCell(BOTTOM_LEFT, NewStyle(ColorWhite)),\n\t\timage.Pt(self.Inner.Min.X+yAxisLabelsWidth, self.Inner.Max.Y-xAxisLabelsHeight-1),\n\t)\n\t// draw x axis line\n\tfor i := yAxisLabelsWidth + 1; i < self.Inner.Dx(); i++ {\n\t\tbuf.SetCell(\n\t\t\tNewCell(HORIZONTAL_DASH, NewStyle(ColorWhite)),\n\t\t\timage.Pt(i+self.Inner.Min.X, self.Inner.Max.Y-xAxisLabelsHeight-1),\n\t\t)\n\t}\n\t// draw y axis line\n\tfor i := 0; i < self.Inner.Dy()-xAxisLabelsHeight-1; i++ {\n\t\tbuf.SetCell(\n\t\t\tNewCell(VERTICAL_DASH, NewStyle(ColorWhite)),\n\t\t\timage.Pt(self.Inner.Min.X+yAxisLabelsWidth, i+self.Inner.Min.Y),\n\t\t)\n\t}\n\t// draw x axis labels\n\t// draw 0\n\tbuf.SetString(\n\t\t\"0\",\n\t\tNewStyle(ColorWhite),\n\t\timage.Pt(self.Inner.Min.X+yAxisLabelsWidth, self.Inner.Max.Y-1),\n\t)\n\t// draw rest\n\tfor x := self.Inner.Min.X + yAxisLabelsWidth + (xAxisLabelsGap)*self.HorizontalScale + 1; x < self.Inner.Max.X-1; {\n\t\tlabel := fmt.Sprintf(\n\t\t\t\"%d\",\n\t\t\t(x-(self.Inner.Min.X+yAxisLabelsWidth)-1)/(self.HorizontalScale)+1,\n\t\t)\n\t\tbuf.SetString(\n\t\t\tlabel,\n\t\t\tNewStyle(ColorWhite),\n\t\t\timage.Pt(x, self.Inner.Max.Y-1),\n\t\t)\n\t\tx += (len(label) + xAxisLabelsGap) * self.HorizontalScale\n\t}\n\t// draw y axis labels\n\tverticalScale := maxVal / float64(self.Inner.Dy()-xAxisLabelsHeight-1)\n\tfor i := 0; i*(yAxisLabelsGap+1) < self.Inner.Dy()-1; i++ {\n\t\tbuf.SetString(\n\t\t\tfmt.Sprintf(\"%.2f\", float64(i)*verticalScale*(yAxisLabelsGap+1)),\n\t\t\tNewStyle(ColorWhite),\n\t\t\timage.Pt(self.Inner.Min.X, self.Inner.Max.Y-(i*(yAxisLabelsGap+1))-2),\n\t\t)\n\t}\n}\n\nfunc (self *Plot) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tmaxVal := self.MaxVal\n\tif maxVal == 0 {\n\t\tmaxVal, _ = GetMaxFloat64From2dSlice(self.Data)\n\t}\n\n\tif self.ShowAxes {\n\t\tself.plotAxes(buf, maxVal)\n\t}\n\n\tdrawArea := self.Inner\n\tif self.ShowAxes {\n\t\tdrawArea = image.Rect(\n\t\t\tself.Inner.Min.X+yAxisLabelsWidth+1, self.Inner.Min.Y,\n\t\t\tself.Inner.Max.X, self.Inner.Max.Y-xAxisLabelsHeight-1,\n\t\t)\n\t}\n\n\tswitch self.Marker {\n\tcase MarkerBraille:\n\t\tself.renderBraille(buf, drawArea, maxVal)\n\tcase MarkerDot:\n\t\tself.renderDot(buf, drawArea, maxVal)\n\t}\n}\n"
  },
  {
    "path": "widgets/sparkline.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"image\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\n// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.\ntype Sparkline struct {\n\tData       []float64\n\tTitle      string\n\tTitleStyle Style\n\tLineColor  Color\n\tMaxVal     float64\n\tMaxHeight  int\n}\n\n// SparklineGroup is a renderable widget which groups together the given sparklines.\ntype SparklineGroup struct {\n\tBlock\n\tSparklines []*Sparkline\n}\n\n// NewSparkline returns a unrenderable single sparkline that needs to be added to a SparklineGroup\nfunc NewSparkline() *Sparkline {\n\treturn &Sparkline{\n\t\tTitleStyle: Theme.Sparkline.Title,\n\t\tLineColor:  Theme.Sparkline.Line,\n\t}\n}\n\nfunc NewSparklineGroup(sls ...*Sparkline) *SparklineGroup {\n\treturn &SparklineGroup{\n\t\tBlock:      *NewBlock(),\n\t\tSparklines: sls,\n\t}\n}\n\nfunc (self *SparklineGroup) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tsparklineHeight := self.Inner.Dy() / len(self.Sparklines)\n\n\tfor i, sl := range self.Sparklines {\n\t\theightOffset := (sparklineHeight * (i + 1))\n\t\tbarHeight := sparklineHeight\n\t\tif i == len(self.Sparklines)-1 {\n\t\t\theightOffset = self.Inner.Dy()\n\t\t\tbarHeight = self.Inner.Dy() - (sparklineHeight * i)\n\t\t}\n\t\tif sl.Title != \"\" {\n\t\t\tbarHeight--\n\t\t}\n\n\t\tmaxVal := sl.MaxVal\n\t\tif maxVal == 0 {\n\t\t\tmaxVal, _ = GetMaxFloat64FromSlice(sl.Data)\n\t\t}\n\n\t\t// draw line\n\t\tfor j := 0; j < len(sl.Data) && j < self.Inner.Dx(); j++ {\n\t\t\tdata := sl.Data[j]\n\t\t\theight := int((data / maxVal) * float64(barHeight))\n\t\t\tif height > sl.MaxHeight {\n\t\t\t\theight = sl.MaxHeight\n\t\t\t}\n\t\t\tsparkChar := BARS[len(BARS)-1]\n\t\t\tfor k := 0; k < height; k++ {\n\t\t\t\tbuf.SetCell(\n\t\t\t\t\tNewCell(sparkChar, NewStyle(sl.LineColor)),\n\t\t\t\t\timage.Pt(j+self.Inner.Min.X, self.Inner.Min.Y-1+heightOffset-k),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif height == 0 {\n\t\t\t\tsparkChar = BARS[1]\n\t\t\t\tbuf.SetCell(\n\t\t\t\t\tNewCell(sparkChar, NewStyle(sl.LineColor)),\n\t\t\t\t\timage.Pt(j+self.Inner.Min.X, self.Inner.Min.Y-1+heightOffset),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tif sl.Title != \"\" {\n\t\t\t// draw title\n\t\t\tbuf.SetString(\n\t\t\t\tTrimString(sl.Title, self.Inner.Dx()),\n\t\t\t\tsl.TitleStyle,\n\t\t\t\timage.Pt(self.Inner.Min.X, self.Inner.Min.Y-1+heightOffset-barHeight),\n\t\t\t)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "widgets/stacked_barchart.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\n\trw \"github.com/mattn/go-runewidth\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\ntype StackedBarChart struct {\n\tBlock\n\tBarColors    []Color\n\tLabelStyles  []Style\n\tNumStyles    []Style // only Fg and Modifier are used\n\tNumFormatter func(float64) string\n\tData         [][]float64\n\tLabels       []string\n\tBarWidth     int\n\tBarGap       int\n\tMaxVal       float64\n}\n\nfunc NewStackedBarChart() *StackedBarChart {\n\treturn &StackedBarChart{\n\t\tBlock:        *NewBlock(),\n\t\tBarColors:    Theme.StackedBarChart.Bars,\n\t\tLabelStyles:  Theme.StackedBarChart.Labels,\n\t\tNumStyles:    Theme.StackedBarChart.Nums,\n\t\tNumFormatter: func(n float64) string { return fmt.Sprint(n) },\n\t\tBarGap:       1,\n\t\tBarWidth:     3,\n\t}\n}\n\nfunc (self *StackedBarChart) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tmaxVal := self.MaxVal\n\tif maxVal == 0 {\n\t\tfor _, data := range self.Data {\n\t\t\tmaxVal = MaxFloat64(maxVal, SumFloat64Slice(data))\n\t\t}\n\t}\n\n\tbarXCoordinate := self.Inner.Min.X\n\n\tfor i, bar := range self.Data {\n\t\t// draw stacked bars\n\t\tstackedBarYCoordinate := 0\n\t\tfor j, data := range bar {\n\t\t\t// draw each stacked bar\n\t\t\theight := int((data / maxVal) * float64(self.Inner.Dy()-1))\n\t\t\tfor x := barXCoordinate; x < MinInt(barXCoordinate+self.BarWidth, self.Inner.Max.X); x++ {\n\t\t\t\tfor y := (self.Inner.Max.Y - 2) - stackedBarYCoordinate; y > (self.Inner.Max.Y-2)-stackedBarYCoordinate-height; y-- {\n\t\t\t\t\tc := NewCell(' ', NewStyle(ColorClear, SelectColor(self.BarColors, j)))\n\t\t\t\t\tbuf.SetCell(c, image.Pt(x, y))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// draw number\n\t\t\tnumberXCoordinate := barXCoordinate + int((float64(self.BarWidth) / 2)) - 1\n\t\t\tbuf.SetString(\n\t\t\t\tself.NumFormatter(data),\n\t\t\t\tNewStyle(\n\t\t\t\t\tSelectStyle(self.NumStyles, j+1).Fg,\n\t\t\t\t\tSelectColor(self.BarColors, j),\n\t\t\t\t\tSelectStyle(self.NumStyles, j+1).Modifier,\n\t\t\t\t),\n\t\t\t\timage.Pt(numberXCoordinate, (self.Inner.Max.Y-2)-stackedBarYCoordinate),\n\t\t\t)\n\n\t\t\tstackedBarYCoordinate += height\n\t\t}\n\n\t\t// draw label\n\t\tif i < len(self.Labels) {\n\t\t\tlabelXCoordinate := barXCoordinate + MaxInt(\n\t\t\t\tint((float64(self.BarWidth)/2))-int((float64(rw.StringWidth(self.Labels[i]))/2)),\n\t\t\t\t0,\n\t\t\t)\n\t\t\tbuf.SetString(\n\t\t\t\tTrimString(self.Labels[i], self.BarWidth),\n\t\t\t\tSelectStyle(self.LabelStyles, i),\n\t\t\t\timage.Pt(labelXCoordinate, self.Inner.Max.Y-1),\n\t\t\t)\n\t\t}\n\n\t\tbarXCoordinate += (self.BarWidth + self.BarGap)\n\t}\n}\n"
  },
  {
    "path": "widgets/table.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"image\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\n/*Table is like:\n┌ Awesome Table ───────────────────────────────────────────────┐\n│  Col0          | Col1 | Col2 | Col3  | Col4  | Col5  | Col6  |\n│──────────────────────────────────────────────────────────────│\n│  Some Item #1  | AAA  | 123  | CCCCC | EEEEE | GGGGG | IIIII |\n│──────────────────────────────────────────────────────────────│\n│  Some Item #2  | BBB  | 456  | DDDDD | FFFFF | HHHHH | JJJJJ |\n└──────────────────────────────────────────────────────────────┘\n*/\ntype Table struct {\n\tBlock\n\tRows          [][]string\n\tColumnWidths  []int\n\tTextStyle     Style\n\tRowSeparator  bool\n\tTextAlignment Alignment\n\tRowStyles     map[int]Style\n\tFillRow       bool\n\n\t// ColumnResizer is called on each Draw. Can be used for custom column sizing.\n\tColumnResizer func()\n}\n\nfunc NewTable() *Table {\n\treturn &Table{\n\t\tBlock:         *NewBlock(),\n\t\tTextStyle:     Theme.Table.Text,\n\t\tRowSeparator:  true,\n\t\tRowStyles:     make(map[int]Style),\n\t\tColumnResizer: func() {},\n\t}\n}\n\nfunc (self *Table) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\tself.ColumnResizer()\n\n\tcolumnWidths := self.ColumnWidths\n\tif len(columnWidths) == 0 {\n\t\tcolumnCount := len(self.Rows[0])\n\t\tcolumnWidth := self.Inner.Dx() / columnCount\n\t\tfor i := 0; i < columnCount; i++ {\n\t\t\tcolumnWidths = append(columnWidths, columnWidth)\n\t\t}\n\t}\n\n\tyCoordinate := self.Inner.Min.Y\n\n\t// draw rows\n\tfor i := 0; i < len(self.Rows) && yCoordinate < self.Inner.Max.Y; i++ {\n\t\trow := self.Rows[i]\n\t\tcolXCoordinate := self.Inner.Min.X\n\n\t\trowStyle := self.TextStyle\n\t\t// get the row style if one exists\n\t\tif style, ok := self.RowStyles[i]; ok {\n\t\t\trowStyle = style\n\t\t}\n\n\t\tif self.FillRow {\n\t\t\tblankCell := NewCell(' ', rowStyle)\n\t\t\tbuf.Fill(blankCell, image.Rect(self.Inner.Min.X, yCoordinate, self.Inner.Max.X, yCoordinate+1))\n\t\t}\n\n\t\t// draw row cells\n\t\tfor j := 0; j < len(row); j++ {\n\t\t\tcol := ParseStyles(row[j], rowStyle)\n\t\t\t// draw row cell\n\t\t\tif len(col) > columnWidths[j] || self.TextAlignment == AlignLeft {\n\t\t\t\tfor _, cx := range BuildCellWithXArray(col) {\n\t\t\t\t\tk, cell := cx.X, cx.Cell\n\t\t\t\t\tif k == columnWidths[j] || colXCoordinate+k == self.Inner.Max.X {\n\t\t\t\t\t\tcell.Rune = ELLIPSES\n\t\t\t\t\t\tbuf.SetCell(cell, image.Pt(colXCoordinate+k-1, yCoordinate))\n\t\t\t\t\t\tbreak\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbuf.SetCell(cell, image.Pt(colXCoordinate+k, yCoordinate))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if self.TextAlignment == AlignCenter {\n\t\t\t\txCoordinateOffset := (columnWidths[j] - len(col)) / 2\n\t\t\t\tstringXCoordinate := xCoordinateOffset + colXCoordinate\n\t\t\t\tfor _, cx := range BuildCellWithXArray(col) {\n\t\t\t\t\tk, cell := cx.X, cx.Cell\n\t\t\t\t\tbuf.SetCell(cell, image.Pt(stringXCoordinate+k, yCoordinate))\n\t\t\t\t}\n\t\t\t} else if self.TextAlignment == AlignRight {\n\t\t\t\tstringXCoordinate := MinInt(colXCoordinate+columnWidths[j], self.Inner.Max.X) - len(col)\n\t\t\t\tfor _, cx := range BuildCellWithXArray(col) {\n\t\t\t\t\tk, cell := cx.X, cx.Cell\n\t\t\t\t\tbuf.SetCell(cell, image.Pt(stringXCoordinate+k, yCoordinate))\n\t\t\t\t}\n\t\t\t}\n\t\t\tcolXCoordinate += columnWidths[j] + 1\n\t\t}\n\n\t\t// draw vertical separators\n\t\tseparatorStyle := self.Block.BorderStyle\n\n\t\tseparatorXCoordinate := self.Inner.Min.X\n\t\tverticalCell := NewCell(VERTICAL_LINE, separatorStyle)\n\t\tfor i, width := range columnWidths {\n\t\t\tif self.FillRow && i < len(columnWidths)-1 {\n\t\t\t\tverticalCell.Style.Bg = rowStyle.Bg\n\t\t\t} else {\n\t\t\t\tverticalCell.Style.Bg = self.Block.BorderStyle.Bg\n\t\t\t}\n\n\t\t\tseparatorXCoordinate += width\n\t\t\tbuf.SetCell(verticalCell, image.Pt(separatorXCoordinate, yCoordinate))\n\t\t\tseparatorXCoordinate++\n\t\t}\n\n\t\tyCoordinate++\n\n\t\t// draw horizontal separator\n\t\thorizontalCell := NewCell(HORIZONTAL_LINE, separatorStyle)\n\t\tif self.RowSeparator && yCoordinate < self.Inner.Max.Y && i != len(self.Rows)-1 {\n\t\t\tbuf.Fill(horizontalCell, image.Rect(self.Inner.Min.X, yCoordinate, self.Inner.Max.X, yCoordinate+1))\n\t\t\tyCoordinate++\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "widgets/tabs.go",
    "content": "// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.\n// Use of this source code is governed by a MIT license that can\n// be found in the LICENSE file.\n\npackage widgets\n\nimport (\n\t\"image\"\n\n\t. \"github.com/gizak/termui/v3\"\n)\n\n// TabPane is a renderable widget which can be used to conditionally render certain tabs/views.\n// TabPane shows a list of Tab names.\n// The currently selected tab can be found through the `ActiveTabIndex` field.\ntype TabPane struct {\n\tBlock\n\tTabNames         []string\n\tActiveTabIndex   int\n\tActiveTabStyle   Style\n\tInactiveTabStyle Style\n}\n\nfunc NewTabPane(names ...string) *TabPane {\n\treturn &TabPane{\n\t\tBlock:            *NewBlock(),\n\t\tTabNames:         names,\n\t\tActiveTabStyle:   Theme.Tab.Active,\n\t\tInactiveTabStyle: Theme.Tab.Inactive,\n\t}\n}\n\nfunc (self *TabPane) FocusLeft() {\n\tif self.ActiveTabIndex > 0 {\n\t\tself.ActiveTabIndex--\n\t}\n}\n\nfunc (self *TabPane) FocusRight() {\n\tif self.ActiveTabIndex < len(self.TabNames)-1 {\n\t\tself.ActiveTabIndex++\n\t}\n}\n\nfunc (self *TabPane) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\n\txCoordinate := self.Inner.Min.X\n\tfor i, name := range self.TabNames {\n\t\tColorPair := self.InactiveTabStyle\n\t\tif i == self.ActiveTabIndex {\n\t\t\tColorPair = self.ActiveTabStyle\n\t\t}\n\t\tbuf.SetString(\n\t\t\tTrimString(name, self.Inner.Max.X-xCoordinate),\n\t\t\tColorPair,\n\t\t\timage.Pt(xCoordinate, self.Inner.Min.Y),\n\t\t)\n\n\t\txCoordinate += 1 + len(name)\n\n\t\tif i < len(self.TabNames)-1 && xCoordinate < self.Inner.Max.X {\n\t\t\tbuf.SetCell(\n\t\t\t\tNewCell(VERTICAL_LINE, NewStyle(ColorWhite)),\n\t\t\t\timage.Pt(xCoordinate, self.Inner.Min.Y),\n\t\t\t)\n\t\t}\n\n\t\txCoordinate += 2\n\t}\n}\n"
  },
  {
    "path": "widgets/tree.go",
    "content": "package widgets\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\t\"strings\"\n\n\t. \"github.com/gizak/termui/v3\"\n\trw \"github.com/mattn/go-runewidth\"\n)\n\nconst treeIndent = \"  \"\n\n// TreeNode is a tree node.\ntype TreeNode struct {\n\tValue    fmt.Stringer\n\tExpanded bool\n\tNodes    []*TreeNode\n\n\t// level stores the node level in the tree.\n\tlevel int\n}\n\n// TreeWalkFn is a function used for walking a Tree.\n// To interrupt the walking process function should return false.\ntype TreeWalkFn func(*TreeNode) bool\n\nfunc (self *TreeNode) parseStyles(style Style) []Cell {\n\tvar sb strings.Builder\n\tif len(self.Nodes) == 0 {\n\t\tsb.WriteString(strings.Repeat(treeIndent, self.level+1))\n\t} else {\n\t\tsb.WriteString(strings.Repeat(treeIndent, self.level))\n\t\tif self.Expanded {\n\t\t\tsb.WriteRune(Theme.Tree.Expanded)\n\t\t} else {\n\t\t\tsb.WriteRune(Theme.Tree.Collapsed)\n\t\t}\n\t\tsb.WriteByte(' ')\n\t}\n\tsb.WriteString(self.Value.String())\n\treturn ParseStyles(sb.String(), style)\n}\n\n// Tree is a tree widget.\ntype Tree struct {\n\tBlock\n\tTextStyle        Style\n\tSelectedRowStyle Style\n\tWrapText         bool\n\tSelectedRow      int\n\n\tnodes []*TreeNode\n\t// rows is flatten nodes for rendering.\n\trows   []*TreeNode\n\ttopRow int\n}\n\n// NewTree creates a new Tree widget.\nfunc NewTree() *Tree {\n\treturn &Tree{\n\t\tBlock:            *NewBlock(),\n\t\tTextStyle:        Theme.Tree.Text,\n\t\tSelectedRowStyle: Theme.Tree.Text,\n\t\tWrapText:         true,\n\t}\n}\n\nfunc (self *Tree) SetNodes(nodes []*TreeNode) {\n\tself.nodes = nodes\n\tself.prepareNodes()\n}\n\nfunc (self *Tree) prepareNodes() {\n\tself.rows = make([]*TreeNode, 0)\n\tfor _, node := range self.nodes {\n\t\tself.prepareNode(node, 0)\n\t}\n}\n\nfunc (self *Tree) prepareNode(node *TreeNode, level int) {\n\tself.rows = append(self.rows, node)\n\tnode.level = level\n\n\tif node.Expanded {\n\t\tfor _, n := range node.Nodes {\n\t\t\tself.prepareNode(n, level+1)\n\t\t}\n\t}\n}\n\nfunc (self *Tree) Walk(fn TreeWalkFn) {\n\tfor _, n := range self.nodes {\n\t\tif !self.walk(n, fn) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (self *Tree) walk(n *TreeNode, fn TreeWalkFn) bool {\n\tif !fn(n) {\n\t\treturn false\n\t}\n\n\tfor _, node := range n.Nodes {\n\t\tif !self.walk(node, fn) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (self *Tree) Draw(buf *Buffer) {\n\tself.Block.Draw(buf)\n\tpoint := self.Inner.Min\n\n\t// adjusts view into widget\n\tif self.SelectedRow >= self.Inner.Dy()+self.topRow {\n\t\tself.topRow = self.SelectedRow - self.Inner.Dy() + 1\n\t} else if self.SelectedRow < self.topRow {\n\t\tself.topRow = self.SelectedRow\n\t}\n\n\t// draw rows\n\tfor row := self.topRow; row < len(self.rows) && point.Y < self.Inner.Max.Y; row++ {\n\t\tcells := self.rows[row].parseStyles(self.TextStyle)\n\t\tif self.WrapText {\n\t\t\tcells = WrapCells(cells, uint(self.Inner.Dx()))\n\t\t}\n\t\tfor j := 0; j < len(cells) && point.Y < self.Inner.Max.Y; j++ {\n\t\t\tstyle := cells[j].Style\n\t\t\tif row == self.SelectedRow {\n\t\t\t\tstyle = self.SelectedRowStyle\n\t\t\t}\n\t\t\tif point.X+1 == self.Inner.Max.X+1 && len(cells) > self.Inner.Dx() {\n\t\t\t\tbuf.SetCell(NewCell(ELLIPSES, style), point.Add(image.Pt(-1, 0)))\n\t\t\t} else {\n\t\t\t\tbuf.SetCell(NewCell(cells[j].Rune, style), point)\n\t\t\t\tpoint = point.Add(image.Pt(rw.RuneWidth(cells[j].Rune), 0))\n\t\t\t}\n\t\t}\n\t\tpoint = image.Pt(self.Inner.Min.X, point.Y+1)\n\t}\n\n\t// draw UP_ARROW if needed\n\tif self.topRow > 0 {\n\t\tbuf.SetCell(\n\t\t\tNewCell(UP_ARROW, NewStyle(ColorWhite)),\n\t\t\timage.Pt(self.Inner.Max.X-1, self.Inner.Min.Y),\n\t\t)\n\t}\n\n\t// draw DOWN_ARROW if needed\n\tif len(self.rows) > int(self.topRow)+self.Inner.Dy() {\n\t\tbuf.SetCell(\n\t\t\tNewCell(DOWN_ARROW, NewStyle(ColorWhite)),\n\t\t\timage.Pt(self.Inner.Max.X-1, self.Inner.Max.Y-1),\n\t\t)\n\t}\n}\n\n// ScrollAmount scrolls by amount given. If amount is < 0, then scroll up.\n// There is no need to set self.topRow, as this will be set automatically when drawn,\n// since if the selected item is off screen then the topRow variable will change accordingly.\nfunc (self *Tree) ScrollAmount(amount int) {\n\tif len(self.rows)-int(self.SelectedRow) <= amount {\n\t\tself.SelectedRow = len(self.rows) - 1\n\t} else if int(self.SelectedRow)+amount < 0 {\n\t\tself.SelectedRow = 0\n\t} else {\n\t\tself.SelectedRow += amount\n\t}\n}\n\nfunc (self *Tree) SelectedNode() *TreeNode {\n\tif len(self.rows) == 0 {\n\t\treturn nil\n\t}\n\treturn self.rows[self.SelectedRow]\n}\n\nfunc (self *Tree) ScrollUp() {\n\tself.ScrollAmount(-1)\n}\n\nfunc (self *Tree) ScrollDown() {\n\tself.ScrollAmount(1)\n}\n\nfunc (self *Tree) ScrollPageUp() {\n\t// If an item is selected below top row, then go to the top row.\n\tif self.SelectedRow > self.topRow {\n\t\tself.SelectedRow = self.topRow\n\t} else {\n\t\tself.ScrollAmount(-self.Inner.Dy())\n\t}\n}\n\nfunc (self *Tree) ScrollPageDown() {\n\tself.ScrollAmount(self.Inner.Dy())\n}\n\nfunc (self *Tree) ScrollHalfPageUp() {\n\tself.ScrollAmount(-int(FloorFloat64(float64(self.Inner.Dy()) / 2)))\n}\n\nfunc (self *Tree) ScrollHalfPageDown() {\n\tself.ScrollAmount(int(FloorFloat64(float64(self.Inner.Dy()) / 2)))\n}\n\nfunc (self *Tree) ScrollTop() {\n\tself.SelectedRow = 0\n}\n\nfunc (self *Tree) ScrollBottom() {\n\tself.SelectedRow = len(self.rows) - 1\n}\n\nfunc (self *Tree) Collapse() {\n\tself.rows[self.SelectedRow].Expanded = false\n\tself.prepareNodes()\n}\n\nfunc (self *Tree) Expand() {\n\tnode := self.rows[self.SelectedRow]\n\tif len(node.Nodes) > 0 {\n\t\tself.rows[self.SelectedRow].Expanded = true\n\t}\n\tself.prepareNodes()\n}\n\nfunc (self *Tree) ToggleExpand() {\n\tnode := self.rows[self.SelectedRow]\n\tif len(node.Nodes) > 0 {\n\t\tnode.Expanded = !node.Expanded\n\t}\n\tself.prepareNodes()\n}\n\nfunc (self *Tree) ExpandAll() {\n\tself.Walk(func(n *TreeNode) bool {\n\t\tif len(n.Nodes) > 0 {\n\t\t\tn.Expanded = true\n\t\t}\n\t\treturn true\n\t})\n\tself.prepareNodes()\n}\n\nfunc (self *Tree) CollapseAll() {\n\tself.Walk(func(n *TreeNode) bool {\n\t\tn.Expanded = false\n\t\treturn true\n\t})\n\tself.prepareNodes()\n}\n"
  }
]