Skip to content

Commit

Permalink
First commit. Some basic functionality. Publishing to GitHub now.
Browse files Browse the repository at this point in the history
  • Loading branch information
rivo committed Dec 15, 2017
0 parents commit f9f139c
Show file tree
Hide file tree
Showing 10 changed files with 962 additions and 0 deletions.
157 changes: 157 additions & 0 deletions Application.go
@@ -0,0 +1,157 @@
package tview

import (
"sync"

"github.com/gdamore/tcell"
)

// Application represents the top node of an application.
type Application struct {
sync.Mutex

// The application's screen.
screen tcell.Screen

// The primitive which currently has the keyboard focus.
focus Primitive

// The root primitive to be seen on the screen.
root Primitive

// Whether or not the application resizes the root primitive.
rootAutoSize bool
}

// NewApplication creates and returns a new application.
func NewApplication() *Application {
return &Application{}
}

// Run starts the application and thus the event loop. This function returns
// when Stop() was called.
func (a *Application) Run() error {
var err error

// Make a screen.
a.screen, err = tcell.NewScreen()
if err != nil {
return err
}
if err = a.screen.Init(); err != nil {
return err
}

// We catch panics to clean up because they mess up the terminal.
defer func() {
if p := recover(); p != nil {
a.screen.Fini()
panic(p)
}
}()

// Draw the screen for the first time.
if a.rootAutoSize && a.root != nil {
width, height := a.screen.Size()
a.root.SetRect(0, 0, width, height)
}
a.Draw()

// Start event loop.
for {
event := a.screen.PollEvent()
if event == nil {
break // The screen was finalized.
}
switch event := event.(type) {
case *tcell.EventKey:
if event.Key() == tcell.KeyCtrlC {
a.Stop()
}
a.Lock()
p := a.focus
a.Unlock()
if p != nil {
if handler := p.InputHandler(); handler != nil {
handler(event)
a.Draw()
}
}
case *tcell.EventResize:
if a.rootAutoSize && a.root != nil {
width, height := a.screen.Size()
a.Lock()
a.root.SetRect(0, 0, width, height)
a.Unlock()
a.Draw()
}
}
}

return nil
}

// Stop stops the application, causing Run() to return.
func (a *Application) Stop() {
a.screen.Fini()
}

// Draw refreshes the screen. It calls the Draw() function of the application's
// root primitive and then syncs the screen buffer.
func (a *Application) Draw() *Application {
a.Lock()
defer a.Unlock()

// Maybe we're not ready yet.
if a.screen == nil {
return a
}

// Draw all primitives.
if a.root != nil {
a.root.Draw(a.screen)
}

// Sync screen.
a.screen.Show()

return a
}

// SetRoot sets the root primitive for this application. This function must be
// called or nothing will be displayed when the application starts.
//
// If autoSize is set to true, the application will set the root primitive's
// position to (0,0) and its size to the screen's size. It will also resize and
// redraw it when the screen resizes.
func (a *Application) SetRoot(root Primitive, autoSize bool) *Application {
a.Lock()
defer a.Unlock()

a.root = root
a.rootAutoSize = autoSize

return a
}

// SetFocus sets the focus on a new primitive. All key events will be redirected
// to that primitive. Callers must ensure that the primitive will handle key
// events.
//
// Blur() will be called on the previously focused primitive. Focus() will be
// called on the new primitive.
func (a *Application) SetFocus(p Primitive) *Application {
if p.InputHandler() == nil {
return a
}

a.Lock()
if a.focus != nil {
a.focus.Blur()
}
a.focus = p
a.Unlock()
p.Focus(a)

return a
}
21 changes: 21 additions & 0 deletions LICENSE.txt
@@ -0,0 +1,21 @@
MIT License

Copyright (c) [year] [fullname]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
5 changes: 5 additions & 0 deletions README.md
@@ -0,0 +1,5 @@
# Widgets for Terminal GUIs

Based on [github.com/gdamore/tcell](https://github.com/gdamore/tcell).

Work in progress.
174 changes: 174 additions & 0 deletions box.go
@@ -0,0 +1,174 @@
package tview

import "github.com/gdamore/tcell"

// Characters to draw the box border.
const (
BoxVertBar = '\u2500'
BoxHorBar = '\u2502'
BoxTopLeftCorner = '\u250c'
BoxTopRightCorner = '\u2510'
BoxBottomRightCorner = '\u2518'
BoxBottomLeftCorner = '\u2514'
BoxEllipsis = '\u2026'
)

// Box implements Rect with a background and optional elements such as a border
// and a title.
type Box struct {
// The position of the rect.
x, y, width, height int

// Whether or not the box has focus.
hasFocus bool

// The box's background color.
backgroundColor tcell.Color

// Whether or not a border is drawn, reducing the box's space for content by
// two in width and height.
border bool

// The color of the border.
borderColor tcell.Color

// The color of the border when the box has focus.
focusedBorderColor tcell.Color

// The title. Only visible if there is a border, too.
title string

// The color of the title.
titleColor tcell.Color
}

// NewBox returns a Box without a border.
func NewBox() *Box {
return &Box{
width: 15,
height: 10,
borderColor: tcell.ColorWhite,
focusedBorderColor: tcell.ColorYellow,
titleColor: tcell.ColorWhite,
}
}

// Draw draws this primitive onto the screen.
func (b *Box) Draw(screen tcell.Screen) {
// Don't draw anything if there is no space.
if b.width <= 0 || b.height <= 0 {
return
}

def := tcell.StyleDefault

// Fill background.
background := def.Background(b.backgroundColor)
for y := b.y; y < b.y+b.height; y++ {
for x := b.x; x < b.x+b.width; x++ {
screen.SetContent(x, y, ' ', nil, background)
}
}

// Draw border.
if b.border && b.width >= 2 && b.height >= 2 {
border := background.Foreground(b.borderColor)
if b.hasFocus {
border = background.Foreground(b.focusedBorderColor)
}
for x := b.x + 1; x < b.x+b.width-1; x++ {
screen.SetContent(x, b.y, BoxVertBar, nil, border)
screen.SetContent(x, b.y+b.height-1, BoxVertBar, nil, border)
}
for y := b.y + 1; y < b.y+b.height-1; y++ {
screen.SetContent(b.x, y, BoxHorBar, nil, border)
screen.SetContent(b.x+b.width-1, y, BoxHorBar, nil, border)
}
screen.SetContent(b.x, b.y, BoxTopLeftCorner, nil, border)
screen.SetContent(b.x+b.width-1, b.y, BoxTopRightCorner, nil, border)
screen.SetContent(b.x, b.y+b.height-1, BoxBottomLeftCorner, nil, border)
screen.SetContent(b.x+b.width-1, b.y+b.height-1, BoxBottomRightCorner, nil, border)

// Draw title.
if b.title != "" && b.width >= 4 {
title := background.Foreground(b.titleColor)
x := b.x
for index, ch := range b.title {
x++
if x >= b.x+b.width-1 {
break
}
if x == b.x+b.width-2 && index < len(b.title)-1 {
ch = BoxEllipsis
}
screen.SetContent(x, b.y, ch, nil, title)
}
}
}
}

// GetRect returns the current position of the rectangle, x, y, width, and
// height.
func (b *Box) GetRect() (int, int, int, int) {
return b.x, b.y, b.width, b.height
}

// SetRect sets a new position of the rectangle.
func (b *Box) SetRect(x, y, width, height int) {
b.x = x
b.y = y
b.width = width
b.height = height
}

// InputHandler returns nil.
func (b *Box) InputHandler() func(event *tcell.EventKey) {
return nil
}

// SetBackgroundColor sets the box's background color.
func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
b.backgroundColor = color
return b
}

// SetBorder sets the flag indicating whether or not the box should have a
// border.
func (b *Box) SetBorder(show bool) *Box {
b.border = show
return b
}

// SetBorderColor sets the box's border color.
func (b *Box) SetBorderColor(color tcell.Color) *Box {
b.borderColor = color
return b
}

// SetFocusedBorderColor sets the box's border color for when the box has focus.
func (b *Box) SetFocusedBorderColor(color tcell.Color) *Box {
b.focusedBorderColor = color
return b
}

// SetTitle sets the box's title.
func (b *Box) SetTitle(title string) *Box {
b.title = title
return b
}

// SetTitleColor sets the box's title color.
func (b *Box) SetTitleColor(color tcell.Color) *Box {
b.titleColor = color
return b
}

// Focus is called when this primitive receives focus.
func (b *Box) Focus(app *Application) {
b.hasFocus = true
}

// Blur is called when this primitive loses focus.
func (b *Box) Blur() {
b.hasFocus = false
}
34 changes: 34 additions & 0 deletions demos/basic.go
@@ -0,0 +1,34 @@
package main

import "github.com/rivo/tview"

func main() {
form := tview.NewForm().AddItem("First name", "", 20, nil).AddItem("Last name", "", 20, nil).AddItem("Age", "", 4, nil)
form.SetBorder(true)

box := tview.NewFlex(tview.FlexColumn, []tview.Primitive{
form,
tview.NewFlex(tview.FlexRow, []tview.Primitive{
tview.NewBox().SetBorder(true).SetTitle("Second"),
tview.NewBox().SetBorder(true).SetTitle("Third"),
}),
tview.NewBox().SetBorder(true).SetTitle("Fourth"),
})
box.AddItem(tview.NewBox().SetBorder(true).SetTitle("Fifth"), 20)

inputField := tview.NewInputField().
SetLabel("Type something: ").
SetFieldLength(10).
SetAcceptanceFunc(tview.InputFieldFloat)
inputField.SetBorder(true).SetTitle("Type!")

final := tview.NewFlex(tview.FlexRow, []tview.Primitive{box})
final.AddItem(inputField, 3)

app := tview.NewApplication()
app.SetRoot(final, true).SetFocus(form)

if err := app.Run(); err != nil {
panic(err)
}
}

0 comments on commit f9f139c

Please sign in to comment.