├── README.md ├── strings.go ├── rect.go ├── demos └── demos.go ├── interfaces.go ├── canvas_test.go ├── focusable_window.go ├── _demos ├── scrollviews.go └── windows.go ├── canvas.go ├── window.go └── scrollview.go /README.md: -------------------------------------------------------------------------------- 1 | # Windeau 2 | 3 | Windeau is a windowing/widgeting extension for termbox that came out 4 | of the necessity for more flexible and controllable windows than just 5 | what came with termbox. 6 | 7 | Currently it just supports building empty windows that have titles. 8 | 9 | You can see the windows in action by doing: 10 | 11 | `go run _demos/windows.go` 12 | 13 | Press the any key to quit the demo 14 | -------------------------------------------------------------------------------- /strings.go: -------------------------------------------------------------------------------- 1 | package windeau 2 | 3 | import "github.com/nsf/termbox-go" 4 | 5 | func DrawString(s string, x, y int, fg, bg termbox.Attribute) { 6 | DrawStringWithinSize(s, x, y, len(s), fg, bg) 7 | } 8 | 9 | func DrawStringWithinSize(s string, x, y, width int, fg, bg termbox.Attribute) { 10 | for px, char := range ConvertToRuneArray(s) { 11 | if px >= width { 12 | break 13 | } 14 | termbox.SetCell(x+px, y, char, fg, bg) 15 | } 16 | } 17 | 18 | func ConvertToRuneArray(s string) []rune { 19 | runes := make([]rune, len(s)) 20 | for px := 0; px < len(s) && px < len(s); px++ { 21 | runes[px] = rune(s[px]) 22 | } 23 | return runes 24 | } 25 | -------------------------------------------------------------------------------- /rect.go: -------------------------------------------------------------------------------- 1 | package windeau 2 | 3 | type Rect struct { 4 | X, Y, Width, Height int 5 | } 6 | 7 | func (r Rect) WithinRect(x, y int) bool { 8 | return x >= r.X && x < r.X+r.Width && y >= r.Y && y < r.Y+r.Height 9 | } 10 | 11 | func (r Rect) Contains(other Rect) bool { 12 | if r.X <= other.X && r.X+r.Width >= other.X+other.Width { 13 | if r.Y <= other.Y && r.Y+r.Height >= other.Y+other.Height { 14 | return true 15 | } 16 | } 17 | return false 18 | } 19 | 20 | func (r Rect) DoesNotContain(other Rect) bool { 21 | return !r.Contains(other) 22 | } 23 | 24 | func (r Rect) ShrinkBy(amount int) Rect { 25 | return Rect{r.X + amount, r.Y + amount, r.Width - amount, r.Height - amount} 26 | } 27 | -------------------------------------------------------------------------------- /demos/demos.go: -------------------------------------------------------------------------------- 1 | package demos 2 | 3 | import ( 4 | "github.com/nsf/termbox-go" 5 | ) 6 | 7 | func RunLoop(callbacks map[string]func(ev *termbox.Event)) { 8 | termbox.Init() 9 | defer termbox.Close() 10 | termbox.SetInputMode(termbox.InputMouse) 11 | 12 | draw := func() { 13 | callbacks["draw"](nil) 14 | termbox.Flush() 15 | } 16 | 17 | if setup := callbacks["setup"]; setup != nil { 18 | setup(nil) 19 | draw() 20 | } 21 | 22 | for true { 23 | termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) 24 | 25 | event := termbox.PollEvent() 26 | switch { 27 | case event.Key == termbox.KeyEsc: 28 | break 29 | default: 30 | callbacks["update"](&event) 31 | } 32 | draw() 33 | } 34 | 35 | if teardown := callbacks["teardown"]; teardown != nil { 36 | teardown(nil) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /interfaces.go: -------------------------------------------------------------------------------- 1 | package windeau 2 | 3 | import ( 4 | "fmt" 5 | "github.com/nsf/termbox-go" 6 | ) 7 | 8 | type WindowBorder interface { 9 | Edge(x, y int, window Window) rune 10 | VerticalBorder() rune 11 | HorizontalBorder() rune 12 | } 13 | 14 | type Drawable interface { 15 | SetParent(parent Drawable) 16 | GetColors() (fg, bg termbox.Attribute) 17 | IsRoot() bool 18 | IsFocused() bool 19 | SetFocused(focused bool) 20 | GetRect() Rect 21 | WithinBox(x, y int) bool 22 | Draw() 23 | } 24 | 25 | type DataSource interface { 26 | GetEntries() []fmt.Stringer 27 | GetPosition() int 28 | } 29 | 30 | type Event struct { 31 | termbox.Event 32 | } 33 | 34 | type EventHandler interface { 35 | OnFocus(context Event) 36 | OnUnfocus(context Event) 37 | OnHighlight(context Event) 38 | OnSelect(context Event) 39 | } 40 | -------------------------------------------------------------------------------- /canvas_test.go: -------------------------------------------------------------------------------- 1 | package windeau 2 | 3 | import ( 4 | "fmt" 5 | "github.com/nsf/termbox-go" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func assert_canvas_cells(t *testing.T, c rune, fg, bg termbox.Attribute, cells [][]Cell) { 11 | for _, row := range cells { 12 | for _, cell := range row { 13 | assert_canvas_cell(t, c, fg, bg, cell) 14 | } 15 | } 16 | } 17 | 18 | func assert_canvas_cell(t *testing.T, c rune, fg, bg termbox.Attribute, cell Cell) { 19 | assert.Equal(t, c, cell.Char, fmt.Sprintf("Cell should've been %v but was %v instead", c, cell.Char)) 20 | assert.Equal(t, fg, cell.Fg, "Cell foreground colour should be Red") 21 | assert.Equal(t, bg, cell.Bg, "Cell background colour should be Default") 22 | } 23 | 24 | func TestFillingCanvas(t *testing.T) { 25 | canvas := MakeCanvas(0, 0, 2, 2) 26 | canvas.Fill('x', termbox.ColorRed, termbox.ColorDefault) 27 | cells := canvas.Cells() 28 | assert_canvas_cells(t, 'x', termbox.ColorRed, termbox.ColorDefault, cells) 29 | } 30 | 31 | func TestFillingARegionInsideTheCanvas(t *testing.T) { 32 | canvas := MakeCanvas(0, 0, 5, 5) 33 | canvas.Fill('x', termbox.ColorRed, termbox.ColorDefault) 34 | canvas.FilledRect('y', termbox.ColorGreen, termbox.ColorDefault, Rect{2, 2, 1, 1}) 35 | cells := canvas.Cells() 36 | assert_canvas_cell(t, 'y', termbox.ColorGreen, termbox.ColorDefault, cells[2][2]) 37 | } 38 | -------------------------------------------------------------------------------- /focusable_window.go: -------------------------------------------------------------------------------- 1 | package windeau 2 | 3 | import "github.com/nsf/termbox-go" 4 | 5 | type WindowState struct { 6 | FgColor termbox.Attribute 7 | BgColor termbox.Attribute 8 | } 9 | 10 | type FocusableWindow struct { 11 | Parent *Window 12 | FocusOn, FocusOff WindowState 13 | Focused bool 14 | } 15 | 16 | func MakeFocusableWindow(x, y, w, h int, on, off WindowState, border WindowBorder) *FocusableWindow { 17 | parent := &Window{X: x, Y: y, Width: w, Height: h, Border: border} 18 | return &FocusableWindow{parent, on, off, false} 19 | } 20 | 21 | func (c *FocusableWindow) ToggleFocus() { 22 | c.Focused = !c.Focused 23 | } 24 | 25 | func (c *FocusableWindow) Draw() { 26 | c.setColors() 27 | c.Parent.Draw() 28 | } 29 | 30 | func (c *FocusableWindow) SetParent(parent Drawable) { 31 | window, ok := parent.(*Window) 32 | if ok { 33 | c.Parent = window 34 | } 35 | } 36 | 37 | func (c *FocusableWindow) GetColors() (termbox.Attribute, termbox.Attribute) { 38 | return c.Parent.GetColors() 39 | } 40 | 41 | func (c *FocusableWindow) IsRoot() bool { 42 | return false 43 | } 44 | 45 | func (c *FocusableWindow) IsFocused() bool { 46 | return c.Focused 47 | } 48 | 49 | func (c *FocusableWindow) SetFocused(focused bool) { 50 | c.Focused = focused 51 | } 52 | 53 | func (c *FocusableWindow) GetRect() Rect { 54 | return c.Parent.GetRect() 55 | } 56 | 57 | func (c *FocusableWindow) WithinBox(x, y int) bool { 58 | c.Focused = c.Parent.WithinBox(x, y) 59 | return c.Focused 60 | } 61 | 62 | func (c *FocusableWindow) setColors() { 63 | color := c.FocusOff 64 | if c.Focused { 65 | color = c.FocusOn 66 | } 67 | c.Parent.Fg = color.FgColor 68 | c.Parent.Bg = color.BgColor 69 | } 70 | -------------------------------------------------------------------------------- /_demos/scrollviews.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/csaunders/windeau" 5 | "github.com/csaunders/windeau/demos" 6 | "github.com/nsf/termbox-go" 7 | ) 8 | 9 | var focusWindow *windeau.FocusableWindow 10 | var scrollview *windeau.Scrollview 11 | var handler *ScrollviewHandler 12 | 13 | type ScrollviewHandler struct { 14 | windeau.EventHandler 15 | actualPosition int 16 | } 17 | 18 | func (h *ScrollviewHandler) OnFocus(context windeau.Event) { 19 | scrollview.Position = h.actualPosition 20 | } 21 | 22 | func (h *ScrollviewHandler) OnUnfocus(context windeau.Event) { 23 | scrollview.Position = -1 24 | } 25 | 26 | func setup(ev *termbox.Event) { 27 | on := windeau.WindowState{termbox.ColorGreen, termbox.ColorDefault} 28 | off := windeau.WindowState{termbox.ColorWhite, termbox.ColorDefault} 29 | border := windeau.MakeSimpleBorder('+', '|', '-') 30 | focusWindow = windeau.MakeFocusableWindow(20, 5, 20, 5, on, off, border) 31 | focusWindow.Parent.Title = "Pokemon" 32 | entries := []string{"Pikachu", "Charmander", "Bulbasaur", "Squirtle", "Meowth", "Pidgey", "Vulpix", "Golem"} 33 | handler = &ScrollviewHandler{actualPosition: 0} 34 | scrollview = windeau.MakeScrollview(focusWindow, entries, handler) 35 | } 36 | 37 | func draw(ev *termbox.Event) { 38 | windeau.DrawString("Click on the window labeled 'Pokemon' to interact with the scrollview", 5, 1, termbox.ColorRed, termbox.ColorDefault) 39 | scrollview.Draw() 40 | } 41 | 42 | func update(ev *termbox.Event) { 43 | if scrollview.IsFocused() { 44 | switch ev.Key { 45 | case termbox.KeyArrowDown: 46 | handler.actualPosition++ 47 | case termbox.KeyArrowUp: 48 | handler.actualPosition-- 49 | } 50 | } 51 | if ev.Type == termbox.EventMouse { 52 | scrollview.WithinBox(ev.MouseX, ev.MouseY) 53 | } 54 | handler.actualPosition = scrollview.SetPosition(handler.actualPosition) 55 | } 56 | 57 | func main() { 58 | callbacks := map[string]func(ev *termbox.Event){ 59 | "setup": setup, 60 | "draw": draw, 61 | "update": update, 62 | } 63 | demos.RunLoop(callbacks) 64 | } 65 | -------------------------------------------------------------------------------- /canvas.go: -------------------------------------------------------------------------------- 1 | package windeau 2 | 3 | import ( 4 | "github.com/nsf/termbox-go" 5 | ) 6 | 7 | type Cell struct { 8 | Char rune 9 | Fg, Bg termbox.Attribute 10 | } 11 | 12 | var BlankCell Cell = Cell{' ', termbox.ColorDefault, termbox.ColorDefault} 13 | 14 | type Canvas struct { 15 | X, Y, Width, Height int 16 | Parent Drawable 17 | cells [][]Cell 18 | } 19 | 20 | func MakeCanvas(x, y, w, h int) *Canvas { 21 | canvas := &Canvas{X: x, Y: y, Width: w, Height: h} 22 | canvas.initialize() 23 | return canvas 24 | } 25 | 26 | func (c *Canvas) GetRect() Rect { 27 | if c.Parent != nil { 28 | return c.Parent.GetRect().ShrinkBy(1) 29 | } else { 30 | return Rect{c.X, c.Y, c.Width, c.Height} 31 | } 32 | } 33 | 34 | func (c *Canvas) Fill(char rune, fg, bg termbox.Attribute) { 35 | c.FilledRect(char, fg, bg, c.GetRect()) 36 | } 37 | 38 | func (c *Canvas) FilledRect(char rune, fg, bg termbox.Attribute, rect Rect) { 39 | if c.GetRect().DoesNotContain(rect) { 40 | return 41 | } 42 | 43 | offsetX := rect.X - c.X 44 | offsetY := rect.Y - c.Y 45 | 46 | for i := 0; i < rect.Width; i++ { 47 | for j := 0; j < rect.Height; j++ { 48 | c.MarkCell(i+offsetX, j+offsetY, char, fg, bg) 49 | } 50 | } 51 | } 52 | 53 | func (c *Canvas) MarkCell(x, y int, char rune, fg, bg termbox.Attribute) { 54 | c.cells[x][y] = Cell{char, fg, bg} 55 | } 56 | 57 | func (c *Canvas) DrawString(x, y int, s string, fg, bg termbox.Attribute) { 58 | runes := ConvertToRuneArray(s) 59 | width := c.Width - x 60 | if y > c.Height || y < 0 { 61 | return 62 | } 63 | 64 | for i := 0; i < width && i < len(runes); i++ { 65 | c.MarkCell(i+x, y, runes[i], fg, bg) 66 | } 67 | } 68 | 69 | func (c *Canvas) Draw() { 70 | rect := c.GetRect() 71 | for x, row := range c.cells { 72 | for y, cell := range row { 73 | px := x + rect.X 74 | py := y + rect.Y 75 | 76 | if rect.WithinRect(px, py) && px < rect.X+rect.Width-1 && py < rect.Y+rect.Height-1 { 77 | termbox.SetCell(px, py, cell.Char, cell.Fg, cell.Bg) 78 | } 79 | } 80 | } 81 | } 82 | 83 | func (c *Canvas) Cells() [][]Cell { 84 | return c.cells 85 | } 86 | 87 | func (c *Canvas) initialize() { 88 | c.cells = make([][]Cell, c.Width) 89 | for i := range c.cells { 90 | c.cells[i] = make([]Cell, c.Height) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /_demos/windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/csaunders/windeau" 5 | "github.com/nsf/termbox-go" 6 | ) 7 | 8 | var window, fancyWindow *windeau.Window 9 | var focusableWindow *windeau.FocusableWindow 10 | var canvas *windeau.Canvas 11 | 12 | func main() { 13 | running := true 14 | termbox.Init() 15 | defer termbox.Close() 16 | termbox.SetInputMode(termbox.InputMouse) 17 | 18 | prepareWindows() 19 | 20 | for running { 21 | draw() 22 | switch event := termbox.PollEvent(); event.Type { 23 | case termbox.EventMouse: 24 | handleMouse(&event) 25 | case termbox.EventKey: 26 | if event.Key == termbox.KeyEsc { 27 | running = false 28 | } 29 | } 30 | } 31 | } 32 | 33 | type FancyBorder struct { 34 | TopLeft, TopRight, BottomLeft, BottomRight rune 35 | Vertical, Horizontal rune 36 | } 37 | 38 | func (f FancyBorder) Edge(x, y int, w windeau.Window) rune { 39 | switch { 40 | case x == w.X && y == w.Y: 41 | return f.TopLeft 42 | case x == w.X+w.Width-1 && y == w.Y: 43 | return f.TopRight 44 | case x == w.X && y == w.Y+w.Height-1: 45 | return f.BottomLeft 46 | case x == w.X+w.Width-1 && y == w.Y+w.Height-1: 47 | return f.BottomRight 48 | default: 49 | return ' ' 50 | } 51 | } 52 | 53 | func (f FancyBorder) VerticalBorder() rune { 54 | return f.Vertical 55 | } 56 | 57 | func (f FancyBorder) HorizontalBorder() rune { 58 | return f.Horizontal 59 | } 60 | 61 | func draw() { 62 | termbox.Clear(termbox.ColorGreen, termbox.ColorBlack) 63 | window.Draw() 64 | fancyWindow.Draw() 65 | focusableWindow.Draw() 66 | canvas.Draw() 67 | termbox.Flush() 68 | } 69 | 70 | func handleMouse(ev *termbox.Event) { 71 | if focusableWindow.WithinBox(ev.MouseX, ev.MouseY) == false { 72 | canvas.X = ev.MouseX 73 | canvas.Y = ev.MouseY 74 | } 75 | 76 | } 77 | 78 | func prepareWindows() { 79 | border := windeau.MakeSimpleBorder('+', '|', '-') 80 | window = &windeau.Window{X: 0, Y: 0, Width: 30, Height: 25, Fg: termbox.ColorGreen, Bg: termbox.ColorDefault, Border: border} 81 | window.Title = "Hello World" 82 | window.Draw() 83 | 84 | fancyBorder := FancyBorder{TopLeft: '┏', TopRight: '┓', BottomLeft: '┗', BottomRight: '┛', Vertical: '┃', Horizontal: '━'} 85 | fancyWindow = &windeau.Window{X: 40, Y: 20, Width: 40, Height: 30, Fg: termbox.ColorBlue, Bg: termbox.ColorDefault, Border: fancyBorder} 86 | fancyWindow.Title = "Fancy Window" 87 | 88 | underlyingWindow := &windeau.Window{X: 40, Y: 0, Width: 15, Height: 15, Border: border} 89 | focusColor := windeau.WindowState{FgColor: termbox.ColorGreen, BgColor: termbox.ColorDefault} 90 | unfocusColor := windeau.WindowState{FgColor: termbox.ColorWhite, BgColor: termbox.ColorBlack} 91 | focusableWindow = &windeau.FocusableWindow{FocusOn: focusColor, FocusOff: unfocusColor, Focused: false} 92 | focusableWindow.SetParent(underlyingWindow) 93 | 94 | canvas = windeau.MakeCanvas(70, 5, 20, 20) 95 | canvas.Fill('x', termbox.ColorYellow, termbox.ColorYellow) 96 | canvas.FilledRect('y', termbox.ColorBlack, termbox.ColorYellow, windeau.Rect{75, 7, 3, 3}) 97 | canvas.FilledRect('z', termbox.ColorBlue, termbox.ColorDefault, windeau.Rect{72, 12, 2, 3}) 98 | canvas.DrawString(0, 8, "Hello", termbox.ColorBlack, termbox.ColorYellow) 99 | canvas.Parent = focusableWindow 100 | } 101 | -------------------------------------------------------------------------------- /window.go: -------------------------------------------------------------------------------- 1 | package windeau 2 | 3 | import ( 4 | "github.com/nsf/termbox-go" 5 | ) 6 | 7 | type SimpleBorder struct { 8 | edge, verticalBorder, horizontalBorder rune 9 | } 10 | 11 | func (sb SimpleBorder) Edge(x, y int, window Window) rune { 12 | return sb.edge 13 | } 14 | 15 | func (sb SimpleBorder) VerticalBorder() rune { 16 | return sb.verticalBorder 17 | } 18 | 19 | func (sb SimpleBorder) HorizontalBorder() rune { 20 | return sb.horizontalBorder 21 | } 22 | 23 | func MakeSimpleBorder(edge, vertical, horizontal rune) SimpleBorder { 24 | return SimpleBorder{edge: edge, verticalBorder: vertical, horizontalBorder: horizontal} 25 | } 26 | 27 | type PositionHandler func(x, y int) 28 | 29 | func WalkRegion(x, y, w, h int, handler PositionHandler) { 30 | for px := x; px < x+w; px++ { 31 | for py := y; py < y+h; py++ { 32 | handler(px, py) 33 | } 34 | } 35 | } 36 | 37 | type Window struct { 38 | X, Y int 39 | Width, Height int 40 | Border WindowBorder 41 | Title string 42 | Fg, Bg termbox.Attribute 43 | View *Drawable 44 | } 45 | 46 | func MakeBasicWindow(x, y, w, h int) *Window { 47 | border := MakeSimpleBorder('+', '|', '-') 48 | return &Window{X: x, Y: y, Width: w, Height: h, Border: border} 49 | } 50 | 51 | func (w *Window) Draw() { 52 | w.drawBorder() 53 | w.drawTitle() 54 | if w.View != nil { 55 | (*w.View).Draw() 56 | } 57 | } 58 | 59 | func (w *Window) SetParent(parent Drawable) {} 60 | 61 | func (w *Window) GetColors() (termbox.Attribute, termbox.Attribute) { 62 | return w.Fg, w.Bg 63 | } 64 | 65 | func (w *Window) IsRoot() bool { 66 | return true 67 | } 68 | 69 | func (w *Window) IsFocused() bool { 70 | return true 71 | } 72 | 73 | func (w *Window) SetFocused(f bool) {} 74 | 75 | func (w *Window) GetRect() Rect { 76 | return Rect{X: w.X, Y: w.Y, Width: w.Width, Height: w.Height} 77 | } 78 | 79 | func (w *Window) WithinBox(x, y int) bool { 80 | return w.GetRect().WithinRect(x, y) 81 | } 82 | 83 | func (w *Window) drawBorder() { 84 | WalkRegion(w.X, w.Y, w.Width, w.Height, func(x, y int) { 85 | var element rune 86 | if w.isEdge(x, y) { 87 | element = w.Border.Edge(x, y, *w) 88 | } else if w.isTopOrBottom(x, y) { 89 | element = w.Border.HorizontalBorder() 90 | } else if w.isLeftOrRight(x, y) { 91 | element = w.Border.VerticalBorder() 92 | } else { 93 | element = ' ' 94 | } 95 | termbox.SetCell(x, y, element, w.Fg, w.Bg) 96 | }) 97 | } 98 | 99 | func (w *Window) drawTitle() { 100 | title := w.Title 101 | if len(title) > 0 { 102 | if len(title) > w.titleWidth() { 103 | title = title[0:w.titleWidth()] 104 | } 105 | for c := 0; c < len(title); c++ { 106 | termbox.SetCell(c+w.X+w.titleStart(), w.Y, rune(title[c]), w.Fg, w.Bg) 107 | } 108 | } 109 | } 110 | 111 | func (w *Window) titleWidth() int { 112 | return w.Width - w.titleStart() - w.titlePadding() 113 | } 114 | 115 | func (w *Window) titleStart() int { 116 | return w.titlePadding() 117 | } 118 | 119 | func (w *Window) titlePadding() int { 120 | return 2 121 | } 122 | 123 | func (w *Window) isEdge(x, y int) bool { 124 | return w.isTopOrBottom(x, y) && w.isLeftOrRight(x, y) 125 | } 126 | 127 | func (w *Window) isTopOrBottom(x, y int) bool { 128 | return y == w.Y || y == w.Y+w.Height-1 129 | } 130 | 131 | func (w *Window) isLeftOrRight(x, y int) bool { 132 | return x == w.X || x == w.X+w.Width-1 133 | } 134 | -------------------------------------------------------------------------------- /scrollview.go: -------------------------------------------------------------------------------- 1 | package windeau 2 | 3 | import ( 4 | "github.com/nsf/termbox-go" 5 | ) 6 | 7 | const ( 8 | SVMoreUp string = "/\\more/\\" 9 | SVMoreDown string = "\\/more\\/" 10 | ) 11 | 12 | type Scrollview struct { 13 | Parent Drawable 14 | Entries []string 15 | Position int 16 | Handler EventHandler 17 | visibleRowCount int 18 | } 19 | 20 | func MakeScrollview(parent *FocusableWindow, entries []string, handler EventHandler) *Scrollview { 21 | scrollview := &Scrollview{Entries: entries, Position: -1, Handler: handler} 22 | scrollview.SetParent(parent) 23 | return scrollview 24 | } 25 | 26 | func (s *Scrollview) SetParent(parent Drawable) { 27 | s.Parent = parent 28 | s.determineVisibleRowCount() 29 | } 30 | 31 | func (s *Scrollview) IsRoot() bool { 32 | return false 33 | } 34 | 35 | func (s *Scrollview) IsFocused() bool { 36 | return s.Parent.IsFocused() 37 | } 38 | 39 | func (s *Scrollview) GetRect() Rect { 40 | return s.Parent.GetRect().ShrinkBy(1) 41 | } 42 | 43 | func (s *Scrollview) WithinBox(x, y int) bool { 44 | return s.Parent.WithinBox(x, y) 45 | } 46 | 47 | func (s *Scrollview) GetEntries() []string { 48 | return s.Entries 49 | } 50 | 51 | func (s *Scrollview) GetPosition() int { 52 | return s.Position 53 | } 54 | 55 | func (s *Scrollview) SetPosition(position int) int { 56 | if position >= 0 && position < len(s.GetEntries()) { 57 | s.Position = position 58 | } 59 | return s.Position 60 | } 61 | 62 | func (s *Scrollview) SetHandler(handler EventHandler) { 63 | s.Handler = handler 64 | } 65 | 66 | func (s *Scrollview) Draw() { 67 | if s.Parent == nil { 68 | panic("Parent cannot be nil for a Scrollview") 69 | } 70 | s.Parent.Draw() 71 | rect := s.GetRect() 72 | for i := 0; i < s.visibleRowCount; i++ { 73 | entry := i + s.startingPosition() 74 | if i >= len(s.GetEntries()) { 75 | break 76 | } 77 | fg, bg := termbox.ColorWhite, termbox.ColorDefault 78 | if s.Position == entry && s.IsFocused() { 79 | bg = termbox.ColorBlue 80 | } 81 | 82 | x := rect.X 83 | y := rect.Y + i 84 | message := s.GetEntries()[entry] 85 | DrawStringWithinSize(message, x, y, rect.Width, fg, bg) 86 | } 87 | s.hintMoreDataExists() 88 | } 89 | 90 | func (s *Scrollview) startingPosition() int { 91 | halfway := s.visibleRowCount / 2 92 | numberOfEntries := len(s.GetEntries()) 93 | switch { 94 | case numberOfEntries < s.visibleRowCount: 95 | return 0 96 | case s.Position < s.visibleRowCount: 97 | return 0 98 | case s.Position >= numberOfEntries-s.visibleRowCount: 99 | return numberOfEntries - s.visibleRowCount 100 | default: 101 | return s.Position - halfway 102 | } 103 | } 104 | 105 | func (s *Scrollview) determineVisibleRowCount() { 106 | bindingBox := s.GetRect() 107 | s.visibleRowCount = bindingBox.Height - 1 108 | } 109 | 110 | func (s *Scrollview) hintMoreDataExists() { 111 | if len(s.GetEntries()) < s.visibleRowCount { 112 | return 113 | } 114 | parentRect := s.Parent.GetRect() 115 | fg, bg := s.Parent.GetColors() 116 | leftEdge := parentRect.X + parentRect.Width - 2 117 | if s.Position >= s.visibleRowCount { 118 | DrawStringWithinSize(SVMoreUp, leftEdge-len(SVMoreUp), parentRect.Y, parentRect.Width-2, fg, bg) 119 | } 120 | if s.Position < len(s.GetEntries())-s.visibleRowCount { 121 | DrawStringWithinSize(SVMoreDown, leftEdge-len(SVMoreDown), parentRect.Y+parentRect.Height-1, parentRect.Width-2, fg, bg) 122 | } 123 | } 124 | --------------------------------------------------------------------------------