Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First commit. Some basic functionality. Publishing to GitHub now.
- Loading branch information
0 parents
commit f9f139c
Showing
10 changed files
with
962 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Widgets for Terminal GUIs | ||
|
||
Based on [github.com/gdamore/tcell](https://github.com/gdamore/tcell). | ||
|
||
Work in progress. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
Oops, something went wrong.