pax_global_header 0000666 0000000 0000000 00000000064 14262344540 0014516 g ustar 00root root 0000000 0000000 52 comment=fe6367ab360345bffb6ba27a681227a3a9366a3b
gowid-1.4.0/ 0000775 0000000 0000000 00000000000 14262344540 0012631 5 ustar 00root root 0000000 0000000 gowid-1.4.0/.github/ 0000775 0000000 0000000 00000000000 14262344540 0014171 5 ustar 00root root 0000000 0000000 gowid-1.4.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14262344540 0016226 5 ustar 00root root 0000000 0000000 gowid-1.4.0/.github/workflows/go.yml 0000664 0000000 0000000 00000001142 14262344540 0017354 0 ustar 00root root 0000000 0000000 name: Go
on: [push]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Get dependencies
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
gowid-1.4.0/.gitignore 0000664 0000000 0000000 00000000034 14262344540 0014616 0 ustar 00root root 0000000 0000000 *.log
/go.work
/go.work.sum
gowid-1.4.0/.travis.yml 0000664 0000000 0000000 00000000262 14262344540 0014742 0 ustar 00root root 0000000 0000000 language: go
env:
- GO111MODULE=on
arch:
- amd64
- ppc64le
go:
- 1.13.x
- 1.14.x
- 1.15.x
notifications:
email: true
script:
- go test -v -race ./...
gowid-1.4.0/LICENSE 0000664 0000000 0000000 00000002055 14262344540 0013640 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2019 Graham Clark
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.
gowid-1.4.0/README.md 0000664 0000000 0000000 00000015471 14262344540 0014120 0 ustar 00root root 0000000 0000000 # Terminal User Interface Widgets in Go
Gowid provides widgets and a framework for making terminal user interfaces. It's written in Go and inspired by [urwid](http://urwid.org).
Widgets out-of-the-box include:
- input components like button, checkbox and an editable text field with support for passwords
- layout components for arranging widgets in columns, rows and a grid
- structured components - a tree, an infinite list and a table
- pre-canned widgets - a progress bar, a modal dialog, a bar graph and a menu
- a VT220-compatible terminal widget, heavily cribbed from urwid :smiley:
All widgets support interaction with the mouse when the terminal allows.
Gowid is built on top of the fantastic [tcell](https://github.com/gdamore/tcell) package.
There are many alternatives to gowid - see [Similar Projects](#similar-projects)
The most developed gowid application is currently [termshark](https://termshark.io), a terminal UI for tshark.
## Installation
```bash
go get github.com/gcla/gowid/...
```
## Examples
Make sure `$GOPATH/bin` is in your PATH (or `~/go/bin` if `GOPATH` isn't set), then tab complete "gowid-" e.g.
```bash
gowid-fib
```
Here is a port of urwid's [palette](https://github.com/urwid/urwid/blob/master/examples/palette_test.py) example:
Here is urwid's [graph](https://github.com/urwid/urwid/blob/master/examples/graph.py) example:
And urwid's [fibonacci](https://github.com/urwid/urwid/blob/master/examples/fib.py) example:
A demonstration of gowid's terminal widget, a port of urwid's [terminal widget](https://github.com/urwid/urwid/blob/master/examples/terminal.py):
Finally, here is an animation of termshark in action:
## Hello World
This example is an attempt to mimic urwid's ["Hello World"](http://urwid.org/tutorial/index.html) example.
```go
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
)
//======================================================================
func main() {
palette := gowid.Palette{
"banner": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.MakeRGBColor("#60d")),
"streak": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#60a")),
"inside": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#808")),
"outside": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#a06")),
"bg": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#d06")),
}
div := divider.NewBlank()
outside := styled.New(div, gowid.MakePaletteRef("outside"))
inside := styled.New(div, gowid.MakePaletteRef("inside"))
helloworld := styled.New(
text.NewFromContentExt(
text.NewContent([]text.ContentSegment{
text.StyledContent("Hello World", gowid.MakePaletteRef("banner")),
}),
text.Options{
Align: gowid.HAlignMiddle{},
},
),
gowid.MakePaletteRef("streak"),
)
f := gowid.RenderFlow{}
view := styled.New(
vpadding.New(
pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{IWidget: outside, D: f},
&gowid.ContainerWidget{IWidget: inside, D: f},
&gowid.ContainerWidget{IWidget: helloworld, D: f},
&gowid.ContainerWidget{IWidget: inside, D: f},
&gowid.ContainerWidget{IWidget: outside, D: f},
}),
gowid.VAlignMiddle{},
f),
gowid.MakePaletteRef("bg"),
)
app, _ := gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
})
app.SimpleMainLoop()
}
```
Running the example above displays this:
## Documentation
- The beginnings of a [tutorial](docs/Tutorial.md)
- A list of most of the [widgets](docs/Widgets.md)
- Some [FAQs](docs/FAQ.md) (which I guessed at...)
- Some gowid [programming tricks](docs/Debugging.md)
## Similar Projects
Gowid is late to the TUI party. There are many options from which to choose - please read https://appliedgo.net/tui/ for a nice summary for the Go language. Here is a selection:
- [urwid](http://urwid.org/) - one of the oldest, for those working in python
- [tview](https://github.com/rivo/tview) - active, polished, concise, lots of widgets, Go
- [termui](https://github.com/gizak/termui) - focus on graphing and dataviz, Go
- [gocui](https://github.com/jroimartin/gocui) - focus on layout, good input options, mouse support, Go
- [clui](https://github.com/VladimirMarkelov/clui) - active, many widgets, mouse support, Go
- [tui-go](https://github.com/marcusolsson/tui-go) - QT-inspired, experimental, nice examples, Go
## Dependencies
Gowid depends on these great open-source packages:
- [urwid](http://urwid.org) - not a Go-dependency, but the model for most of gowid's design
- [tcell](https://github.com/gdamore/tcell) - a cell based view for text terminals, like xterm, inspired by termbox
- [asciigraph](https://github.com/guptarohit/asciigraph) - lightweight ASCII line-graphs for Go
- [logrus](https://github.com/sirupsen/logrus) - structured pluggable logging for Go
- [testify](https://github.com/stretchr/testify) - tools for testifying that your code will behave as you intend
## Contact
- The author - Graham Clark (grclark@gmail.com)
## License
[](https://opensource.org/licenses/MIT)
gowid-1.4.0/app.go 0000664 0000000 0000000 00000064567 14262344540 0013762 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
package gowid
import (
"fmt"
"os"
"path/filepath"
"runtime/debug"
"strings"
"sync"
"time"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
//======================================================================
// IGetScreen provides access to a tcell.Screen object e.g. for rendering
// a canvas to the terminal.
type IGetScreen interface {
GetScreen() tcell.Screen
}
// IColorMode provides access to a ColorMode value which represents the current
// mode of the terminal e.g. 24-bit color, 256-color, monochrome.
type IColorMode interface {
GetColorMode() ColorMode
}
// IPalette provides application "palette" information - it can look up a
// Cell styling interface by name (e.g. "main text" -> (black, white, underline))
// and it can let clients apply a function to each member of the palette (e.g.
// in order to construct a new modified palette).
type IPalette interface {
CellStyler(name string) (ICellStyler, bool)
RangeOverPalette(f func(key string, value ICellStyler) bool)
}
// IRenderContext proviees palette and color mode information.
type IRenderContext interface {
IPalette
IColorMode
}
// IApp is the interface of the application passed to every widget during Render or UserInput.
// It provides several features:
// - a function to terminate the application
// - access to the state of the mouse
// - access to the underlying tcell screen
// - access to an application-specific logger
// - functions to get and set the root widget of the widget hierarchy
// - a method to keep track of which widgets were last "clicked"
//
type IApp interface {
IRenderContext
IGetScreen
ISettableComposite
Quit() // Terminate the running gowid app + main loop soon
Redraw() // Issue a redraw of the terminal soon
Sync() // From tcell's screen - refresh every screen cell e.g. if screen becomes corrupted
SetColorMode(mode ColorMode) // Change the terminal's color mode - 256, 16, mono, etc
Run(f IAfterRenderEvent) error // Send a function to run on the widget rendering goroutine
SetClickTarget(k tcell.ButtonMask, w IIdentityWidget) bool // When a mouse is clicked on a widget, track that widget. So...
ClickTarget(func(tcell.ButtonMask, IIdentityWidget)) // when the button is released, we can activate the widget if we are still "over" it
GetMouseState() MouseState // Which buttons are currently clicked
GetLastMouseState() MouseState // Which buttons were clicked before current event
RegisterMenu(menu IMenuCompatible) // Required for an app to display an overlaying menu
UnregisterMenu(menu IMenuCompatible) bool // Returns false if the menu is not found in the hierarchy
InCopyMode(...bool) bool // A getter/setter - to set the app into copy mode. Widgets might render differently as a result
CopyModeClaimedAt(...int) int // the level that claims copy, 0 means deepest should claim
CopyModeClaimedBy(...IIdentity) IIdentity // the level that claims copy, 0 means deepest should claim
RefreshCopyMode() // Give widgets another chance to display copy options (after the user perhaps adjusted the scope of a copy selection)
Clips() []ICopyResult // If in copy-mode, the app will descend the widget hierarchy with a special user input, gathering options for copying data
CopyLevel(...int) int // level we're at as we descend
}
// App is an implementation of IApp. The App struct conforms to IApp and
// provides services to a running gowid application, such as access to the
// palette, the screen and the state of the mouse.
type App struct {
IPalette // App holds an IPalette and provides it to each widget when rendering
screen tcell.Screen // Each app has one screen
TCellEvents chan tcell.Event // Events from tcell e.g. resize
AfterRenderEvents chan IAfterRenderEvent // Functions intended to run on the widget goroutine
closing bool // If true then app is in process of closing - it may be draining AfterRenderEvents.
closingMtx sync.Mutex // Make sure an AfterRenderEvent and closing don't race.
viewPlusMenus IWidget // The base widget that is displayed - includes registered menus
view IWidget // The base widget that is displayed under registered menus
colorMode ColorMode // The current color mode of the terminal - 256, 16, mono, etc
inCopyMode bool // True if the app has been switched into "copy mode", for the user to copy a widget value
copyClaimed int // True if a widget has "claimed" copy mode during this Render pass
copyClaimedBy IIdentity
copyLevel int
refreshCopy bool
prevWasMouseMove bool // True if we last processed simple mouse movement. We can optimize on slow
enableMouseMotion bool
enableBracketedPaste bool
screenInited bool
dontOwnScreen bool
tty string
lastMouse MouseState // So I can tell if a button was previously clicked
MouseState // Track which mouse buttons are currently down
ClickTargets // When mouse is clicked, track potential interaction here
log log.StdLogger // For any application logging
}
var _ IApp = (*App)(nil)
// AppArgs is a helper struct, providing arguments for the initialization of App.
type AppArgs struct {
Screen tcell.Screen
View IWidget
Palette IPalette
EnableMouseMotion bool
EnableBracketedPaste bool
Log log.StdLogger
DontActivate bool
Tty string
}
// IUnhandledInput is used as a handler for application user input that is not handled by any
// widget in the widget hierarchy.
type IUnhandledInput interface {
UnhandledInput(app IApp, ev interface{}) bool
}
// UnhandledInputFunc satisfies IUnhandledInput, allowing use of a simple function for
// handling input not claimed by any widget.
type UnhandledInputFunc func(app IApp, ev interface{}) bool
func (f UnhandledInputFunc) UnhandledInput(app IApp, ev interface{}) bool {
return f(app, ev)
}
// IgnoreUnhandledInput is a helper function for main loops that don't need to deal
// with hanlding input that the widgets haven't claimed.
var IgnoreUnhandledInput UnhandledInputFunc = func(app IApp, ev interface{}) bool {
return false
}
//======================================================================
// ClickTargets is used by the App to keep track of which widgets have been
// clicked. This allows the application to determine if a widget has been
// "selected" which may be best determined across two calls to UserInput - click
// and release.
type ClickTargets struct {
click map[tcell.ButtonMask][]IIdentityWidget // When mouse is clicked, track potential interaction here
}
func MakeClickTargets() ClickTargets {
return ClickTargets{
click: make(map[tcell.ButtonMask][]IIdentityWidget),
}
}
// SetClickTarget expects a Widget that provides an ID() function. Most
// widgets that can be clicked on can just use the default (&w). But if a
// widget might be recreated between the click down and release, and the
// widget under focus at the time of the release provides the same ID()
// (even if not the same object), then it can be given the click.
//
func (t ClickTargets) SetClickTarget(k tcell.ButtonMask, w IIdentityWidget) bool {
targets, ok := t.click[k]
if !ok {
targets = make([]IIdentityWidget, 1)
targets[0] = w
} else {
targets = append(targets, w)
}
t.click[k] = targets
return !ok
}
func (t ClickTargets) ClickTarget(f func(tcell.ButtonMask, IIdentityWidget)) {
for k, v := range t.click {
for _, t := range v {
f(k, t)
}
}
}
func (t ClickTargets) DeleteClickTargets(k tcell.ButtonMask) {
if ws, ok := t.click[k]; ok {
for _, w := range ws {
if w2, ok := w.(IClickTracker); ok {
w2.SetClickPending(false)
}
}
delete(t.click, k)
}
}
//======================================================================
type MouseState struct {
MouseLeftClicked bool
MouseMiddleClicked bool
MouseRightClicked bool
}
func (m MouseState) String() string {
return fmt.Sprintf("LeftClicked: %v, MiddleClicked: %v, RightClicked: %v",
m.MouseLeftClicked,
m.MouseMiddleClicked,
m.MouseRightClicked,
)
}
func (m MouseState) NoButtonClicked() bool {
return !m.LeftIsClicked() && !m.MiddleIsClicked() && !m.RightIsClicked()
}
func (m MouseState) LeftIsClicked() bool {
return m.MouseLeftClicked
}
func (m MouseState) MiddleIsClicked() bool {
return m.MouseMiddleClicked
}
func (m MouseState) RightIsClicked() bool {
return m.MouseRightClicked
}
//======================================================================
func NewApp(args AppArgs) (rapp *App, rerr error) {
app, err := newApp(args)
if err != nil {
return nil, err
}
return app, nil
}
// NewAppSafe returns an initialized App struct, or an error on failure. It will
// initialize a tcell.Screen object and enable mouse support if its not provided,
// meaning that tcell will receive mouse events if the terminal supports them.
func newApp(args AppArgs) (rapp *App, rerr error) {
screen := args.Screen
if screen == nil {
var err error
screen, err = tcellScreen(args.Tty)
if err != nil {
rerr = WithKVs(err, map[string]interface{}{"TERM": os.Getenv("TERM")})
return
}
}
var palette IPalette = args.Palette
if palette == nil {
palette = make(Palette)
}
tch := make(chan tcell.Event, 1000)
wch := make(chan IAfterRenderEvent, 1000)
clicks := MakeClickTargets()
if args.Log == nil {
logname := filepath.Base(os.Args[0])
logname = fmt.Sprintf("%s.log", strings.TrimSuffix(logname, filepath.Ext(logname)))
logfile, err := os.Create(logname)
if err != nil {
return nil, err
}
logger := log.New()
logger.Out = logfile
args.Log = logger
}
res := &App{
IPalette: palette,
screen: screen,
TCellEvents: tch,
AfterRenderEvents: wch,
closing: false,
view: args.View,
viewPlusMenus: args.View,
colorMode: Mode256Colors,
ClickTargets: clicks,
log: args.Log,
enableMouseMotion: args.EnableMouseMotion,
enableBracketedPaste: args.EnableBracketedPaste,
dontOwnScreen: args.Screen != nil,
tty: args.Tty,
}
if !res.dontOwnScreen && !args.DontActivate {
if err := res.initScreen(); err != nil {
return nil, err
}
res.initColorMode()
if res.enableBracketedPaste {
screen.EnablePaste()
}
}
screen.Clear()
rapp = res
return
}
func (a *App) initColorMode() {
cols := a.screen.Colors()
switch {
case cols > 256:
a.SetColorMode(Mode24BitColors)
case cols == 256:
a.SetColorMode(Mode256Colors)
case cols == 88:
a.SetColorMode(Mode88Colors)
case cols == 16:
a.SetColorMode(Mode16Colors)
case cols < 0:
a.SetColorMode(ModeMonochrome)
default:
a.SetColorMode(Mode8Colors)
}
}
func (a *App) GetScreen() tcell.Screen {
return a.screen
}
func (a *App) RefreshCopyMode() {
a.refreshCopy = true
}
func (a *App) CopyLevel(lvl ...int) int {
if len(lvl) > 0 {
a.copyLevel = lvl[0]
}
return a.copyLevel
}
func (a *App) InCopyMode(on ...bool) bool {
if len(on) > 0 {
a.inCopyMode = on[0]
}
return a.inCopyMode
}
func (a *App) CopyModeClaimedAt(lvl ...int) int {
if len(lvl) > 0 {
a.copyClaimed = lvl[0]
}
return a.copyClaimed
}
func (a *App) CopyModeClaimedBy(id ...IIdentity) IIdentity {
if len(id) > 0 {
a.copyClaimedBy = id[0]
}
return a.copyClaimedBy
}
func (a *App) SetSubWidget(widget IWidget, app IApp) {
a.view = widget
if a.viewPlusMenus == nil {
a.viewPlusMenus = widget
}
}
func (a *App) SubWidget() IWidget {
return a.view
}
func (a *App) SetPalette(palette IPalette) {
a.IPalette = palette
}
func (a *App) GetPalette() IPalette {
return a.IPalette
}
func (a *App) GetMouseState() MouseState {
return a.MouseState
}
func (a *App) GetLastMouseState() MouseState {
return a.lastMouse
}
func (a *App) SetColorMode(mode ColorMode) {
a.colorMode = mode
}
func (a *App) GetColorMode() ColorMode {
return a.colorMode
}
// TerminalSize returns the terminal's size.
func (a *App) TerminalSize() (x, y int) {
x, y = a.screen.Size()
if x == 0 && y == 0 {
// vim uses the following rules (https://github.com/vim/vim/blob/master/runtime/doc/term.txt#L629)
// - an ioctl call (TIOCGSIZE or TIOCGWINSZ, depends on your system)
// - the environment variables "LINES" and "COLUMNS"
// - from the termcap entries "li" and "co"
//
// If tcell still reports (0,0) after following these rules, fall
// back to a default like vim does:
//
// https://github.com/vim/vim/blob/master/runtime/doc/term.txt#L642
// "If everything fails a default size of 24 lines and 80 columns is assumed."
//
x = 80
y = 24
}
return
}
type LogField struct {
Name string
Val interface{}
}
type CopyModeEvent struct{}
func (c CopyModeEvent) When() time.Time {
return time.Time{}
}
type ICopyModeClips interface {
Collect([]ICopyResult)
}
type CopyModeClipsFn func([]ICopyResult)
func (f CopyModeClipsFn) Collect(clips []ICopyResult) {
f(clips)
}
type CopyModeClipsEvent struct {
Action ICopyModeClips
}
func (c CopyModeClipsEvent) When() time.Time {
return time.Time{}
}
type privateId struct{}
func (n privateId) ID() interface{} {
return n
}
func (a *App) Clips() []ICopyResult {
res := make([]ICopyResult, 0)
cb := CopyModeClipsFn(func(clips []ICopyResult) {
res = append(res, clips...)
})
unh := UnhandledInputFunc(func(app IApp, ev interface{}) bool {
return true
})
a.handleInputEvent(
CopyModeClipsEvent{
Action: cb,
},
unh,
)
return res
}
// HandleTCellEvent handles an event from the underlying TCell library,
// based on its type (key-press, error, etc.) User input events are sent
// to onInputEvent, which will check the widget hierarchy to see if the
// input can be processed; other events might result in gowid updating its
// internal state, like the size of the underlying terminal.
func (a *App) HandleTCellEvent(ev interface{}, unhandled IUnhandledInput) {
switch ev := ev.(type) {
case *tcell.EventKey, *tcell.EventPaste:
// This makes for a better experience on limited hardware like raspberry pi
debug.SetGCPercent(-1)
defer debug.SetGCPercent(100)
cm := a.InCopyMode()
a.handleInputEvent(ev, unhandled)
newCopyMode := (!cm && a.InCopyMode())
if newCopyMode || a.refreshCopy {
// Now need to work out which widget claims the copy - choose deepest
a.copyLevel = 0 // current level as we traverse - start at highest
if newCopyMode { // newly entered
a.copyClaimed = 100000 // won't ever nest this deep - widget claims beyond this point or at leaf
a.copyClaimedBy = privateId{}
}
a.handleInputEvent(CopyModeEvent{}, unhandled)
a.refreshCopy = false
}
a.RedrawTerminal()
//case *tcell.EventPaste:
//log.Infof("GCLA: app.go tcell paste")
case *tcell.EventMouse:
if !a.prevWasMouseMove || a.enableMouseMotion || ev.Modifiers() != 0 || ev.Buttons() != 0 {
switch ev.Buttons() {
case tcell.Button1:
a.MouseLeftClicked = true
case tcell.Button2:
a.MouseMiddleClicked = true
case tcell.Button3:
a.MouseRightClicked = true
default:
}
debug.SetGCPercent(-1)
defer debug.SetGCPercent(100)
a.handleInputEvent(ev, unhandled)
// Make sure we don't hold on to references longer than we need to
if ev.Buttons() == tcell.ButtonNone {
a.ClickTargets.DeleteClickTargets(tcell.Button1)
a.ClickTargets.DeleteClickTargets(tcell.Button2)
a.ClickTargets.DeleteClickTargets(tcell.Button3)
}
a.lastMouse = a.MouseState
a.MouseState = MouseState{}
a.RedrawTerminal()
}
case *tcell.EventResize:
if flog, ok := a.log.(log.FieldLogger); ok {
flog.WithField("event", ev).Infof("Terminal was resized")
} else {
a.log.Printf("Terminal was resized\n")
}
a.RedrawTerminal()
case *tcell.EventInterrupt:
if flog, ok := a.log.(log.FieldLogger); ok {
flog.WithField("event", ev).Infof("Interrupt event from tcell")
} else {
a.log.Printf("Interrupt event from tcell: %v\n", ev)
}
case *tcell.EventError:
if flog, ok := a.log.(log.FieldLogger); ok {
flog.WithField("event", ev).WithField("error", ev.Error()).Errorf("Error event from tcell")
} else {
a.log.Printf("Error event from tcell: %v, %v\n", ev, ev.Error())
}
default:
if flog, ok := a.log.(log.FieldLogger); ok {
flog.WithField("event", ev).Infof("Unanticipated event from tcell")
} else {
a.log.Printf("Unanticipated event from tcell: %v\n", ev)
}
}
if ev, ok := ev.(*tcell.EventMouse); ok && ev.Buttons() == 0 && ev.Modifiers() == 0 {
a.prevWasMouseMove = true
} else {
a.prevWasMouseMove = false
}
}
// Close should be called by a gowid application after the user terminates the application.
// It will cleanup tcell's screen object.
func (a *App) Close() {
a.screen.Fini()
}
// StartTCellEvents starts a goroutine that listens for events from TCell. The
// PollEvent function will block until TCell has something to report - when
// something arrives, it is written to the tcellEvents channel. The function
// is provided with a quit channel which is consulted for an event that will
// terminate this goroutine.
func (a *App) StartTCellEvents(quit <-chan Unit, wg *sync.WaitGroup) {
wg.Add(1)
go func(quit <-chan Unit) {
defer wg.Done()
Loop:
for {
a.TCellEvents <- a.screen.PollEvent()
select {
case <-quit:
break Loop
default:
}
}
}(quit)
}
// StopTCellEvents will cause TCell to generate an interrupt event; an event is posted
// to the quit channel first to stop the TCell event goroutine.
func (a *App) StopTCellEvents(quit chan<- Unit, wg *sync.WaitGroup) {
quit <- Unit{}
a.screen.PostEventWait(tcell.NewEventInterrupt(nil))
wg.Wait()
}
// SimpleMainLoop will run your application using a default unhandled input function
// that will terminate your application on q/Q, ctrl-c and escape.
func (a *App) SimpleMainLoop() {
a.MainLoop(UnhandledInputFunc(HandleQuitKeys))
}
// HandleQuitKeys is provided as a simple way to terminate your application using typical
// "quit" keys - q/Q, ctrl-c, escape.
func HandleQuitKeys(app IApp, event interface{}) bool {
handled := false
if ev, ok := event.(*tcell.EventKey); ok {
if ev.Key() == tcell.KeyCtrlC || ev.Key() == tcell.KeyEsc || ev.Rune() == 'q' || ev.Rune() == 'Q' {
app.Quit()
handled = true
}
}
return handled
}
type AppRunner struct {
app *App
wg sync.WaitGroup
started bool
quitCh chan Unit
}
func (a *App) Runner() *AppRunner {
res := &AppRunner{
app: a,
quitCh: make(chan Unit, 100),
}
return res
}
func (st *AppRunner) Start() {
st.app.StartTCellEvents(st.quitCh, &st.wg)
st.started = true
}
func (st *AppRunner) Stop() {
if st.started {
st.app.StopTCellEvents(st.quitCh, &st.wg)
st.started = false
}
}
// MainLoop is the intended gowid entry point for typical applications. After the App
// is instantiated and the widget hierarchy set up, the application should call MainLoop
// with a handler for processing input that is not consumed by any widget.
func (a *App) MainLoop(unhandled IUnhandledInput) {
defer a.Close()
st := a.Runner()
st.Start()
defer st.Stop()
a.handleEvents(unhandled)
}
// RunThenRenderEvent dispatches the event by calling it with the
// app as an argument - then it will force the application to re-render
// itself.
func (a *App) RunThenRenderEvent(ev IAfterRenderEvent) {
ev.RunThenRenderEvent(a)
a.RedrawTerminal()
}
// handleEvents processes all gowid events. These can be either app-generated events
// like a function which must be executed on the render goroutine, or events from
// the underlying TCell library like user input or terminal resize.
func (a *App) handleEvents(unhandled IUnhandledInput) {
Loop:
for {
select {
case ev := <-a.TCellEvents:
a.HandleTCellEvent(ev, unhandled)
case ev := <-a.AfterRenderEvents:
if ev == nil {
break Loop
}
a.RunThenRenderEvent(ev)
}
}
}
// handleInputEvent manages key-press events. A keybinding handler is called when
// a key-press or mouse event satisfies a configured keybinding. Furthermore,
// currentView's internal buffer is modified if currentView.Editable is true.
func (a *App) handleInputEvent(ev interface{}, unhandled IUnhandledInput) {
switch ev.(type) {
case *tcell.EventKey, *tcell.EventPaste, *tcell.EventMouse:
x, y := a.TerminalSize()
handled := UserInputIfSelectable(a.viewPlusMenus, ev, RenderBox{C: x, R: y}, Focused, a)
if !handled {
handled = unhandled.UnhandledInput(a, ev)
if !handled {
if flog, ok := a.log.(log.FieldLogger); ok {
flog.WithField("event", ev).Debugf("Input was not handled")
} else {
a.log.Printf("Input was not handled: %v\n", ev)
}
}
}
default:
x, y := a.TerminalSize()
UserInputIfSelectable(a.viewPlusMenus, ev, RenderBox{C: x, R: y}, Focused, a)
}
}
// Sync defers immediately to tcell's Screen's Sync() function - it is for updating
// every screen cell in the event something corrupts the screen (e.g. ssh -v logging)
func (a *App) Sync() {
a.screen.Sync()
}
// RedrawTerminal updates the gui, re-drawing frames and buffers. Call this from
// the widget-handling goroutine only. Intended for use by apps that construct their
// own main loops and handle gowid events themselves.
func (a *App) RedrawTerminal() {
RenderRoot(a.viewPlusMenus, a)
a.screen.Show()
}
// RegisterMenu should be called by any widget that wants to display a
// menu. The call could be made after initializing the App object. This call
// adds the menu above the current root of the widget hierarchy - when the App
// renders from the root down, any open menus will be rendered on top of the
// original root (using the overlay widget).
func (a *App) RegisterMenu(menu IMenuCompatible) {
menu.SetSubWidget(a.viewPlusMenus, a)
a.viewPlusMenus = menu
}
type menuView struct {
*App
}
// SetSubWidget will set the real root of the widget hierarchy rather than
// the one visible to users of the App. i.e. it allows for a menu to be injected
// into the hierarchy.
func (a *menuView) SetSubWidget(widget IWidget, app IApp) {
a.viewPlusMenus = widget
}
func (a *App) unregisterMenu(cur ISettableComposite, removeMe IMenuCompatible) bool {
res := true
for {
if sm, ok := cur.SubWidget().(IMenuCompatible); ok {
if sm == removeMe {
cur.SetSubWidget(sm.SubWidget(), a)
break
} else {
cur = sm
}
} else {
res = false
break
}
}
return res
}
// UnregisterMenu will remove a menu from the widget hierarchy. If it's not found,
// false is returned.
func (a *App) UnregisterMenu(menu IMenuCompatible) bool {
return a.unregisterMenu(&menuView{a}, menu)
}
//======================================================================
type RunFunction func(IApp)
// IAfterRenderEvent is implemented by clients that wish to run a function on the
// gowid rendering goroutine, directly after the widget hierarchy is rendered. This
// allows the client to be sure that there is no race condition with the
// widget rendering code.
type IAfterRenderEvent interface {
RunThenRenderEvent(IApp)
}
// RunThenRenderEvent lets the receiver RunOnRenderFunction implement IOnRenderEvent. This
// lets a regular function be executed on the same goroutine as the rendering code.
func (f RunFunction) RunThenRenderEvent(app IApp) {
f(app)
}
var AppClosingErr = fmt.Errorf("App is closing - no more events accepted.")
// Run executes this function on the goroutine that renders
// widgets and processes their callbacks. Any function that manipulates
// widget state outside of the Render/UserInput chain should be run this
// way for thread-safety e.g. a function that changes the UI from a timer
// event.
func (a *App) Run(f IAfterRenderEvent) error {
a.closingMtx.Lock()
defer a.closingMtx.Unlock()
if !a.closing {
a.AfterRenderEvents <- f
return nil
}
return AppClosingErr
}
// Redraw will re-render the widget hierarchy.
func (a *App) Redraw() {
a.Run(RunFunction(func(IApp) {}))
}
// Quit will terminate the gowid main loop.
func (a *App) Quit() {
a.closingMtx.Lock()
defer a.closingMtx.Unlock()
a.closing = true
close(a.AfterRenderEvents)
}
// Let screen be taken over by gowid/tcell. A new screen struct is created because
// I can't make tcell claim and release the same screen successfully. Clients of
// the app struct shouldn't cache the screen object returned via GetScreen().
//
// Assumes we own the screen...
func (a *App) ActivateScreen() error {
screen, err := tcellScreen(a.tty)
if err != nil {
return WithKVs(err, map[string]interface{}{"TERM": os.Getenv("TERM")})
}
a.DeactivateScreen()
a.screen = screen
if err := a.initScreen(); err != nil {
return err
}
if a.enableBracketedPaste {
a.screen.EnablePaste()
}
return nil
}
// Assumes we own the screen
func (a *App) DeactivateScreen() {
if a.screen != nil && a.screenInited {
a.screen.Fini()
a.screen = nil
a.screenInited = false
}
}
func (a *App) initScreen() error {
if err := a.screen.Init(); err != nil {
return WithKVs(err, map[string]interface{}{"TERM": os.Getenv("TERM")})
}
a.screenInited = true
a.initColorMode()
defFg := ColorDefault
defBg := ColorDefault
defSt := StyleNone
if paletteDefault, ok := a.IPalette.CellStyler("default"); ok {
fgCol, bgCol, style := paletteDefault.GetStyle(a)
defFg = IColorToTCell(fgCol, defFg, a.GetColorMode())
defBg = IColorToTCell(bgCol, defBg, a.GetColorMode())
defSt = defSt.MergeUnder(style)
}
defStyle := tcell.Style{}.Attributes(defSt.OnOff).Background(defBg.ToTCell()).Foreground(defFg.ToTCell())
// Ask TCell to set the screen's default style according to the palette's "default"
// config, if one is provided. This might make every screen cell underlined, for example,
// in the absence of overriding styling from widgets.
a.screen.SetStyle(defStyle)
a.screen.EnableMouse()
if a.enableBracketedPaste {
a.screen.EnablePaste()
}
return nil
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/callbacks.go 0000664 0000000 0000000 00000006571 14262344540 0015110 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
package gowid
import (
"sync"
)
//======================================================================
type ClickCB struct{}
type KeyPressCB struct{}
type SubWidgetCB struct{}
type SubWidgetsCB struct{}
type DimensionsCB struct{}
type FocusCB struct{}
type VAlignCB struct{}
type HAlignCB struct{}
type HeightCB struct{}
type WidthCB struct{}
// ICallback represents any object that can provide a way to be compared to others,
// and that can be called with an arbitrary number of arguments returning no result.
// The comparison is expected to be used by having the callback object provide a name
// to identify the callback operation e.g. "buttonclicked", so that it can later
// be removed.
type ICallback interface {
IIdentity
Call(args ...interface{})
}
type CallbackFunction func(args ...interface{})
type CallbackID struct {
Name interface{}
}
// Callback is a simple implementation of ICallback.
type Callback struct {
Name interface{}
CallbackFunction
}
func (f CallbackFunction) Call(args ...interface{}) {
f(args...)
}
func (f CallbackID) ID() interface{} {
return f.Name
}
func (f Callback) ID() interface{} {
return f.Name
}
type Callbacks struct {
sync.Mutex
callbacks map[interface{}][]ICallback
}
type ICallbacks interface {
RunCallbacks(name interface{}, args ...interface{})
AddCallback(name interface{}, cb ICallback)
RemoveCallback(name interface{}, cb IIdentity) bool
}
func NewCallbacks() *Callbacks {
cb := &Callbacks{}
cb.callbacks = make(map[interface{}][]ICallback)
var _ ICallbacks = cb
return cb
}
// CopyOfCallbacks is used when callbacks are run - they are copied
// so that any callers modifying the callbacks themselves can do so
// safely with the modifications taking effect after all callbacks
// are run. Can be called with a nil receiver if the widget's callback
// object has not been initialized and e.g. RunWidgetCallbacks is called.
func (c *Callbacks) CopyOfCallbacks(name interface{}) ([]ICallback, bool) {
if c != nil {
c.Lock()
defer c.Unlock()
cbs, ok := c.callbacks[name]
if ok {
cbscopy := make([]ICallback, len(cbs))
copy(cbscopy, cbs)
return cbscopy, true
}
}
return []ICallback{}, false
}
func (c *Callbacks) RunCallbacks(name interface{}, args ...interface{}) {
if cbs, ok := c.CopyOfCallbacks(name); ok {
for _, cb := range cbs {
if cb != nil {
cb.Call(args...)
}
}
}
}
func (c *Callbacks) AddCallback(name interface{}, cb ICallback) {
c.Lock()
defer c.Unlock()
cbs := c.callbacks[name]
cbs = append(cbs, cb)
c.callbacks[name] = cbs
}
func (c *Callbacks) RemoveCallback(name interface{}, cb IIdentity) bool {
if c == nil {
return false
}
c.Lock()
defer c.Unlock()
cbs, ok := c.callbacks[name]
if ok {
idxs := make([]int, 0)
ok = false
for i, cb2 := range cbs {
if cb.ID() == cb2.ID() {
// Append backwards for easier deletion later
idxs = append([]int{i}, idxs...)
}
}
if len(idxs) > 0 {
ok = true
for _, j := range idxs {
cbs = append(cbs[:j], cbs[j+1:]...)
}
if len(cbs) == 0 {
delete(c.callbacks, name)
} else {
c.callbacks[name] = cbs
}
}
}
return ok
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/callbacks_test.go 0000664 0000000 0000000 00000002337 14262344540 0016143 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
package gowid
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test1(t *testing.T) {
cb := NewCallbacks()
x := 1
cb.RunCallbacks("test1", 1)
assert.Equal(t, 1, x)
cb.AddCallback("test2", Callback{"addit", CallbackFunction(func(args ...interface{}) {
y := args[0].(int)
x = x + y
})})
cb.RunCallbacks("test1", 1)
assert.Equal(t, 1, x)
cb.RunCallbacks("test2", 1)
assert.Equal(t, 2, x)
cb.RunCallbacks("test2", 2)
assert.Equal(t, 4, x)
cb.AddCallback("test2", Callback{"addit100", CallbackFunction(func(args ...interface{}) {
y := args[0].(int)
x = x + (y * 100)
})})
cb.RunCallbacks("test2", 3)
assert.Equal(t, 307, x)
assert.Equal(t, false, cb.RemoveCallback("test2bad", CallbackID{"addit100"}))
assert.Equal(t, false, cb.RemoveCallback("test2", CallbackID{"addit100bad"}))
assert.Equal(t, true, cb.RemoveCallback("test2", CallbackID{"addit100"}))
cb.RunCallbacks("test2", 8)
assert.Equal(t, 315, x)
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/canvas.go 0000664 0000000 0000000 00000057100 14262344540 0014436 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
package gowid
import (
"fmt"
"io"
"strings"
"unicode/utf8"
"github.com/gcla/gowid/gwutil"
tcell "github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
"github.com/pkg/errors"
)
//======================================================================
// ICanvasLineReader can provide a particular line of Cells, at the specified y
// offset. The result may or may not be a copy of the actual Cells, and is determined
// by whether the user requested a copy and/or the capability of the ICanvasLineReader
// (maybe it has to provide a copy).
type ICanvasLineReader interface {
Line(int, LineCopy) LineResult
}
// ICanvasMarkIterator will call the supplied function argument with the name and
// position of every mark set on the canvas. If the function returns true, the
// loop is terminated early.
type ICanvasMarkIterator interface {
RangeOverMarks(f func(key string, value CanvasPos) bool)
}
// ICanvasCellReader can provide a Cell given a row and a column.
type ICanvasCellReader interface {
CellAt(col, row int) Cell
}
type IAppendCanvas interface {
IRenderBox
ICanvasLineReader
ICanvasMarkIterator
}
type IMergeCanvas interface {
IRenderBox
ICanvasCellReader
ICanvasMarkIterator
}
type IDrawCanvas interface {
IRenderBox
ICanvasLineReader
CursorEnabled() bool
CursorCoords() CanvasPos
}
// ICanvas is the interface of any object which can generate a 2-dimensional
// array of Cells that are intended to be rendered on a terminal. This interface is
// pretty awful - cluttered and inconsistent and subject to cleanup... Note though
// that this interface is not here as the minimum requirement for providing arguments
// to a function or module - instead it's supposed to be an API surface for widgets
// so includes features that I am trying to guess will be needed, or that widgets
// already need.
type ICanvas interface {
Duplicate() ICanvas
MergeUnder(c IMergeCanvas, leftOffset, topOffset int, bottomGetsCursor bool)
AppendBelow(c IAppendCanvas, doCursor bool, makeCopy bool)
AppendRight(c IMergeCanvas, useCursor bool)
SetCellAt(col, row int, c Cell)
SetLineAt(row int, line []Cell)
Truncate(above, below int)
ExtendRight(cells []Cell)
ExtendLeft(cells []Cell)
TrimRight(cols int)
TrimLeft(cols int)
SetCursorCoords(col, row int)
SetMark(name string, col, row int)
GetMark(name string) (CanvasPos, bool)
RemoveMark(name string)
ICanvasMarkIterator
ICanvasCellReader
IDrawCanvas
fmt.Stringer
}
// LineResult is returned by some Canvas Line-accessing APIs. If the Canvas
// can return a line without copying it, the Copied field will be false, and
// the caller is expected to make a copy if necessary (or risk modifying the
// original).
type LineResult struct {
Line []Cell
Copied bool
}
// LineCopy is an argument provided to some Canvas APIs, like Line(). It tells
// the function how to allocate the backing array for a line if the line it
// returns must be a copy. Typically the API will return a type that indicates
// whether the result is a copy or not. Since the caller may receive a copy,
// it can help to indicate the allocation details like length and capacity in
// case the caller intends to extend the line returned for some other use.
type LineCopy struct {
Len int
Cap int
}
//======================================================================
// LineCanvas exists to make an array of Cells conform to some interfaces, specifically
// IRenderBox (it has a width of len(.) and a height of 1), IAppendCanvas, to allow
// an array of Cells to be passed to the canvas function AppendLine(), and ICanvasLineReader
// so that an array of Cells can act as a line returned from a canvas.
type LineCanvas []Cell
// BoxColumns lets LineCanvas conform to IRenderBox
func (c LineCanvas) BoxColumns() int {
return len(c)
}
// BoxRows lets LineCanvas conform to IRenderBox
func (c LineCanvas) BoxRows() int {
return 1
}
// BoxRows lets LineCanvas conform to IWidgetDimension
func (c LineCanvas) ImplementsWidgetDimension() {}
// Line lets LineCanvas conform to ICanvasLineReader
func (c LineCanvas) Line(y int, cp LineCopy) LineResult {
return LineResult{
Line: c,
Copied: false,
}
}
// RangeOverMarks lets LineCanvas conform to ICanvasMarkIterator
func (c LineCanvas) RangeOverMarks(f func(key string, value CanvasPos) bool) {}
var _ IAppendCanvas = (*LineCanvas)(nil)
var _ ICanvasLineReader = (*LineCanvas)(nil)
var _ ICanvasMarkIterator = (*LineCanvas)(nil)
//======================================================================
var emptyLine [4096]Cell
type EmptyLineTooLong struct {
Requested int
}
var _ error = EmptyLineTooLong{}
func (e EmptyLineTooLong) Error() string {
return fmt.Sprintf("Tried to make an empty line too long - tried %d, max is %d", e.Requested, len(emptyLine))
}
// EmptyLine provides a ready-allocated source of empty cells. Of course this is to be
// treated as read-only.
func EmptyLine(length int) []Cell {
if length < 0 {
length = 0
}
if length > len(emptyLine) {
panic(errors.WithStack(EmptyLineTooLong{Requested: length}))
}
return emptyLine[0:length]
}
// CanvasPos is a convenience struct to represent the coordinates of a position on a canvas.
type CanvasPos struct {
X, Y int
}
func (c CanvasPos) PlusX(n int) CanvasPos {
return CanvasPos{X: c.X + n, Y: c.Y}
}
func (c CanvasPos) PlusY(n int) CanvasPos {
return CanvasPos{X: c.X, Y: c.Y + n}
}
//======================================================================
type CanvasSizeWrong struct {
Requested IRenderSize
Actual IRenderBox
}
var _ error = CanvasSizeWrong{}
func (e CanvasSizeWrong) Error() string {
return fmt.Sprintf("Canvas size %v, %v does not match render size %v", e.Actual.BoxColumns(), e.Actual.BoxRows(), e.Requested)
}
// PanicIfCanvasNotRightSize is for debugging - it panics if the size of the supplied canvas does
// not conform to the size specified by the size argument. For a box argument, columns and rows are
// checked; for a flow argument, columns are checked.
func PanicIfCanvasNotRightSize(c IRenderBox, size IRenderSize) {
switch sz := size.(type) {
case IRenderBox:
if (c.BoxColumns() != sz.BoxColumns() && c.BoxRows() > 0) || c.BoxRows() != sz.BoxRows() {
panic(errors.WithStack(CanvasSizeWrong{Requested: size, Actual: c}))
}
case IRenderFlowWith:
if c.BoxColumns() != sz.FlowColumns() {
panic(errors.WithStack(CanvasSizeWrong{Requested: size, Actual: c}))
}
}
}
type IRightSizeCanvas interface {
IRenderBox
ExtendRight(cells []Cell)
TrimRight(cols int)
Truncate(above, below int)
AppendBelow(c IAppendCanvas, doCursor bool, makeCopy bool)
}
func MakeCanvasRightSize(c IRightSizeCanvas, size IRenderSize) {
switch sz := size.(type) {
case IRenderBox:
rightSizeCanvas(c, sz.BoxColumns(), sz.BoxRows())
case IRenderFlowWith:
rightSizeCanvasHorizontally(c, sz.FlowColumns())
}
}
func rightSizeCanvas(c IRightSizeCanvas, cols int, rows int) {
rightSizeCanvasHorizontally(c, cols)
rightSizeCanvasVertically(c, rows)
}
func rightSizeCanvasHorizontally(c IRightSizeCanvas, cols int) {
if c.BoxColumns() > cols {
c.TrimRight(cols)
} else if c.BoxColumns() < cols {
c.ExtendRight(EmptyLine(cols - c.BoxColumns()))
}
}
func rightSizeCanvasVertically(c IRightSizeCanvas, rows int) {
if c.BoxRows() > rows {
c.Truncate(0, c.BoxRows()-rows)
} else if c.BoxRows() < rows {
AppendBlankLines(c, rows-c.BoxRows())
}
}
//======================================================================
// Canvas is a simple implementation of ICanvas, and is returned by the Render() function
// of all the current widgets. It represents the canvas by a 2-dimensional array of Cells -
// no tricks or attempts to optimize this yet! The canvas also stores a map of string
// identifiers to positions - for example, the cursor position is tracked this way, and the
// menu widget keeps track of where it should render a "dropdown" using canvas marks. Most
// Canvas APIs expect that each line has the same length.
type Canvas struct {
Lines [][]Cell // inner array is a line
Marks *map[string]CanvasPos
maxCol int
}
// NewCanvas returns an initialized Canvas struct. Its size is 0 columns and
// 0 rows.
func NewCanvas() *Canvas {
lines := make([][]Cell, 0, 120)
res := &Canvas{
Lines: lines[:0],
}
var _ io.Writer = res
return res
}
// NewCanvasWithLines allocates a canvas struct and sets its contents to the
// 2-d array provided as an argument.
func NewCanvasWithLines(lines [][]Cell) *Canvas {
c := &Canvas{
Lines: lines,
}
c.AlignRight()
c.maxCol = c.ComputeCurrentMaxColumn()
var _ io.Writer = c
return c
}
// NewCanvasOfSize returns a canvas struct of size cols x rows, where
// each Cell is default-initialized (i.e. empty).
func NewCanvasOfSize(cols, rows int) *Canvas {
return NewCanvasOfSizeExt(cols, rows, Cell{})
}
// NewCanvasOfSize returns a canvas struct of size cols x rows, where
// each Cell is initialized by copying the fill argument.
func NewCanvasOfSizeExt(cols, rows int, fill Cell) *Canvas {
fillArr := make([]Cell, cols)
for i := 0; i < cols; i++ {
fillArr[i] = fill
}
res := NewCanvas()
if rows > 0 {
res.Lines = append(res.Lines, fillArr)
for i := 0; i < rows-1; i++ {
res.Lines = append(res.Lines, make([]Cell, 0, 120))
}
}
res.AlignRightWith(fill)
res.maxCol = res.ComputeCurrentMaxColumn()
var _ io.Writer = res
return res
}
// Duplicate returns a deep copy of the receiver canvas.
func (c *Canvas) Duplicate() ICanvas {
res := NewCanvasOfSize(c.BoxColumns(), c.BoxRows())
for i := 0; i < c.BoxRows(); i++ {
copy(res.Lines[i], c.Lines[i])
}
if c.Marks != nil {
marks := make(map[string]CanvasPos)
res.Marks = &marks
for k, v := range *c.Marks {
(*res.Marks)[k] = v
}
}
return res
}
type IRangeOverCanvas interface {
IRenderBox
ICanvasCellReader
SetCellAt(col, row int, c Cell)
}
// RangeOverCanvas applies the supplied function to each cell,
// modifying it in place.
func RangeOverCanvas(c IRangeOverCanvas, f ICellProcessor) {
for i := 0; i < c.BoxRows(); i++ {
for j := 0; j < c.BoxColumns(); j++ {
c.SetCellAt(j, i, f.ProcessCell(c.CellAt(j, i)))
}
}
}
// Line provides access to the lines of the canvas. LineCopy
// determines what the Line() function should allocate if it
// needs to make a copy of the Line. Return true if line was
// copied.
func (c *Canvas) Line(y int, cp LineCopy) LineResult {
return LineResult{
Line: c.Lines[y],
Copied: false,
}
}
// BoxColumns helps Canvas conform to IRenderBox.
func (c *Canvas) BoxColumns() int {
return c.maxCol
}
// BoxRows helps Canvas conform to IRenderBox.
func (c *Canvas) BoxRows() int {
return len(c.Lines)
}
// BoxRows helps Canvas conform to IWidgetDimension.
func (c *Canvas) ImplementsWidgetDimension() {}
// ComputeCurrentMaxColumn walks the 2-d array of Cells to determine
// the length of the longest line. This is used by certain APIs that
// manipulate the canvas.
func (c *Canvas) ComputeCurrentMaxColumn() int {
res := 0
for _, line := range c.Lines {
res = gwutil.Max(res, len(line))
}
return res
}
// Write lets Canvas conform to io.Writer. Since each Canvas Cell holds a
// rune, the byte array argument is interpreted as the UTF-8 encoding of
// a sequence of runes.
func (c *Canvas) Write(p []byte) (n int, err error) {
return WriteToCanvas(c, p)
}
// WriteToCanvas extracts the logic of implementing io.Writer into a free
// function that can be used by any canvas implementing ICanvas.
func WriteToCanvas(c IRangeOverCanvas, p []byte) (n int, err error) {
done := 0
maxcol := c.BoxColumns()
line := 0
col := 0
for i, chr := range string(p) {
if c.BoxRows() > line {
switch chr {
case '\n':
for col < maxcol {
c.SetCellAt(col, line, Cell{})
col++
}
line++
col = 0
default:
wid := runewidth.RuneWidth(chr)
if col+wid > maxcol {
col = 0
line++
}
c.SetCellAt(col, line, c.CellAt(col, line).WithRune(chr))
col += wid
}
done = i + utf8.RuneLen(chr)
} else {
break
}
}
return done, nil
}
// CursorEnabled returns true if the cursor is enabled in this canvas, false otherwise.
func (c *Canvas) CursorEnabled() bool {
ok := false
if c.Marks != nil {
_, ok = (*c.Marks)["cursor"]
}
return ok
}
// CursorCoords returns a pair of ints representing the current cursor coordinates. Note
// that the caller must be sure the Canvas's cursor is enabled.
func (c *Canvas) CursorCoords() CanvasPos {
var pos CanvasPos
ok := false
if c.Marks != nil {
pos, ok = (*c.Marks)["cursor"]
}
if !ok {
// Caller must check first
panic(errors.New("Cursor is off!"))
}
return pos
}
// SetCursorCoords will set the Canvas's cursor coordinates. The special input of (-1,-1)
// will disable the cursor.
func (c *Canvas) SetCursorCoords(x, y int) {
if x == -1 && y == -1 {
c.RemoveMark("cursor")
} else {
c.SetMark("cursor", x, y)
}
}
// SetMark allows the caller to store a string identifier at a particular position in the
// Canvas. The menu widget uses this feature to keep track of where it should "open", acting
// as an overlay over the widgets below.
func (c *Canvas) SetMark(name string, x, y int) {
if c.Marks == nil {
marks := make(map[string]CanvasPos)
c.Marks = &marks
}
(*c.Marks)[name] = CanvasPos{X: x, Y: y}
}
// GetMark returns the position and presence/absence of the specified string identifier
// in the Canvas.
func (c *Canvas) GetMark(name string) (CanvasPos, bool) {
ok := false
var i CanvasPos
if c.Marks != nil {
i, ok = (*c.Marks)[name]
}
return i, ok
}
// RemoveMark removes a mark from the Canvas.
func (c *Canvas) RemoveMark(name string) {
if c.Marks != nil {
delete(*c.Marks, name)
}
}
// RangeOverMarks applies the supplied function to each mark and position in the
// received Canvas. If the function returns false, the loop is terminated.
func (c *Canvas) RangeOverMarks(f func(key string, value CanvasPos) bool) {
if c.Marks != nil {
for k, v := range *c.Marks {
if !f(k, v) {
break
}
}
}
}
// CellAt returns the Cell at the Canvas position provided. Note that the
// function assumes the caller has ensured the position is not out of
// bounds.
func (c *Canvas) CellAt(col, row int) Cell {
return c.Lines[row][col]
}
// SetCellAt sets the Canvas Cell at the position provided. Note that the
// function assumes the caller has ensured the position is not out of
// bounds.
func (c *Canvas) SetCellAt(col, row int, cell Cell) {
c.Lines[row][col] = cell
}
// SetLineAt sets a line of the Canvas at the given y position. The function
// assumes a line of the correct width has been provided.
func (c *Canvas) SetLineAt(row int, line []Cell) {
c.Lines[row] = line
}
// AppendLine will append the array of Cells provided to the bottom of
// the receiver Canvas. If the makeCopy argument is true, a copy is made
// of the provided Cell array; otherwise, a slice is taken and used
// directly, meaning the Canvas will hold a reference to the underlying
// array.
func (c *Canvas) AppendLine(line []Cell, makeCopy bool) {
newwidth := gwutil.Max(c.BoxColumns(), len(line))
var newline []Cell
if cap(line) < newwidth {
makeCopy = true
}
if makeCopy {
newline = make([]Cell, newwidth)
copy(newline, line)
} else if len(line) < newwidth {
// extend slice
newline = line[0:newwidth]
} else {
newline = line
}
c.Lines = append(c.Lines, newline)
c.AlignRight()
}
// String lets Canvas conform to fmt.Stringer.
func (c *Canvas) String() string {
return CanvasToString(c)
}
func CanvasToString(c ICanvas) string {
lineStrings := make([]string, c.BoxRows())
for i := 0; i < c.BoxRows(); i++ {
line := c.Line(i, LineCopy{}).Line
curLine := make([]rune, 0)
for x := 0; x < len(line); {
r := line[x].Rune()
curLine = append(curLine, r)
x += runewidth.RuneWidth(r)
}
lineStrings[i] = string(curLine)
}
return strings.Join(lineStrings, "\n")
}
// ExtendRight appends to each line of the receiver Canvas the array of
// Cells provided as an argument.
func (c *Canvas) ExtendRight(cells []Cell) {
if len(cells) > 0 {
for i := 0; i < len(c.Lines); i++ {
if len(c.Lines[i])+len(cells) > cap(c.Lines[i]) {
widerLine := make([]Cell, len(c.Lines[i]), len(c.Lines[i])+len(cells))
copy(widerLine, c.Lines[i])
c.Lines[i] = widerLine
}
c.Lines[i] = append(c.Lines[i], cells...)
}
c.maxCol += len(cells)
}
}
// ExtendLeft prepends to each line of the receiver Canvas the array of
// Cells provided as an argument.
func (c *Canvas) ExtendLeft(cells []Cell) {
if len(cells) > 0 {
for i := 0; i < len(c.Lines); i++ {
cellsCopy := make([]Cell, len(cells)+len(c.Lines[i]))
copy(cellsCopy, cells)
copy(cellsCopy[len(cells):], c.Lines[i])
c.Lines[i] = cellsCopy
}
if c.Marks != nil {
for k, pos := range *c.Marks {
(*c.Marks)[k] = pos.PlusX(len(cells))
}
}
c.maxCol += len(cells)
}
}
// AppendBelow appends the supplied Canvas to the "bottom" of the receiver Canvas. If
// doCursor is true and the supplied Canvas has an enabled cursor, it is applied to
// the received Canvas, with a suitable Y offset. If makeCopy is true then the supplied
// Canvas is copied; if false, and the supplied Canvas is capable of giving up
// ownership of its data structures, then they are moved to the receiver Canvas.
func (c *Canvas) AppendBelow(c2 IAppendCanvas, doCursor bool, makeCopy bool) {
cw := c.BoxColumns()
lenc := len(c.Lines)
for i := 0; i < c2.BoxRows(); i++ {
lr := c2.Line(i, LineCopy{
Len: cw,
Cap: cw,
})
if makeCopy && !lr.Copied {
line := make([]Cell, cw)
copy(line, lr.Line)
c.Lines = append(c.Lines, line)
} else {
c.Lines = append(c.Lines, lr.Line)
}
}
c.AlignRight()
c2.RangeOverMarks(func(k string, pos CanvasPos) bool {
if doCursor || (k != "cursor") {
if c.Marks == nil {
marks := make(map[string]CanvasPos)
c.Marks = &marks
}
(*c.Marks)[k] = pos.PlusY(lenc)
}
return true
})
}
// Truncate removes "above" lines from above the receiver Canvas, and
// "below" lines from below.
func (c *Canvas) Truncate(above, below int) {
if above < 0 {
panic(errors.New("Lines to cut above must be >= 0"))
}
if below < 0 {
panic(errors.New("Lines to cut below must be >= 0"))
}
cutAbove := gwutil.Min(len(c.Lines), above)
c.Lines = c.Lines[cutAbove:]
cutBelow := len(c.Lines) - gwutil.Min(len(c.Lines), below)
c.Lines = c.Lines[:cutBelow]
if c.Marks != nil {
for k, pos := range *c.Marks {
(*c.Marks)[k] = pos.PlusY(-cutAbove)
}
}
}
type CellMergeFunc func(lower, upper Cell) Cell
// MergeWithFunc merges the supplied Canvas with the receiver canvas, where the receiver canvas
// is considered to start at column leftOffset and at row topOffset, therefore translated some
// distance from the top-left, and the receiver Canvas is the one modified. A function argument
// is supplied which specifies how Cells are merged, one by one e.g. which style takes effect,
// which rune, and so on.
func (c *Canvas) MergeWithFunc(c2 IMergeCanvas, leftOffset, topOffset int, fn CellMergeFunc, bottomGetsCursor bool) {
c2w := c2.BoxColumns()
for i := 0; i < c2.BoxRows(); i++ {
if i+topOffset < len(c.Lines) {
cl := len(c.Lines[i+topOffset])
for j := 0; j < c2w; j++ {
if j+leftOffset < cl {
c2ij := c2.CellAt(j, i)
c.Lines[i+topOffset][j+leftOffset] = fn(c.Lines[i+topOffset][j+leftOffset], c2ij)
} else {
break
}
}
}
}
c2.RangeOverMarks(func(k string, v CanvasPos) bool {
// Special treatment for the cursor mark - to allow widgets to display the cursor via
// a "lower" widget. The terminal will typically support displaying one cursor only.
if k != "cursor" || !bottomGetsCursor {
if c.Marks == nil {
marks := make(map[string]CanvasPos)
c.Marks = &marks
}
(*c.Marks)[k] = v.PlusX(leftOffset).PlusY(topOffset)
}
return true
})
}
// MergeUnder merges the supplied Canvas "under" the receiver Canvas, meaning the
// receiver Canvas's Cells' settings are given priority.
func (c *Canvas) MergeUnder(c2 IMergeCanvas, leftOffset, topOffset int, bottomGetsCursor bool) {
c.MergeWithFunc(c2, leftOffset, topOffset, Cell.MergeUnder, bottomGetsCursor)
}
// AppendRight appends the supplied Canvas to the right of the receiver Canvas. It
// assumes both Canvases have the same number of rows. If useCursor is true and the
// supplied Canvas has an enabled cursor, then it is applied with a suitable X
// offset applied.
func (c *Canvas) AppendRight(c2 IMergeCanvas, useCursor bool) {
m := c.BoxColumns()
c2w := c2.BoxColumns()
for y := 0; y < c2.BoxRows(); y++ {
if cap(c.Lines[y]) < len(c.Lines[y])+c2w {
widerLine := make([]Cell, len(c.Lines[y])+c2w)
copy(widerLine, c.Lines[y])
c.Lines[y] = widerLine
} else {
c.Lines[y] = c.Lines[y][0 : len(c.Lines[y])+c2w]
}
for x := 0; x < c2w; x++ {
c.Lines[y][x+m] = c2.CellAt(x, y)
}
}
c2.RangeOverMarks(func(k string, v CanvasPos) bool {
if (k != "cursor") || useCursor {
if c.Marks == nil {
marks := make(map[string]CanvasPos)
c.Marks = &marks
}
(*c.Marks)[k] = v.PlusX(m)
}
return true
})
c.maxCol = m + c2w
}
// TrimRight removes columns from the right of the receiver Canvas until there
// is the specified number left.
func (c *Canvas) TrimRight(colsToHave int) {
for i := 0; i < len(c.Lines); i++ {
if len(c.Lines[i]) > colsToHave {
c.Lines[i] = c.Lines[i][0:colsToHave]
}
}
c.maxCol = colsToHave
}
// TrimLeft removes columns from the left of the receiver Canvas until there
// is the specified number left.
func (c *Canvas) TrimLeft(colsToHave int) {
colsToTrim := 0
for i := 0; i < len(c.Lines); i++ {
colsToTrim = gwutil.Max(colsToTrim, len(c.Lines[i])-colsToHave)
}
for i := 0; i < len(c.Lines); i++ {
if len(c.Lines[i]) >= colsToTrim {
c.Lines[i] = c.Lines[i][colsToTrim:]
}
}
if c.Marks != nil {
for k, v := range *c.Marks {
(*c.Marks)[k] = v.PlusX(-colsToTrim)
}
}
}
func appendCell(slice []Cell, data Cell, num int) []Cell {
m := len(slice)
n := m + num
if n > cap(slice) { // if necessary, reallocate
newSlice := make([]Cell, (n+1)*2)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:n]
for i := 0; i < num; i++ {
slice[m+i] = data
}
return slice
}
// AlignRightWith will extend each row of Cells in the receiver Canvas with
// the supplied Cell in order to ensure all rows are the same length. Note
// that the Canvas will not increase in width as a result.
func (c *Canvas) AlignRightWith(cell Cell) {
m := c.ComputeCurrentMaxColumn()
for j, line := range c.Lines {
lineLen := len(line)
cols := m - lineLen
if len(c.Lines[j])+cols > cap(c.Lines[j]) {
tmp := make([]Cell, len(c.Lines[j]), len(c.Lines[j])+cols+32)
copy(tmp, c.Lines[j])
c.Lines[j] = tmp[0:len(c.Lines[j])]
}
c.Lines[j] = appendCell(c.Lines[j], cell, m-lineLen)
}
c.maxCol = m
}
// AlignRight will extend each row of Cells in the receiver Canvas with an
// empty Cell in order to ensure all rows are the same length. Note that
// the Canvas will not increase in width as a result.
func (c *Canvas) AlignRight() {
c.AlignRightWith(Cell{})
}
// Draw will render a Canvas to a tcell Screen.
func Draw(canvas IDrawCanvas, mode IColorMode, screen tcell.Screen) {
cpos := CanvasPos{X: -1, Y: -1}
if canvas.CursorEnabled() {
cpos = canvas.CursorCoords()
}
screen.ShowCursor(-1, -1)
for y := 0; y < canvas.BoxRows(); y++ {
line := canvas.Line(y, LineCopy{})
vline := line.Line
for x := 0; x < len(vline); {
c := vline[x]
f, b, s := c.ForegroundColor(), c.BackgroundColor(), c.Style()
st := MakeCellStyle(f, b, s)
screen.SetContent(x, y, c.Rune(), nil, st)
x += runewidth.RuneWidth(c.Rune())
if x == cpos.X && y == cpos.Y {
screen.ShowCursor(x, y)
}
}
}
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/canvas_test.go 0000664 0000000 0000000 00000004273 14262344540 0015500 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
package gowid
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestInterfaces1(t *testing.T) {
var _ io.Writer = (*Canvas)(nil)
}
func TestInterfaces5(t *testing.T) {
var _ IComposite = (*App)(nil)
}
func TestCanvas19(t *testing.T) {
canvas := NewCanvas()
c := canvas.BoxColumns()
r := canvas.BoxRows()
if c != 0 || r != 0 {
t.Errorf("Failed")
}
r1 := CellsFromString("abc")
r2 := CellsFromString("12")
canvas.AppendLine(r1, false)
canvas.AppendLine(r2, false)
c = canvas.BoxColumns()
r = canvas.BoxRows()
if c != 3 || r != 2 {
t.Errorf("Failed c is %d r is %d", c, r)
}
cs := canvas.String()
if cs != "abc\n12 " {
t.Errorf("Failed cs is %v", cs)
}
canvas.AlignRight()
cs = canvas.String()
if cs != "abc\n12 " {
t.Errorf("Failed")
}
canvas2 := NewCanvas()
r21 := CellsFromString(" X Z")
r22 := CellsFromString("Y2")
canvas2.AppendLine(r21, false)
canvas2.AppendLine(r22, false)
canvas.MergeUnder(canvas2, 0, 0, false)
cs = canvas.String()
if cs != "aXc\nY2 " {
t.Errorf("Failed")
}
assert.Equal(t, canvas.BoxColumns(), 3)
var n int
var err error
n, err = canvas.Write([]byte{'1', '2', '3', 'Q'})
assert.NoError(t, err)
assert.Equal(t, 4, n)
assert.Equal(t, canvas.String(), "123\nQ2 ")
n, err = canvas.Write([]byte{'5', '\n'})
assert.NoError(t, err)
assert.Equal(t, 2, n)
assert.Equal(t, canvas.String(), "5 \nQ2 ")
n, err = canvas.Write([]byte{0xe4, 0xbd, 0xa0, '\n'})
assert.NoError(t, err)
assert.Equal(t, 4, n)
assert.Equal(t, "你 \nQ2 ", canvas.String())
n, err = canvas.Write([]byte{'1', '2', '\n', 'R'})
assert.NoError(t, err)
assert.Equal(t, 4, n)
assert.Equal(t, "12 \nR2 ", canvas.String())
}
type MyString string
func (s MyString) Tester() int {
return len(s)
}
type FooType interface {
Tester() int
}
func MyTestFn(f FooType) {
}
func TestCanvas1(t *testing.T) {
f := MyString("xyz")
MyTestFn(f)
assert.Equal(t, f.Tester(), 3)
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/cell.go 0000664 0000000 0000000 00000012523 14262344540 0014102 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
package gowid
//======================================================================
// Cell represents a single element of terminal output. The empty value
// is a blank cell with default colors, style, and a 'blank' rune. It is
// closely tied to TCell's underlying cell representation - colors are
// TCell-specific, so are translated from anything more general before a
// Cell is instantiated.
type Cell struct {
codePoint rune
fg TCellColor
bg TCellColor
style StyleAttrs
}
// MakeCell returns a Cell initialized with the supplied run (char to display),
// foreground color, background color and style attributes. Each color can specify
// "default" meaning whatever the terminal default foreground/background is, or
// "none" meaning no preference, allowing it to be overridden when laid on top
// of another Cell during the render process.
func MakeCell(codePoint rune, fg TCellColor, bg TCellColor, Attr StyleAttrs) Cell {
return Cell{
codePoint: codePoint,
fg: fg,
bg: bg,
style: Attr,
}
}
// MergeUnder returns a Cell representing the receiver merged "underneath" the
// Cell argument provided. This means the argument's rune value will be used
// unless it is "empty", and the cell's color and styling come from the
// argument's value in a similar fashion.
func (c Cell) MergeUnder(upper Cell) Cell {
res := c
if upper.codePoint != 0 {
res.codePoint = upper.codePoint
}
return res.MergeDisplayAttrsUnder(upper)
}
// MergeDisplayAttrsUnder returns a Cell representing the receiver Cell with the
// argument Cell's color and styling applied, if they are explicitly set.
func (c Cell) MergeDisplayAttrsUnder(upper Cell) Cell {
res := c
ufg, ubg, ust := upper.GetDisplayAttrs()
if ubg != ColorNone {
res = res.WithBackgroundColor(ubg)
}
if ufg != ColorNone {
res = res.WithForegroundColor(ufg)
}
res.style = res.style.MergeUnder(ust)
return res
}
// GetDisplayAttrs returns the receiver Cell's foreground and background color
// and styling.
func (c Cell) GetDisplayAttrs() (x TCellColor, y TCellColor, z StyleAttrs) {
x = c.ForegroundColor()
y = c.BackgroundColor()
z = c.Style()
return
}
// HasRune returns true if the Cell actively specifies a rune to display; otherwise
// false, meaning there it is "empty", and a Cell layered underneath it will have its
// rune displayed.
func (c Cell) HasRune() bool {
return c.codePoint != 0
}
// Rune will return a rune that can be displayed, if this Cell is being rendered in some
// fashion. If the Cell is empty, then a space rune is returned.
func (c Cell) Rune() rune {
if !c.HasRune() {
return ' '
} else {
return c.codePoint
}
}
// WithRune returns a Cell equal to the receiver Cell but that will render the supplied
// rune instead.
func (c Cell) WithRune(r rune) Cell {
c.codePoint = r
return c
}
// BackgroundColor returns the background color of the receiver Cell.
func (c Cell) BackgroundColor() TCellColor {
return c.bg
}
// ForegroundColor returns the foreground color of the receiver Cell.
func (c Cell) ForegroundColor() TCellColor {
return c.fg
}
// Style returns the style of the receiver Cell.
func (c Cell) Style() StyleAttrs {
return c.style
}
// WithRune returns a Cell equal to the receiver Cell but that will render no
// rune instead i.e. it is "empty".
func (c Cell) WithNoRune() Cell {
c.codePoint = 0
return c
}
// WithBackgroundColor returns a Cell equal to the receiver Cell but that
// will render with the supplied background color instead. Note that this color
// can be set to "none" by passing the value gowid.ColorNone, meaning allow
// Cells layered underneath to determine the background color.
func (c Cell) WithBackgroundColor(a TCellColor) Cell {
c.bg = a
return c
}
// WithForegroundColor returns a Cell equal to the receiver Cell but that
// will render with the supplied foreground color instead. Note that this color
// can be set to "none" by passing the value gowid.ColorNone, meaning allow
// Cells layered underneath to determine the background color.
func (c Cell) WithForegroundColor(a TCellColor) Cell {
c.fg = a
return c
}
// WithStyle returns a Cell equal to the receiver Cell but that will render
// with the supplied style (e.g. underline) instead. Note that this style
// can be set to "none" by passing the value gowid.AttrNone, meaning allow
// Cells layered underneath to determine the style.
func (c Cell) WithStyle(attr StyleAttrs) Cell {
c.style = attr
return c
}
//======================================================================
// CellFromRune returns a Cell with the supplied rune and with default
// coloring and styling.
func CellFromRune(r rune) Cell {
return MakeCell(r, ColorNone, ColorNone, StyleNone)
}
// CellsFromString is a utility function to turn a string into an array
// of Cells. Note that each Cell has no color or style set.
func CellsFromString(s string) []Cell {
res := make([]Cell, 0, len(s)) // overcommits, counts chars and not runes, but minimizes reallocations.
for _, r := range s {
if r != ' ' {
res = append(res, CellFromRune(r))
} else {
res = append(res, Cell{})
}
}
return res
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/decoration.go 0000664 0000000 0000000 00000237460 14262344540 0015323 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
package gowid
import (
"fmt"
"os"
"regexp"
"strconv"
"github.com/gcla/gowid/gwutil"
tcell "github.com/gdamore/tcell/v2"
lru "github.com/hashicorp/golang-lru"
"github.com/lucasb-eyer/go-colorful"
"github.com/pkg/errors"
)
//======================================================================
// These are used as bitmasks - a style is two AttrMasks. The first bitmask says whether or not the style declares an
// e.g. underline setting; if it's declared, the second bitmask says whether or not underline is affirmatively on or off.
// This allows styles to be layered e.g. the lower style declares underline is on, the upper style does not declare
// an underline preference, so when layered, the cell is rendered with an underline.
const (
StyleNoneSet tcell.AttrMask = 0 // Just unstyled text.
StyleAllSet tcell.AttrMask = tcell.AttrBold | tcell.AttrBlink | tcell.AttrReverse | tcell.AttrUnderline | tcell.AttrDim
)
// StyleAttrs allows the user to represent a set of styles, either affirmatively set (on) or unset (off)
// with the rest of the styles being unspecified, meaning they can be determined by styles layered
// "underneath".
type StyleAttrs struct {
OnOff tcell.AttrMask // If the specific bit in Set is 1, then the specific bit on OnOff says whether the style is on or off
Set tcell.AttrMask // If the specific bit in Set is 0, then no style preference is declared (e.g. for underline)
}
// AllStyleMasks is an array of all the styles that can be applied to a Cell.
var AllStyleMasks = [...]tcell.AttrMask{tcell.AttrBold, tcell.AttrBlink, tcell.AttrDim, tcell.AttrReverse, tcell.AttrUnderline}
// StyleNone expresses no preference for any text styles.
var StyleNone = StyleAttrs{}
// StyleBold specifies the text should be bold, but expresses no preference for other text styles.
var StyleBold = StyleAttrs{tcell.AttrBold, tcell.AttrBold}
// StyleBlink specifies the text should blink, but expresses no preference for other text styles.
var StyleBlink = StyleAttrs{tcell.AttrBlink, tcell.AttrBlink}
// StyleDim specifies the text should be dim, but expresses no preference for other text styles.
var StyleDim = StyleAttrs{tcell.AttrDim, tcell.AttrDim}
// StyleReverse specifies the text should be displayed as reverse-video, but expresses no preference for other text styles.
var StyleReverse = StyleAttrs{tcell.AttrReverse, tcell.AttrReverse}
// StyleUnderline specifies the text should be underlined, but expresses no preference for other text styles.
var StyleUnderline = StyleAttrs{tcell.AttrUnderline, tcell.AttrUnderline}
// StyleBoldOnly specifies the text should be bold, and no other styling should apply.
var StyleBoldOnly = StyleAttrs{tcell.AttrBold, StyleAllSet}
// StyleBlinkOnly specifies the text should blink, and no other styling should apply.
var StyleBlinkOnly = StyleAttrs{tcell.AttrBlink, StyleAllSet}
// StyleDimOnly specifies the text should be dim, and no other styling should apply.
var StyleDimOnly = StyleAttrs{tcell.AttrDim, StyleAllSet}
// StyleReverseOnly specifies the text should be displayed reverse-video, and no other styling should apply.
var StyleReverseOnly = StyleAttrs{tcell.AttrReverse, StyleAllSet}
// StyleUnderlineOnly specifies the text should be underlined, and no other styling should apply.
var StyleUnderlineOnly = StyleAttrs{tcell.AttrUnderline, StyleAllSet}
// IgnoreBase16 should be set to true if gowid should not consider colors 0-21 for closest-match when
// interpolating RGB colors in 256-color space. You might use this if you use base16-shell, for example,
// to make use of base16-themes for all terminal applications (https://github.com/chriskempson/base16-shell)
var IgnoreBase16 = false
// MergeUnder merges cell styles. E.g. if a is {underline, underline}, and upper is {!bold, bold}, that
// means a declares that it should be rendered with underline and doesn't care about other styles; and
// upper declares it should NOT be rendered bold, and doesn't declare about other styles. When merged,
// the result is {underline|!bold, underline|bold}.
func (a StyleAttrs) MergeUnder(upper StyleAttrs) StyleAttrs {
res := a
for _, am := range AllStyleMasks {
if (upper.Set & am) != 0 {
if (upper.OnOff & am) != 0 {
res.OnOff |= am
} else {
res.OnOff &= ^am
}
res.Set |= am
}
}
return res
}
//======================================================================
// ColorMode represents the color capability of a terminal.
type ColorMode int
const (
// Mode256Colors represents a terminal with 256-color support.
Mode256Colors = ColorMode(iota)
// Mode88Colors represents a terminal with 88-color support such as rxvt.
Mode88Colors
// Mode16Colors represents a terminal with 16-color support.
Mode16Colors
// Mode8Colors represents a terminal with 8-color support.
Mode8Colors
// Mode8Colors represents a terminal with support for monochrome only.
ModeMonochrome
// Mode24BitColors represents a terminal with 24-bit color support like KDE's terminal.
Mode24BitColors
)
func (c ColorMode) String() string {
switch c {
case Mode256Colors:
return "256 colors"
case Mode88Colors:
return "88 colors"
case Mode16Colors:
return "16 colors"
case Mode8Colors:
return "8 colors"
case ModeMonochrome:
return "monochrome"
case Mode24BitColors:
return "24-bit truecolor"
default:
return fmt.Sprintf("Unknown (%d)", int(c))
}
}
const (
colorDefaultName = "default"
colorBlackName = "black"
colorRedName = "red"
colorDarkRedName = "dark red"
colorGreenName = "green"
colorDarkGreenName = "dark green"
colorBrownName = "brown"
colorBlueName = "blue"
colorDarkBlueName = "dark blue"
colorMagentaName = "magenta"
colorDarkMagentaName = "dark magenta"
colorCyanName = "cyan"
colorDarkCyanName = "dark cyan"
colorLightGrayName = "light gray"
colorDarkGrayName = "dark gray"
colorLightRedName = "light red"
colorLightGreenName = "light green"
colorYellowName = "yellow"
colorLightBlueName = "light blue"
colorLightMagentaName = "light magenta"
colorLightCyanName = "light cyan"
colorWhiteName = "white"
)
var (
basicColors = map[string]int{
colorDefaultName: 0,
colorBlackName: 1,
colorDarkRedName: 2,
colorDarkGreenName: 3,
colorBrownName: 4,
colorDarkBlueName: 5,
colorDarkMagentaName: 6,
colorDarkCyanName: 7,
colorLightGrayName: 8,
colorDarkGrayName: 9,
colorLightRedName: 10,
colorLightGreenName: 11,
colorYellowName: 12,
colorLightBlueName: 13,
colorLightMagentaName: 14,
colorLightCyanName: 15,
colorWhiteName: 16,
colorRedName: 10,
colorGreenName: 11,
colorBlueName: 13,
colorMagentaName: 14,
colorCyanName: 15,
}
tBasicColors = map[string]int{
colorDefaultName: 0,
colorBlackName: 1,
colorDarkRedName: 2,
colorDarkGreenName: 3,
colorBrownName: 4,
colorDarkBlueName: 5,
colorDarkMagentaName: 6,
colorDarkCyanName: 7,
colorLightGrayName: 8,
colorDarkGrayName: 1,
colorLightRedName: 2,
colorLightGreenName: 3,
colorYellowName: 4,
colorLightBlueName: 5,
colorLightMagentaName: 6,
colorLightCyanName: 7,
colorWhiteName: 8,
colorRedName: 2,
colorGreenName: 3,
colorBlueName: 5,
colorMagentaName: 6,
colorCyanName: 7,
}
CubeStart = 16 // first index of color cube
CubeSize256 = 6 // one side of the color cube
graySize256 = 24
grayStart256 = gwutil.IPow(CubeSize256, 3) + CubeStart
cubeWhite256 = grayStart256 - 1
cubeSize88 = 4
graySize88 = 8
grayStart88 = gwutil.IPow(cubeSize88, 3) + CubeStart
cubeWhite88 = grayStart88 - 1
cubeBlack = CubeStart
cubeSteps256 = []int{0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff}
graySteps256 = []int{
0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62,
0x6c, 0x76, 0x80, 0x84, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0,
0xda, 0xe4, 0xee,
}
cubeSteps88 = []int{0x00, 0x8b, 0xcd, 0xff}
graySteps88 = []int{0x2e, 0x5c, 0x73, 0x8b, 0xa2, 0xb9, 0xd0, 0xe7}
cubeLookup256 = makeColorLookup(cubeSteps256, 256)
grayLookup256 = makeColorLookup(append([]int{0x00}, append(graySteps256, 0xff)...), 256)
cubeLookup88 = makeColorLookup(cubeSteps88, 256)
grayLookup88 = makeColorLookup(append([]int{0x00}, append(graySteps88, 0xff)...), 256)
cubeLookup256_16 []int
grayLookup256_101 []int
cubeLookup88_16 []int
grayLookup88_101 []int
// ColorNone means no preference if anything is layered underneath
ColorNone = MakeTCellNoColor()
// ColorDefault is an affirmative preference for the default terminal color
ColorDefault = MakeTCellColorExt(tcell.ColorDefault)
// Some pre-initialized color objects for use in applications e.g.
// MakePaletteEntry(ColorBlack, ColorRed)
ColorBlack = MakeTCellColorExt(tcell.ColorBlack)
ColorRed = MakeTCellColorExt(tcell.ColorRed)
ColorGreen = MakeTCellColorExt(tcell.ColorGreen)
ColorLightGreen = MakeTCellColorExt(tcell.ColorLightGreen)
ColorYellow = MakeTCellColorExt(tcell.ColorYellow)
ColorBlue = MakeTCellColorExt(tcell.ColorBlue)
ColorLightBlue = MakeTCellColorExt(tcell.ColorLightBlue)
ColorMagenta = MakeTCellColorExt(tcell.ColorDarkMagenta)
ColorCyan = MakeTCellColorExt(tcell.ColorDarkCyan)
ColorWhite = MakeTCellColorExt(tcell.ColorWhite)
ColorDarkRed = MakeTCellColorExt(tcell.ColorDarkRed)
ColorDarkGreen = MakeTCellColorExt(tcell.ColorDarkGreen)
ColorDarkBlue = MakeTCellColorExt(tcell.ColorDarkBlue)
ColorLightGray = MakeTCellColorExt(tcell.ColorLightGray)
ColorDarkGray = MakeTCellColorExt(tcell.ColorDarkGray)
ColorPurple = MakeTCellColorExt(tcell.ColorPurple)
ColorOrange = MakeTCellColorExt(tcell.ColorOrange)
longColorRE = regexp.MustCompile(`^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$`)
shortColorRE = regexp.MustCompile(`^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$`)
grayHexColorRE = regexp.MustCompile(`^g#([0-9a-fA-F][0-9a-fA-F])$`)
grayDecColorRE = regexp.MustCompile(`^g(1?[0-9][0-9]?)$`)
colorfulBlack8 = colorful.Color{R: 0.0, G: 0.0, B: 0.0}
colorfulWhite8 = colorful.Color{R: 1.0, G: 1.0, B: 1.0}
colorfulRed8 = colorful.Color{R: 1.0, G: 0.0, B: 0.0}
colorfulGreen8 = colorful.Color{R: 0.0, G: 1.0, B: 0.0}
colorfulBlue8 = colorful.Color{R: 0.0, G: 0.0, B: 1.0}
colorfulYellow8 = colorful.Color{R: 1.0, G: 1.0, B: 0.0}
colorfulMagenta8 = colorful.Color{R: 1.0, G: 0.0, B: 1.0}
colorfulCyan8 = colorful.Color{R: 0.0, G: 1.0, B: 1.0}
colorfulBlack16 = colorful.Color{R: 0.0, G: 0.0, B: 0.0}
colorfulWhite16 = colorful.Color{R: 0.66, G: 0.66, B: 0.66}
colorfulRed16 = colorful.Color{R: 0.5, G: 0.0, B: 0.0}
colorfulGreen16 = colorful.Color{R: 0.0, G: 0.5, B: 0.0}
colorfulBlue16 = colorful.Color{R: 0.0, G: 0.0, B: 0.5}
colorfulYellow16 = colorful.Color{R: 0.5, G: 0.5, B: 0.5}
colorfulMagenta16 = colorful.Color{R: 0.5, G: 0.0, B: 0.5}
colorfulCyan16 = colorful.Color{R: 0.0, G: 0.5, B: 0.5}
colorfulBrightBlack16 = colorful.Color{R: 0.33, G: 0.33, B: 0.33}
colorfulBrightWhite16 = colorful.Color{R: 1.0, G: 1.0, B: 1.0}
colorfulBrightRed16 = colorful.Color{R: 1.0, G: 0.0, B: 0.0}
colorfulBrightGreen16 = colorful.Color{R: 0.0, G: 1.0, B: 0.0}
colorfulBrightBlue16 = colorful.Color{R: 0.0, G: 0.0, B: 1.0}
colorfulBrightYellow16 = colorful.Color{R: 1.0, G: 1.0, B: 1.0}
colorfulBrightMagenta16 = colorful.Color{R: 1.0, G: 0.0, B: 1.0}
colorfulBrightCyan16 = colorful.Color{R: 0.0, G: 1.0, B: 1.0}
// Used in mapping RGB colors down to 8 terminal colors.
colorful8 = []colorful.Color{
colorfulBlack8,
colorfulWhite8,
colorfulRed8,
colorfulGreen8,
colorfulBlue8,
colorfulYellow8,
colorfulMagenta8,
colorfulCyan8,
}
// Used in mapping RGB colors down to 16 terminal colors.
colorful16 = []colorful.Color{
colorfulBlack16,
colorfulWhite16,
colorfulRed16,
colorfulGreen16,
colorfulBlue16,
colorfulYellow16,
colorfulMagenta16,
colorfulCyan16,
colorfulBrightBlack16,
colorfulBrightWhite16,
colorfulBrightRed16,
colorfulBrightGreen16,
colorfulBrightBlue16,
colorfulBrightYellow16,
colorfulBrightMagenta16,
colorfulBrightCyan16,
}
colorful256 = []colorful.Color{
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x00) / float64(256), B: float64(0x00) / float64(256)}, //'000000'),
colorful.Color{R: float64(0x80) / float64(256), G: float64(0x00) / float64(256), B: float64(0x00) / float64(256)}, //'800000'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x80) / float64(256), B: float64(0x00) / float64(256)}, //'008000'),
colorful.Color{R: float64(0x80) / float64(256), G: float64(0x80) / float64(256), B: float64(0x00) / float64(256)}, //'808000'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x00) / float64(256), B: float64(0x80) / float64(256)}, //'000080'),
colorful.Color{R: float64(0x80) / float64(256), G: float64(0x00) / float64(256), B: float64(0x80) / float64(256)}, //'800080'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x80) / float64(256), B: float64(0x80) / float64(256)}, //'008080'),
colorful.Color{R: float64(0xc0) / float64(256), G: float64(0xc0) / float64(256), B: float64(0xc0) / float64(256)}, //'c0c0c0'),
colorful.Color{R: float64(0x80) / float64(256), G: float64(0x80) / float64(256), B: float64(0x80) / float64(256)}, //'808080'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x00) / float64(256), B: float64(0x00) / float64(256)}, //'ff0000'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xff) / float64(256), B: float64(0x00) / float64(256)}, //'00ff00'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xff) / float64(256), B: float64(0x00) / float64(256)}, //'ffff00'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x00) / float64(256), B: float64(0xff) / float64(256)}, //'0000ff'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x00) / float64(256), B: float64(0xff) / float64(256)}, //'ff00ff'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xff) / float64(256), B: float64(0xff) / float64(256)}, //'00ffff'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xff) / float64(256), B: float64(0xff) / float64(256)}, //'ffffff'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x00) / float64(256), B: float64(0x00) / float64(256)}, //'000000'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x00) / float64(256), B: float64(0x5f) / float64(256)}, //'00005f'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x00) / float64(256), B: float64(0x87) / float64(256)}, //'000087'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x00) / float64(256), B: float64(0xaf) / float64(256)}, //'0000af'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x00) / float64(256), B: float64(0xd7) / float64(256)}, //'0000d7'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x00) / float64(256), B: float64(0xff) / float64(256)}, //'0000ff'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x00) / float64(256)}, //'005f00'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x5f) / float64(256)}, //'005f5f'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x87) / float64(256)}, //'005f87'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xaf) / float64(256)}, //'005faf'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xd7) / float64(256)}, //'005fd7'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xff) / float64(256)}, //'005fff'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x87) / float64(256), B: float64(0x00) / float64(256)}, //'008700'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x87) / float64(256), B: float64(0x5f) / float64(256)}, //'00875f'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x87) / float64(256), B: float64(0x87) / float64(256)}, //'008787'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x87) / float64(256), B: float64(0xaf) / float64(256)}, //'0087af'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x87) / float64(256), B: float64(0xd7) / float64(256)}, //'0087d7'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0x87) / float64(256), B: float64(0xff) / float64(256)}, //'0087ff'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x00) / float64(256)}, //'00af00'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x5f) / float64(256)}, //'00af5f'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x87) / float64(256)}, //'00af87'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xaf) / float64(256)}, //'00afaf'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xd7) / float64(256)}, //'00afd7'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xff) / float64(256)}, //'00afff'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x00) / float64(256)}, //'00d700'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x5f) / float64(256)}, //'00d75f'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x87) / float64(256)}, //'00d787'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xaf) / float64(256)}, //'00d7af'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xd7) / float64(256)}, //'00d7d7'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xff) / float64(256)}, //'00d7ff'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xff) / float64(256), B: float64(0x00) / float64(256)}, //'00ff00'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xff) / float64(256), B: float64(0x5f) / float64(256)}, //'00ff5f'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xff) / float64(256), B: float64(0x87) / float64(256)}, //'00ff87'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xff) / float64(256), B: float64(0xaf) / float64(256)}, //'00ffaf'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xff) / float64(256), B: float64(0xd7) / float64(256)}, //'00ffd7'),
colorful.Color{R: float64(0x00) / float64(256), G: float64(0xff) / float64(256), B: float64(0xff) / float64(256)}, //'00ffff'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x00) / float64(256), B: float64(0x00) / float64(256)}, //'5f0000'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x00) / float64(256), B: float64(0x5f) / float64(256)}, //'5f005f'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x00) / float64(256), B: float64(0x87) / float64(256)}, //'5f0087'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x00) / float64(256), B: float64(0xaf) / float64(256)}, //'5f00af'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x00) / float64(256), B: float64(0xd7) / float64(256)}, //'5f00d7'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x00) / float64(256), B: float64(0xff) / float64(256)}, //'5f00ff'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x00) / float64(256)}, //'5f5f00'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x5f) / float64(256)}, //'5f5f5f'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x87) / float64(256)}, //'5f5f87'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xaf) / float64(256)}, //'5f5faf'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xd7) / float64(256)}, //'5f5fd7'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xff) / float64(256)}, //'5f5fff'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x87) / float64(256), B: float64(0x00) / float64(256)}, //'5f8700'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x87) / float64(256), B: float64(0x5f) / float64(256)}, //'5f875f'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x87) / float64(256), B: float64(0x87) / float64(256)}, //'5f8787'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x87) / float64(256), B: float64(0xaf) / float64(256)}, //'5f87af'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x87) / float64(256), B: float64(0xd7) / float64(256)}, //'5f87d7'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0x87) / float64(256), B: float64(0xff) / float64(256)}, //'5f87ff'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x00) / float64(256)}, //'5faf00'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x5f) / float64(256)}, //'5faf5f'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x87) / float64(256)}, //'5faf87'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xaf) / float64(256)}, //'5fafaf'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xd7) / float64(256)}, //'5fafd7'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xff) / float64(256)}, //'5fafff'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x00) / float64(256)}, //'5fd700'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x5f) / float64(256)}, //'5fd75f'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x87) / float64(256)}, //'5fd787'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xaf) / float64(256)}, //'5fd7af'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xd7) / float64(256)}, //'5fd7d7'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xff) / float64(256)}, //'5fd7ff'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xff) / float64(256), B: float64(0x00) / float64(256)}, //'5fff00'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xff) / float64(256), B: float64(0x5f) / float64(256)}, //'5fff5f'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xff) / float64(256), B: float64(0x87) / float64(256)}, //'5fff87'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xff) / float64(256), B: float64(0xaf) / float64(256)}, //'5fffaf'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xff) / float64(256), B: float64(0xd7) / float64(256)}, //'5fffd7'),
colorful.Color{R: float64(0x5f) / float64(256), G: float64(0xff) / float64(256), B: float64(0xff) / float64(256)}, //'5fffff'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x00) / float64(256), B: float64(0x00) / float64(256)}, //'870000'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x00) / float64(256), B: float64(0x5f) / float64(256)}, //'87005f'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x00) / float64(256), B: float64(0x87) / float64(256)}, //'870087'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x00) / float64(256), B: float64(0xaf) / float64(256)}, //'8700af'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x00) / float64(256), B: float64(0xd7) / float64(256)}, //'8700d7'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x00) / float64(256), B: float64(0xff) / float64(256)}, //'8700ff'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x00) / float64(256)}, //'875f00'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x5f) / float64(256)}, //'875f5f'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x87) / float64(256)}, //'875f87'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xaf) / float64(256)}, //'875faf'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xd7) / float64(256)}, //'875fd7'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xff) / float64(256)}, //'875fff'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x87) / float64(256), B: float64(0x00) / float64(256)}, //'878700'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x87) / float64(256), B: float64(0x5f) / float64(256)}, //'87875f'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x87) / float64(256), B: float64(0x87) / float64(256)}, //'878787'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x87) / float64(256), B: float64(0xaf) / float64(256)}, //'8787af'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x87) / float64(256), B: float64(0xd7) / float64(256)}, //'8787d7'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0x87) / float64(256), B: float64(0xff) / float64(256)}, //'8787ff'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x00) / float64(256)}, //'87af00'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x5f) / float64(256)}, //'87af5f'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x87) / float64(256)}, //'87af87'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xaf) / float64(256)}, //'87afaf'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xd7) / float64(256)}, //'87afd7'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xff) / float64(256)}, //'87afff'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x00) / float64(256)}, //'87d700'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x5f) / float64(256)}, //'87d75f'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x87) / float64(256)}, //'87d787'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xaf) / float64(256)}, //'87d7af'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xd7) / float64(256)}, //'87d7d7'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xff) / float64(256)}, //'87d7ff'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xff) / float64(256), B: float64(0x00) / float64(256)}, //'87ff00'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xff) / float64(256), B: float64(0x5f) / float64(256)}, //'87ff5f'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xff) / float64(256), B: float64(0x87) / float64(256)}, //'87ff87'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xff) / float64(256), B: float64(0xaf) / float64(256)}, //'87ffaf'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xff) / float64(256), B: float64(0xd7) / float64(256)}, //'87ffd7'),
colorful.Color{R: float64(0x87) / float64(256), G: float64(0xff) / float64(256), B: float64(0xff) / float64(256)}, //'87ffff'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x00) / float64(256), B: float64(0x00) / float64(256)}, //'af0000'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x00) / float64(256), B: float64(0x5f) / float64(256)}, //'af005f'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x00) / float64(256), B: float64(0x87) / float64(256)}, //'af0087'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x00) / float64(256), B: float64(0xaf) / float64(256)}, //'af00af'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x00) / float64(256), B: float64(0xd7) / float64(256)}, //'af00d7'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x00) / float64(256), B: float64(0xff) / float64(256)}, //'af00ff'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x00) / float64(256)}, //'af5f00'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x5f) / float64(256)}, //'af5f5f'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x87) / float64(256)}, //'af5f87'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xaf) / float64(256)}, //'af5faf'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xd7) / float64(256)}, //'af5fd7'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xff) / float64(256)}, //'af5fff'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x87) / float64(256), B: float64(0x00) / float64(256)}, //'af8700'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x87) / float64(256), B: float64(0x5f) / float64(256)}, //'af875f'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x87) / float64(256), B: float64(0x87) / float64(256)}, //'af8787'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x87) / float64(256), B: float64(0xaf) / float64(256)}, //'af87af'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x87) / float64(256), B: float64(0xd7) / float64(256)}, //'af87d7'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0x87) / float64(256), B: float64(0xff) / float64(256)}, //'af87ff'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x00) / float64(256)}, //'afaf00'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x5f) / float64(256)}, //'afaf5f'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x87) / float64(256)}, //'afaf87'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xaf) / float64(256)}, //'afafaf'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xd7) / float64(256)}, //'afafd7'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xff) / float64(256)}, //'afafff'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x00) / float64(256)}, //'afd700'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x5f) / float64(256)}, //'afd75f'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x87) / float64(256)}, //'afd787'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xaf) / float64(256)}, //'afd7af'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xd7) / float64(256)}, //'afd7d7'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xff) / float64(256)}, //'afd7ff'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xff) / float64(256), B: float64(0x00) / float64(256)}, //'afff00'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xff) / float64(256), B: float64(0x5f) / float64(256)}, //'afff5f'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xff) / float64(256), B: float64(0x87) / float64(256)}, //'afff87'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xff) / float64(256), B: float64(0xaf) / float64(256)}, //'afffaf'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xff) / float64(256), B: float64(0xd7) / float64(256)}, //'afffd7'),
colorful.Color{R: float64(0xaf) / float64(256), G: float64(0xff) / float64(256), B: float64(0xff) / float64(256)}, //'afffff'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x00) / float64(256), B: float64(0x00) / float64(256)}, //'d70000'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x00) / float64(256), B: float64(0x5f) / float64(256)}, //'d7005f'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x00) / float64(256), B: float64(0x87) / float64(256)}, //'d70087'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x00) / float64(256), B: float64(0xaf) / float64(256)}, //'d700af'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x00) / float64(256), B: float64(0xd7) / float64(256)}, //'d700d7'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x00) / float64(256), B: float64(0xff) / float64(256)}, //'d700ff'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x00) / float64(256)}, //'d75f00'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x5f) / float64(256)}, //'d75f5f'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x87) / float64(256)}, //'d75f87'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xaf) / float64(256)}, //'d75faf'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xd7) / float64(256)}, //'d75fd7'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xff) / float64(256)}, //'d75fff'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x87) / float64(256), B: float64(0x00) / float64(256)}, //'d78700'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x87) / float64(256), B: float64(0x5f) / float64(256)}, //'d7875f'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x87) / float64(256), B: float64(0x87) / float64(256)}, //'d78787'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x87) / float64(256), B: float64(0xaf) / float64(256)}, //'d787af'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x87) / float64(256), B: float64(0xd7) / float64(256)}, //'d787d7'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0x87) / float64(256), B: float64(0xff) / float64(256)}, //'d787ff'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x00) / float64(256)}, //'d7af00'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x5f) / float64(256)}, //'d7af5f'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x87) / float64(256)}, //'d7af87'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xaf) / float64(256)}, //'d7afaf'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xd7) / float64(256)}, //'d7afd7'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xff) / float64(256)}, //'d7afff'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x00) / float64(256)}, //'d7d700'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x5f) / float64(256)}, //'d7d75f'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x87) / float64(256)}, //'d7d787'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xaf) / float64(256)}, //'d7d7af'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xd7) / float64(256)}, //'d7d7d7'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xff) / float64(256)}, //'d7d7ff'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xff) / float64(256), B: float64(0x00) / float64(256)}, //'d7ff00'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xff) / float64(256), B: float64(0x5f) / float64(256)}, //'d7ff5f'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xff) / float64(256), B: float64(0x87) / float64(256)}, //'d7ff87'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xff) / float64(256), B: float64(0xaf) / float64(256)}, //'d7ffaf'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xff) / float64(256), B: float64(0xd7) / float64(256)}, //'d7ffd7'),
colorful.Color{R: float64(0xd7) / float64(256), G: float64(0xff) / float64(256), B: float64(0xff) / float64(256)}, //'d7ffff'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x00) / float64(256), B: float64(0x00) / float64(256)}, //'ff0000'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x00) / float64(256), B: float64(0x5f) / float64(256)}, //'ff005f'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x00) / float64(256), B: float64(0x87) / float64(256)}, //'ff0087'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x00) / float64(256), B: float64(0xaf) / float64(256)}, //'ff00af'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x00) / float64(256), B: float64(0xd7) / float64(256)}, //'ff00d7'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x00) / float64(256), B: float64(0xff) / float64(256)}, //'ff00ff'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x00) / float64(256)}, //'ff5f00'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x5f) / float64(256)}, //'ff5f5f'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x5f) / float64(256), B: float64(0x87) / float64(256)}, //'ff5f87'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xaf) / float64(256)}, //'ff5faf'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xd7) / float64(256)}, //'ff5fd7'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x5f) / float64(256), B: float64(0xff) / float64(256)}, //'ff5fff'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x87) / float64(256), B: float64(0x00) / float64(256)}, //'ff8700'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x87) / float64(256), B: float64(0x5f) / float64(256)}, //'ff875f'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x87) / float64(256), B: float64(0x87) / float64(256)}, //'ff8787'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x87) / float64(256), B: float64(0xaf) / float64(256)}, //'ff87af'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x87) / float64(256), B: float64(0xd7) / float64(256)}, //'ff87d7'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0x87) / float64(256), B: float64(0xff) / float64(256)}, //'ff87ff'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x00) / float64(256)}, //'ffaf00'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x5f) / float64(256)}, //'ffaf5f'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xaf) / float64(256), B: float64(0x87) / float64(256)}, //'ffaf87'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xaf) / float64(256)}, //'ffafaf'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xd7) / float64(256)}, //'ffafd7'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xaf) / float64(256), B: float64(0xff) / float64(256)}, //'ffafff'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x00) / float64(256)}, //'ffd700'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x5f) / float64(256)}, //'ffd75f'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xd7) / float64(256), B: float64(0x87) / float64(256)}, //'ffd787'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xaf) / float64(256)}, //'ffd7af'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xd7) / float64(256)}, //'ffd7d7'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xd7) / float64(256), B: float64(0xff) / float64(256)}, //'ffd7ff'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xff) / float64(256), B: float64(0x00) / float64(256)}, //'ffff00'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xff) / float64(256), B: float64(0x5f) / float64(256)}, //'ffff5f'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xff) / float64(256), B: float64(0x87) / float64(256)}, //'ffff87'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xff) / float64(256), B: float64(0xaf) / float64(256)}, //'ffffaf'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xff) / float64(256), B: float64(0xd7) / float64(256)}, //'ffffd7'),
colorful.Color{R: float64(0xff) / float64(256), G: float64(0xff) / float64(256), B: float64(0xff) / float64(256)}, //'ffffff'),
colorful.Color{R: float64(0x08) / float64(256), G: float64(0x08) / float64(256), B: float64(0x08) / float64(256)}, //'080808'),
colorful.Color{R: float64(0x12) / float64(256), G: float64(0x12) / float64(256), B: float64(0x12) / float64(256)}, //'121212'),
colorful.Color{R: float64(0x1c) / float64(256), G: float64(0x1c) / float64(256), B: float64(0x1c) / float64(256)}, //'1c1c1c'),
colorful.Color{R: float64(0x26) / float64(256), G: float64(0x26) / float64(256), B: float64(0x26) / float64(256)}, //'262626'),
colorful.Color{R: float64(0x30) / float64(256), G: float64(0x30) / float64(256), B: float64(0x30) / float64(256)}, //'303030'),
colorful.Color{R: float64(0x3a) / float64(256), G: float64(0x3a) / float64(256), B: float64(0x3a) / float64(256)}, //'3a3a3a'),
colorful.Color{R: float64(0x44) / float64(256), G: float64(0x44) / float64(256), B: float64(0x44) / float64(256)}, //'444444'),
colorful.Color{R: float64(0x4e) / float64(256), G: float64(0x4e) / float64(256), B: float64(0x4e) / float64(256)}, //'4e4e4e'),
colorful.Color{R: float64(0x58) / float64(256), G: float64(0x58) / float64(256), B: float64(0x58) / float64(256)}, //'585858'),
colorful.Color{R: float64(0x62) / float64(256), G: float64(0x62) / float64(256), B: float64(0x62) / float64(256)}, //'626262'),
colorful.Color{R: float64(0x6c) / float64(256), G: float64(0x6c) / float64(256), B: float64(0x6c) / float64(256)}, //'6c6c6c'),
colorful.Color{R: float64(0x76) / float64(256), G: float64(0x76) / float64(256), B: float64(0x76) / float64(256)}, //'767676'),
colorful.Color{R: float64(0x80) / float64(256), G: float64(0x80) / float64(256), B: float64(0x80) / float64(256)}, //'808080'),
colorful.Color{R: float64(0x8a) / float64(256), G: float64(0x8a) / float64(256), B: float64(0x8a) / float64(256)}, //'8a8a8a'),
colorful.Color{R: float64(0x94) / float64(256), G: float64(0x94) / float64(256), B: float64(0x94) / float64(256)}, //'949494'),
colorful.Color{R: float64(0x9e) / float64(256), G: float64(0x9e) / float64(256), B: float64(0x9e) / float64(256)}, //'9e9e9e'),
colorful.Color{R: float64(0xa8) / float64(256), G: float64(0xa8) / float64(256), B: float64(0xa8) / float64(256)}, //'a8a8a8'),
colorful.Color{R: float64(0xb2) / float64(256), G: float64(0xb2) / float64(256), B: float64(0xb2) / float64(256)}, //'b2b2b2'),
colorful.Color{R: float64(0xbc) / float64(256), G: float64(0xbc) / float64(256), B: float64(0xbc) / float64(256)}, //'bcbcbc'),
colorful.Color{R: float64(0xc6) / float64(256), G: float64(0xc6) / float64(256), B: float64(0xc6) / float64(256)}, //'c6c6c6'),
colorful.Color{R: float64(0xd0) / float64(256), G: float64(0xd0) / float64(256), B: float64(0xd0) / float64(256)}, //'d0d0d0'),
colorful.Color{R: float64(0xda) / float64(256), G: float64(0xda) / float64(256), B: float64(0xda) / float64(256)}, //'dadada'),
colorful.Color{R: float64(0xe4) / float64(256), G: float64(0xe4) / float64(256), B: float64(0xe4) / float64(256)}, //'e4e4e4'),
colorful.Color{R: float64(0xee) / float64(256), G: float64(0xee) / float64(256), B: float64(0xee) / float64(256)}, //'eeeeee'),
}
term8 = []TCellColor{
ColorBlack,
ColorWhite,
ColorRed,
ColorGreen,
ColorBlue,
ColorYellow,
ColorMagenta,
ColorCyan,
}
term16 = []TCellColor{
ColorBlack,
ColorLightGray,
ColorDarkRed,
ColorDarkGreen,
ColorDarkBlue,
ColorYellow,
ColorMagenta,
ColorCyan,
ColorDarkGray,
ColorWhite,
ColorRed,
ColorGreen,
ColorBlue,
ColorYellow,
ColorMagenta,
ColorCyan, // TODO - figure out these colors
}
term256 = []TCellColor{
MakeTCellColorExt(tcell.ColorBlack),
MakeTCellColorExt(tcell.ColorMaroon),
MakeTCellColorExt(tcell.ColorGreen),
MakeTCellColorExt(tcell.ColorOlive),
MakeTCellColorExt(tcell.ColorNavy),
MakeTCellColorExt(tcell.ColorPurple),
MakeTCellColorExt(tcell.ColorTeal),
MakeTCellColorExt(tcell.ColorSilver),
MakeTCellColorExt(tcell.ColorGray),
MakeTCellColorExt(tcell.ColorRed),
MakeTCellColorExt(tcell.ColorLime),
MakeTCellColorExt(tcell.ColorYellow),
MakeTCellColorExt(tcell.ColorBlue),
MakeTCellColorExt(tcell.ColorFuchsia),
MakeTCellColorExt(tcell.ColorAqua),
MakeTCellColorExt(tcell.ColorWhite),
//
MakeTCellColorExt(tcell.Color16),
MakeTCellColorExt(tcell.Color17),
MakeTCellColorExt(tcell.Color18),
MakeTCellColorExt(tcell.Color19),
MakeTCellColorExt(tcell.Color20),
MakeTCellColorExt(tcell.Color21),
MakeTCellColorExt(tcell.Color22),
MakeTCellColorExt(tcell.Color23),
MakeTCellColorExt(tcell.Color24),
MakeTCellColorExt(tcell.Color25),
MakeTCellColorExt(tcell.Color26),
MakeTCellColorExt(tcell.Color27),
MakeTCellColorExt(tcell.Color28),
MakeTCellColorExt(tcell.Color29),
MakeTCellColorExt(tcell.Color30),
MakeTCellColorExt(tcell.Color31),
MakeTCellColorExt(tcell.Color32),
MakeTCellColorExt(tcell.Color33),
MakeTCellColorExt(tcell.Color34),
MakeTCellColorExt(tcell.Color35),
MakeTCellColorExt(tcell.Color36),
MakeTCellColorExt(tcell.Color37),
MakeTCellColorExt(tcell.Color38),
MakeTCellColorExt(tcell.Color39),
MakeTCellColorExt(tcell.Color40),
MakeTCellColorExt(tcell.Color41),
MakeTCellColorExt(tcell.Color42),
MakeTCellColorExt(tcell.Color43),
MakeTCellColorExt(tcell.Color44),
MakeTCellColorExt(tcell.Color45),
MakeTCellColorExt(tcell.Color46),
MakeTCellColorExt(tcell.Color47),
MakeTCellColorExt(tcell.Color48),
MakeTCellColorExt(tcell.Color49),
MakeTCellColorExt(tcell.Color50),
MakeTCellColorExt(tcell.Color51),
MakeTCellColorExt(tcell.Color52),
MakeTCellColorExt(tcell.Color53),
MakeTCellColorExt(tcell.Color54),
MakeTCellColorExt(tcell.Color55),
MakeTCellColorExt(tcell.Color56),
MakeTCellColorExt(tcell.Color57),
MakeTCellColorExt(tcell.Color58),
MakeTCellColorExt(tcell.Color59),
MakeTCellColorExt(tcell.Color60),
MakeTCellColorExt(tcell.Color61),
MakeTCellColorExt(tcell.Color62),
MakeTCellColorExt(tcell.Color63),
MakeTCellColorExt(tcell.Color64),
MakeTCellColorExt(tcell.Color65),
MakeTCellColorExt(tcell.Color66),
MakeTCellColorExt(tcell.Color67),
MakeTCellColorExt(tcell.Color68),
MakeTCellColorExt(tcell.Color69),
MakeTCellColorExt(tcell.Color70),
MakeTCellColorExt(tcell.Color71),
MakeTCellColorExt(tcell.Color72),
MakeTCellColorExt(tcell.Color73),
MakeTCellColorExt(tcell.Color74),
MakeTCellColorExt(tcell.Color75),
MakeTCellColorExt(tcell.Color76),
MakeTCellColorExt(tcell.Color77),
MakeTCellColorExt(tcell.Color78),
MakeTCellColorExt(tcell.Color79),
MakeTCellColorExt(tcell.Color80),
MakeTCellColorExt(tcell.Color81),
MakeTCellColorExt(tcell.Color82),
MakeTCellColorExt(tcell.Color83),
MakeTCellColorExt(tcell.Color84),
MakeTCellColorExt(tcell.Color85),
MakeTCellColorExt(tcell.Color86),
MakeTCellColorExt(tcell.Color87),
MakeTCellColorExt(tcell.Color88),
MakeTCellColorExt(tcell.Color89),
MakeTCellColorExt(tcell.Color90),
MakeTCellColorExt(tcell.Color91),
MakeTCellColorExt(tcell.Color92),
MakeTCellColorExt(tcell.Color93),
MakeTCellColorExt(tcell.Color94),
MakeTCellColorExt(tcell.Color95),
MakeTCellColorExt(tcell.Color96),
MakeTCellColorExt(tcell.Color97),
MakeTCellColorExt(tcell.Color98),
MakeTCellColorExt(tcell.Color99),
MakeTCellColorExt(tcell.Color100),
MakeTCellColorExt(tcell.Color101),
MakeTCellColorExt(tcell.Color102),
MakeTCellColorExt(tcell.Color103),
MakeTCellColorExt(tcell.Color104),
MakeTCellColorExt(tcell.Color105),
MakeTCellColorExt(tcell.Color106),
MakeTCellColorExt(tcell.Color107),
MakeTCellColorExt(tcell.Color108),
MakeTCellColorExt(tcell.Color109),
MakeTCellColorExt(tcell.Color110),
MakeTCellColorExt(tcell.Color111),
MakeTCellColorExt(tcell.Color112),
MakeTCellColorExt(tcell.Color113),
MakeTCellColorExt(tcell.Color114),
MakeTCellColorExt(tcell.Color115),
MakeTCellColorExt(tcell.Color116),
MakeTCellColorExt(tcell.Color117),
MakeTCellColorExt(tcell.Color118),
MakeTCellColorExt(tcell.Color119),
MakeTCellColorExt(tcell.Color120),
MakeTCellColorExt(tcell.Color121),
MakeTCellColorExt(tcell.Color122),
MakeTCellColorExt(tcell.Color123),
MakeTCellColorExt(tcell.Color124),
MakeTCellColorExt(tcell.Color125),
MakeTCellColorExt(tcell.Color126),
MakeTCellColorExt(tcell.Color127),
MakeTCellColorExt(tcell.Color128),
MakeTCellColorExt(tcell.Color129),
MakeTCellColorExt(tcell.Color130),
MakeTCellColorExt(tcell.Color131),
MakeTCellColorExt(tcell.Color132),
MakeTCellColorExt(tcell.Color133),
MakeTCellColorExt(tcell.Color134),
MakeTCellColorExt(tcell.Color135),
MakeTCellColorExt(tcell.Color136),
MakeTCellColorExt(tcell.Color137),
MakeTCellColorExt(tcell.Color138),
MakeTCellColorExt(tcell.Color139),
MakeTCellColorExt(tcell.Color140),
MakeTCellColorExt(tcell.Color141),
MakeTCellColorExt(tcell.Color142),
MakeTCellColorExt(tcell.Color143),
MakeTCellColorExt(tcell.Color144),
MakeTCellColorExt(tcell.Color145),
MakeTCellColorExt(tcell.Color146),
MakeTCellColorExt(tcell.Color147),
MakeTCellColorExt(tcell.Color148),
MakeTCellColorExt(tcell.Color149),
MakeTCellColorExt(tcell.Color150),
MakeTCellColorExt(tcell.Color151),
MakeTCellColorExt(tcell.Color152),
MakeTCellColorExt(tcell.Color153),
MakeTCellColorExt(tcell.Color154),
MakeTCellColorExt(tcell.Color155),
MakeTCellColorExt(tcell.Color156),
MakeTCellColorExt(tcell.Color157),
MakeTCellColorExt(tcell.Color158),
MakeTCellColorExt(tcell.Color159),
MakeTCellColorExt(tcell.Color160),
MakeTCellColorExt(tcell.Color161),
MakeTCellColorExt(tcell.Color162),
MakeTCellColorExt(tcell.Color163),
MakeTCellColorExt(tcell.Color164),
MakeTCellColorExt(tcell.Color165),
MakeTCellColorExt(tcell.Color166),
MakeTCellColorExt(tcell.Color167),
MakeTCellColorExt(tcell.Color168),
MakeTCellColorExt(tcell.Color169),
MakeTCellColorExt(tcell.Color170),
MakeTCellColorExt(tcell.Color171),
MakeTCellColorExt(tcell.Color172),
MakeTCellColorExt(tcell.Color173),
MakeTCellColorExt(tcell.Color174),
MakeTCellColorExt(tcell.Color175),
MakeTCellColorExt(tcell.Color176),
MakeTCellColorExt(tcell.Color177),
MakeTCellColorExt(tcell.Color178),
MakeTCellColorExt(tcell.Color179),
MakeTCellColorExt(tcell.Color180),
MakeTCellColorExt(tcell.Color181),
MakeTCellColorExt(tcell.Color182),
MakeTCellColorExt(tcell.Color183),
MakeTCellColorExt(tcell.Color184),
MakeTCellColorExt(tcell.Color185),
MakeTCellColorExt(tcell.Color186),
MakeTCellColorExt(tcell.Color187),
MakeTCellColorExt(tcell.Color188),
MakeTCellColorExt(tcell.Color189),
MakeTCellColorExt(tcell.Color190),
MakeTCellColorExt(tcell.Color191),
MakeTCellColorExt(tcell.Color192),
MakeTCellColorExt(tcell.Color193),
MakeTCellColorExt(tcell.Color194),
MakeTCellColorExt(tcell.Color195),
MakeTCellColorExt(tcell.Color196),
MakeTCellColorExt(tcell.Color197),
MakeTCellColorExt(tcell.Color198),
MakeTCellColorExt(tcell.Color199),
MakeTCellColorExt(tcell.Color200),
MakeTCellColorExt(tcell.Color201),
MakeTCellColorExt(tcell.Color202),
MakeTCellColorExt(tcell.Color203),
MakeTCellColorExt(tcell.Color204),
MakeTCellColorExt(tcell.Color205),
MakeTCellColorExt(tcell.Color206),
MakeTCellColorExt(tcell.Color207),
MakeTCellColorExt(tcell.Color208),
MakeTCellColorExt(tcell.Color209),
MakeTCellColorExt(tcell.Color210),
MakeTCellColorExt(tcell.Color211),
MakeTCellColorExt(tcell.Color212),
MakeTCellColorExt(tcell.Color213),
MakeTCellColorExt(tcell.Color214),
MakeTCellColorExt(tcell.Color215),
MakeTCellColorExt(tcell.Color216),
MakeTCellColorExt(tcell.Color217),
MakeTCellColorExt(tcell.Color218),
MakeTCellColorExt(tcell.Color219),
MakeTCellColorExt(tcell.Color220),
MakeTCellColorExt(tcell.Color221),
MakeTCellColorExt(tcell.Color222),
MakeTCellColorExt(tcell.Color223),
MakeTCellColorExt(tcell.Color224),
MakeTCellColorExt(tcell.Color225),
MakeTCellColorExt(tcell.Color226),
MakeTCellColorExt(tcell.Color227),
MakeTCellColorExt(tcell.Color228),
MakeTCellColorExt(tcell.Color229),
MakeTCellColorExt(tcell.Color230),
MakeTCellColorExt(tcell.Color231),
MakeTCellColorExt(tcell.Color232),
MakeTCellColorExt(tcell.Color233),
MakeTCellColorExt(tcell.Color234),
MakeTCellColorExt(tcell.Color235),
MakeTCellColorExt(tcell.Color236),
MakeTCellColorExt(tcell.Color237),
MakeTCellColorExt(tcell.Color238),
MakeTCellColorExt(tcell.Color239),
MakeTCellColorExt(tcell.Color240),
MakeTCellColorExt(tcell.Color241),
MakeTCellColorExt(tcell.Color242),
MakeTCellColorExt(tcell.Color243),
MakeTCellColorExt(tcell.Color244),
MakeTCellColorExt(tcell.Color245),
MakeTCellColorExt(tcell.Color246),
MakeTCellColorExt(tcell.Color247),
MakeTCellColorExt(tcell.Color248),
MakeTCellColorExt(tcell.Color249),
MakeTCellColorExt(tcell.Color250),
MakeTCellColorExt(tcell.Color251),
MakeTCellColorExt(tcell.Color252),
MakeTCellColorExt(tcell.Color253),
MakeTCellColorExt(tcell.Color254),
MakeTCellColorExt(tcell.Color255),
}
term2Cache *lru.Cache
term8Cache *lru.Cache
term16Cache *lru.Cache
term256Cache *lru.Cache
term256CacheIgnoreBase16 *lru.Cache
)
//======================================================================
func init() {
cubeLookup256_16 = make([]int, 16)
cubeLookup88_16 = make([]int, 16)
grayLookup256_101 = make([]int, 101)
grayLookup88_101 = make([]int, 101)
for i := 0; i < 16; i++ {
cubeLookup256_16[i] = cubeLookup256[intScale(i, 16, 0x100)]
cubeLookup88_16[i] = cubeLookup88[intScale(i, 16, 0x100)]
}
for i := 0; i < 101; i++ {
grayLookup256_101[i] = grayLookup256[intScale(i, 101, 0x100)]
grayLookup88_101[i] = grayLookup88[intScale(i, 101, 0x100)]
}
var err error
for _, cache := range []**lru.Cache{&term2Cache, &term8Cache, &term16Cache, &term256Cache, &term256CacheIgnoreBase16} {
*cache, err = lru.New(100)
if err != nil {
panic(err)
}
}
if os.Getenv("GOWID_IGNORE_BASE16") == "1" {
IgnoreBase16 = true
}
}
// makeColorLookup([0, 7, 9], 10)
// [0, 0, 0, 0, 1, 1, 1, 1, 2, 2]
//
func makeColorLookup(vals []int, length int) []int {
res := make([]int, length)
vi := 0
for i := 0; i < len(res); i++ {
if vi+1 < len(vals) {
if i <= (vals[vi]+vals[vi+1])/2 {
res[i] = vi
} else {
vi++
res[i] = vi
}
} else if vi < len(vals) {
// only last vi is valid
res[i] = vi
}
}
return res
}
// Scale val in the range [0, val_range-1] to an integer in the range
// [0, out_range-1]. This implementation uses the "round-half-up" rounding
// method.
//
func intScale(val int, val_range int, out_range int) int {
num := val*(out_range-1)*2 + (val_range - 1)
dem := (val_range - 1) * 2
return num / dem
}
//======================================================================
type ColorModeMismatch struct {
Color IColor
Mode ColorMode
}
var _ error = ColorModeMismatch{}
func (e ColorModeMismatch) Error() string {
return fmt.Sprintf("Color %v of type %T not supported in mode %v", e.Color, e.Color, e.Mode)
}
type InvalidColor struct {
Color interface{}
}
var _ error = InvalidColor{}
func (e InvalidColor) Error() string {
return fmt.Sprintf("Color %v of type %T is invalid", e.Color, e.Color)
}
//======================================================================
// ICellStyler is an analog to urwid's AttrSpec (http://urwid.org/reference/attrspec.html). When provided
// a RenderContext (specifically the color mode in which to be rendered), the GetStyle() function will
// return foreground, background and style values with which a cell should be rendered. The IRenderContext
// argument provides access to the global palette, so an ICellStyle implementation can look up palette
// entries by name.
type ICellStyler interface {
GetStyle(IRenderContext) (IColor, IColor, StyleAttrs)
}
// IColor is implemented by any object that can turn itself into a TCellColor, meaning a color with
// which a cell can be rendered. The display mode (e.g. 256 colors) is provided. If no TCellColor is
// available, the second argument should be set to false e.g. no color can be found given a particular
// string name.
type IColor interface {
ToTCellColor(mode ColorMode) (TCellColor, bool)
}
// MakeCellStyle constructs a tcell.Style from gowid colors and styles. The return value can be provided
// to tcell in order to style a particular region of the screen.
func MakeCellStyle(fg TCellColor, bg TCellColor, attr StyleAttrs) tcell.Style {
var fgt, bgt tcell.Color
if fg == ColorNone {
fgt = tcell.ColorDefault
} else {
fgt = fg.ToTCell()
}
if bg == ColorNone {
bgt = tcell.ColorDefault
} else {
bgt = bg.ToTCell()
}
st := StyleNone.MergeUnder(attr)
return tcell.Style{}.Attributes(st.OnOff).Foreground(fgt).Background(bgt)
}
//======================================================================
// Color satisfies IColor, embeds an IColor, and allows a gowid Color to be
// constructed from a string alone. Each of the more specific color types is
// tried in turn with the string until one succeeds.
type Color struct {
IColor
Id string
}
func (c Color) String() string {
return fmt.Sprintf("%v", c.IColor)
}
// MakeColorSafe returns a Color struct specified by the string argument, in a
// do-what-I-mean fashion - it tries the Color struct maker functions in
// a pre-determined order until one successfully initialized a Color, or
// until all fail - in which case an error is returned. The order tried is
// TCellColor, RGBColor, GrayColor, UrwidColor.
func MakeColorSafe(s string) (Color, error) {
var col IColor
var err error
col, err = MakeTCellColor(s)
if err == nil {
return Color{col, s}, nil
}
col, err = MakeRGBColorSafe(s)
if err == nil {
return Color{col, s}, nil
}
col, err = MakeGrayColorSafe(s)
if err == nil {
return Color{col, s}, nil
}
col, err = NewUrwidColorSafe(s)
if err == nil {
return Color{col, s}, nil
}
return Color{}, errors.WithStack(InvalidColor{Color: s})
}
func MakeColor(s string) Color {
res, err := MakeColorSafe(s)
if err != nil {
panic(err)
}
return res
}
//======================================================================
type ColorByMode struct {
Colors map[ColorMode]IColor // Indexed by ColorMode
}
var _ IColor = (*ColorByMode)(nil)
func MakeColorByMode(cols map[ColorMode]IColor) ColorByMode {
res, err := MakeColorByModeSafe(cols)
if err != nil {
panic(err)
}
return res
}
func MakeColorByModeSafe(cols map[ColorMode]IColor) (ColorByMode, error) {
return ColorByMode{Colors: cols}, nil
}
func (c ColorByMode) ToTCellColor(mode ColorMode) (TCellColor, bool) {
if col, ok := c.Colors[mode]; ok {
col2, ok := col.ToTCellColor(mode)
return col2, ok
}
panic(ColorModeMismatch{Color: c, Mode: mode})
}
//======================================================================
// RGBColor allows for use of colors specified as three components, each with values from 0x0 to 0xf.
// Note that an RGBColor should render as close to the components specify regardless of the color mode
// of the terminal - 24-bit color, 256-color, 88-color. Gowid constructs a color cube, just like urwid,
// and for each color mode, has a lookup table that maps the rgb values to a color cube value which is
// closest to the intended color. Note that RGBColor is not supported in 16-color, 8-color or
// monochrome.
type RGBColor struct {
Red, Green, Blue int
}
var _ IColor = (*RGBColor)(nil)
// MakeRGBColor constructs an RGBColor from a string e.g. "#f00" is red. Note that
// MakeRGBColorSafe should be used unless you are sure the string provided is valid
// (otherwise there will be a panic).
func MakeRGBColor(s string) RGBColor {
res, err := MakeRGBColorSafe(s)
if err != nil {
panic(err)
}
return res
}
func (r RGBColor) String() string {
return fmt.Sprintf("RGBColor(#%02x,#%02x,#%02x)", r.Red, r.Green, r.Blue)
}
// MakeRGBColorSafe does the same as MakeRGBColor except will return an
// error if provided with invalid input.
func MakeRGBColorSafe(s string) (RGBColor, error) {
var mult int64 = 1
match := longColorRE.FindAllStringSubmatch(s, -1)
if len(match) == 0 {
match = shortColorRE.FindAllStringSubmatch(s, -1)
if len(match) == 0 {
return RGBColor{}, errors.WithStack(InvalidColor{Color: s})
}
mult = 16
}
d1, _ := strconv.ParseInt(match[0][1], 16, 16)
d2, _ := strconv.ParseInt(match[0][2], 16, 16)
d3, _ := strconv.ParseInt(match[0][3], 16, 16)
d1 *= mult
d2 *= mult
d3 *= mult
x := MakeRGBColorExt(int(d1), int(d2), int(d3))
return x, nil
}
// MakeRGBColorExtSafe builds an RGBColor from the red, green and blue components
// provided as integers. If the values are out of range, an error is returned.
func MakeRGBColorExtSafe(r, g, b int) (RGBColor, error) {
col := RGBColor{r, g, b}
if r > 0xff || g > 0xff || b > 0xff {
return RGBColor{}, errors.WithStack(errors.WithMessage(InvalidColor{Color: col}, "RGBColor parameters must be between 0x00 and 0xfff"))
}
return col, nil
}
// MakeRGBColorExt builds an RGBColor from the red, green and blue components
// provided as integers. If the values are out of range, the function will panic.
func MakeRGBColorExt(r, g, b int) RGBColor {
res, err := MakeRGBColorExtSafe(r, g, b)
if err != nil {
panic(err)
}
return res
}
// Implements golang standard library's color.Color
func (rgb RGBColor) RGBA() (r, g, b, a uint32) {
r = uint32(rgb.Red << 8)
g = uint32(rgb.Green << 8)
b = uint32(rgb.Blue << 8)
a = 0xffff
return
}
func (r RGBColor) findClosest(from []colorful.Color, corresponding []TCellColor, cache *lru.Cache) TCellColor {
var best float64 = 100.0
var j int
if res, ok := cache.Get(r); ok {
return res.(TCellColor)
}
ccol, _ := colorful.MakeColor(r)
for i, c := range from {
x := c.DistanceLab(ccol)
if x < best {
best = x
j = i
}
}
cache.Add(r, corresponding[j])
return corresponding[j]
}
// ToTCellColor converts an RGBColor to a TCellColor, suitable for rendering to the screen
// with tcell. It lets RGBColor conform to IColor.
func (r RGBColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
switch mode {
case Mode24BitColors:
c := tcell.NewRGBColor(int32(r.Red), int32(r.Green), int32(r.Blue))
return MakeTCellColorExt(c), true
case Mode256Colors:
if IgnoreBase16 {
return r.findClosest(colorful256[22:], term256[22:], term256CacheIgnoreBase16), true
} else {
return r.findClosest(colorful256, term256, term256Cache), true
}
case Mode88Colors:
rd := cubeLookup88_16[r.Red>>4]
g := cubeLookup88_16[r.Green>>4]
b := cubeLookup88_16[r.Blue>>4]
c := tcell.Color((CubeStart + (((rd * cubeSize88) + g) * cubeSize88) + b) + 0) + tcell.ColorValid
return MakeTCellColorExt(c), true
case Mode16Colors:
return r.findClosest(colorful16, term16, term16Cache), true
case Mode8Colors:
return r.findClosest(colorful8, term8, term8Cache), true
case ModeMonochrome:
return r.findClosest(colorful8[0:1], term8[0:1], term2Cache), true
default:
return TCellColor{}, false
}
}
//======================================================================
// UrwidColor is a gowid Color implementing IColor and which allows urwid color names to be used
// (http://urwid.org/manual/displayattributes.html#foreground-and-background-settings) e.g.
// "dark blue", "light gray".
type UrwidColor struct {
Id string
cached bool
cache [2]TCellColor
}
var _ IColor = (*UrwidColor)(nil)
// NewUrwidColorSafe returns a pointer to an UrwidColor struct and builds the UrwidColor from
// a string argument e.g. "yellow". Note that in urwid proper (python), a color can also specify
// a style, like "yellow, underline". UrwidColor does not support specifying styles in that manner.
func NewUrwidColorSafe(val string) (*UrwidColor, error) {
if _, ok := basicColors[val]; !ok {
return nil, errors.WithStack(InvalidColor{Color: val})
}
return &UrwidColor{
Id: val,
}, nil
}
// NewUrwidColorSafe returns a pointer to an UrwidColor struct and builds the UrwidColor from
// a string argument e.g. "yellow"; this function will panic if the there is an error during
// initialization.
func NewUrwidColor(val string) *UrwidColor {
res, err := NewUrwidColorSafe(val)
if err != nil {
panic(err)
}
return res
}
func (r UrwidColor) String() string {
return fmt.Sprintf("UrwidColor(%s)", r.Id)
}
// ToTCellColor converts the receiver UrwidColor to a TCellColor, ready for rendering to a
// tcell screen. This lets UrwidColor conform to IColor.
func (s *UrwidColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
if s.cached {
switch mode {
case Mode24BitColors, Mode256Colors, Mode88Colors, Mode16Colors:
return s.cache[0], true
case Mode8Colors, ModeMonochrome:
return s.cache[1], true
default:
panic(errors.WithStack(ColorModeMismatch{Color: s, Mode: mode}))
}
}
idx := -1
switch mode {
case Mode24BitColors, Mode256Colors, Mode88Colors, Mode16Colors:
idx = posInMap(s.Id, basicColors)
case Mode8Colors, ModeMonochrome:
idx = posInMap(s.Id, tBasicColors)
default:
panic(errors.WithStack(ColorModeMismatch{Color: s, Mode: mode}))
}
if idx == -1 {
panic(errors.WithStack(InvalidColor{Color: s}))
}
col := tcell.ColorDefault
if idx > 0 {
idx = idx - 1
col = tcell.ColorValid + tcell.Color(idx)
}
c := MakeTCellColorExt(col)
switch mode {
case Mode24BitColors, Mode256Colors, Mode88Colors, Mode16Colors:
s.cache[0] = c
case Mode8Colors, ModeMonochrome:
s.cache[1] = c
}
s.cached = true
return c, true
}
//======================================================================
// GrayColor is an IColor that represents a greyscale specified by the
// same syntax as urwid - http://urwid.org/manual/displayattributes.html
// and search for "gray scale entries". Strings may be of the form "g3",
// "g100" or "g#a1", "g#ff" if hexadecimal is preferred. These index the
// grayscale color cube.
type GrayColor struct {
Val int
}
func (g GrayColor) String() string {
return fmt.Sprintf("GrayColor(%d)", g.Val)
}
// MakeGrayColorSafe returns an initialized GrayColor provided with a string
// input like "g50" or "g#ab". If the input is invalid, an error is returned.
func MakeGrayColorSafe(val string) (GrayColor, error) {
var d uint64
match := grayDecColorRE.FindAllStringSubmatch(val, -1)
if len(match) == 0 || len(match[0]) != 2 {
match := grayHexColorRE.FindAllStringSubmatch(val, -1)
if len(match) == 0 || len(match[0]) != 2 {
return GrayColor{}, errors.WithStack(InvalidColor{Color: val})
}
d, _ = strconv.ParseUint(match[0][1], 16, 8)
} else {
d, _ = strconv.ParseUint(match[0][1], 10, 8)
if d > 100 {
return GrayColor{}, errors.WithStack(InvalidColor{Color: val})
}
}
return GrayColor{int(d)}, nil
}
// MakeGrayColor returns an initialized GrayColor provided with a string
// input like "g50" or "g#ab". If the input is invalid, the function panics.
func MakeGrayColor(val string) GrayColor {
res, err := MakeGrayColorSafe(val)
if err != nil {
panic(err)
}
return res
}
func grayAdjustment88(val int) int {
if val == 0 {
return cubeBlack
}
val -= 1
if val == graySize88 {
return cubeWhite88
}
y := grayStart88 + val
return y
}
func grayAdjustment256(val int) int {
if val == 0 {
return cubeBlack
}
val -= 1
if val == graySize256 {
return cubeWhite256
}
y := grayStart256 + val
return y
}
// ToTCellColor converts the receiver GrayColor to a TCellColor, ready for rendering to a
// tcell screen. This lets GrayColor conform to IColor.
func (s GrayColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
switch mode {
case Mode24BitColors:
adj := intScale(s.Val, 101, 0x100)
c := tcell.NewRGBColor(int32(adj), int32(adj), int32(adj))
return MakeTCellColorExt(c), true
case Mode256Colors:
x := tcell.Color(grayAdjustment256(grayLookup256_101[s.Val]) + 1) + tcell.ColorValid
return MakeTCellColorExt(x), true
case Mode88Colors:
x := tcell.Color(grayAdjustment88(grayLookup88_101[s.Val]) + 1) + tcell.ColorValid
return MakeTCellColorExt(x), true
default:
panic(errors.WithStack(ColorModeMismatch{Color: s, Mode: mode}))
}
}
//======================================================================
// TCellColor is an IColor using tcell's color primitives. If you are not porting from urwid or translating
// from urwid, this is the simplest approach to using color. Gowid's layering approach means that the empty
// value for a color should mean "no color preference" - so we want the zero value to mean that. A tcell.Color
// of 0 means "default color". So gowid coopts nil to mean "no color preference".
type TCellColor struct {
tc *tcell.Color
}
var (
_ IColor = (*TCellColor)(nil)
_ fmt.Stringer = (*TCellColor)(nil)
)
var tcellColorRE = regexp.MustCompile(`^[Cc]olor([0-9a-fA-F]{2})$`)
// MakeTCellColor returns an initialized TCellColor given a string input like "yellow". The names that can be
// used are provided here: https://github.com/gdamore/tcell/blob/master/color.go#L821.
func MakeTCellColor(val string) (TCellColor, error) {
match := tcellColorRE.FindStringSubmatch(val) // e.g. "Color00"
if len(match) == 2 {
n, _ := strconv.ParseUint(match[1], 16, 8)
return MakeTCellColorExt(tcell.Color(n) + tcell.ColorValid), nil
} else if col, ok := tcell.ColorNames[val]; !ok {
return TCellColor{}, errors.WithStack(InvalidColor{Color: val})
} else {
return MakeTCellColorExt(col), nil
}
}
// MakeTCellColor returns an initialized TCellColor given a tcell.Color input. The values that can be
// used are provided here: https://github.com/gdamore/tcell/blob/master/color.go#L41.
func MakeTCellColorExt(val tcell.Color) TCellColor {
return TCellColor{&val}
}
// MakeTCellNoColor returns an initialized TCellColor that represents "no color" - meaning if another
// color is rendered "under" this one, then the color underneath will be displayed.
func MakeTCellNoColor() TCellColor {
return TCellColor{}
}
// String implements Stringer for '%v' support.
func (r TCellColor) String() string {
if r.tc == nil {
return "[no-color]"
} else {
c := *r.tc
return fmt.Sprintf("TCellColor(%v)", tcell.Color(c))
}
}
// ToTCell converts a TCellColor back to a tcell.Color for passing to tcell APIs.
func (r TCellColor) ToTCell() tcell.Color {
if r.tc == nil {
return tcell.ColorDefault
}
return *r.tc
}
// ToTCellColor is a no-op, and exists so that TCellColor conforms to the IColor interface.
func (r TCellColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
return r, true
}
//======================================================================
// NoColor implements IColor, and represents "no color preference", distinct from the default terminal color,
// white, black, etc. This means that if a NoColor is rendered over another color, the color underneath will
// be displayed.
type NoColor struct{}
// ToTCellColor converts NoColor to TCellColor. This lets NoColor conform to the IColor interface.
func (r NoColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
return ColorNone, true
}
func (r NoColor) String() string {
return "NoColor"
}
//======================================================================
// DefaultColor implements IColor and means use whatever the default terminal color is. This is
// different to NoColor, which expresses no preference.
type DefaultColor struct{}
// ToTCellColor converts DefaultColor to TCellColor. This lets DefaultColor conform to the IColor interface.
func (r DefaultColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
return MakeTCellColorExt(tcell.ColorDefault), true
}
func (r DefaultColor) String() string {
return "DefaultColor"
}
//======================================================================
// ColorInverter implements ICellStyler, and simply swaps foreground and background colors.
type ColorInverter struct {
ICellStyler
}
func (c ColorInverter) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
y, x, z = c.ICellStyler.GetStyle(prov)
return
}
//======================================================================
// PaletteEntry is typically used by a gowid application to represent a set of color and style
// preferences for use by different application widgets e.g. black text on a white background
// with text underlined. PaletteEntry implements the ICellStyler interface meaning it can
// provide a triple of foreground and background IColor, and a StyleAttrs struct.
type PaletteEntry struct {
FG IColor
BG IColor
Style StyleAttrs
}
var _ ICellStyler = (*PaletteEntry)(nil)
// MakeStyledPaletteEntry simply stores the three parameters provided - a foreground and
// background IColor, and a StyleAttrs struct.
func MakeStyledPaletteEntry(fg, bg IColor, style StyleAttrs) PaletteEntry {
return PaletteEntry{fg, bg, style}
}
// MakePaletteEntry stores the two IColor parameters provided, and has no style preference.
func MakePaletteEntry(fg, bg IColor) PaletteEntry {
return PaletteEntry{fg, bg, StyleNone}
}
// GetStyle returns the individual colors and style attributes.
func (a PaletteEntry) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
x, y, z = a.FG, a.BG, a.Style
return
}
//======================================================================
// PaletteRef is intended to represent a PaletteEntry, looked up by name. The ICellStyler
// API GetStyle() provides an IRenderContext and should return two colors and style attributes.
// PaletteRef provides these by looking up the IRenderContext with the name (string) provided
// to it at initialization.
type PaletteRef struct {
Name string
}
var _ ICellStyler = (*PaletteRef)(nil)
// MakePaletteRef returns a PaletteRef struct storing the (string) name of the PaletteEntry
// which will be looked up in the IRenderContext.
func MakePaletteRef(name string) PaletteRef {
return PaletteRef{name}
}
// GetStyle returns the two colors and a style, looked up in the IRenderContext by name.
func (a PaletteRef) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
spec, ok := prov.CellStyler(a.Name)
if ok {
x, y, z = spec.GetStyle(prov)
} else {
x, y, z = NoColor{}, NoColor{}, StyleAttrs{}
}
return
}
//======================================================================
// EmptyPalette implements ICellStyler and returns no preference for any colors or styling.
type EmptyPalette struct{}
var _ ICellStyler = (*EmptyPalette)(nil)
func MakeEmptyPalette() EmptyPalette {
return EmptyPalette{}
}
// GetStyle implements ICellStyler.
func (a EmptyPalette) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
x, y, z = NoColor{}, NoColor{}, StyleAttrs{}
return
}
//======================================================================
// StyleMod implements ICellStyler. It returns colors and styles from its Cur field unless they are
// overridden by settings in its Mod field. This provides a way for a layering of ICellStylers.
type StyleMod struct {
Cur ICellStyler
Mod ICellStyler
}
var _ ICellStyler = (*StyleMod)(nil)
// MakeStyleMod implements ICellStyler and stores two ICellStylers, one to layer on top of the
// other.
func MakeStyleMod(cur, mod ICellStyler) StyleMod {
return StyleMod{cur, mod}
}
// GetStyle returns the IColors and StyleAttrs from the Mod ICellStyler if they express an
// affirmative preference, otherwise defers to the values from the Cur ICellStyler.
func (a StyleMod) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
fcur, bcur, scur := a.Cur.GetStyle(prov)
fmod, bmod, smod := a.Mod.GetStyle(prov)
var ok bool
_, ok = fmod.ToTCellColor(prov.GetColorMode())
if ok {
x = fmod
} else {
x = fcur
}
_, ok = bmod.ToTCellColor(prov.GetColorMode())
if ok {
y = bmod
} else {
y = bcur
}
z = scur.MergeUnder(smod)
return
}
//======================================================================
// ForegroundColor is an ICellStyler that expresses a specific foreground color and no preference for
// background color or style.
type ForegroundColor struct {
IColor
}
var _ ICellStyler = (*ForegroundColor)(nil)
func MakeForeground(c IColor) ForegroundColor {
return ForegroundColor{c}
}
// GetStyle implements ICellStyler.
func (a ForegroundColor) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
x = a.IColor
y = NoColor{}
z = StyleNone
return
}
//======================================================================
// BackgroundColor is an ICellStyler that expresses a specific background color and no preference for
// foreground color or style.
type BackgroundColor struct {
IColor
}
var _ ICellStyler = (*BackgroundColor)(nil)
func MakeBackground(c IColor) BackgroundColor {
return BackgroundColor{c}
}
// GetStyle implements ICellStyler.
func (a BackgroundColor) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
x = NoColor{}
y = a.IColor
z = StyleNone
return
}
//======================================================================
// StyledAs is an ICellStyler that expresses a specific text style and no preference for
// foreground and background color.
type StyledAs struct {
StyleAttrs
}
var _ ICellStyler = (*StyledAs)(nil)
func MakeStyledAs(s StyleAttrs) StyledAs {
return StyledAs{s}
}
// GetStyle implements ICellStyler.
func (a StyledAs) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
x = NoColor{}
y = NoColor{}
z = a.StyleAttrs
return
}
//======================================================================
// Palette implements IPalette and is a trivial implementation of a type that can store
// cell stylers and provide access to them via iteration.
type Palette map[string]ICellStyler
var _ IPalette = (*Palette)(nil)
// CellStyler will return an ICellStyler by name, if it exists.
func (m Palette) CellStyler(name string) (ICellStyler, bool) {
i, ok := m[name]
return i, ok
}
// RangeOverPalette applies the supplied function to each member of the
// palette. If the function returns false, the loop terminates early.
func (m Palette) RangeOverPalette(f func(k string, v ICellStyler) bool) {
for k, v := range m {
if !f(k, v) {
break
}
}
}
//======================================================================
// IColorToTCell is a utility function that will convert an IColor to a TCellColor
// in preparation for passing to tcell to render; if the conversion fails, a default
// TCellColor is returned (provided to the function via a parameter)
func IColorToTCell(color IColor, def TCellColor, mode ColorMode) TCellColor {
res := def
colTC, ok := color.ToTCellColor(mode) // Is there a color specified affirmatively? (i.e. not NoColor)
if ok && colTC != ColorNone { // Yes a color specified
res = colTC
}
return res
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/decoration_test.go 0000664 0000000 0000000 00000007024 14262344540 0016351 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
package gowid
import (
"testing"
tcell "github.com/gdamore/tcell/v2"
"github.com/go-test/deep"
"github.com/stretchr/testify/assert"
)
func TestColor1(t *testing.T) {
IgnoreBase16 = true
c, _ := MakeRGBColorExtSafe(0, 0, 0)
i2a, _ := c.ToTCellColor(Mode256Colors)
i2 := i2a.ToTCell()
// See https://jonasjacek.github.io/colors/ - we are skipping
// colors 0-21 inclusive
if i2 != tcell.Color232 {
t.Errorf("Failed")
}
}
func TestColor1b(t *testing.T) {
IgnoreBase16 = false
c, _ := MakeRGBColorExtSafe(0, 0, 0)
i2a, _ := c.ToTCellColor(Mode256Colors)
i2 := i2a.ToTCell()
if i2 != tcell.ColorValid {
t.Errorf("Failed")
}
}
func TestColor2(t *testing.T) {
c := NewUrwidColor("dark red")
i2a, _ := c.ToTCellColor(Mode256Colors)
i2 := i2a.ToTCell()
if i2 != tcell.ColorMaroon {
t.Errorf("Failed")
}
}
func TestColor3(t *testing.T) {
c := MakeGrayColor("g#ff")
if c.Val != 255 {
t.Errorf("Failed")
}
}
func TestColor4(t *testing.T) {
c := MakeGrayColor("g99")
if c.Val != 99 {
t.Errorf("Failed")
}
}
func TestColor5(t *testing.T) {
c := MakeGrayColor("g100")
if c.Val != 100 {
t.Errorf("Failed")
}
if v, _ := c.ToTCellColor(Mode256Colors); v.ToTCell() != tcell.Color232 {
t.Errorf("Failed")
}
}
func TestColor6(t *testing.T) {
c := MakeGrayColor("g3")
if c.Val != 3 {
t.Errorf("Failed")
}
if v, _ := c.ToTCellColor(Mode256Colors); v.ToTCell() != tcell.Color233 {
t.Errorf("Failed")
}
}
func TestColor7(t *testing.T) {
c := MakeGrayColor("g0")
if c.Val != 0 {
t.Errorf("Failed")
}
if v, _ := c.ToTCellColor(Mode256Colors); v.ToTCell() != tcell.Color17 {
t.Errorf("Failed")
}
}
func TestColorLookup1(t *testing.T) {
res := makeColorLookup([]int{0, 7, 9}, 10)
if deep.Equal(res, []int{0, 0, 0, 0, 1, 1, 1, 1, 1, 2}) != nil {
t.Errorf("Failed")
}
}
func TestIntScale1(t *testing.T) {
if intScale(0x7, 0x10, 0x10000) != 0x7777 {
t.Errorf("Failed val was %d", intScale(0x7, 0x10, 0x10000))
}
if intScale(0x5f, 0x100, 0x10) != 6 {
t.Errorf("Failed val was %d", intScale(0x5f, 0x100, 0x10))
}
if intScale(2, 6, 101) != 40 {
t.Errorf("Failed")
}
if intScale(1, 3, 4) != 2 {
t.Errorf("Failed")
}
}
func TestStringColor1(t *testing.T) {
col1, _ := MakeRGBColorSafe("#12f")
col2, _ := MakeRGBColorExtSafe(1*16, 2*16, 15*16)
if deep.Equal(col1, col2) != nil {
t.Errorf("Failed")
}
}
func TestStringColor2(t *testing.T) {
col1, _ := MakeRGBColorSafe("#12fgogogog")
col2, _ := MakeRGBColorExtSafe(1*16, 2*16, 15*16)
if deep.Equal(col1, col2) == nil {
t.Errorf("Failed")
}
}
func TestStringColor3(t *testing.T) {
_, err := MakeRGBColorSafe("#34g")
if err == nil {
t.Errorf("Failed")
}
}
func TestGray881(t *testing.T) {
c := MakeGrayColor("g100")
v, _ := c.ToTCellColor(Mode88Colors)
assert.Equal(t, v.ToTCell(), tcell.Color80)
}
func TestDefault1(t *testing.T) {
c, _ := MakeColorSafe("default")
v, _ := c.ToTCellColor(Mode256Colors)
assert.Equal(t, v.ToTCell(), tcell.ColorDefault)
}
func TestTCell1(t *testing.T) {
c, _ := MakeColorSafe("maroon")
v, _ := c.ToTCellColor(Mode256Colors)
assert.Equal(t, v.ToTCell(), tcell.ColorMaroon)
}
func TestTCell2(t *testing.T) {
c := MakeTCellColorExt(tcell.ColorMaroon)
v, _ := c.ToTCellColor(Mode256Colors)
assert.Equal(t, v.ToTCell(), tcell.ColorMaroon)
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/docs/ 0000775 0000000 0000000 00000000000 14262344540 0013561 5 ustar 00root root 0000000 0000000 gowid-1.4.0/docs/Debugging.md 0000664 0000000 0000000 00000004314 14262344540 0016000 0 ustar 00root root 0000000 0000000 # Debugging Techniques
In no particular order, here is a list of tricks for debugging gowid applications.
## Run on a different tty
- Make a local clone of the `tcell` repo:
```bash
git clone https://github.com/gdamore/tcell
cd tcell
```
- Apply the patch from this gist: https://gist.github.com/gcla/29628006828e57ece336554f26e0bde9
```bash
patch -p1 < gowidtty.patch
```
- Make your gowid application compile against your local clone of `tcell`. Adjust your application's `go.mod` like this, replacing `` with your username (or adjust to where you cloned `tcell`)
```bash
replace github.com/gdamore/tcell => /home//tcell
```
- Run your application in tmux - make a split screen.
- On one side, determine the tty, then block input
- On the other side, set the environment variable `GOWID_TTY`

- Run your gowid application e.g. using [tm.sh](https://gist.github.com/gcla/e52ea391c4001cedcfa2cf22d124a750)
```bash
tm.sh 1 go run examples/gowid-fib/fib.go
```

Then you can add `fmt.Printf(...)` calls to quickly debug and not have them interfere with your application's tty.
## Watch Flow of User Input Events
Gowid widgets are arranged in a hierarchy, with outer widgets passing events through to inner widgets for processing,
possibly altering them or handling them themselves on the way. Outer widgets could call
```go
child.UserInput(ev, ...)
```
to determine whether or not the child is handling the event. But instead, all gowid widgets currently call
```go
gowid.UserInput(child, ev, ...)
```
instead. This has the same effect, but means that `gowid.UserInput()` can be used to inspect events flowing through
the application. For example, you can modify the function in `support.go`:
```go
func UserInput(w IWidget, ev interface{}, size IRenderSize, focus Selector, app IApp) bool {
if evm, ok := ev.(*tcell.EventMouse); ok {
// Do something
fmt.Printf("Sending event %v of type %T to widget %v", ev, ev, w)
}
return w.UserInput(ev, size, focus, app)
}
gowid-1.4.0/docs/FAQ.md 0000664 0000000 0000000 00000035113 14262344540 0014515 0 ustar 00root root 0000000 0000000 # FAQ
Gowid is a new Go package, so this is my best guess at some useful tips and tricks.
## What is the difference between `RenderBox`, `RenderFlowWith` and `RenderFixed`?
This concept comes directly from [urwid](http://urwid.org/manual/widgets.html#box-flow-and-fixed-widgets). Each widget supports being rendered in one or more of these three modes:
- Box
- Flow
- Fixed
A box widget is a widget that can render itself given a width and a height i.e. #columns and #rows. It should render a canvas of the correct size. You can render a box widget by calling its `Render()` function with a `RenderBox` struct for the `size` argument e.g. `RenderBox{C: 20, R:16}`. Gowid will always render the root of the widget hierarchy with a `RenderBox` `size` argument, so the root widget should be a box widget.
A flow widget is a widget that can render itself given a width only. The idea is that the widget itself should determine how many rows it requires. A good example of this is a `text.Widget`. If its given fewer columns in which to render, it might need to build a canvas with more rows. A `listbox.Widget` renders its children with a `RenderFlowWith{C:...}` argument and lets each child determine how many lines it needs. You can see this in action by running `gowid-fib` and paging down a few times until the numbers are so long that each scrolls onto the next line.
A fixed widget is a widget that will render itself without any guidance about the width and height. For example, a `checkbox.Widget` can render itself this way - it will make a 3x1 canvas which contains the text `[x]`.
When a container widget, like a `pile.Widget` or `columns.Widget` renders its children, it will use one of these types of size arguments for each child. Sometimes the child widget may not support being rendered with a particular size type. For example, a fixed widget won't automatically expand to accommodate the size given with `RenderBox`. Gowid provides adapter widgets to let you choose how your application should handle this. `boxadadapter.Widget` is initialized with a child widget and an integer that means number-of-rows. The child widget should be a box widget. `boxadapter` allows it to be rendered in flow mode e.g. to be used in a `listbox.Widget`. When `boxadapter.Widget` renders its child, it turns its flow size into a box size by setting the number of rows to render from its initialization parameter. Another option is `vpadding.Widget`. It is initialized with a child widget, an alignment, and a "subsize" that tells the widget how to transform its size argument when rendering its child. A `vpadding.Widget` can turn a box size into a flow size, render its child in flow mode, and then align the rendered child within a canvas of the right size determined by the box, potentially chopping lines from the top and bottom if the child is too large.
## How does Gowid use goroutines? How can I stay thread-safe?
A gowid app is typically launched with a line of code like this:
```go
app.SimpleMainLoop()
```
That function does the following:
1. Starts a goroutine to collect events from tcell. These are pushed into a gowid channel for tcell events.
2. Enters a loop that runs a `select` on three channels:
- tcell events channel
- run-after-render channel
- quit channel
3. The loop is terminated by an event on the quit channel. Then gowid will tell tcell to stop sending events and wait for the tcell event-collecting goroutine to stop.
Example events that appear on the tcell event channel are key-presses, mouse-clicks and terminal changes like a resize. Gowid responds to user input by calling the root widget's `UserInput()` function.
The quit channel receives an event when the gowid application calls `app.Quit()`.
The run-after-render channel receives functions to be executed. A gowid application can send such a function by calling `app.Run()` and passing the function to call. On receipt of such a function, gowid will call the function, then redraw the widgets. Note that the function is executed by the main application goroutine - the one executing `app.SimpleMainLoop()` - so the `Run()` function will not race with any widget rendering or user-input processing.
If your application starts other goroutines that might update the widgets' state or hierarchy, it is best to make those state changes in a function that is issued via `app.Run()`. For an example of this, see `github.com/gcla/gowid/examples/gowid-editor` - in particular code that runs on a timer and updates the editor's status bar.
## How do I write code to respond to a button click?
Here is an example of a callback issued in response to a button click:
```go
rb.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
if rb.Selected {
switch txt {
case "256-Color":
app.SetColorMode(gowid.Mode256Colors)
[...elided for brevity...]
case "Monochrome":
app.SetColorMode(gowid.ModeMonochrome)
}
updateChartHolder(app.GetColorMode(), app)
}
}})
```
This is based on the example `github.com/gcla/gowid/examples/gowid-palette`.
These callbacks are run in the main gowid goroutine, within the call stack that starts with the root widget's `UserInput()`.
The `OnClick()` function takes an `IWidgetChangedCallback`. A simple implementation of that is `WidgetCallback`. To satisfy the interface, you need an ID ("cb" here) and a function that is called with the app and the widget issuing the callback. The ID is present so you can easily remove the callback later if necessary - you just supply the ID, so it must be comparable. Note that if it's more convenient, you can just exploit Go's scoping rules to refer to and capture the callback-issuing widget by its name in the outer scope i.e. "rb".
## Why do all your interfaces start with "I" and not all end in "er"?
When I started writing gowid, I didn't appreciate or understand the convention. For official discussion, you can read this - https://golang.org/doc/effective_go.html#interface-names. When programming I found I wanted a visible way to distinguish arguments to functions - interfaces or values. Using the old I-prefix made that simple for me. As time passed I could see the elegance of small simple interfaces that "do things", hence `Doer()`, of limiting functions with receivers and instead using free functions. But I haven't gone back to try to retro-fit to that new appreciation. So as gowid stands, interfaces start with an I. One of the most important gowid interfaces is `IWidget`. In light of a better understanding of this recommended naming convention, could `IWidget` be broken down? For example
```go
type InputHandler interface {
UserInput(ev interface{}, size IRenderSize, focus Selector, app IApp) bool
}
```
So perhaps each composite widget could store one or more `InputHandler`s, and defer input to the right child. But in figuring out which is the right child to accept the input (e.g. mouse click), a composite widget such as `columns.Widget` may need to figure out the rendered-size of each child and do some arithmetic on the input event coordinates. Now the children need to implement `RenderedSizeProvider` too. Some widgets fall back to calling `Render()` in order to compute the rendered canvas size (slower but simpler) so then they would need to also implement a `Renderer` interface. This gets the requirements close to the current `IWidget` interface. So my view has been that `IWidget` represents a reasonable interface needed to satisfy all widget processing, and it's still pretty small - only four methods.
## How do I write a new widget?
The quick answer is you need to implement `IWidget`:
```go
type IWidget interface {
Render(size IRenderSize, focus Selector, app IApp) ICanvas
RenderSize(size IRenderSize, focus Selector, app IApp) IRenderBox
UserInput(ev interface{}, size IRenderSize, focus Selector, app IApp) bool
Selectable() bool
}
```
The `focus.Focus` argument will be true if your widget has the application focus; otherwise false (`focus.Selected` is intended for letting widgets like columns and pile highlight the subwidget that *would* be in focus if the outer widget was in focus).
Some helper types are provided for the common case. If your widget is always selectable, you can do this:
```go
type MyWidget struct {
...
gowid.IsSelectable
}
```
Or if the opposite, use `NotSelectable`. If your widget will always reject user input, you can embed `RejectUserInput` which will provide a default implementation returning `false`.
Sometimes it's simpler to extend an existing widget. There are some examples of this e.g. `github.com/gcla/gowid/examples/gowid-tutorial4` - see `QuestionBox`. It chooses to embed an interface, `IWidget`, so that it can replace the implementation at runtime. It starts out as an `*edit.Widget` and then is replaced with a `*text.Widget`. `QuestionBox` provides its own `UserInput()` function but the embedded `IWidget` provides the other functions needed to satisfy the widget interface. But be careful and remember that Go does not have dynamic dispatch for structs. If you embed another widget, and that embedded widget's method is called, the receiver will be the embedded widget, not the containing widget. You can't "escape" back to the containing widget. I misunderstood this fundamental design feature when I started programming with Go.
Most gowid widgets are structured into two groups of functions. The essence of the widget is distilled into an interface that rests on `IWidget` - for example, here is a checkbox (in the `github.com/gcla/gowid/widgets/checkbox` package):
```go
// IWidget scoped to "checkbox" here.
type IWidget interface {
gowid.IWidget
IChecked
}
```
So checkbox is a widget that satisfies `checkbox.IChecked`. The `checkbox` package provides a free function that implements some of the expected widget functionality:
```go
func Render(w IChecked, size gowid.IRenderSize, focus Selector, app gowid.IApp) gowid.ICanvas
```
The checkbox widget's `Render()` method looks like this:
```go
func (w *Widget) Render(size gowid.IRenderSize, focus Selector, app gowid.IApp) gowid.ICanvas {
return Render(w, size, focus, app)
}
```
The goal is to make it easier to override parts of a widget, and use the default implementations for the rest. The rendering algorithm is contained in a free function that needs only an `IChecked`, so a new implementation that can be rendered similarly can call the same free function. If instead your new widget's `Render()` function did this:
```go
func (w *Widget) Render(size gowid.IRenderSize, focus Selector, app gowid.IApp) gowid.ICanvas {
return w.IWidget.Render(size, focus, app)
}
```
then the embedded `IWidget` - presumably a `*gowid.Checkbox` - would call `Render()` with a `*gowid.Checkbox` as the receiver, so calling the free function `Render()` with the `IChecked` argument being a `*gowid.Checkbox` instead of your new type. The effect would be your new widget would render like the original checkbox.
## What is the difference between being selectable and handling user input?
A widget that is selectable is intended to be able to take the focus. For example, if a `listbox` is displaying a range of widgets, hitting the down arrow will make the `listbox` look for the next selectable widget to take the focus. If it can, it will skip any widget that is not selectable, like `text.Widget`s. But note that if *no* candidate widgets are selectable, then one will be chosen anyway. So your widget may still be rendered and provided user input (which you can then just reject).
A widget that returns `false` to a call to its `UserInput()` function is indicating that it has not handled the input. Gowid will then try to give the input to another widget. For example, if you hit down arrow in a `listbox`, it will first see if the currently focused listbox widget will accept the keypress. If that widget is an `edit.Widget`, it might move the cursor down a line inside its editing area. The `edit.Widget` will return `true`, and the `listbox` will not process the keypress further. But if, say, the focus `edit.Widget`is on the last line in its editing area, it can't move down a line, and will return `false` to the invocation of its `UserInput()`. The parent `listbox` will then accept the keypress and try to change its own focus widget. You can see this in action in `github.com/gcla/gowid/examples/gowid-widgets2` with left and right arrow keypresses.
## How do I color my widget?
You can use `styled.Widget` and pass it your own widget e.g.
```go
styled.New(myWidget, gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorCyan))
```
The second argument must be an `ICellStyler`:
```go
type ICellStyler interface {
GetStyle(IRenderContext) (IColor, IColor, StyleAttrs)
}
```
You can use `MakePaletteEntry()` to construct one on the fly. The first argument is foreground color, the second background. If you register a palette when you initialize your `app`, you can refer to entries in that palette:
```go
styled.New(myWidget, gowid.MakePaletteRef("eyegrabbing"))
```
If you would like a different style to be used when your widget is in focus, then you can do this:
```go
styled.NewWithFocus(myWidget,
gowid.MakePaletteRef("boring")),
gowid.MakePaletteRef("eyegrabbing"))
)
```
You can easily just invert the colors on focus by using `styled.NewWithSimpleFocus()`. It simply defers to `NewWithFocus()` and uses `ColorInverter{s}` as its third argument where `s` is the second argument.
## How do I apply text styles like underline?
The `StyledAs` struct implements `ICellStyler`, providing no color preferences and the requested "style". So something like this:
```go
styled.New(myTextWidget, gowid.MakeStyledAs(gowid.StyleUnderline))
```
will do the job.
## Why do all the Set...() functions take an IApp Argument?
I decided that it could be useful for widgets to support issuing callbacks when properties change - so that you could tie together the behavior of groups of widgets. Those callbacks might also wish to interact with the app e.g. to run the `Quit()` function, or to inspect the state of the mouse buttons. So that decision necessitates having access to the `App`. To make access possible, there are a couple of other options:
- having a single, global app
- having every widget store a pointer to its app when initialized
Both aren't ideal, and put arbitrary restrictions on the applications using the widgets (though in practice, surely each application will only have one `App`?) Having a magic global `App` also seems to go against Go best practices such as those described in https://peter.bourgon.org/blog/2017/06/09/theory-of-modern-go.html. So I added an explicit `IApp` parameter to each function that might be connected to a subsequent use of the `App`, like calling `Quit()`.
gowid-1.4.0/docs/Tutorial.md 0000664 0000000 0000000 00000052311 14262344540 0015710 0 ustar 00root root 0000000 0000000 # Gowid Tutorial
This tutorial closely follows the structure of the [urwid tutorial](http://urwid.org/tutorial/index.html). When a type is named without an explicit Go package specifier for brevity, then the package is `gowid`.
## Minimal Application

Here is the traditional Hello World program, written for gowid. It displays "hello world" in the top left-hand corner of the terminal and will run until terminated with one of a few keypresses - Escape, Ctrl-c, q or Q. You can find this example at `github.com/gcla/gowid/examples/gowid-tutorial1` and run it via `gowid-tutorial1`.
```go
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/text"
)
func main() {
txt := text.New("hello world")
app, _ := gowid.NewApp(gowid.AppArgs{View: txt})
app.SimpleMainLoop()
}
```
- txt is a `text.Widget` and renders strings to its canvas. This widget also supports rendering collections of text with style and color attributes attached, called markup. A `text.Widget` can render in urwid's "flow-mode", meaning its `Render()` function is provided with a number of columns, but with no specified number of rows. The widget will create a canvas with as many rows as it needs to render suitably. A widget that renders in urwid's "box-mode" will be given both a number of columns *and* a number of rows, and must create a canvas of that size.
- - The second value returned from `NewApp` is an error, which you should check - though there's not much to do except exit gracefully.
- The `app`'s `SimpleMainLoop()` function will hand control over to gowid. Terminal events will be handled by gowid, and in particular, user input will be processed by the hierarchy of widgets that constitute the user interface. Input is handed to the root widget provided as the `View` parameter to `NewApp()`. It may handle the event and it may also hand the event to its children. In this case, `text.Widget` is the root of the hierarchy, and it does not accept user input. Gowid will then hand the input to an `IUnhandledInput` which is provided in this case by `SimpleMainLoop()`. It checks for Escape, Ctrl-c, q or Q - if any are detected, the `app`'s `Quit()` function is called. After processing input, `SimpleMainLoop()` will then terminate.
## Global Input

The second example features a function that processes user input. If the user does not press a quit key, the "hello world" message is updated to show what key was pressed. You can find this example at `github.com/gcla/gowid/examples/gowid-tutorial2` and run it via `gowid-tutorial2`.
```go
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/text"
"github.com/gdamore/tcell"
)
var txt *text.Widget
func unhandled(app gowid.IApp, ev interface{}) bool {
if evk, ok := ev.(*tcell.EventKey); ok {
switch evk.Rune() {
case 'q', 'Q':
app.Quit()
default:
txt.SetText(fmt.Sprintf("hello world - %c", evk.Rune()), app)
}
}
return true
}
func main() {
txt = text.New("hello world")
app, _ := gowid.NewApp(gowid.AppArgs{View: txt})
app.MainLoop(gowid.UnhandledInputFunc(unhandled))
}
```
- The main loop is now provided an explicit function to process input that is not handled by any widget in the hierarchy. The `app`'s`MainLoop()` function expects a type that implements `IUnhandledInput`. The gowid type `UnhandledInputFunc` is a simple function adapter that allows use of a regular Go function.
- The function `unhandled()` is given the `app` and the user input in the form of a `tcell.Event`. Gowid relies throughout on the Go package `tcell` and its representation of terminal input, both from the keyboard and the mouse. If the input provided is from the keyboard and is not one of the quit keys, the root `text.Widget` is updated to display the key that was pressed.
## Display Attributes


The third example demonstrates the use of color. You can find this example at `github.com/gcla/gowid/examples/gowid-tutorial3` and run it via `gowid-tutorial3`.
```go
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
)
func main() {
palette := gowid.Palette{
"banner": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.NewUrwidColor("light gray")),
"streak": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed),
"bg": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorDarkBlue),
}
txt := text.NewFromContentExt(
text.NewContent([]text.ContentSegment{
text.StyledContent("hello world", gowid.MakePaletteRef("banner")),
}), text.Options{
Align: gowid.HAlignMiddle{},
})
map1 := styled.New(txt, gowid.MakePaletteRef("streak"))
vert := vpadding.New(map1, gowid.VAlignMiddle{}, gowid.RenderFlow{})
map2 := styled.New(vert, gowid.MakePaletteRef("bg"))
app, _ := gowid.NewApp(gowid.AppArgs{
View: map2,
Palette: palette,
})
app.SimpleMainLoop()
}
```
- Display attributes are defined and named in a `Palette`. The first argument to `MakePaletteEntry()` represents a foreground color and the second a background color. A similar gowid API allows for a third argument which represents text "styles" like underline and bold.
- Gowid allows colors to be defined in a number of ways. Each color type must implement `IColor`, an interface which provides for a conversion to `tcell` color primitives (depending on the color mode of the terminal), ready for rendering on the terminal screen.
- `ColorBlack` is one of a set of predefined `TCellColor`s you can use. It trivially implements `IColor`.
- `NewUrwidColor()` allows you to provide the name of a color that would be accepted by urwid and returns a `*UrwidColor`. You can read about urwid's color options [here](http://urwid.org/manual/displayattributes.html).
- You can pass the palette when initializing an `App`. Certain gowid widgets that use colors and styles can then refer to palette entries by name when rendering by using the `app`'s `GetCellStyler()` function and providing the name of the palette entry. For example, "hello world" appears in a called to `text.StyledContent()` which binds the display string together with a "cell styler" that comes from a reference to the palette. When this text widget is rendered, the string hello world is displayed in black text with a light gray background.
- You can also give `text.Widget` an alignment parameter. When rendering, the widget will then shift the text left or right depending on how many columns are required. But note that only "hello world" is styled, so the extra space on the left and right is blank.
- The text widget is enclosed in a `styled.Widget` and then inside a `vpadding.Widget` that is also styled. The `styled.Widget` will apply the supplied style "underneath" any styling currently in use for the given widget. This has the effect of applying "streak" in the unstyled areas to the left and right of "hello world". Similarly, `map2` will apply "bg" in the unstyled areas above and below "hello world".
- `vpadding.New()` has a third argument, `RenderFlow{}`. This determines how the inner widget, `map1`, is rendered. In this case, it says that whatever size argument is provided when rendering `vert`, use flow-mode to render `map`.
The screenshots above show how the app reacts to being resized. You can see here that gowid's text widget is less sophisticated than urwid's. When made too narrow to fit on one line, the widget should really break "hello world" on the space in the middle. At the moment it doesn't do that. Room for improvement!
## High-Color Modes

This program is a glitzier "hello world". This example is at `github.com/gcla/gowid/examples/helloworld` and you can run it via `gowid-helloworld`.
```go
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
)
func main() {
palette := gowid.Palette{
"banner": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.MakeRGBColor("#60d")),
"streak": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#60a")),
"inside": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#808")),
"outside": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#a06")),
"bg": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#d06")),
}
div := divider.NewBlank()
outside := styled.New(div, gowid.MakePaletteRef("outside"))
inside := styled.New(div, gowid.MakePaletteRef("inside"))
helloworld := styled.New(
text.NewFromContentExt(
text.NewContent([]text.ContentSegment{
text.StyledContent("Hello World", gowid.MakePaletteRef("banner")),
}),
text.Options{
Align: gowid.HAlignMiddle{},
},
),
gowid.MakePaletteRef("streak"),
)
f := gowid.RenderFlow{}
view := styled.New(
vpadding.New(
pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{IWidget: outside, D: f},
&gowid.ContainerWidget{IWidget: inside, D: f},
&gowid.ContainerWidget{IWidget: helloworld, D: f},
&gowid.ContainerWidget{IWidget: inside, D: f},
&gowid.ContainerWidget{IWidget: outside, D: f},
}),
gowid.VAlignMiddle{},
f),
gowid.MakePaletteRef("bg"),
)
app, _ := gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
})
app.SimpleMainLoop()
}
```
- To create the vertical effect, a `pile.Widget` is used. The blank lines are made with a `divider.Widget`, where `outside` and `inside` are styled with different colors. The widget pile is centered with a `vpadding.Widget` and `VAlignMiddle{}`, and the rest of the blank space is styled with "bg".
- This example uses a new `IColor`-creating function, `MakeRGBColor()`. You can provide hex values for red, green and blue, where each value should range from 0x0 to 0xF. If the terminal is in a mode with fewer color combinations, such as 256-color mode, the chosen RGB value is interpolated into an 8x8x8 color cube to find the closest match - in exactly the same fashion as urwid.
## Question and Answer


The next example asks for the user's name. When the user presses enter, it displays a friendly personalized message. The q or Q key will terminate the app. You can find this example at `github.com/gcla/gowid/examples/gowid-tutorial4` and run it via `gowid-tutorial4`.
```go
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/text"
"github.com/gdamore/tcell"
)
//======================================================================
type QuestionBox struct {
gowid.IWidget
}
func (w *QuestionBox) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool {
res := true
if evk, ok := ev.(*tcell.EventKey); ok {
switch evk.Key() {
case tcell.KeyEnter:
w.IWidget = text.New(fmt.Sprintf("Nice to meet you, %s.\n\nPress Q to exit.", w.IWidget.(*edit.Widget).Text()))
default:
res = w.IWidget.UserInput(w.IWidget, ev, size, focus, app)
}
}
return res
}
func main() {
edit := edit.New(edit.Options{Caption: "What is your name?\n"})
qb := &QuestionBox{edit}
app, _ := gowid.NewApp(gowid.AppArgs{View: qb})
app.MainLoop(gowid.UnhandledInputFunc(gowid.HandleQuitKeys))
}
```
- This example shows how you can extend a widget. `QuestionBox` embeds an `IWidget` meaning that it itself implements `IWidget`. The `main()` function sets up a `QuestionBox` widget that extends an `edit.Widget`. That means `QuestionBox` will render like `edit.Widget`. But `QuestionBox` provides a new implementation of `UserInput()`, one of the requirements of `IWidget`. If the key pressed is not "enter" then it defers to its embedded `IWidget`'s implementation of `UserInput()`. That means the embedded `edit.Widget` will process it, and it will accumulate the user's typed input and display that when rendered. But if the user presses "enter", `QuestionBox` replaces its embedded widget with a new `text.Widget` that displays a message to the name the user has typed in.
- When constructing the "Nice to meet you" message, the embedded `IWidget` is cast to an `*edit.Widget`. That's safe because we control the embedded widget, so we know its type. Note that the concrete type is a pointer - gowid widgets have pointer-receiver functions, for the most part, including all methods used to implement `IWidget`.
- There are pitfalls if your mindset is "object-oriented" like Java or older-style C++. My first instinct was to view `UserInput()` as "overriding" the embedded widget's `UserInput()`. And it's true that our new implementation will be called from an `IWidget` if the interface's type is a `QuestionBox` pointer. But let's say you also provide a specialized implementation for `RenderSize()` another `IWidget` requirement. And let's say `UserInput()` calls a method which is not "overridden" in `edit.Widget`, and that in turn calls `RenderSize()`; then your new version will not be called. The receiver will be the `edit.Widget` pointer. Go does not support dynamic dispatch except for calls through an interface. I certainly misunderstood that when getting going. More details here: https://golang.org/doc/faq#How_do_I_get_dynamic_dispatch_of_methods.
## Widget Callbacks


This example shows how you can respond to widget actions, like a button click. See this example at `github.com/gcla/gowid/examples/gowid-tutorial5` and run it via `gowid-tutorial5`.
```go
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/button"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
)
//======================================================================
func main() {
ask := edit.New(edit.Options{Caption: "What is your name?\n"})
reply := text.New("")
btn := button.New(text.New("Exit"))
sbtn := styled.New(btn, gowid.MakeStyledAs(gowid.StyleReverse))
div := divider.NewBlank()
btn.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
app.Quit()
}})
ask.OnTextSet(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
if ask.Text() == "" {
reply.SetText("", app)
} else {
reply.SetText(fmt.Sprintf("Nice to meet you, %s", ask.Text()), app)
}
}})
f := gowid.RenderFlow{}
view := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{IWidget: ask, D: f},
&gowid.ContainerWidget{IWidget: div, D: f},
&gowid.ContainerWidget{IWidget: reply, D: f},
&gowid.ContainerWidget{IWidget: div, D: f},
&gowid.ContainerWidget{IWidget: sbtn, D: f},
})
app, _ := gowid.NewApp(gowid.AppArgs{View: view})
app.SimpleMainLoop()
}
```
- The bottom-most widget in the pile is a `button.Widget`. It itself wraps an inner widget, and when rendered will add characters on the left and right of the inner widget to create a button effect.
- `button.Widget` can call an interface method when it's clicked. `OnClick()` expects an `IWidgetChangedCallback`. You can use the `WidgetCallback()` adapter to pass a simple function.
- The first parameter of `WidgetCallback` is an `interface{}`. It's meant to uniquely identify this callback instance so that if you later need to remove the callback, you can by passing the same `interface{}`. Here I've used a simple string, "cb". The callbacks are scoped to the widget, so you can use the same callback identifier when registering callbacks for other widgets.
- `edit.Widget` can call an interface method when its text changes. In this example, every time the user enters a character, `ask` will update the `reply` widget so that it displays a message.
- The callback will be called with two arguments - the application `app` and the widget issuing the callback. But if it's more convenient, you can rely on Go's scope rules to capture the widgets that you need to modify in the callback. `ask`'s callback refers to `reply` and not the callback parameter `w`.
- The `` button is styled using `MakeStyleAs()`, which applies a text style like underline, bold or reverse-video. No colors are given, so the button will use the terminal's default colors.
## Multiple Questions

The final example asks the same question over and over, and collects the results. You can go back and edit previous answers and the program will update its response. It demonstrates the use of a gowid listbox. This example is available at `github.com/gcla/gowid/examples/gowid-tutorial6` and you can run it via `gowid-tutorial6`.
```go
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/list"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/text"
"github.com/gdamore/tcell"
)
//======================================================================
func question() *pile.Widget {
return pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{
IWidget: edit.New(edit.Options{Caption: "What is your name?\n"}),
D: gowid.RenderFlow{},
},
})
}
func answer(name string) *gowid.ContainerWidget {
return &gowid.ContainerWidget{
IWidget: text.New(fmt.Sprintf("Nice to meet you, %s", name)),
D: gowid.RenderFlow{},
}
}
type ConversationWidget struct {
*list.Widget
}
func NewConversationWidget() *ConversationWidget {
widgets := make([]gowid.IWidget, 1)
widgets[0] = question()
lb := list.New(list.NewSimpleListWalker(widgets))
return &ConversationWidget{lb}
}
func (w *ConversationWidget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool {
res := false
if evk, ok := ev.(*tcell.EventKey); ok && evk.Key() == tcell.KeyEnter {
res = true
focus := w.Walker().Focus()
focusPile := focus.Widget.(*pile.Widget)
pileChildren := focusPile.SubWidgets()
ed := pileChildren[0].(*gowid.ContainerWidget).SubWidget().(*edit.Widget)
focusPile.SetSubWidgets(append(pileChildren[0:1], answer(ed.Text())), app)
walker := w.Widget.Walker().(*list.SimpleListWalker)
walker.Widgets = append(walker.Widgets, question())
nextPos := walker.Next(focus.Pos).Pos
walker.SetFocus(nextPos)
w.Widget.GoToBottom(app)
} else {
res = gowid.UserInput(w.Widget, ev, size, focus, app)
}
return res
}
func main() {
app, _ := gowid.NewApp(gowid.AppArgs{View: NewConversationWidget()})
app.SimpleMainLoop()
}
```
- In this example I've created a new widget called `ConversationWidget`. It embeds a `*list.Widget` and renders like one, but its input is handled specially. A `list.Widget` is a more general form of `pile.Widget`. You provide a `list.Widget` with a `list.IListWalker`which is like a widget iterator. It can return the current "focus" widget, move to the next widget and move to the previous widget. This allows it, potentially, to be unbounded. For an example of that in action, see `github.com/gcla/gowid/examples/gowid-fib` which is plagiarized heavily from urwid's `fib.py` example.
- The list walker in this example is a wrapper around a Go array of widgets. Each widget in the list is a `pile.Widget` containing either
- A single `edit.Widget` asking for a name, or
- An `edit.Widget` asking for a name and the user's response as a `text.Widget`.
- When the user presses "enter" in an `edit.Widget`, the current focus `pile.Widget` is manipulated. Any previous answer is eliminated, and a new answer is appended. The walker is advanced one position, and finally, the `list.Widget` is told to render so the focus widget is at the bottom of the canvas. There is a good deal of type-casting here, but again it's safe because we control the concrete types involved in the construction of this widget hierarchy.
- When the user presses the up and down cursor keys in the context of a `list.Widget`, the widget's walker adjusts its focus widget. In this example, focus will move from one `pile.Widget` to another. Within that `pile.Widget` there are at most two widgets - one `edit.Widget` and one `text.Widget`. An `edit.Widget` is "selectable", which means it is useful for it to be given the focus. A `text.Widget` is not selectable, which means there's no point in it being given the focus. That means that as the user moves up and down, focus will always be given to an `edit.Widget`. Do note though that just like in urwid, a non-selectable widget can still be given focus e.g. if there is no other selectable widget in the current widget scope.
- If you're following along with the urwid tutorial, you'll noticed that this example is a little longer than the corresponding urwid program. I attribute that to Go having fewer short-cuts than python, and forcing the programmer to be more explicit. I like that, personally.
gowid-1.4.0/docs/Widgets.md 0000664 0000000 0000000 00000060170 14262344540 0015515 0 ustar 00root root 0000000 0000000 # Gowid Widgets
Gowid supplies a number of widgets out-of-the-box.
## asciigraph
**Purpose:** The `asciigraph` widget renders line graphs. It uses the Go package `github.com/guptarohit/asciigraph`.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-asciigraph`
- `github.com/gcla/gowid/examples/gowid-overlay2`
## bargraph
**Purpose**: renders bar graphs. Based heavily on urwid's `graph.py`.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-graph`
## boxadapter
**Purpose**: allow a box widget to be rendered in a flow context.
**Examples:**
- `github.com/gcla/gowid/examples/gowid-dir`
## button
**Purpose**: a clickable widget. The app can register callbacks to handle click events.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-dir`
- `github.com/gcla/gowid/examples/gowid-menu`
- `github.com/gcla/gowid/examples/gowid-palette`
- `github.com/gcla/gowid/examples/gowid-graph`
- `github.com/gcla/gowid/examples/gowid-tree1`
-
## cellmod
**Purpose**: modify the canvas of a child widget by applying a user-supplied function to each `Cell` .
**Examples:**
- `github.com/gcla/gowid/widgets/dialog`
## checkbox
**Purpose**: a clickable widget with two states - selected and unselected.

**Examples:**
- `github.com/gcla/gowid/gowid-asciigraph`
## clicktracker
**Purpose**: to highlight a widget that has been clicked with the mouse, but which has not yet been activated because the mouse button has not been released. The idea is to highlight which widget will be activated when the mouse is released, if focus remains over that widget.
**Examples**:
- `github.com/gcla/gowid/examples/gowid-graph`
- `github.com/gcla/gowid/examples/gowid-widgets3`
## columns
**Purpose**: arrange child widgets into vertical columns, with configurable column widths.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-widgets2`
- `github.com/gcla/gowid/examples/gowid-widgets3`
- `github.com/gcla/gowid/examples/gowid-editor`
- `github.com/gcla/gowid/examples/gowid-graph`
-
## dialog
**Purpose**: a modal dialog box that can be opened on top of another widget and will process the user input preferentially.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-editor`
## divider
**Purpose**: a configurable horizontal line that can be used to separate widgets arranged vertically. Can render using ascii or unicode.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-graph`
- `github.com/gcla/gowid/examples/gowid-helloworld`
- `github.com/gcla/gowid/examples/gowid-palette`
## edit
**Purpose**: a text area that will display text typed in by the user, with an optional caption/prefix.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-editor`
- `github.com/gcla/gowid/examples/gowid-widgets4`
- `github.com/gcla/gowid/examples/gowid-widgets6`
## fill
**Purpose**: a widget that when rendered returns a canvas full of the same user-supplied `Cell`.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-overlay1`
- `github.com/gcla/gowid/examples/gowid-terminal`
- `github.com/gcla/gowid/examples/gowid-widgets2`
## fixedadapter
**Purpose**: a simple way to allow a fixed widget to be used in a box or flow context.
**Examples:**
- `github.com/gcla/gowid/widgets/list/list_test.go`
## framed
**Purpose**: surround a child widget with a configurable "frame", using unicode or ascii characters.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-widgets1`
- `github.com/gcla/gowid/examples/gowid-tree1`
- `github.com/gcla/gowid/examples/gowid-graph`
- `github.com/gcla/gowid/examples/gowid-terminal`
## grid
**Purpose**: a way to arrange widgets in a grid, with configurable horizontal alignment.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-widgets3`
## holder
**Purpose**: wraps a child widget and defers all behavior to it. Allows the child to be swapped out for another.
**Examples:**
- `github.com/gcla/gowid/examples/gowid-editor`
- `github.com/gcla/gowid/examples/gowid-palette`
- `github.com/gcla/gowid/examples/gowid-terminal`
## hpadding
**Purpose**: a widget to render and align a child widget horizontally in a wider space.
**Examples:**
- `github.com/gcla/gowid/examples/gowid-fib`
- `github.com/gcla/gowid/examples/gowid-graph`
- `github.com/gcla/gowid/examples/gowid-menu`
## list
**Purpose**: a flexible widget to navigate a vertical list of widgets rendered in flow mode.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-fib`
- `github.com/gcla/gowid/examples/gowid-menu`
- `github.com/gcla/gowid/examples/gowid-widgets4`
- `github.com/gcla/gowid/examples/gowid-widgets7`
## menu
**Purpose**: a drop-down menu supporting arbitrarily many sub-menus.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-menu`
## overlay
**Purpose**: a widget to render one widget over another, only passing user input to the occluded widget if the input coordinates are outside the boundaries of the widget on top.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-overlay1`
- `github.com/gcla/gowid/examples/gowid-overlay2`
## palettemap
**Purpose**: a widget that will render an inner widget with style X if it would otherwise be rendered with style Y, if configured to map Y -> X, and if the widget is styled using references to palette entries.
**Examples:**
- `github.com/gcla/gowid/gowid-widgets1`
- `github.com/gcla/gowid/gowid-widgets4`
- `github.com/gcla/gowid/gowid-fib`
- `github.com/gcla/gowid/gowid-tree1`
The `palettemap` widget is best used in conjunction with an app that relies on a global palette for its styling and colors. Let's say somewhere in your app's widget hierarchy you have a widget like this:
```go
w := styled.New(x, gowid.MakePaletteRef("red"))
```
The widget `w` will obviously be rendered in `red` according to the app's palette. But if you wrap `w` in something like
```go
z := palettemap.New(w, palettemap.Map{"red": "green"}, palettemap.Map{})
```
Then when `w` is rendered and is the focus widget, the app's palette will be looked up with the name "green" instead. This provides a convenient way of changing the color of widgets, especially when they are in focus.
## pile
**Purpose**: arrange child widgets into horizontal bands, with configurable heights.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-helloworld`
- `github.com/gcla/gowid/examples/gowid-palette`
- `github.com/gcla/gowid/examples/gowid-widgets5`
- `github.com/gcla/gowid/examples/gowid-widgets6`
## progress
**Purpose**: a simple progress monitor.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-graph`
- `github.com/gcla/gowid/examples/gowid-widgets1`
Here is an example initialization of a progress bar:
```go
pb := progress.New(progress.Options{
Normal: gowid.MakePaletteRef("pg normal"),
Complete: gowid.MakePaletteRef("pg complete"),
})
```
The struct `progress.Options` is used to pass arguments to the progress bar. You can also set the target number of units for completion, and the current number of units completed. If target is not set, it will default to 100; if current is not set it will default to 0.
Any type implementing `progress.IWidget` can be rendered as a progress bar. Here is an example of how to customize the widget, from `gowid-widgets1`:
```go
type PBWidget struct {
*progress.Widget
}
func (w *PBWidget) Text() string {
cur, done := w.Progress(), w.Target()
percent := gwutil.Min(100, gwutil.Max(0, cur*100/done))
return fmt.Sprintf("At %d %% (%d/%d)", percent, cur, done)
}
func (w *PBWidget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas {
return progress.Render(w, size, focus, app)
}
```
The `Text()` method provides an alternative label inside the rendered progress bar. Note that we also provided a `Render()` function. The `Text()` function is called when rendering the progress bar, and we need our implementation to take effect. Without a dedicated `Render()` method for `PBWidget`, when the enclosing widget - presumably holding an `IWidget` type - calls `Render()`, the implementation will be provided by the embedded `*progress.Widget`, meaning that will be the receiver type when `Render()` is called. It will defer to the free function `progress.Render()`, but the `progress.IWidget` will hold a `*progress.Widget` not a `*PBWidget`, and so `progress.Render()` will not use our new `Text()` implementation. For a fuller explanation, see the [FAQ](FAQ).
## radio
**Purpose**: a widget that, as part of a group, can be in a selected state (if no others in the group are selected) or is otherwise unselected.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-graph`
- `github.com/gcla/gowid/examples/gowid-overlay2`
- `github.com/gcla/gowid/examples/gowid-palette`
- `github.com/gcla/gowid/examples/gowid-widgets3`
## selectable
**Purpose**: make a widget always be selectable, even if it rejects user input.
**Examples:**
- `github.com/gcla/gowid/examples/gowid-widgets2`
- `github.com/gcla/gowid/examples/gowid-dir`
- `github.com/gcla/gowid/examples/gowid-tree1`
## shadow
**Purpose**: adds a drop-shadow effect to a widget.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-graph`
- `github.com/gcla/gowid/widgets/dialog/dialog.go`
## styled
**Purpose**: apply foreground and background coloring and text styling to a widget.
**Examples:**
- `github.com/gcla/gowid/examples/gowid-dir`
- `github.com/gcla/gowid/examples/gowid-fib`
- `github.com/gcla/gowid/examples/gowid-helloworld`
- `github.com/gcla/gowid/examples/gowid-menu`
## table
**Purpose**: a widget to display tabular data in columns and rows.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-table`
Any type that implements the following interface can be used as the source for a table widget:
```go
type IModel interface {
Columns() int
RowIdentifier(row int) (RowId, bool) // return a unique ID for row
RowWidgets(row RowId) []gowid.IWidget // nil means EOD
HeaderWidgets() []gowid.IWidget // nil means no headers
VerticalSeparator() gowid.IWidget
HorizontalSeparator() gowid.IWidget
HeaderSeparator() gowid.IWidget
Widths() []gowid.IWidgetDimension
}
```
The interface distinguishes a row number (int) from a row identifier (RowId). In order to render the nth row, the widget asks the IModel for the identifier of the nth row. With that in hand, the widget then asks the IModel for the row widgets corresponding to the provided RowId - and with these it can render the row. For simple tables, the RowId value and row number will be the same - n for the nth row. For tables that support sorting (e.g. on a specific column), the underlying implementation of IModel can track the new ordering and ensure the correct row widgets are returned for the row to be displayed at the nth position - perhaps using a simple map.
Any implementation of IModel should consider caching the widgets returned via calls to `RowWidgets()`. If their state has changed from the default, then returning a cached widget when `RowWidgets()` is called will result in the display reflecting that changed state - color change, clicked checkboxes, etc. This can be seen in the `gowid-table` example. If the widgets are "read-only" and do not change state, then they can be safely generated anew each time `RowWidgets()` is called.
`HeaderWidgets()` should return an array of widgets used as column headers. It can also return `nil`, which means the rendered widget will have no column headers. `VerticalSeparator()`, `HorizontalSeparator()` and `HeaderSeparator()` can also return nil, if the cells don't need to be explicitly boxed or separated from the column headers.
`Widths()` determines how the row widgets are laid out.
- To use equal space for each column, return an array of `gowid.IRenderWithWeight` with value 1.
- To have a table column use a fixed number of display columns and overflow into subsequent display rows, return a `gowid.IRenderFlow` in that column's position.
- If a column widget is fixed (determines its own render size), return a `gowid.IRenderFixed` at that column's index.
Each table row is rendered using `columns.Widget`, so values suitable for column widths are also suitable for table widths.
An implementation of IModel that returns data from a CSV file is available as `table.NewCsvTable()`. Here is an example of its use:
```go
model := table.NewCsvTable(csvFile, table.SimpleOptions{
FirstLineIsHeaders: true,
Style: table.StyleOptions{
HorizontalSeparator: divider.NewAscii(),
TableSeparator: divider.NewUnicode(),
VerticalSeparator: fill.New('|'),
},
})
```
The `csvFile` argument should implement `io.Reader` and be suitable for processing by the standard library's `csv.NewReader()`. The table widget itself is then easily created:
```go
table := table.New(model)
```
## terminal
**Purpose**: a VT-220 capable terminal emulator widget, heavily plagiarized from urwid's `vterm.py`.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-terminal`
This widget lets you embed a featureful terminal - or collection of terminals - in your application. The `gowid-terminal` example included demonstrates a very simple copy of tmux - three terminals open running different programs. You can resize the terminal panes using the default hotkey `ctrl-b` and then hitting any of `>`, `<`, `+` or `-`.
You can have code execute on specific terminal events by registering callbacks:
- when the terminal's process exits
- when the terminal's bell rings
- when the terminal's title is set
You can create a terminal widget simply like this:
```go
tw, err := terminal.New("/bin/bash")
```
There is also a `NewExt()` if you need more control, which takes a `terminal.Params` struct:
```go
type Params struct {
Command []string
Env []string
HotKey IHotKeyProvider
HotKeyPersistence IHotKeyPersistence
}
```
With that you can provide the environment for the terminal's running process. When a terminal widget has focus, it makes sense for the terminal to be able to process all of the user's keypresses. The `HotKey` field lets you choose a specific keypress (a `tcell.Key`) that will temporarily cause the terminal widget to reject keyboard input. If you have a terminal embedded in your app, this gives the user an opportunity to switch focus to another widget using the keyboard, just like the default `ctrl-b` key in tmux. You can configure how long the hotkey keypress will remain in effect with the `HotKeyPersistence` field.
Terminal widgets expect to be rendered in box-mode. If your application reorganizes its widget layout, or perhaps if the user simply resizes the terminal window in which your app is running, the terminal widget(s) may be rendered with a different size than was used in the last call to `Render()`. `Gowid` will detect this and send `syscall.TIOCSWINSZ` to the underlying PTY.
When the process underlying the terminal starts running (at least before the first render), the widget will start a goroutine to read from the terminal's master file descriptor. During normal operation, the data read will be terminal-specific control codes. For some examples, see http://www.termsys.demon.co.uk/vtansi.htm. Many of these codes will represent characters that are to be emitted at the current cursor position on the terminal screen, advancing the cursor. Other codes will have a special meaning, like "move the cursor" or "erase part of the screen". The widget implements some simple state machines to track multi-byte sequences, such as the ANSI CSI codes - `ESC[3;4H` - "move the cursor to row 3 column 4". The full-set of `terminal.Widget`'s emulation amounts approximately to VT-220 support. The widget has been tested by running within it the standard `vttest` program and checking the output. All of the credit for this terminal code parsing and state tracking belong's to `urwid`s `vterm.py` implementation.
As with all other `gowid` widgets, `terminal.Widget` supports use of the mouse, where possible. The `gowid-terminal` example demonstrates this - try clicking inside `vim`, or change focus to `emacs` and do the same (you might need to run `M-x xterm-mouse-mode` first). There are several standards for encoding mouse events in the terminal. An SGR encoding of a left-mouse click, for example, might be `ESC[0;3;4M` - a click at row 4, column 3. An older style encoding might be `ESC[M $%` - where the click position is translated to a printable character. The terminal library underlying an application will typically send CSI codes to advertise the various modes the terminal supports and expects - `terminal.Widget` tracks these, and in particular which mouse mode is enabled. `Gowid`'s user input is provided via `tcell` APIs, meaning key-presses and mouse-clicks appear to `gowid` as one of `tcell.EventKey` or `tcell.EventMouse` - that is, because `gowid` runs on top of `tcell`, it does not see the exact byte sequences that the terminal containing the `app` generates. Instead it sees `tcell`'s representation. `terminal.Widget` will convert these `tcell` structs back into byte sequences to send to the widget's underlying terminals file descriptor, and will use its knowledge of the terminal's current mode to choose the correct conversion. To illustrate, let's say you have written a `gowid` application which embeds a `terminal.Widget`. When your app has started, `tcell` will have a PTY to talk to the terminal in which you started the application; and `terminal.Widget` will have a PTY to talk to the terminal running the command embedded in your widget. Your `gowid` application's `TERM` environment variable will determine which `terminfo` database is used to encode and decode terminal sequences to and from `tcell`. And the environment of the process running in your widget will determine the same for `gowid.Widget`. Let's say the user clicks a mouse button inside your widget.
1. Under your `gowid` app, `tcell` talks to the terminal. The app's `TERM` environment variable will determine the byte sequence `tcell` receives for the mouse event from the app's terminal.
2. `tcell` will translate that sequence into a `tcell.MouseEvent`
3. The `gowid` application will pass that event down through the widget hierarchy until it is accepted by the `terminal.Widget`
4. The `terminal.Widget` will understand from `tcell` that a mouse button has been clicked, and the coordinates of the click. `Gowid` itself will have translated the coordinates of the click as the event was pushed down through the widget hierarchy. The `terminal.Widget` will know the mouse-mode of its underlying terminal because it has tracked the CSI codes sent by its underlying terminal, determined by its process's `TERM` variable.
5. The `terminal.Widget` will convert the `tcell.EventMouse` back to a sequence of bytes according to the correct mouse mode, and send it to the underlying terminal's file descriptor.
The terminal widget defers most of its state tracking to a specialized implementation of `gowid.ICanvas`. The terminal canvas embeds a `gowid.Canvas`, which it renders as normal, but also contains the state-machines and logic to decode and encode terminal byte sequences. The terminal's canvas, when rendered, will always represent the latest state of the terminal underlying the widget. The code is in `github.com/gcla/gowid/widgets/terminal/term_canvas.go`. The terminal canvas implements `io.Writer` allowing a client to write ANSI codes using this standard Golang interface.
## text
**Purpose**: a widget to render text, optionally styled and aligned.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-widgets1`
- `github.com/gcla/gowid/examples/gowid-widgets2`
- `github.com/gcla/gowid/examples/gowid-widgets3`
- `github.com/gcla/gowid/examples/gowid-widgets4`
This widget provides a way to render styled and unstyled text. It represents text using this interface:
```go
type IContent interface {
Length() int
ChrAt(idx int) rune
RangeOver(start, end int, attrs gowid.IRenderContext, proc gowid.ICellProcessor)
AddAt(idx int, markup ContentSegment)
DeleteAt(idx, length int)
fmt.Stringer
}
```
The user constructs the widget by providing an array of `ContentSegment` each of which is a string with an associated `gowid.ICellStyler`. When rendering, the widget will built an array of `Cell` using the `RangeOver()` function - that will use the supplied `IRenderContext` (implemented by `gowid.App`) to turn each rune of the markup, along with its `ICellStyler` into a `Cell` - respecting the current color mode of the terminal.
Here is an example of how to build a simple text widget:
```go
w := text.New("Do you want to quit?")
```
Here is an example of how to build a styled text widget:
```go
w := text.NewFromContent(
text.NewContent([]text.ContentSegment{
text.StyledContent("hello", gowid.MakePaletteRef("red")),
text.StringContent(" "),
text.StyledContent("world", gowid.MakePaletteRef("green")),
}))
```
There is also a `NewExt()` function that you can supply with these arguments:
```go
type Options struct {
Wrap WrapType
Align gowid.IHAlignment
}
```
- Wrap supports `WrapAny` meaning text will be wrapped to the next line, and `WrapClip` which means the text will be clipped at the end of the current line (and so will render to one canvas line only).
- Align supports any of `HAlignLeft`, `HAlignRight` and `HAlignMiddle`. This option can be used to e.g. center each rendered line of text by sharing the white-space at either edge.
## tree
**Purpose**: a generalization of the `list` widget to render a tree structure.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-dir`
- `github.com/gcla/gowid/examples/gowid-tree1`
## vpadding
**Purpose**: a widget to render and align a child widget vertically in a wider space.
**Examples:**
- `github.com/gcla/gowid/examples/gowid-helloworld`
- `github.com/gcla/gowid/examples/gowid-overlay2`
- `github.com/gcla/gowid/examples/gowid-widgets1`
## vscroll
**Purpose**: a vertical scroll bar with clickable arrows on either end.

**Examples:**
- `github.com/gcla/gowid/examples/gowid-editor`
gowid-1.4.0/examples/ 0000775 0000000 0000000 00000000000 14262344540 0014447 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-asciigraph/ 0000775 0000000 0000000 00000000000 14262344540 0017670 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-asciigraph/asciigraph.go 0000664 0000000 0000000 00000002236 14262344540 0022334 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// An example of the gowid asciigraph widget which relies upon the
// asciigraph package at github.com/guptarohit/asciigraph.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/asciigraph"
asc "github.com/guptarohit/asciigraph"
log "github.com/sirupsen/logrus"
)
//======================================================================
var app *gowid.App
//======================================================================
func main() {
var err error
f := examples.RedirectLogger("asciigraph.log")
defer f.Close()
palette := gowid.Palette{}
data := []float64{2, 1, 1, 2, -2, 5, 7, 11, 3, 7, 1}
graph := asciigraph.New(data, []asc.Option{})
app, err = gowid.NewApp(gowid.AppArgs{
View: graph,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-dir/ 0000775 0000000 0000000 00000000000 14262344540 0016334 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-dir/dir.go 0000664 0000000 0000000 00000023430 14262344540 0017443 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A simple gowid directory browser.
package main
import (
"fmt"
"io/ioutil"
"os"
"syscall"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/gwutil"
"github.com/gcla/gowid/widgets/boxadapter"
"github.com/gcla/gowid/widgets/button"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/list"
"github.com/gcla/gowid/widgets/selectable"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/tree"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
var pos *tree.TreePos
var tb *list.Widget
var parent1 *DirTree
var walker *tree.TreeWalker
var dirname = kingpin.Arg("dir", "Directory to scan.").Required().String()
var expandedCache map[string]int
//======================================================================
func IsDir(name string) bool {
res := false
if f, err := os.OpenFile(name, syscall.O_NONBLOCK|os.O_RDONLY, 0); err == nil {
defer f.Close()
if st, err2 := f.Stat(); err2 == nil {
if st.IsDir() {
res = true
}
}
}
return res
}
func NodeState(name string, cache map[string]int) int {
state := notDir
if lookup, ok := cache[name]; ok {
state = lookup
} else {
if IsDir(name) {
state = collapsed
} else {
state = notDir
}
cache[name] = state
}
return state
}
//======================================================================
type NoIterator struct{}
func (d *NoIterator) Next() bool {
return false
}
func (d *NoIterator) Value() tree.IModel {
panic("Do not call")
}
//======================================================================
const (
notDir = iota
collapsed
expanded
)
type DirIterator struct {
init bool
parent tree.ICollapsible
files []os.FileInfo
pos int
cache *map[string]int // shared - track whether any path is collapsed or expanded
}
func (d *DirIterator) FullPath() string {
return d.parent.(*DirTree).FullPath() + "/" + d.files[d.pos].Name()
}
func (d *DirIterator) Next() bool {
if !d.init {
// Ignore the error because if there's a problem, we'll simply have files thus no children
d.files, _ = ioutil.ReadDir(d.parent.(*DirTree).FullPath())
d.init = true
}
d.pos++
return !d.parent.IsCollapsed() && d.pos < len(d.files)
}
func (d *DirIterator) Value() tree.IModel {
var res tree.IModel
state := NodeState(d.FullPath(), *d.cache)
switch state {
case notDir:
res = &DirTree{
name: d.files[d.pos].Name(),
cache: d.cache,
parent: d.parent,
notDir: true,
}
default:
res = &DirTree{
name: d.files[d.pos].Name(),
cache: d.cache,
parent: d.parent,
}
}
return res
}
//======================================================================
type DirTree struct {
parent tree.IModel
name string
iter *DirIterator
notDir bool
cache *map[string]int // shared - track whether any path is collapsed or expanded
}
var _ tree.ICollapsible = (*DirTree)(nil)
func (d *DirTree) FullPath() string {
if d.parent == nil {
return d.name
} else {
return d.parent.(*DirTree).FullPath() + "/" + d.name
}
}
func (d *DirTree) Leaf() string {
return d.name
}
func (d *DirTree) String() string {
return d.name
}
func (d *DirTree) Children() tree.IIterator {
var res tree.IIterator
res = &NoIterator{}
if d.iter != nil {
d.iter.pos = -1
res = d.iter
} else {
state := NodeState(d.FullPath(), *d.cache)
if state != notDir {
d.iter = &DirIterator{
init: false,
pos: -1,
cache: d.cache,
parent: d,
}
res = d.iter
}
}
return res
}
func (d *DirTree) IsCollapsed() bool {
fp := d.FullPath()
if v, res := (*d.cache)[fp]; res {
return (v == collapsed)
} else {
return true
}
}
func (d *DirTree) SetCollapsed(app gowid.IApp, isCollapsed bool) {
fp := d.FullPath()
if isCollapsed {
(*d.cache)[fp] = collapsed
} else {
(*d.cache)[fp] = expanded
}
}
//======================================================================
// DirButton is a button widget that provides its own ID function, rather than relying on the default
// address of an embedded struct. This is in case the button widget isn't the exact same object when
// the mouse is clicked and when the mouse is released. Instead, the pathname is used as the ID, ensuring
// that if the user clicks on "/tmp/foo" then releases the mouse button on "/tmp/foo", the button's
// Click() method will be called. We need to provide a wrapper for UserInput(), else UserInput() will come
// from the embedded *button.Widget, which will then result in the IButtonWidget interface being
// built from *button.Widget and not DirButton.
//
type DirButton struct {
*button.Widget
id string
}
func (d *DirButton) ID() interface{} {
return d.id
}
func (d *DirButton) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool {
return button.UserInput(d, ev, size, focus, app)
}
//======================================================================
func MakeDecoration(pos tree.IPos, tr tree.IModel, wmaker tree.IWidgetMaker) gowid.IWidget {
var res gowid.IWidget
level := -1
for cur := pos; cur != nil; cur = tree.ParentPosition(cur) {
level += 1
}
pad := gwutil.StringOfLength(' ', level*3)
lineWidgets := make([]gowid.IContainerWidget, 0)
// indentation
lineWidgets = append(lineWidgets, &gowid.ContainerWidget{
IWidget: text.New(pad),
D: gowid.RenderWithUnits{U: len(pad)},
})
if ctree2, ok := tr.(tree.ICollapsible); ok {
ctree := ctree2.(*DirTree)
if !ctree.notDir {
btnChr := gwutil.If(ctree.IsCollapsed(), "+", "-").(string)
dirButton := &DirButton{button.New(text.New(btnChr)), pos.String()}
// If I use one button with conditional logic in the callback, rather than make
// a separate button depending on whether or not the tree is collapsed, it will
// correctly work when the DecoratorMaker is caching the widgets i.e. it will
// collapse or expand even when the widget is rendered from the cache
dirButton.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
// Note that I don't change the button widget itself ([+]/[-]) - just the underlying model, from which
// the widget will be recreated
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ctree.SetCollapsed(app, !ctree.IsCollapsed())
}))
}})
coloredDirButton := styled.NewExt(dirButton, gowid.MakePaletteRef("body"), gowid.MakePaletteRef("selected"))
// [+] / [-] if the widget is a directory
lineWidgets = append(lineWidgets, &gowid.ContainerWidget{
IWidget: coloredDirButton,
D: gowid.RenderFixed{},
})
lineWidgets = append(lineWidgets, &gowid.ContainerWidget{
IWidget: text.New(" "),
D: gowid.RenderFixed{},
})
}
}
inner := wmaker.MakeWidget(pos, tr)
// filename/dirname
lineWidgets = append(lineWidgets, &gowid.ContainerWidget{
IWidget: inner,
D: gowid.RenderFixed{},
})
line := columns.New(lineWidgets)
res = line
res = boxadapter.New(res, 1) // force the widget to be on one line
return res
}
func MakeWidget(pos tree.IPos, tr tree.IModel) gowid.IWidget {
var res gowid.IWidget
pr := "body"
ctree := tr.(*DirTree)
if !ctree.notDir {
pr = "dirbody"
}
cwidgets := make([]gowid.IContainerWidget, 1)
cwidgets[0] = &gowid.ContainerWidget{
IWidget: styled.NewExt(
selectable.New(
styled.NewExt(
text.New(
tr.Leaf(),
),
gowid.MakePaletteRef(pr), gowid.MakePaletteRef("selected"),
),
),
gowid.MakePaletteRef("body"), gowid.MakePaletteRef("selected"),
),
D: gowid.RenderFixed{},
}
res = columns.New(cwidgets)
return res
}
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
handled := false
if evk, ok := ev.(*tcell.EventKey); ok {
handled = true
if evk.Key() == tcell.KeyCtrlC || evk.Rune() == 'q' || evk.Rune() == 'Q' {
app.Quit()
} else if evk.Rune() == 'x' {
pos := walker.Focus()
tpos := pos.(tree.IPos)
itree := tpos.GetSubStructure(parent1)
if ctree, ok := itree.(tree.ICollapsible); ok {
ctree.SetCollapsed(app, true)
}
} else if evk.Rune() == 'z' {
pos := walker.Focus()
tpos := pos.(tree.IPos)
itree := tpos.GetSubStructure(parent1)
if ctree, ok := itree.(tree.ICollapsible); ok {
ctree.SetCollapsed(app, false)
}
} else {
handled = false
}
}
return handled
}
//======================================================================
func main() {
kingpin.Parse()
if _, err := os.Stat(*dirname); os.IsNotExist(err) {
fmt.Printf("Directory \"%v\" does not exist.", *dirname)
os.Exit(1)
}
f := examples.RedirectLogger("dir.log")
defer f.Close()
palette := gowid.Palette{
"body": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack),
"dirbody": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorCyan),
"selected": gowid.MakePaletteEntry(gowid.ColorDefault, gowid.ColorWhite),
}
expandedCache := make(map[string]int)
// We start expanded at the top level
expandedCache[*dirname] = expanded
body := gowid.MakePaletteRef("body")
parent1 = &DirTree{
name: *dirname,
cache: &expandedCache,
}
pos = tree.NewPos()
walker = tree.NewWalker(parent1, pos,
tree.NewCachingMaker(tree.WidgetMakerFunction(MakeWidget)),
tree.NewCachingDecorator(tree.DecoratorFunction(MakeDecoration)))
tb = tree.New(walker)
view := styled.New(tb, body)
app, err := gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.MainLoop(handler{})
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-editor/ 0000775 0000000 0000000 00000000000 14262344540 0017044 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-editor/editor.go 0000664 0000000 0000000 00000017604 14262344540 0020671 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A poor-man's editor using gowid widgets - shows dialog, edit and vscroll.
package main
import (
"fmt"
"io"
"os"
"sync"
"time"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/gwutil"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/dialog"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/holder"
"github.com/gcla/gowid/widgets/hpadding"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vscroll"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
//======================================================================
var editWidget *edit.Widget
var footerContent []text.ContentSegment
var footerText *styled.Widget
var yesno *dialog.Widget
var viewHolder *holder.Widget
var app *gowid.App
var wg sync.WaitGroup
var updates chan string
var filename = kingpin.Arg("file", "File to edit.").Required().String()
//======================================================================
func updateStatusBar(message string) {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
footerContent[1].Text = message
footerWidget2 := text.NewFromContent(text.NewContent(footerContent))
footerText.SetSubWidget(footerWidget2, app)
}))
}
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
handled := false
if evk, ok := ev.(*tcell.EventKey); ok {
if evk.Key() == tcell.KeyEsc {
handled = true
app.Quit()
}
switch evk.Key() {
case tcell.KeyCtrlC:
handled = true
msg := text.New("Do you want to quit?")
yesno = dialog.New(
framed.NewSpace(hpadding.New(msg, gowid.HAlignMiddle{}, gowid.RenderFixed{})),
dialog.Options{
Buttons: dialog.OkCancel,
},
)
yesno.Open(viewHolder, gowid.RenderWithRatio{R: 0.5}, app)
case tcell.KeyCtrlF:
handled = true
fi, err := os.Open(*filename)
if err != nil {
fi, err = os.Create(*filename)
if err != nil {
updates <- fmt.Sprintf("(FAILED to create %s)", *filename)
}
}
if err == nil {
defer fi.Close()
_, err = io.Copy(&edit.Writer{editWidget, app}, fi)
if err != nil {
updates <- fmt.Sprintf("(FAILED to load %s)", *filename)
} else {
updates <- "(OPENED)"
}
}
case tcell.KeyCtrlS:
handled = true
fo, err := os.Create(*filename)
if err != nil {
updates <- fmt.Sprintf("(FAILED to create %s)", *filename)
} else {
defer fo.Close()
_, err = io.Copy(fo, editWidget)
if err != nil {
updates <- fmt.Sprintf("(FAILED to write %s)", *filename)
} else {
updates <- "(SAVED!)"
}
}
}
}
return handled
}
//======================================================================
type EditWithScrollbar struct {
*columns.Widget
e *edit.Widget
sb *vscroll.Widget
goUpDown int // positive means down
pgUpDown int // positive means down
}
func NewEditWithScrollbar(e *edit.Widget) *EditWithScrollbar {
sb := vscroll.NewExt(vscroll.VerticalScrollbarUnicodeRunes)
res := &EditWithScrollbar{
columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{e, gowid.RenderWithWeight{W: 1}},
&gowid.ContainerWidget{sb, gowid.RenderWithUnits{U: 1}},
}),
e, sb, 0, 0,
}
sb.OnClickAbove(gowid.WidgetCallback{"cb", res.clickUp})
sb.OnClickBelow(gowid.WidgetCallback{"cb", res.clickDown})
sb.OnClickUpArrow(gowid.WidgetCallback{"cb", res.clickUpArrow})
sb.OnClickDownArrow(gowid.WidgetCallback{"cb", res.clickDownArrow})
return res
}
func (e *EditWithScrollbar) clickUp(app gowid.IApp, w gowid.IWidget) {
e.pgUpDown -= 1
}
func (e *EditWithScrollbar) clickDown(app gowid.IApp, w gowid.IWidget) {
e.pgUpDown += 1
}
func (e *EditWithScrollbar) clickUpArrow(app gowid.IApp, w gowid.IWidget) {
e.goUpDown -= 1
}
func (e *EditWithScrollbar) clickDownArrow(app gowid.IApp, w gowid.IWidget) {
e.goUpDown += 1
}
// gcdoc - do this so columns navigation e.g. ctrl-f doesn't get passed to columns
func (w *EditWithScrollbar) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool {
// Stop these keys moving focus in the columns used by this widget. C-f is used to
// open a file.
if evk, ok := ev.(*tcell.EventKey); ok {
switch evk.Key() {
case tcell.KeyCtrlF, tcell.KeyCtrlB:
return false
}
}
box, _ := size.(gowid.IRenderBox)
w.sb.Top, w.sb.Middle, w.sb.Bottom = w.e.CalculateTopMiddleBottom(gowid.MakeRenderBox(box.BoxColumns()-1, box.BoxRows()))
res := w.Widget.UserInput(ev, size, focus, app)
if res {
w.Widget.SetFocus(app, 0)
}
return res
}
func (w *EditWithScrollbar) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas {
box, _ := size.(gowid.IRenderBox)
ecols := box.BoxColumns() - 1
ebox := gowid.MakeRenderBox(ecols, box.BoxRows())
if w.goUpDown != 0 || w.pgUpDown != 0 {
w.e.SetLinesFromTop(gwutil.Max(0, w.e.LinesFromTop()+w.goUpDown+(w.pgUpDown*box.BoxRows())), app)
txt := w.e.MakeText()
layout := text.MakeTextLayout(txt.Content(), ecols, txt.Wrap(), gowid.HAlignLeft{})
_, y := text.GetCoordsFromCursorPos(w.e.CursorPos(), ecols, layout, w.e)
if y < w.e.LinesFromTop() {
for i := y; i < w.e.LinesFromTop(); i++ {
w.e.DownLines(ebox, false, app)
}
} else if y >= w.e.LinesFromTop()+box.BoxRows() {
for i := w.e.LinesFromTop() + box.BoxRows(); i <= y; i++ {
w.e.UpLines(ebox, false, app)
}
}
}
w.goUpDown = 0
w.pgUpDown = 0
w.sb.Top, w.sb.Middle, w.sb.Bottom = w.e.CalculateTopMiddleBottom(ebox)
canvas := w.Widget.Render(size, focus, app)
return canvas
}
//======================================================================
func main() {
var err error
kingpin.Parse()
if _, err := os.Stat(*filename); os.IsNotExist(err) {
fmt.Printf("Requested file \"%v\" does not exist.", *filename)
os.Exit(1)
}
f := examples.RedirectLogger("editor.log")
defer f.Close()
palette := gowid.Palette{
"mainpane": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.NewUrwidColor("light gray")),
"cyan": gowid.MakePaletteEntry(gowid.ColorCyan, gowid.ColorBlack),
"inv": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack),
}
editWidget = edit.New()
footerContent = []text.ContentSegment{
text.StyledContent("Gowid Text Editor. ", gowid.MakePaletteRef("inv")),
text.StyledContent("", gowid.MakePaletteRef("cyan")), // emptyContent,
text.StyledContent(" C-c to exit. C-s to save. C-f to open test file.", gowid.MakePaletteRef("inv")),
}
footerText = styled.New(
text.NewFromContent(
text.NewContent(footerContent),
),
gowid.MakePaletteRef("inv"),
)
mainView := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{
styled.New(
framed.NewUnicode(
NewEditWithScrollbar(editWidget),
),
gowid.MakePaletteRef("mainpane"),
),
gowid.RenderWithWeight{1},
},
&gowid.ContainerWidget{
footerText,
gowid.RenderFlow{},
},
})
viewHolder = holder.New(mainView)
updates = make(chan string)
wg.Add(1)
go func() {
defer wg.Done()
for {
status := <-updates
if status == "done" {
break
} else if status == "clear" {
updateStatusBar("")
} else {
updateStatusBar(status)
go func() {
timer := time.NewTimer(time.Second)
<-timer.C
updates <- "clear"
}()
}
}
}()
app, err = gowid.NewApp(gowid.AppArgs{
View: viewHolder,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.MainLoop(handler{})
updates <- "done"
wg.Wait()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-fib/ 0000775 0000000 0000000 00000000000 14262344540 0016316 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-fib/fib.go 0000664 0000000 0000000 00000011010 14262344540 0017376 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A port of urwid's fib.py example using gowid widgets.
package main
import (
"fmt"
"math/big"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/hpadding"
"github.com/gcla/gowid/widgets/list"
"github.com/gcla/gowid/widgets/palettemap"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/selectable"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
log "github.com/sirupsen/logrus"
)
//======================================================================
type FibWalker struct {
a, b big.Int
}
var _ list.IWalker = (*FibWalker)(nil)
var _ list.IWalkerPosition = FibWalker{}
var _ list.IWalkerHome = FibWalker{}
func (f FibWalker) First() list.IWalkerPosition {
return FibWalker{*big.NewInt(0), *big.NewInt(1)}
}
func (f FibWalker) Equal(other list.IWalkerPosition) bool {
switch o := other.(type) {
case FibWalker:
return (f.a.Cmp(&o.a) == 0) && (f.b.Cmp(&o.b) == 0)
default:
return false
}
}
func (f FibWalker) GreaterThan(other list.IWalkerPosition) bool {
switch o := other.(type) {
case FibWalker:
return (f.b.Cmp(&o.b) > 0)
default:
panic(fmt.Errorf("Invalid type to compare against FibWalker - %T", other))
}
}
func getWidget(f FibWalker) gowid.IWidget {
var res gowid.IWidget
res = selectable.New(
palettemap.New(
hpadding.New(
text.NewFromContent(
text.NewContent([]text.ContentSegment{
text.StyledContent(f.b.Text(10), gowid.MakePaletteRef("body")),
}),
),
gowid.HAlignRight{}, gowid.RenderFlow{},
),
palettemap.Map{"body": "fbody"},
palettemap.Map{},
),
)
return res
}
func (f *FibWalker) At(pos list.IWalkerPosition) gowid.IWidget {
f2 := pos.(FibWalker)
return getWidget(f2)
}
func (f *FibWalker) Focus() list.IWalkerPosition {
return *f
}
func (f *FibWalker) SetFocus(pos list.IWalkerPosition, app gowid.IApp) {
*f = pos.(FibWalker)
}
func (f *FibWalker) Next(pos list.IWalkerPosition) list.IWalkerPosition {
fc := pos.(FibWalker)
var sum big.Int
sum.Add(&fc.a, &fc.b)
fn := FibWalker{fc.b, sum}
return fn
}
func (f *FibWalker) Previous(pos list.IWalkerPosition) list.IWalkerPosition {
fc := pos.(FibWalker)
var diff big.Int
diff.Sub(&fc.b, &fc.a)
fn := FibWalker{diff, fc.a}
return fn
}
//======================================================================
func main() {
f := examples.RedirectLogger("fib.log")
defer f.Close()
palette := gowid.Palette{
"title": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack),
"key": gowid.MakePaletteEntry(gowid.ColorCyan, gowid.ColorBlack),
"foot": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack),
"body": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorCyan),
"fbody": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack),
}
key := gowid.MakePaletteRef("key")
foot := gowid.MakePaletteRef("foot")
title := gowid.MakePaletteRef("title")
body := gowid.MakePaletteRef("body")
footerContent := []text.ContentSegment{
text.StyledContent("Fibonacci Set Viewer", title),
text.StringContent(" "),
text.StyledContent("UP", key),
text.StringContent(", "),
text.StyledContent("DOWN", key),
text.StringContent(", "),
text.StyledContent("PAGE_UP", key),
text.StringContent(", "),
text.StyledContent("PAGE_DOWN", key),
text.StringContent(", "),
text.StyledContent("HOME", key),
text.StringContent(", "),
text.StyledContent("CTRL-L", key),
text.StringContent(" move view "),
text.StyledContent("Q", key),
text.StringContent(" exits. Try the mouse wheel."),
}
footerText := styled.New(text.NewFromContent(text.NewContent(footerContent)), foot)
walker := FibWalker{*big.NewInt(0), *big.NewInt(1)}
lb := list.New(&walker)
styledLb := styled.New(lb, body)
lb.OnFocusChanged(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
log.Infof("Focus changed - widget is now %p", w)
}})
view := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{
IWidget: styledLb,
D: gowid.RenderWithWeight{W: 1},
},
&gowid.ContainerWidget{
IWidget: footerText,
D: gowid.RenderFlow{},
},
})
app, err := gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-graph/ 0000775 0000000 0000000 00000000000 14262344540 0016657 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-graph/graph.go 0000664 0000000 0000000 00000026331 14262344540 0020314 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A port of urwid's graph.py example using gowid widgets.
package main
import (
"math"
"time"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/gwutil"
"github.com/gcla/gowid/widgets/bargraph"
"github.com/gcla/gowid/widgets/button"
"github.com/gcla/gowid/widgets/clicktracker"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/fill"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/hpadding"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/progress"
"github.com/gcla/gowid/widgets/radio"
"github.com/gcla/gowid/widgets/shadow"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
log "github.com/sirupsen/logrus"
)
//======================================================================
var app *gowid.App
var controller *GraphController
//======================================================================
func sin100(x int) int {
return int(50 + 50*math.Sin(float64(x)*math.Pi/50.0))
}
func round(f float64) float64 {
return math.Floor(f + 0.5)
}
func sum(x ...int) int {
res := 0
for _, i := range x {
res += i
}
return res
}
//======================================================================
type GraphModel struct {
Data map[string][]int
Modes []string
CurrentMode string
}
func NewGraphModel() *GraphModel {
modes := make([]string, 0)
data := make(map[string][]int)
var a1 []int
for i := 0; i < 50; i++ {
a1 = append(a1, i*2)
}
a2 := append(a1, a1...)
data["Saw"] = a2
modes = append(modes, "Saw")
var a3 []int
var a4 []int
for i := 0; i < 30; i++ {
a3 = append(a3, 0)
a4 = append(a4, 100)
}
data["Square"] = append(a3, a4...)
modes = append(modes, "Square")
var a5 []int
for i := 0; i < 100; i++ {
a5 = append(a5, sin100(i))
}
data["Sine 1"] = a5
modes = append(modes, "Sine 1")
var a6 []int
for i := 0; i < 100; i++ {
a6 = append(a6, (sin100(i)+sin100(i*2))/2)
}
data["Sine 2"] = a6
modes = append(modes, "Sine 2")
var a7 []int
for i := 0; i < 100; i++ {
a7 = append(a7, (sin100(i)+sin100(i*3))/2)
}
data["Sine 3"] = a7
modes = append(modes, "Sine 3")
return &GraphModel{data, modes, modes[0]}
}
func (g *GraphModel) SetMode(mode string) {
g.CurrentMode = mode
}
func (g *GraphModel) GetData(offset, r int) ([]int, int, int) {
l := make([]int, 0)
d := g.Data[g.CurrentMode]
for r > 0 {
offset = offset % len(d)
segment := d[offset:gwutil.Min(offset+r, len(d))]
if len(segment) == 0 {
break
}
r = r - len(segment)
offset += len(segment)
l = append(l, segment...)
}
return l, 100, len(d)
}
//======================================================================
type GraphView struct {
*styled.Widget
controller *GraphController
started bool
startTime *time.Time
offset int
lastOffset *int
graph *bargraph.Widget
pb *progress.Widget
}
func NewGraphView(controller *GraphController) *GraphView {
t := time.Now()
pb := progress.New(progress.Options{
Normal: gowid.MakePaletteRef("pg normal"),
Complete: gowid.MakePaletteRef("pg complete"),
})
graph := MakeBarGraph()
controls := MakeBarGraphControls(controller, pb)
weight1 := gowid.RenderWithWeight{1}
weight2 := gowid.RenderWithWeight{2}
unit1 := gowid.RenderWithUnits{U: 1}
vline := styled.New(fill.New('│'), gowid.MakePaletteRef("line"))
view := styled.New(
framed.NewSpace(
shadow.New(
styled.New(
framed.NewUnicode(
columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{graph, weight2},
&gowid.ContainerWidget{vline, unit1},
&gowid.ContainerWidget{controls, weight1},
}),
),
gowid.MakePaletteRef("body"),
),
1,
),
),
gowid.MakePaletteRef("screen edge"),
)
res := &GraphView{
Widget: view,
controller: controller,
startTime: &t,
graph: graph,
pb: pb,
}
return res
}
func (g *GraphView) Selectable() bool {
return true
}
func (g *GraphView) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool {
return g.Widget.UserInput(ev, size, focus, app)
}
func MakeBarGraph() *bargraph.Widget {
w := bargraph.New([]gowid.IColor{
gowid.NewUrwidColor("dark gray"),
gowid.NewUrwidColor("dark blue"),
gowid.NewUrwidColor("dark cyan"),
})
return w
}
func MakeBarGraphControls(controller *GraphController, pb progress.IWidget) *pile.Widget {
modeButtons := make([]gowid.IContainerWidget, 0)
rbgroup := make([]radio.IWidget, 0)
p := gowid.RenderFixed{}
f := gowid.RenderFlow{}
var firstrb *radio.Widget
for _, mode := range controller.model.Modes {
capturedMode := mode
rb1 := radio.New(&rbgroup)
if firstrb == nil {
firstrb = rb1
}
rbt1 := text.New(" " + mode)
rb1.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
controller.model.SetMode(capturedMode)
controller.view.UpdateGraph(true, app)
controller.view.lastOffset = nil
}})
modeButton := make([]gowid.IContainerWidget, 0)
modeButton = append(modeButton, &gowid.ContainerWidget{rb1, p})
modeButton = append(modeButton, &gowid.ContainerWidget{rbt1, p})
modeButtonCols := styled.NewExt(columns.New(modeButton),
gowid.MakePaletteRef("button normal"),
gowid.MakePaletteRef("button select"))
modeButtons = append(modeButtons, &gowid.ContainerWidget{modeButtonCols, f})
}
animateText := text.New("Start")
animateButton := button.New(animateText)
resetText := text.New("Reset")
resetButton := button.New(resetText)
quitText := text.New("Quit")
quitButton := button.New(quitText)
animateButton.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
if animateText.Content().Length() == 5 {
controller.AnimateGraph(app)
animateText.SetText("Stop", app)
} else {
controller.StopAnimation()
animateText.SetText("Start", app)
}
}})
resetButton.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
controller.ResetGraph(app)
}})
quitButton.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
app.Quit()
}})
animateButtonStyled := styled.NewExt(animateButton,
gowid.MakePaletteRef("button normal"),
gowid.MakePaletteRef("button select"))
resetButtonStyled := styled.NewExt(resetButton,
gowid.MakePaletteRef("button normal"),
gowid.MakePaletteRef("button select"))
quitButtonStyled := styled.NewExt(quitButton,
gowid.MakePaletteRef("button normal"),
gowid.MakePaletteRef("button select"))
animateButtonTracker := clicktracker.New(animateButtonStyled)
resetButtonTracker := clicktracker.New(resetButtonStyled)
quitButtonTracker := clicktracker.New(quitButtonStyled)
buttonGrid := columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{hpadding.New(animateButtonTracker, gowid.HAlignMiddle{}, p), gowid.RenderWithWeight{1}},
&gowid.ContainerWidget{hpadding.New(resetButtonTracker, gowid.HAlignMiddle{}, p), gowid.RenderWithWeight{1}},
})
controls := make([]gowid.IContainerWidget, 0, 7+len(modeButtons))
controls = append(controls, modeButtons...)
controls = append(controls, &gowid.ContainerWidget{divider.NewBlank(), f})
controls = append(controls, &gowid.ContainerWidget{hpadding.New(text.New("Animation"), gowid.HAlignMiddle{}, p), f})
controls = append(controls, &gowid.ContainerWidget{buttonGrid, f})
controls = append(controls, &gowid.ContainerWidget{divider.NewBlank(), f})
controls = append(controls, &gowid.ContainerWidget{pb, f})
controls = append(controls, &gowid.ContainerWidget{divider.NewBlank(), f})
controls = append(controls, &gowid.ContainerWidget{hpadding.New(quitButtonTracker, gowid.HAlignMiddle{}, p), f})
controlsPile := pile.New(controls)
return controlsPile
}
func (v *GraphView) GetOffsetNow() int {
if v.startTime == nil {
return 0
}
if !v.started {
return v.offset
}
tdelta := time.Now().Sub(*v.startTime)
tdelta = tdelta * 5
x := v.offset + (int(round(tdelta.Seconds())))
return x
}
func (v *GraphView) UpdateGraph(forceUpdate bool, app gowid.IApp) bool {
o := v.GetOffsetNow()
if v.lastOffset != nil && o == *v.lastOffset && !forceUpdate {
return false
}
v.lastOffset = &o
gspb := 10
r := gspb * 5
d, maxValue, repeat := v.controller.GetData(o, r)
l := make([][]int, 0)
for n := 0; n < 5; n++ {
value := sum(d[n*gspb:(n+1)*gspb]...) / gspb
// toggle between two bar types
if n&1 == 1 {
l = append(l, []int{0, value})
} else {
l = append(l, []int{value, 0})
}
}
v.graph.SetData(l, maxValue, app)
var prog int
// also update progress
if (o/repeat)&1 == 1 {
// show 100% for first half, 0 for second half
if o%repeat > repeat {
prog = 0
} else {
prog = 100
}
} else {
prog = ((o % repeat) * 100) / repeat
}
v.pb.SetProgress(app, prog)
return true
}
//======================================================================
type GraphController struct {
model *GraphModel
view *GraphView
mode string
ticker *time.Ticker
}
func NewGraphController() *GraphController {
res := &GraphController{NewGraphModel(), nil, "", nil}
view := NewGraphView(res)
res.view = view
res.mode = res.model.Modes[0]
return res
}
func (g *GraphController) GetData(offset, r int) ([]int, int, int) {
return g.model.GetData(offset, r)
}
func (g *GraphController) ResetGraph(app gowid.IApp) {
t := time.Now()
g.view.startTime = &t
g.view.offset = 0
g.view.UpdateGraph(true, app)
}
func (g *GraphController) AnimateGraph(app gowid.IApp) {
t := time.Now()
g.view.startTime = &t
g.ticker = time.NewTicker(time.Millisecond * 200)
g.view.started = true
go func() {
for _ = range g.ticker.C {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
g.view.UpdateGraph(true, app)
app.Redraw()
}))
}
}()
}
func (g *GraphController) StopAnimation() {
g.ticker.Stop()
g.view.offset = g.view.GetOffsetNow()
g.view.started = false
}
//======================================================================
func main() {
var err error
f := examples.RedirectLogger("graph.log")
defer f.Close()
palette := gowid.Palette{
"body": gowid.MakeStyledPaletteEntry(gowid.NewUrwidColor("black"), gowid.NewUrwidColor("light gray"), gowid.StyleBold),
"line": gowid.MakePaletteRef("body"),
"button normal": gowid.MakeStyledPaletteEntry(gowid.NewUrwidColor("light gray"), gowid.NewUrwidColor("dark blue"), gowid.StyleBold),
"button select": gowid.MakePaletteEntry(gowid.NewUrwidColor("white"), gowid.NewUrwidColor("dark green")),
"pg normal": gowid.MakeStyledPaletteEntry(gowid.NewUrwidColor("white"), gowid.NewUrwidColor("black"), gowid.StyleBold),
"pg complete": gowid.MakeStyleMod(gowid.MakePaletteRef("pg normal"), gowid.MakeBackground(gowid.NewUrwidColor("dark magenta"))),
"screen edge": gowid.MakePaletteEntry(gowid.NewUrwidColor("light blue"), gowid.NewUrwidColor("dark cyan")),
}
controller = NewGraphController()
app, err = gowid.NewApp(gowid.AppArgs{
View: controller.view,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
controller.ResetGraph(app)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-helloworld/ 0000775 0000000 0000000 00000000000 14262344540 0017731 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-helloworld/helloworld.go 0000664 0000000 0000000 00000004210 14262344540 0022430 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A port of urwid's "Hello World" example from the tutorial, using gowid widgets.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
)
//======================================================================
func main() {
palette := gowid.Palette{
"banner": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.MakeRGBColor("#60d")),
"streak": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#60a")),
"inside": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#808")),
"outside": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#a06")),
"bg": gowid.MakePaletteEntry(gowid.ColorNone, gowid.MakeRGBColor("#d06")),
}
div := divider.NewBlank()
outside := styled.New(div, gowid.MakePaletteRef("outside"))
inside := styled.New(div, gowid.MakePaletteRef("inside"))
helloworld := styled.New(
text.NewFromContentExt(
text.NewContent([]text.ContentSegment{
text.StyledContent("Hello World", gowid.MakePaletteRef("banner")),
}),
text.Options{
Align: gowid.HAlignMiddle{},
},
),
gowid.MakePaletteRef("streak"),
)
sf := gowid.RenderFlow{}
view := styled.New(
vpadding.New(
pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{IWidget: outside, D: sf},
&gowid.ContainerWidget{IWidget: inside, D: sf},
&gowid.ContainerWidget{IWidget: helloworld, D: sf},
&gowid.ContainerWidget{IWidget: inside, D: sf},
&gowid.ContainerWidget{IWidget: outside, D: sf},
}),
gowid.VAlignMiddle{},
sf),
gowid.MakePaletteRef("bg"),
)
app, err := gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-menu/ 0000775 0000000 0000000 00000000000 14262344540 0016522 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-menu/menu.go 0000664 0000000 0000000 00000012423 14262344540 0020017 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A demonstration of gowid's menu, list and overlay widgets.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/button"
"github.com/gcla/gowid/widgets/checkbox"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/hpadding"
"github.com/gcla/gowid/widgets/list"
"github.com/gcla/gowid/widgets/menu"
"github.com/gcla/gowid/widgets/overlay"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
//======================================================================
var ov *overlay.Widget
var menu1 *menu.Widget
var menu2 *menu.Widget
var ovh, ovw int = 50, 50
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
handled := false
if evk, ok := ev.(*tcell.EventKey); ok {
handled = true
if evk.Key() == tcell.KeyCtrlC || evk.Rune() == 'q' || evk.Rune() == 'Q' {
app.Quit()
} else if evk.Key() == tcell.KeyEsc {
if !menu1.IsOpen() {
app.Quit()
} else {
menu1.Close(app)
}
} else {
handled = false
}
}
return handled
}
//======================================================================
func main() {
f := examples.RedirectLogger("menu.log")
defer f.Close()
palette := gowid.Palette{
"red": gowid.MakePaletteEntry(gowid.ColorRed, gowid.ColorDarkBlue),
"green": gowid.MakePaletteEntry(gowid.ColorGreen, gowid.ColorDarkBlue),
"white": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorCyan),
}
fixed := gowid.RenderFixed{}
menu2Widgets := make([]gowid.IWidget, 0)
for i := 0; i < 10; i++ {
clickme := button.New(text.New(fmt.Sprintf("subwidget %d", i)))
clickmeStyled := styled.NewInvertedFocus(clickme, gowid.MakePaletteRef("green"))
clickme.OnClick(gowid.WidgetCallback{gowid.ClickCB{}, func(app gowid.IApp, target gowid.IWidget) {
log.Infof("SUBMENU button CLICKED")
}})
cols := columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{IWidget: clickmeStyled, D: fixed},
})
menu2Widgets = append(menu2Widgets, cols)
}
walker2 := list.NewSimpleListWalker(menu2Widgets)
menuListBox2 := styled.New(list.New(walker2), gowid.MakePaletteRef("green"))
menu1Widgets := make([]gowid.IWidget, 0)
for i := 0; i < 40; i++ {
content := text.NewContent([]text.ContentSegment{
text.StringContent(fmt.Sprintf("widget %d", i)),
})
txt := styled.NewInvertedFocus(text.NewFromContent(content), gowid.MakePaletteRef("red"))
btn := button.NewBare(txt)
btnSite := menu.NewSite()
checkme := checkbox.New(false)
checkmeStyled := styled.NewInvertedFocus(checkme, gowid.MakePaletteRef("red"))
checkme.OnClick(gowid.WidgetCallback{gowid.ClickCB{}, func(app gowid.IApp, target gowid.IWidget) {
log.Infof("MENU checkbox CLICKED")
}})
btn.OnClick(gowid.WidgetCallback{gowid.ClickCB{}, func(app gowid.IApp, target gowid.IWidget) {
if menu2.IsOpen() {
menu2.Close(app)
} else {
menu2.Open(btnSite, app)
}
}})
cols := columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{IWidget: checkmeStyled, D: fixed},
&gowid.ContainerWidget{IWidget: btn, D: fixed},
&gowid.ContainerWidget{IWidget: btnSite, D: fixed},
})
menu1Widgets = append(menu1Widgets, cols)
}
walker1 := list.NewSimpleListWalker(menu1Widgets)
menuListBox1 := styled.New(list.New(walker1), gowid.MakePaletteRef("red"))
menu1 = menu.New("main", menuListBox1, gowid.RenderWithUnits{U: 16})
menu2 = menu.New("main2", menuListBox2, gowid.RenderWithUnits{U: 16})
clickToOpenWidgets := make([]gowid.IContainerWidget, 0)
// Make the on screen buttons to click to open the menu
for i := 0; i < 20; i++ {
btn := button.New(text.New(fmt.Sprintf("clickety%d", i)))
btnStyled := styled.NewExt(btn, gowid.MakePaletteRef("red"), gowid.MakePaletteRef("white"))
btnSite := menu.NewSite(menu.SiteOptions{YOffset: 1})
btn.OnClick(gowid.WidgetCallback{gowid.ClickCB{}, func(app gowid.IApp, target gowid.IWidget) {
menu1.Open(btnSite, app)
}})
clickToOpenWidgets = append(clickToOpenWidgets, &gowid.ContainerWidget{IWidget: btnSite, D: fixed})
clickToOpenWidgets = append(clickToOpenWidgets, &gowid.ContainerWidget{IWidget: btnStyled, D: fixed})
}
clickToOpenCols := columns.New(clickToOpenWidgets)
check := checkbox.New(false)
view1 := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{IWidget: clickToOpenCols, D: fixed},
&gowid.ContainerWidget{IWidget: check, D: fixed},
})
view := vpadding.New(
hpadding.New(view1, gowid.HAlignLeft{}, fixed),
gowid.VAlignTop{Margin: 2},
gowid.RenderFlow{},
)
app, err := gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
// Required for menus to appear overlaid on top of the main view.
app.RegisterMenu(menu1)
app.RegisterMenu(menu2)
app.MainLoop(handler{})
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-overlay1/ 0000775 0000000 0000000 00000000000 14262344540 0017320 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-overlay1/overlay1.go 0000664 0000000 0000000 00000004553 14262344540 0021420 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A demonstration of gowid's overlay and fill widgets.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/gwutil"
"github.com/gcla/gowid/widgets/fill"
"github.com/gcla/gowid/widgets/overlay"
"github.com/gcla/gowid/widgets/styled"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
//======================================================================
var ov *overlay.Widget
var ovh, ovw int = 50, 50
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
handled := false
if evk, ok := ev.(*tcell.EventKey); ok {
handled = true
if evk.Key() == tcell.KeyCtrlC || evk.Key() == tcell.KeyEsc || evk.Rune() == 'q' || evk.Rune() == 'Q' {
app.Quit()
} else if evk.Key() == tcell.KeyUp {
ovh = gwutil.Min(100, ovh+1)
ov.SetHeight(gowid.RenderWithRatio{R: float64(ovh) / 100.0}, app)
} else if evk.Key() == tcell.KeyDown {
ovh = gwutil.Max(0, ovh-1)
ov.SetHeight(gowid.RenderWithRatio{R: float64(ovh) / 100.0}, app)
} else if evk.Key() == tcell.KeyRight {
ovw = gwutil.Min(100, ovw+1)
ov.SetWidth(gowid.RenderWithRatio{R: float64(ovw) / 100.0}, app)
} else if evk.Key() == tcell.KeyLeft {
ovw = gwutil.Max(0, ovw-1)
ov.SetWidth(gowid.RenderWithRatio{R: float64(ovw) / 100.0}, app)
} else {
handled = false
}
}
return handled
}
//======================================================================
func main() {
f := examples.RedirectLogger("overlay1.log")
defer f.Close()
palette := gowid.Palette{
"red": gowid.MakePaletteEntry(gowid.ColorDefault, gowid.ColorRed),
}
top := styled.New(fill.New(' '), gowid.MakePaletteRef("red"))
bottom := fill.New(' ')
ov = overlay.New(top, bottom,
gowid.VAlignMiddle{}, gowid.RenderWithRatio{R: 0.5},
gowid.HAlignMiddle{}, gowid.RenderWithRatio{R: 0.5})
app, err := gowid.NewApp(gowid.AppArgs{
View: ov,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.MainLoop(handler{})
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-overlay2/ 0000775 0000000 0000000 00000000000 14262344540 0017321 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-overlay2/overlay2.go 0000664 0000000 0000000 00000010727 14262344540 0021422 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A demonstration of gowid's overlay, fill, asciigraph and radio widgets.
package main
import (
"math/rand"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/gwutil"
"github.com/gcla/gowid/widgets/asciigraph"
"github.com/gcla/gowid/widgets/checkbox"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/hpadding"
"github.com/gcla/gowid/widgets/overlay"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/radio"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
tcell "github.com/gdamore/tcell/v2"
asc "github.com/guptarohit/asciigraph"
log "github.com/sirupsen/logrus"
)
//======================================================================
var ov *overlay.Widget
var ovh, ovw int = 50, 50
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
handled := false
if evk, ok := ev.(*tcell.EventKey); ok {
handled = true
if evk.Key() == tcell.KeyCtrlC || evk.Key() == tcell.KeyEsc || evk.Rune() == 'q' || evk.Rune() == 'Q' {
app.Quit()
} else if evk.Key() == tcell.KeyUp || evk.Rune() == 'u' {
ovh = gwutil.Min(100, ovh+1)
ov.SetHeight(gowid.RenderWithRatio{float64(ovh) / 100.0}, app)
} else if evk.Key() == tcell.KeyDown || evk.Rune() == 'd' {
ovh = gwutil.Max(0, ovh-1)
ov.SetHeight(gowid.RenderWithRatio{float64(ovh) / 100.0}, app)
} else if evk.Key() == tcell.KeyRight {
ovw = gwutil.Min(100, ovw+1)
ov.SetWidth(gowid.RenderWithRatio{float64(ovw) / 100.0}, app)
} else if evk.Key() == tcell.KeyLeft {
ovw = gwutil.Max(0, ovw-1)
ov.SetWidth(gowid.RenderWithRatio{float64(ovw) / 100.0}, app)
} else {
handled = false
}
}
return handled
}
//======================================================================
func main() {
f := examples.RedirectLogger("overlay2.log")
defer f.Close()
palette := gowid.Palette{
"red": gowid.MakePaletteEntry(gowid.ColorRed, gowid.ColorDefault),
}
fixed := gowid.RenderFixed{}
rbgroup := make([]radio.IWidget, 0)
rb1 := radio.New(&rbgroup)
rbt1 := text.New(" option1 ")
rb2 := radio.New(&rbgroup)
rbt2 := text.New(" option2 ")
rb3 := radio.New(&rbgroup)
rbt3 := text.New(" option3 ")
data := []float64{2, 1, 1, 2, -2, 5, 7, 11, 3, 7, 1, 4, 7, 2, 2, 9}
data2 := []float64{9, 2, 2, 7, 4, 1, 7, 3, 11, 7, 5, -2, 2, 1, 1, 2}
conf := []asc.Option{}
graph := asciigraph.New(data, conf)
callback := func(app gowid.IApp, target gowid.IWidget) {
if rb1.IsChecked() {
graph.SetData(data, app)
}
if rb2.IsChecked() {
graph.SetData(data2, app)
}
if rb3.IsChecked() {
data3 := make([]float64, 40)
for i := 0; i < len(data3); i++ {
data3[i] = gwutil.Round(rand.Float64() * 14)
}
graph.SetData(data3, app)
}
}
rb1.OnClick(gowid.WidgetCallback{gowid.ClickCB{}, callback})
rb2.OnClick(gowid.WidgetCallback{gowid.ClickCB{}, callback})
rb3.OnClick(gowid.WidgetCallback{gowid.ClickCB{}, callback})
c2cols := []gowid.IContainerWidget{
&gowid.ContainerWidget{rb1, fixed},
&gowid.ContainerWidget{rbt1, fixed},
&gowid.ContainerWidget{rb2, fixed},
&gowid.ContainerWidget{rbt2, fixed},
&gowid.ContainerWidget{rb3, fixed},
&gowid.ContainerWidget{rbt3, fixed},
}
cols := columns.New(c2cols)
rows := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{cols, gowid.RenderWithUnits{U: 1}},
&gowid.ContainerWidget{divider.NewUnicode(), gowid.RenderFlow{}},
&gowid.ContainerWidget{graph, gowid.RenderWithWeight{1}},
})
fcols := framed.NewUnicodeAlt(framed.NewUnicodeAlt(rows))
top := styled.New(fcols, gowid.MakePaletteRef("red"))
bottom := vpadding.New(hpadding.New(checkbox.New(false), gowid.HAlignLeft{}, gowid.RenderFixed{}), gowid.VAlignTop{}, gowid.RenderFlow{})
ov = overlay.New(top, bottom,
gowid.VAlignMiddle{}, gowid.RenderWithRatio{0.5},
gowid.HAlignMiddle{}, gowid.RenderWithRatio{0.5})
app, err := gowid.NewApp(gowid.AppArgs{
View: ov,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.MainLoop(handler{})
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-overlay3/ 0000775 0000000 0000000 00000000000 14262344540 0017322 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-overlay3/overlay3.go 0000664 0000000 0000000 00000003576 14262344540 0021430 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A demonstration of gowid's overlay and fill widgets.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/fill"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/overlay"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
//======================================================================
var ov *overlay.Widget
var ovh, ovw int = 50, 50
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
handled := false
if evk, ok := ev.(*tcell.EventKey); ok {
handled = true
if evk.Key() == tcell.KeyCtrlC || evk.Key() == tcell.KeyEsc || evk.Rune() == 'q' || evk.Rune() == 'Q' {
app.Quit()
} else {
handled = false
}
}
return handled
}
//======================================================================
func main() {
f := examples.RedirectLogger("overlay1.log")
defer f.Close()
palette := gowid.Palette{
"red": gowid.MakePaletteEntry(gowid.ColorDefault, gowid.ColorRed),
}
top := styled.New(
framed.NewUnicode(
text.New("hello"),
),
gowid.MakePaletteRef("red"),
)
bottom := fill.New(' ')
ov = overlay.New(top, bottom,
gowid.VAlignMiddle{}, gowid.RenderFixed{},
gowid.HAlignMiddle{}, gowid.RenderFixed{},
)
app, err := gowid.NewApp(gowid.AppArgs{
View: ov,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.MainLoop(handler{})
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-palette/ 0000775 0000000 0000000 00000000000 14262344540 0017214 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-palette/palette.go 0000664 0000000 0000000 00000024727 14262344540 0021215 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A port of urwid's palette_test.py example using gowid widgets.
package main
import (
"errors"
"os"
"regexp"
"strings"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/button"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/holder"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/radio"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
//======================================================================
// t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16 t17 t254 t255
var chart256 = `
brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_
yellow_ light_red light_magenta light_blue light_cyan light_green
#00f#06f#08f#0af#0df#0ff black_______ dark_gray___
#60f#00d#06d#08d#0ad#0dd#0fd light_gray__ white_______
#80f#60d#00a#06a#08a#0aa#0da#0fa
#a0f#80d#60a#008#068#088#0a8#0d8#0f8
#d0f#a0d#80d#608#006#066#086#0a6#0d6#0f6
#f0f#d0d#a0a#808#606#000#060#080#0a0#0d0#0f0#0f6#0f8#0fa#0fd#0ff
#f0d#d0a#a08#806#600#660#680#6a0#6d0#6f0#6f6#6f8#6fa#6fd#6ff#0df
#f0a#d08#a06#800#860#880#8a0#8d0#8f0#8f6#8f8#8fa#8fd#8ff#6df#0af
#f08#d06#a00#a60#a80#aa0#ad0#af0#af6#af8#afa#afd#aff#8df#6af#08f
#f06#d00#d60#d80#da0#dd0#df0#df6#df8#dfa#dfd#dff#adf#8af#68f#06f
#f00#f60#f80#fa0#fd0#ff0#ff6#ff8#ffa#ffd#fff#ddf#aaf#88f#66f#00f
#fd0#fd6#fd8#fda#fdd#fdf#daf#a8f#86f#60f
#66d#68d#6ad#6dd #fa0#fa6#fa8#faa#fad#faf#d8f#a6f#80f
#86d#66a#68a#6aa#6da #f80#f86#f88#f8a#f8d#f8f#d6f#a0f
#a6d#86a#668#688#6a8#6d8 #f60#f66#f68#f6a#f6d#f6f#d0f
#d6d#a6a#868#666#686#6a6#6d6#6d8#6da#6dd #f00#f06#f08#f0a#f0d#f0f
#d6a#a68#866#886#8a6#8d6#8d8#8da#8dd#6ad
#d68#a66#a86#aa6#ad6#ad8#ada#add#8ad#68d
#d66#d86#da6#dd6#dd8#dda#ddd#aad#88d#66d g78_g82_g85_g89_g93_g100_
#da6#da8#daa#dad#a8d#86d g52_g58_g62_g66_g70_g74_
#88a#8aa #d86#d88#d8a#d8d#a6d g27_g31_g35_g38_g42_g46_g50_
#a8a#888#8a8#8aa #d66#d68#d6a#d6d g0__g3__g7__g11_g15_g19_g23_
#a88#aa8#aaa#88a
#a88#a8a
`
var chart88 = `
brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_
yellow_ light_red light_magenta light_blue light_cyan light_green
#00f#08f#0cf#0ff black_______ dark_gray___
#80f#00c#08c#0cc#0fc light_gray__ white_______
#c0f#80c#008#088#0c8#0f8
#f0f#c0c#808#000#080#0c0#0f0#0f8#0fc#0ff #88c#8cc
#f0c#c08#800#880#8c0#8f0#8f8#8fc#8ff#0cf #c8c#888#8c8#8cc
#f08#c00#c80#cc0#cf0#cf8#cfc#cff#8cf#08f #c88#cc8#ccc#88c
#f00#f80#fc0#ff0#ff8#ffc#fff#ccf#88f#00f #c88#c8c
#fc0#fc8#fcc#fcf#c8f#80f
#f80#f88#f8c#f8f#c0f g62_g74_g82_g89_g100
#f00#f08#f0c#f0f g0__g19_g35_g46_g52
`
var chart16 = `
brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_
yellow_ light_red light_magenta light_blue light_cyan light_green
black_______ dark_gray___ light_gray__ white_______
`
var chart8 = `
black__ red_ green_ yellow_ blue_ magenta_ cyan_ white_
`
var chartMono = `
black__ white_
`
var attrRE = regexp.MustCompile(`(?P[ \n]*)(?P((#...)|([a-z_]{2,})|(g[0-9]+_+)|(t[0-9]{1,3})))`)
var attrREIndices = makeRegexpNameMap(attrRE)
var fgColorDefault = gowid.NewUrwidColor("light gray")
var bgColorDefault = gowid.MakeTCellColorExt(tcell.ColorBlack)
var chartHolder *holder.Widget
var chart256Content *text.Widget
var chart88Content *text.Widget
var chart16Content *text.Widget
var chart8Content *text.Widget
var chartMonoContent *text.Widget
var foregroundColors = true
//======================================================================
func makeRegexpNameMap(re *regexp.Regexp) map[string]int {
res := make(map[string]int)
for i, name := range re.SubexpNames() {
res[name] = i
}
return res
}
//======================================================================
func updateChartHolder(mode gowid.ColorMode, app gowid.IApp) {
switch mode {
case gowid.Mode256Colors:
chart256Content = text.NewFromContent(parseChart(chart256))
chartHolder.SetSubWidget(chart256Content, app)
case gowid.Mode88Colors:
chart88Content = text.NewFromContent(parseChart(chart88))
chartHolder.SetSubWidget(chart88Content, app)
case gowid.Mode16Colors:
chart16Content = text.NewFromContent(parseChart(chart16))
chartHolder.SetSubWidget(chart16Content, app)
case gowid.Mode8Colors:
chart8Content = text.NewFromContent(parseChart(chart8))
chartHolder.SetSubWidget(chart8Content, app)
case gowid.ModeMonochrome:
chartMonoContent = text.NewFromContent(parseChart(chartMono))
chartHolder.SetSubWidget(chartMonoContent, app)
default:
panic(errors.New("Invalid mode, something went wrong!"))
}
}
//======================================================================
func modeRb(group *[]radio.IWidget, txt string) gowid.IWidget {
rbt := text.New(" " + txt)
rb := radio.New(group)
widp := gowid.RenderFixed{}
rb.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
if rb.Selected {
switch txt {
case "256-Color":
app.SetColorMode(gowid.Mode256Colors)
case "88-Color":
app.SetColorMode(gowid.Mode88Colors)
case "16-Color":
app.SetColorMode(gowid.Mode16Colors)
case "8-Color":
app.SetColorMode(gowid.Mode8Colors)
case "Monochrome":
app.SetColorMode(gowid.ModeMonochrome)
case "Foreground Colors":
foregroundColors = true
case "Background Colors":
foregroundColors = false
default:
panic(errors.New("Invalid mode, something went wrong!"))
}
updateChartHolder(app.GetColorMode(), app) // Update the chart displayed
}
}})
c := columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{rb, widp},
&gowid.ContainerWidget{rbt, widp},
})
cm := styled.NewExt(c, gowid.MakePaletteRef("panel"), gowid.MakePaletteRef("focus"))
return cm
}
//======================================================================
func parseChart(chart string) *text.Content {
content := make([]text.ContentSegment, 0)
replacer := strings.NewReplacer("_", " ")
for _, match := range attrRE.FindAllStringSubmatch(chart, -1) {
ws := match[attrREIndices["whitespace"]]
if ws != "" {
content = append(content, text.StringContent(ws))
}
entry := match[attrREIndices["entry"]]
entry = replacer.Replace(entry)
entry2 := strings.Trim(entry, " ")
scol, err := gowid.MakeColorSafe(entry2)
if err == nil {
var attrPair gowid.PaletteEntry
if foregroundColors {
attrPair = gowid.MakePaletteEntry(scol, bgColorDefault)
} else {
attrPair = gowid.MakePaletteEntry(fgColorDefault, scol)
}
content = append(content, text.StyledContent(entry, attrPair))
} else {
content = append(content, text.StyledContent(entry, gowid.MakePaletteRef("redinv")))
}
}
return text.NewContent(content)
}
//======================================================================
func main() {
// If this is set to truecolor when a gowid screen is setup, 24-bit truecolor
// support is enabled. Then the program won't output the 256-color/16-color
// terminal codes that this program is supposed to exhibit. So unset the variable
// right away.
os.Unsetenv("COLORTERM")
f := examples.RedirectLogger("palette.log")
defer f.Close()
palette := gowid.Palette{
"header": gowid.MakeStyledPaletteEntry(gowid.ColorBlack, gowid.ColorWhite, gowid.StyleUnderline),
"panel": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorDarkBlue),
"focus": gowid.MakePaletteEntry(gowid.ColorYellow, gowid.ColorRed),
"red": gowid.MakePaletteEntry(gowid.ColorRed, gowid.ColorBlack),
"redinv": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed),
"default": gowid.MakePaletteEntry(gowid.NewUrwidColor("light gray"), gowid.ColorBlack),
}
header := gowid.MakePaletteRef("header")
// make this just text
headerContent := []text.ContentSegment{
text.StyledContent("Gowid Palette Test", header),
}
headerText := styled.New(text.NewFromContent(text.NewContent(headerContent)), header)
rbgroup := make([]radio.IWidget, 0)
bggroup := make([]radio.IWidget, 0)
btn := button.New(text.New("Exit"))
btn.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
app.Quit()
}})
subFlow := gowid.RenderFlow{}
// Put this in the group first so it is the one selected
cols256 := modeRb(&rbgroup, "256-Color")
pw1 := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{modeRb(&rbgroup, "Monochrome"), subFlow},
&gowid.ContainerWidget{modeRb(&rbgroup, "8-Color"), subFlow},
&gowid.ContainerWidget{modeRb(&rbgroup, "16-Color"), subFlow},
&gowid.ContainerWidget{modeRb(&rbgroup, "88-Color"), subFlow},
&gowid.ContainerWidget{cols256, subFlow},
})
pw2 := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{modeRb(&bggroup, "Foreground Colors"), subFlow},
&gowid.ContainerWidget{modeRb(&bggroup, "Background Colors"), subFlow},
&gowid.ContainerWidget{divider.NewBlank(), subFlow},
&gowid.ContainerWidget{divider.NewBlank(), subFlow},
&gowid.ContainerWidget{
styled.NewExt(
btn,
gowid.MakePaletteRef("panel"),
gowid.MakePaletteRef("focus"),
),
subFlow},
})
cs := columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{pw1, gowid.RenderWithWeight{10}},
&gowid.ContainerWidget{pw2, gowid.RenderWithWeight{10}},
})
cs2 := styled.New(cs, gowid.MakePaletteRef("panel"))
chart256Content = text.NewFromContent(parseChart(chart256))
chartHolder = holder.New(chart256Content)
view := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{headerText, subFlow},
&gowid.ContainerWidget{cs2, subFlow},
&gowid.ContainerWidget{chartHolder, gowid.RenderWithWeight{1}},
})
app, err := gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SetColorMode(gowid.Mode256Colors)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-table/ 0000775 0000000 0000000 00000000000 14262344540 0016645 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-table/README.md 0000664 0000000 0000000 00000000107 14262344540 0020122 0 ustar 00root root 0000000 0000000
To re-generate:
$ go get github.com/rakyll/statik
$ statik -src=data
gowid-1.4.0/examples/gowid-table/data/ 0000775 0000000 0000000 00000000000 14262344540 0017556 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-table/data/worldcitiespop1k.csv 0000664 0000000 0000000 00000135076 14262344540 0023612 0 ustar 00root root 0000000 0000000 Country,City,AccentCity,Region,Population,Latitude,Longitude
af,`ali kheyl,`Ali Kheyl,24,,36.697116,68.851365
af,bahadorgay,Bahadorgay,08,,33.183056,68.056944
af,bar-khankheyl',Bar-Khankheyl',37,,33.30201,69.777346
af,butah'i,Butah'i,05,,34.814257,66.855124
af,cal,Cal,27,,34.505057,68.037787
af,kudali,Kudali,29,,33.266111,68.864722
af,malek qyas kalay,Malek Qyas Kalay,18,,34.275501,70.031113
af,mu'menkhel,Mu'menkhel,08,,32.799713,67.752777
af,mukhammedadzhan-kot,Mukhammedadzhan-Kot,29,,32.92643,69.422562
af,qal'a-i-naw,Qal'a-i-Naw,19,,31.536224,62.758054
af,qal`eh-ye qowl-e heydar,Qal`eh-ye Qowl-e Heydar,08,,33.146022,67.504011
af,shinwari,Shinwari,03,,36.000837,68.570617
af,yak pahlu,Yak Pahlu,11,,34.334461,63.738327
af,zowli,Zowli,30,,36.656917,66.79588
al,gadurove,Gadurovë,44,,40.5186111,19.8488889
al,kashisht,Kashisht,44,,40.5983333,19.5405556
al,kopliku-e poshtem,Kopliku-e Poshtëm,49,,42.2136111,19.4363889
al,luniku,Luniku,43,,41.2891667,20.3236111
al,ngureza e madhe,Ngurëza e Madhe,44,,40.8261111,19.7163889
al,ples e zadrims,Ples e Zadrims,49,,41.9697222,19.5538889
al,trovne,Trovnë,49,,42.2208333,19.9297222
al,veseshte,Veseshtë,40,,40.5166667,20.2663889
am,ertich,Ertich,10,,39.7405556,45.2666667
am,shaab,Shaab,05,,40.2522222,44.6383333
ao,bango,Bango,07,,-16.383333,13.616667
ao,cambanzumbe,Cambanzumbe,17,,-8.216667,20.233333
ao,cassai-fana,Cassai-Fana,18,,-10.588912,21.969745
ao,chivanda,Chivanda,09,,-13.45,15.983333
ao,fazenda esperanca,Fazenda Esperança,12,,-9.433333,15.85
ar,rarro negro,Rarro Negro,10,,-24.3,-64.933333
at,badersdorf,Badersdorf,01,,47.2,16.366667
at,mirnig,Mirnig,02,,46.8,14.583333
at,obergassolding,Obergassolding,04,,48.216667,14.75
at,schlattenthal,Schlattenthal,03,,47.65,16.233333
au,cardigan village,Cardigan Village,07,,-37.51091,143.706161
au,sylvaterre,Sylvaterre,07,,-36.116667,144.233333
au,wiyarra,Wiyarra,04,,-28.266667,152.233333
az,hopurlu,Hopurlu,26,,40.03584,45.977294
az,kargalan,Kargalan,29,,38.746181,48.777724
az,kolkyshlak,Kolkyshlak,03,,40.116667,47.05
az,miadzhik,Miadzhik,01,,40.260833,49.438333
ba,dragan selo,Dragan Selo,01,,43.645,17.8033333
ba,laza,Laza,02,,43.2908333,18.5211111
ba,ustikolina,Ustikolina,01,4446,43.5836111,18.79
bd,ghaturia,Ghaturia,83,,24.9833333,89.1833333
bd,khagarjana,Khagarjana,81,,24.7333333,90.1666667
bd,mahmudkati,Mahmudkati,82,,22.6666667,89.3
bd,shahar ahmedpur,Shahar Ahmedpur,81,,24.7666667,90.6666667
bd,uttar chandkhana,Uttar Chandkhana,83,,25.85,89.0333333
be,calvenne,Calvenne,02,,51,4.716667
be,gammel,Gammel,01,,51.366667,4.766667
be,moereinde,Moereinde,01,,51.25,4.783333
be,sint-remigius-geest,Sint-Remigius-Geest,02,,50.75,4.866667
be,zand,Zand,02,,50.9,5.066667
bf,bobeta,Bobéta,69,,10.3666667,-3.5666667
bf,koudougou,Koudougou,19,86965,12.25,-2.3666667
bg,dimitur petkov,Dimitur Petkov,52,,43.7333333,27.1333333
bg,khambardzhilar,Khambardzhilar,43,,41.6166667,25.3833333
bg,obnova,Obnova,46,,43.4333333,24.9833333
bg,orchenlar,Orchenlar,52,,43.2333333,26.4833333
bg,rakovskovo,Rakovskovo,39,,42.8166667,27.75
bg,rebrevtsu,Rebrevtsu,46,,42.9,25.7666667
bg,sabol,Sabol,58,,42.8319444,22.5836111
bi,rurengera,Rurengera,11,,-3.1872222,30.6361111
bj,alafiarou,Alafiarou,10,,9.4,2.6833333
bo,cuchihilga,Cuchihilga,02,,-17.5833333,-65.2333333
bo,kullpachaca,Kullpachaca,07,,-19.1894444,-65.4277778
bo,la cueva chica,La Cueva Chica,01,,-20.9833333,-65.35
bo,lagunilla,Lagunilla,07,,-21.2333333,-65.65
bo,siraomonton,Siraomonton,01,,-19.8666667,-63.8
br,aterradinho,Aterradinho,14,,-16.95,-57.35
br,cajazeiras,Cajazeiras,22,,-6.483333,-38.1
br,curimatai,Curimataí,15,,-17.85,-43.983333
br,fazenda do canal,Fazenda do Canal,05,,-11.35,-41.666667
br,itajahi,Itajahi,26,,-26.883333,-48.65
br,marco prete,Marco Prete,06,,-3.983333,-40.316667
br,passagem,Passagem,15,,-19.283333,-43.216667
br,porto alegre,Pôrto Alegre,16,,-4.350278,-52.737778
br,quinzopolis,Quinzópolis,18,,-23.016667,-50.566667
br,tuneiras do oeste,Tuneiras do Oeste,18,,-23.816667,-52.916667
br,upamirim,Upamirim,05,,-10.466667,-41.25
br,urucu-mirim,Uruçu-Mirim,30,,-8.3,-35.6
by,bazilevka,Bazilevka,07,,55.5,29.05
by,peschanka,Peschanka,07,,54.9,30.2333333
by,volynets,Volynets,07,,55.7,28.1833333
by,wiejna,Wiejna,06,,54.55,27.7333333
ca,dorchester crossing,Dorchester Crossing,04,,46.166667,-64.566667
ca,saint-romuald,Saint-Romuald,10,,46.75,-71.233333
cd,busu-gbolonga,Busu-Gbolonga,00,,1.55,19.45
cd,diobo,Diobo,00,,2.266667,20.483333
cd,djiapanda,Djiapanda,09,,1.4,29.416667
cd,kamususomba,Kamususomba,00,,-6.583333,19.983333
cd,kembowala,Kembowala,02,,-6,17.833333
cd,koiekoie,Koiekoie,00,,-3.366667,23.25
cd,mapanda,Mapanda,00,,-6.666667,17.683333
cd,mulenda,Mulenda,05,,-5.116667,28.9
cf,bangaladeke,Bangaladéké,17,,4.6,18.3333333
ch,arconciel,Arconciel,06,,46.746348,7.124687
ch,jaggisbachau,Jaggisbachau,05,,46.96396,7.314665
ci,bofia,Bofia,81,,6.720045,-5.006264
ci,diamanbo,Diamanbo,83,,7.366234,-5.76568
ci,gitri,Gitri,88,,5.519518,-5.240379
ci,gounioube,Gounioubé,82,,5.393889,-4.146111
ci,korkana,Korkana,87,,9.588071,-5.010828
ci,kouenoufra,Kouénoufra,83,,6.816853,-5.777455
ci,mboka nguessan,Mboka Nguessan,74,,5.696907,-4.399312
ci,ouompleupleu,Ouompleupleu,78,,7.275385,-8.284204
ci,ouorosaniso,Ouorosaniso,75,,8.81597,-7.729201
ci,satiahary,Satiahary,81,,7.0475,-5.278333
cm,guendou,Guendou,12,,10.05,13.3166667
cm,hina-winde,Hina-Winde,12,,10.3333333,13.8666667
cm,mangengues,Mangengués,11,,3.5666667,10.7
cm,roum,Roum,11,,4.7333333,11.1333333
cn,beizhe,Beizhe,07,,23.904545,117.180579
cn,boniaoliangzi,Boniaoliangzi,32,,27.586911,102.53635
cn,cangqian,Cangqian,03,,26.762918,115.512334
cn,changyuho,Changyuho,24,,38.033333,113.183333
cn,chienyangtungtun,Chienyangtungtun,19,,39.9825,124.201389
cn,chingchiang,Chingchiang,32,,28.666667,103.966667
cn,chuchaichen,Chuchaichen,23,,31.21,121.287778
cn,chulin,Chulin,01,,33.141667,116.491667
cn,dadingmiao,Dadingmiao,25,,37.819167,117.347222
cn,dadipo,Dadipo,24,,39.181423,113.806135
cn,daotiejin,Daotiejin,26,,33.796955,106.997045
cn,datiankan,Datiankan,32,,28.662104,105.524478
cn,diaoya,Diaoya,09,,33.601134,112.001099
cn,donghuangnigang,Donghuangnigang,19,,41.113611,123.578056
cn,fujiutian,Fujiutian,30,,21.851963,110.158339
cn,ganpo,Ganpo,15,,35.350817,105.457115
cn,gaoqiaogou,Gaoqiaogou,26,,34.277172,107.4934
cn,heichiaotang,Heichiaotang,20,,41.396944,109.970278
cn,hengdaohezi,Hengdaohezi,05,,41.98507,127.117715
cn,hengshanxia,Hengshanxia,02,,30.114804,119.558857
cn,hojih,Hojih,06,,35.23178,100.986863
cn,houjiaping,Houjiaping,15,,33.196254,104.88896
cn,hsanghawng,Hsanghawng,29,,24.220852,97.786834
cn,huogusi,Huogusi,25,,35.029444,116.2725
cn,huomeichong,Huomeichong,11,,25.658611,112.15
cn,huxibei,Huxibei,07,,24.89095,116.767413
cn,jiajiapu,Jiajiapu,15,,35.682833,105.405902
cn,jiangtang,Jiangtang,02,,29.059295,119.450944
cn,jiuli,Jiuli,11,,29.694685,111.585702
cn,joerhkai,Joerhkai,32,,33.577954,102.964115
cn,kiucheng,Kiucheng,10,,38.188889,117.305556
cn,kuangshantsun,Kuangshantsun,10,,36.814722,114.175833
cn,laojunmiao,Laojunmiao,24,,39.296092,112.550023
cn,lichiaho,Lichiaho,26,,34.183333,110.3
cn,liumengpian,Liumengpian,31,,19.367391,110.330596
cn,malangou,Malangou,26,,33.34417,106.723814
cn,mansian,Mansian,15,,34.668074,104.482618
cn,maochang,Maochang,31,,19.489984,110.183741
cn,meixing,Meixing,32,,31.001069,102.36435
cn,namyung,Namyung,30,,25.116667,114.3
cn,nencheng,Nencheng,08,,49.183333,125.216667
cn,niuwanxu,Niuwanxu,30,,22.438852,112.842622
cn,panpanchiao,Panpanchiao,02,,30.384722,119.871389
cn,pinghsiho,Pinghsiho,26,,32.940514,106.435112
cn,pingle,Pingle,32,,29.013199,103.774636
cn,qiaogoucun,Qiaogoucun,26,,34.038151,109.600844
cn,renjia,Renjia,03,,28.661013,115.673105
cn,shanchungtsun,Shanchungtsun,24,,36.4,110.95
cn,shen,Shen,10,,38.008889,115.548056
cn,shihchichan,Shihchichan,11,,26.275278,111.433611
cn,shixi,Shixi,31,,19.243056,109.547467
cn,sotang,Sotang,14,,30.033333,95.25
cn,tsaiyuan,Tsaiyuan,05,,41.337513,125.712972
cn,tsaokota,Tsaokota,24,,34.919167,110.609444
cn,tuomu,Tuomu,32,,28.413721,103.069972
cn,wanshan,Wanshan,25,,36.574167,115.418333
cn,weiqu,Weiqu,26,,34.151111,108.945
cn,wenlongbao,Wenlongbao,26,,34.792778,108.957778
cn,wuguantang,Wuguantang,04,,31.661885,119.957061
cn,wuzi,Wuzi,15,,34.163942,103.272371
cn,xiaer,Xiaer,32,,29.005935,101.514394
cn,xiaogujiazi,Xiaogujiazi,19,,40.770014,122.295383
cn,xiasiqiao,Xiasiqiao,04,,31.660073,118.987201
cn,xingjialong,Xingjialong,12,,29.892625,115.324057
cn,yangfangying,Yangfangying,24,,37.493333,112.361389
cn,yaojiaxitou,Yaojiaxitou,12,,30.686247,112.963439
cn,yufenfang,Yufenfang,08,,44.993829,127.468469
cn,zhangqing,Zhangqing,24,,37.646389,112.652222
cn,zhongying,Zhongying,18,,26.066667,105.083333
cn,zhoujiagou,Zhoujiagou,19,,40.635278,125.143056
cn,zhoulichang,Zhoulichang,32,,29.893445,105.130617
cn,zhoulijia,Zhoulijia,12,,30.089564,114.308771
co,arrayan,Arrayán,33,,4.556686,-73.933675
co,bruselas,Bruselas,16,,1.77413,-76.172175
co,buenas aires,Buenas Aires,20,,1.58114,-77.077742
co,caserio el cobado,Caserío El Cobado,35,,10.15,-75.5
co,flores,Flores,02,,8.114845,-74.778523
co,guaspucal,Guaspucal,20,,1.043608,-77.440083
co,horta,Horta,26,,6.183333,-73.866667
co,manuel pastrana,Manuel Pastrana,12,,8.806667,-76.300278
co,tipacoque,Tipacoque,36,1036,6.42031,-72.691842
cr,el arbolito,El Arbolito,03,,9.883333,-85.433333
cu,guasimal,Guasimal,05,,20.9833333,-78.05
cu,islena,Isleña,01,,22.5,-83.5666667
cu,pueblo nuevo,Pueblo Nuevo,07,,21.6758333,-78.8875
cu,reparto sans souci,Reparto Sans Souci,02,,23.0602778,-82.4480556
cv,ribeira da barca,Ribeira da Barca,17,,15.1333333,-23.7666667
cy,perahorya,Perahorya,04,,35.0166667,33.3875
cz,kochanov,Kochanov,88,,49.794484,14.779141
cz,lazne libverda,Lazne Libverda,83,,50.891031,15.20255
cz,medlanka,Medlanka,78,,49.242113,16.571495
cz,suchdol,Suchdol,79,,48.682859,14.447513
de,badanhausen,Badanhausen,02,,49.016667,11.45
de,durnod,Dürnöd,02,,48.35,13.3
de,edenserloog,Edenserloog,06,,53.666667,7.733333
de,enghausen,Enghausen,02,,48.533333,11.9
de,englreiching,Englreiching,02,,48.75,13.183333
de,forsterei kesselsohl,Försterei Kesselsohl,14,,52.366667,11.616667
de,frenke,Frenke,06,,52.016667,9.45
de,goldbach,Goldbach,01,,47.783333,9.15
de,hardesby,Hardesby,10,,54.75,9.65
de,helmbacher,Helmbacher,08,,49.35,7.983333
de,korle,Körle,05,,51.166667,9.516667
de,lessau,Lessau,02,,49.916667,11.7
de,leyh,Leyh,02,,49.45,11.016667
de,marsmeir,Marsmeir,02,,48.133333,12.1
de,neuenothe,Neuenothe,07,,51.016667,7.716667
de,polvitz-neuemuhle,Polvitz-Neuemühle,14,,52.483333,11.416667
de,schaalby,Schaalby,10,,54.55,9.633333
de,schwenow,Schwenow,11,,52.15,14.05
de,sebexen,Sebexen,06,,51.816667,10.033333
de,seeon-seebruck,Seeon-Seebruck,02,,47.966667,12.466667
de,torring,Törring,02,,48.016667,12.75
de,tullingen,Tüllingen,01,,47.602065,7.64474
de,vielstedt,Vielstedt,06,,53.083333,8.466667
de,wallhofen,Wallhöfen,06,,53.316667,8.883333
de,wehe,Wehe,07,,52.466667,8.666667
dk,oster gammelby,Øster Gammelby,21,,54.99548,8.766006
dk,snevre,Snevre,20,,55.64832,11.621644
do,el ingenio abajo,El Ingenio Abajo,25,,19.5,-70.75
do,las uvas,Las Uvas,30,,19.3166667,-70.4666667
dz,agneb,Agneb,13,,33.9833333,1.5833333
dz,djillali ben amar,Djillali Ben Amar,51,,35.4455556,0.8508333
dz,douar ouled ali,Douar Ouled Ali,26,,35.3736111,0.55
dz,fleurus,Fleurus,09,,35.7111111,-0.4419444
dz,neurkache,Neurkache,35,,36.5833333,2.2333333
dz,zemoura,Zemoura,39,,36.2694444,4.8472222
ee,piiometsa,Piiometsa,04,,58.8822222,25.2841667
ee,sinalepa asundus,Sinalepa Asundus,07,,58.8266667,23.5947222
ee,terepi,Terepi,12,,58.1966667,27.2722222
ee,uearu,Uearu,01,,59.2469444,25.3569444
eg,`atf abu gindi,`Atf Abu Gindi,05,,30.9044444,30.9219444
eg,`izbat mastaruh,`Izbat Mastaruh,21,,31.4802778,30.7019444
eg,al bajhur,Al Bajhur,10,,28.6708333,30.7513889
eg,kafr tidah,Kafr Tidah,21,,31.2144444,30.8525
eg,nag` el-zuqeim muhafazet abd alla,Nag` el-Zuqeim Muhafazet Abd Alla,23,,25.9833333,32.8333333
es,ballestro,Ballestro,54,,38.841789,-2.456085
es,besians,Besiáns,52,,42.28211,.354326
es,cabeza de campo,Cabeza de Campo,55,,42.545833,-6.881131
es,cangues,Cangues,58,,42.472425,-8.089597
es,caserio pastranas,Caserío Pastranas,51,,36.733333,-6.4
es,denia,Denia,60,38953,38.841344,.10797
es,luama,Luama,58,,43.693873,-7.858789
es,morente,Morente,34,,43.332049,-5.838774
es,villardiga,Villárdiga,55,,41.819496,-5.464391
et,addi aro,Addi Aro,53,,13.216446,39.256772
et,argebet,Argebet,46,,11.533333,39.116667
et,awala bate,Awala Bate,51,,9.8,38.033333
et,dembeccia,Dembeccia,46,,10.55,37.483333
et,derouda ler,Derouda Ler,52,,7.698333,46.992778
et,kachisi,Kachisi,51,,9.6,37.833333
et,may dema,May Dema,53,,13.035871,39.693719
fi,hyrsyla,Hyrsylä,13,,60.383333,24.033333
fi,hyvolankyla,Hyvölänkylä,14,,63.233333,28.383333
fi,pasto,Pasto,15,,62.616667,23.133333
fi,pelkosenniemi,Pelkosenniemi,06,,67.110833,27.510556
fi,pietarinmaki,Pietarinmäki,13,,60.372778,24.773611
fi,storsvik,Störsvik,13,,60.066667,24.283333
fm,vinau,Vinau,04,,9.5297222,138.0922222
fr,albiac,Albiac,B3,,43.551701,1.78024
fr,beauvais,Beauvais,A8,,48.496127,2.467986
fr,brolles,Brolles,A8,,48.475205,2.691743
fr,claire-fontaine,Claire-Fontaine,A4,,49.427756,4.738675
fr,cleguerec,Cléguérec,A2,,48.125775,-3.071616
fr,courbette,Courbette,A6,,46.595845,5.565111
fr,craches,Craches,A3,,48.556575,1.810971
fr,crossac,Crossac,B5,,47.411195,-2.169522
fr,cuzoul,Cuzoul,B3,,44.750467,1.695915
fr,foret,Forêt,A7,,49.224227,1.529738
fr,la gautrais,La Gautrais,B5,,47.380565,-2.114972
fr,la mare goubert,La Mare Goubert,A7,,49.63772,.182824
fr,la ricamarie,La Ricamarie,B9,7527,45.416843,4.311988
fr,lalleyriat,Lalleyriat,B9,,46.150552,5.711573
fr,le chatellier,Le Châtellier,99,,48.676035,-.581481
fr,les platrieres,Les Platrières,B8,,43.55,5.433333
fr,pleine-seve,Pleine-Sève,A7,,49.817949,.754915
fr,rilly-sainte-syre,Rilly-Sainte-Syre,A4,,48.444761,3.956918
fr,roguinet,Roguinet,A3,,47.208093,.12752
fr,saint-mards-de-fresne,Saint-Mards-de-Fresne,A7,,49.077675,.46703
fr,santa-maria-maddalena,Santa-Maria-Maddalena,B8,,44.1,7.5
fr,subligny,Subligny,A3,,47.402951,2.755044
fr,vauciennes,Vauciennes,A4,,49.05,3.883333
ga,akoumounou,Akoumounou,08,,-1.6666667,9.35
gb,farnham,Farnham,E4,,51.9,.15
gb,folkton,Folkton,J7,,54.2,-.383333
gb,llanddowror,Llanddowror,X7,,51.801667,-4.531389
gb,patcham,Patcham,B6,,50.85,-.15
gb,pembrey,Pembrey,X7,,51.689167,-4.277222
gb,roch,Roch,Y7,,51.846944,-5.086667
gb,seven sisters,Seven Sisters,Y5,,51.766667,-3.716667
gb,thatcham,Thatcham,P4,24275,51.4,-1.266667
gb,westbury on severn,Westbury on Severn,E6,,51.816667,-2.416667
ge,sargveshi,Sargveshi,27,,42.0730556,43.2625
ge,shimokmedi,Shimokmedi,40,,41.9125,42.0925
gf,couriege,Couriège,00,,4.8,-53.2833333
gh,adeemmra,Adeemmra,02,,6.6,-2.05
gh,adugyansu akomada,Adugyansu Akomada,03,,7.3666667,-2.0333333
gh,alabolaboe,Alabolaboe,08,,6.1166667,0.2166667
gh,gupaneirigu,Gupaneirigu,06,,9.4833333,-0.95
gh,gyedua,Gyedua,09,,4.85,-2.0
gh,kuguri,Kuguri,10,,10.7833333,-0.2833333
gh,niflapo,Niflapo,08,,6.1333333,0.1666667
gh,takuve,Takuve,08,,6.45,0.7333333
gh,togodo,Togodo,08,,6.0997222,1.1097222
gh,zawire,Zawire,06,,9.4666667,-0.85
gl,ikerasagssuaq,Ikerasagssuaq,02,,65.5666667,-37.1166667
gm,makama,Makama,05,,13.6,-15.3166667
gn,dangoura,Dangoura,19,,10.8333333,-9.5
gn,nafaguile,Nafaguilé,26,,10.8,-8.75
gq,bantabare pequeno,Bantabaré Pequeño,05,,3.45,8.7833333
gq,bedoum,Bedoum,07,,2.15,11.0833333
gq,bokoricho-balacha,Bokoricho-Balachá,05,,3.4,8.5666667
gq,nsangayong,Nsangayong,09,,1.4166667,11.3
gr,ano vaskina,Áno Vaskína,41,,37.2,22.7833333
gr,bambalio,Bambalió,31,,38.8166667,21.3333333
gr,laringovi,Laringóvi,15,,40.4819444,23.5927778
gr,pirgos,Pírgos,48,,37.7116667,26.8041667
gr,porro mesoyia,Pórro Mesóyia,43,,35.4666667,23.6
gr,satobasi,Satóbasi,21,,39.7833333,22.4
gr,vakkhon,Vákkhon,41,,37.4666667,22.2
gt,la haciendita,La Haciendita,17,,15.133333,-92.083333
gt,patut,Patut,20,,14.4,-91.516667
gt,quimal,Quimal,03,,14.833333,-90.766667
gt,san agustin,San Agustín,17,,15.4,-90.283333
gt,san antonio suchitepequez,San Antonio Suchitepéquez,20,10726,14.533333,-91.416667
gw,quentico,Quenticó,04,,12.1,-15.2666667
gw,umaro bela,Umaro Bela,10,,12.4666667,-14.1333333
hk,so lo pun,So Lo Pun,00,,22.5333333,114.25
hn,alto verde,Alto Verde,17,,13.4405556,-87.4516667
hn,carrizalio,Carrizalio,08,,13.7166667,-87.2833333
hn,el tablado,El Tablado,02,,13.5738889,-87.3461111
hn,miraflores,Miraflores,02,,13.2833333,-86.75
hr,butkovici,Butkovici,04,,45.0097222,13.9091667
hr,miljci,Miljci,15,,43.5905556,16.9802778
hr,moravice srpske,Moravice Srpske,12,,45.4333333,15.0333333
ht,bayado,Bayado,08,,18.4833333,-73.4833333
ht,johanisse,Johanisse,06,,19.3333333,-72.5833333
ht,lochard,Lochard,11,,18.4333333,-72.6333333
ht,nan palmiste,Nan Palmiste,13,,18.2638889,-72.0816667
ht,salor,Salor,08,,18.4166667,-73.2333333
hu,alsoszallas,Alsószállás,10,,47.6,21.766667
hu,kulsofecskespuszta,Kulsofecskespuszta,03,,46.383333,20.816667
hu,miklospuszta,Miklóspuszta,04,,48.316667,20.4
hu,noszlopi tanya,Noszlopi Tanya,23,,47.183333,17.466667
hu,pillingerpuszta,Pillingérpuszta,09,,47.7,17.533333
hu,szantoresz,Szántórész,20,,46.8,20.133333
hu,ujpetre,Újpetre,02,,45.936667,18.3625
id,aluw bateu,Aluw Bateu,01,,4.8798,97.8397
id,baheesioena,Baheesioena,21,,-.7576,122.9605
id,balokan,Balokan,08,,-8.388985,114.119839
id,banjar umesalakan,Banjar Umesalakan,02,,-8.5538,115.3847
id,bantannyuh kelod,Bantannyuh Kelod,02,,-8.4289,115.6138
id,bantarkalong,Bantarkalong,30,,-6.856667,107.328889
id,batangwarak,Batangwarak,08,,-7.7525,111.371944
id,benjaran,Benjaran,10,,-8.046111,110.447778
id,blok lebak,Blok Lebak,30,,-6.705,108.351389
id,busak satu,Busak Satu,21,,1.2484,121.3598
id,cieurih satu,Cieurih Satu,30,,-7.2148,108.3551
id,galuwalakari,Galuwalakari,18,,-9.5987,119.5329
id,glodogan,Glodogan,07,,-7.220278,110.4175
id,kalagheng,Kalagheng,31,,3.4992,125.6376
id,kaliuwi,Kaliuwi,22,,-4.2571,122.1234
id,kayut,Kayut,07,,-7.0949,111.1515
id,kedewatan,Kedewatan,02,,-8.4265,114.9506
id,kedungdawa,Kedungdawa,07,,-6.927,109.0426
id,kembangrejo,Kembangrejo,07,,-7.565556,110.510278
id,kunciran,Kunciran,30,,-6.219444,106.667778
id,limboeng,Limboeng,38,,-2.5719,119.913
id,lowayu,Lowayu,08,,-6.9737,112.4192
id,manggarawan 2,Manggarawan 2,15,,-5.168356,105.634634
id,moeara boelan,Moeara Boelan,14,,1.2,117.55
id,pabuwaranjarak,Pabuwaranjarak,30,,-6.098333,106.598611
id,pajagan tonggoh,Pajagan Tonggoh,30,,-6.911389,106.607222
id,pandangalor,Pandangalor,24,,-.490279,100.183306
id,pangerian,Pangerian,08,,-7.0584,112.8758
id,patan bili,Patan Bili,01,,3.2013,97.2899
id,pekandelan,Pekandelan,02,,-8.3468,115.6151
id,pematangkelukut,Pematangkelukut,01,,3.5717,99.0311
id,pilar satu,Pilar Satu,30,,-6.566944,106.756667
id,ramung,Ramung,26,,3.851,97.5111
id,ruda,Ruda,18,,-8.7162,121.0827
id,sabrang-lor,Sabrang-lor,07,,-7.4775,110.351667
id,sangen lor,Sangen Lor,07,,-7.494167,110.718889
id,sepuntuk,Sepuntuk,07,,-7.55,111.080833
id,setukalibiru,Setukalibiru,07,,-6.9234,109.1546
id,sibodadi,Sibodadi,01,,4.3061,98.1829
id,sumbermanggis,Sumbermanggis,08,,-7.9215,111.8586
id,tegineneng,Tegineneng,15,,-5.20365,105.176144
id,temorleke,Temorleke,08,,-7.1141,112.7878
id,tjigalontjang,Tjigalontjang,30,,-7.3224,108.0052
id,urutsewu,Urutsewu,07,,-7.439444,110.536389
id,villabukutbandung,Villabukutbandung,30,,-6.928056,107.748333
id,warmandi,Warmandi,39,,-.366667,132.65
ie,bunnagee,Bunnagee,06,,54.9419444,-7.6941667
ie,dun leire,Dún Léire,19,,53.835,-6.3961111
ie,kilbraney,Kilbraney,30,,52.3244444,-6.8347222
ie,mullaghnaneane,Mullaghnaneane,25,,54.3755556,-8.5316667
ie,sandymount,Sandymount,07,,53.335,-6.2113889
in,astagaon,Astagaon,16,,19.666667,74.5
in,bagra,Bagra,24,,25.2,72.583333
in,binewal,Binewal,23,,31.283333,76.25
in,gangolli,Gangolli,19,,13.65,74.666667
in,hadgaon buzurg,Hadgaon Buzurg,16,,19.316667,76.333333
in,haftan,Haftan,12,,34.368056,73.85
in,kangra,Kangra,11,9159,32.1,76.266667
in,katghara brahmanan,Katghara Brahmanan,36,,26.4778,79.3579
in,madawara,Madawara,24,,25.366667,76.15
in,nal ka talav,Nal ka Talav,24,,25.138611,75.612222
in,pasrai,Pasrai,36,,25.5023,79.0269
in,prahladpur,Prahladpur,36,,25.433333,83.45
in,sehral,Sehral,35,,24.9405,78.8782
in,siryasar,Siryasar,24,,28.0771,75.314
in,watwanpur,Watwanpur,12,,34.1625,74.563889
iq,bahriz,Bahriz,10,,33.7055556,44.6505556
iq,chamsayda,Chamsayda,08,,37.1913889,43.2611111
iq,dewana,Dewana,05,,35.1788889,45.5725
iq,hizana,Hizana,08,,36.8583333,43.6902778
iq,hulwan,Hulwan,10,,34.3330556,45.2008333
iq,sayyid `abd `ali,Sayyid `Abd `Ali,09,,30.9236111,46.7258333
iq,suse-i kon,Suse-i Kon,05,,35.7672222,45.1083333
iq,wihab,Wihab,01,,33.3508333,44.1347222
ir,`abbasabad-e jadid,`Abbasabad-e Jadid,42,,35.035604,59.295116
ir,aqkul,Aqkul,34,,35.11,49.4675
ir,atmiyan,Atmiyan,33,,38.1687,47.2848
ir,banehdar-e `aziz,Banehdar-e `Aziz,13,,34.9126,45.8788
ir,benuk,Benuk,04,,27.395708,60.82304
ir,bushkan,Bushkan,07,,30.06,52.44
ir,chah-e sang,Chah-e Sang,29,,28.5027,56.0173
ir,chashmeh-e sabz,Chashmeh-e Sabz,29,,29.464444,56.425
ir,dar mian-e nahr,Dar Mian-e Nahr,29,,30.480343,57.320026
ir,daraj-e `olya,Daraj-e `Olya,42,,33.394317,60.171259
ir,emameh-ye bala,Emameh-ye Bala,39,,34.9,51.583333
ir,gaisur bala,Gaisur Bala,42,,34.266839,59.261859
ir,garang,Garang,29,,28.783333,56.816667
ir,gelyard,Gelyard,35,,36.61119,52.943054
ir,gham towrqeh,Gham Towrqeh,16,,36.277,47.8217
ir,givshad,Givshad,41,,32.654574,59.095305
ir,gowhar,Gowhar,13,,34.0266,46.329
ir,hajji `abbasi,Hajji `Abbasi,11,,27.226667,57.051944
ir,jenablu,Jenablu,32,,38.65,48.116667
ir,kalateh-ye avaz,Kalateh-ye Avaz,41,,31.648467,59.419373
ir,kallu,Kallu,36,,36.0131,48.9319
ir,kat cheshmeh,Kat Cheshmeh,35,,36.540233,53.957785
ir,kazab,Kazab,04,,25.566667,61.316667
ir,khanqah-e pa'in,Khanqah-e Pa'in,32,,38.4,48.333333
ir,kora'i-ye bala,Kora'i-ye Bala,15,,31.805,49.1985
ir,kurak,Kurak,42,,37.358183,59.14337
ir,kushk-e esma`ilabad,Kushk-e Esma`ilabad,07,,29.013,52.5672
ir,mir hashim,Mir Hashim,40,,31.461888,54.253496
ir,mo'asseseh-ye hajji khan dusti,Mo'asseseh-ye Hajji Khan Dusti,15,,31.537778,48.890278
ir,morad tappeh,Morad Tappeh,44,,35.7336,50.2919
ir,mowmenabad,Mowmenabad,25,,35.544364,53.297962
ir,mozdaran,Mozdaran,42,,36.156078,60.53095
ir,owli qeshlaq,Owli Qeshlaq,33,,39.125,47.1958
ir,ramzeyeh-ye yek,Ramzeyeh-ye Yek,15,,31.2534,48.4366
ir,salehabad,Salehabad,36,,35.9994,48.4241
ir,sarbir,Sarbir,11,,26.366667,58.033333
ir,sardehat ja`far,Sardehat Ja`far,36,,36.9649,48.0357
ir,seku mahalleh,Seku Mahalleh,15,,32.716667,48.5
ir,shah kandi,Shah Kandi,09,,34.64973,48.79507
ir,shambrakan,Shambrakan,05,,30.625567,50.725715
ir,sharafabad,Sharafabad,05,,30.682852,51.552946
ir,shekarkash mahalleh,Shekarkash Mahalleh,08,,37.09522,50.2575
ir,takab,Takab,15,,32.739997,48.283737
it,borgomale,Borgomale,12,,44.616667,8.133333
it,bovisa,Bovisa,09,,45.5,9.15
it,castellar,Castellar,12,,44.916667,8.966667
jm,cambridge,Cambridge,15,,18.4333333,-77.6
jo,mote,Mote,09,,31.0925,35.694444
jp,akagawa,Akagawa,12,,42.07,139.455
jp,hikimoto,Hikimoto,23,,34.1,136.233333
jp,kakazi,Kakazi,30,,33.666667,131.516667
jp,kami-kanagawa,Kami-kanagawa,46,,35.683333,138.683333
jp,kataoka,Kataoka,35,,35.05,135.933333
jp,katsura,Katsura,22,,34.983333,135.7
jp,kawara,Kawara,18,,30.333333,130.383333
jp,mukai-awagasaki,Mukai-awagasaki,15,,36.633333,136.633333
jp,obatake,Obatake,11,,34.7,133.25
jp,osame,Osame,03,,40.966667,141.373333
jp,oura,Oura,21,,32.516667,130.366667
jp,yunogo,Yunogo,31,,34.983333,134.133333
kh,kanchung,Kanchung,03,,11.9833333,104.7666667
kh,khum thma edth,Khum Thma Edth,03,,11.9666667,104.6666667
kh,krieng,Krieng,09,,12.6333333,105.9833333
kh,phumi krang kor,Phumi Krang Kor,19,,11.4333333,104.8666667
kh,phumi svay phaem,Phumi Svay Phaem,06,,10.75,104.5166667
kh,trapeang reang,Trapeang Reang,08,,11.2,103.7666667
kp,chongjadong,Chongjadong,06,,38.0088889,126.2588889
kp,hwajonni,Hwajonni,13,,41.4591667,128.2313889
kp,morori,Morori,01,,40.5416667,125.6416667
kp,saemaul,Saemaul,03,,39.7833333,127.4
kp,saetomaul,Saetomaul,06,,37.9641667,126.1925
kp,somunsi,Somunsi,11,,39.7111111,124.6663889
kp,taikayodo,Taikayodo,03,,41.075,128.9047222
kp,takkol,Takkol,07,,38.5219444,126.1538889
kp,tangchon,Tangchon,06,,38.3688889,125.0927778
kp,unjong,Unjong,14,,38.8383333,125.4111111
kp,wolpyongdong,Wolpyongdong,01,,40.8636111,126.0211111
kp,yanghari,Yanghari,03,,39.4519444,127.3533333
kp,yangsandong,Yangsandong,06,,38.2875,125.2075
kp,yokchidong,Yokchidong,11,,40.2733333,125.2752778
kr,andongni,Andongni,05,,37.0353,128.3161
kr,chonwolli,Chonwolli,03,,35.504167,126.793611
kr,gessho,Gessho,16,,34.581389,126.920556
kr,hambakkol,Hambakkol,20,,35.124799,128.452157
kr,hangyangtcheng,Hangyangtcheng,11,,37.5985,126.9783
kr,hasimdong,Hasimdong,17,,36.166667,126.65
kr,kombawi,Kombawi,12,,37.566111,126.675278
kr,majae,Majae,17,,36.3962,126.8927
kr,myongjuri,Myongjuri,16,,34.558611,126.856944
kr,naesadong,Naesadong,06,,37.77884,128.19417
kr,paenamukol,Paenamukol,17,,36.6484,126.8035
kr,taegok,Taegok,06,,37.201491,128.056638
kz,kondrat'yevo,Kondrat'yevo,15,,49.65,83.766667
kz,zamorenov,Zamorenov,07,,51.466667,51.483333
la,ban lamphen,Ban Lamphén,06,,20.025556,100.607222
la,ban samsao noy,Ban Samsao Noy,14,,18.75,103.3
la,same din,Same Din,03,,20.583333,104.1
lb,haret et tahta,Hâret et Tahta,03,,34.1833333,35.9
lk,bayagama,Bayagama,32,,7.7,80.2666667
lk,imiyangoda ihala,Imiyangoda Ihala,32,,7.6,80.25
lk,opata,Opata,34,,6.1,80.2
lr,gohnsua,Gohnsua,05,,7.7758333,-10.1786111
lt,milkunai,Milkunai,60,,55.1833333,26.4333333
lt,podvorishki,Podvorishki,56,,54.4333333,24.1333333
lv,duci,Duci,18,,57.3405556,24.4433333
lv,goveiki,Goveiki,19,,56.4166667,27.6666667
lv,jaungulbenes,Jaungulbenes,09,,57.0666667,26.6
lv,mazbrenguli,Mazbrenguli,31,,57.5166667,25.1333333
lv,sturi,Sturi,27,,56.6,22.75
ma,afauzouart,Afauzouart,55,,30.46,-9.5
ma,ait ali ou youssef,Aït Ali Ou Youssef,56,,32.104818,-6.186068
ma,ait arbi,Aït Arbi,58,,34.96,-4.53
ma,ait arfa,Aït Arfa,56,,32.005265,-6.650892
ma,ait ktab oufella,Aït Ktab Oufella,47,,30.885146,-9.099046
ma,amouguer,Amouguer,48,,30.784107,-5.233821
ma,andouz,Andouz,55,,31.018936,-7.81263
ma,daouina,Daouïna,58,,34.456752,-5.252795
ma,el khetarte n'ouzreg,El Khetarte n'Ouzreg,48,,31.228075,-5.211919
ma,imzil,Imzil,55,,30.521623,-8
ma,tarjicht,Tarjicht,53,,29.058273,-9.428775
ma,tioukarine,Tioukarine,55,,30.631695,-7.711785
md,buschi,Buschi,58,,47.7175,29.077778
md,romanesti,Romanesti,61,,46.331111,28.972778
mg,ankiakabe,Ankiakabe,03,,-14.75,48.9333333
mg,mitanonoka,Mitanonoka,04,,-17.7666667,48.9
mg,sahantaha,Sahantaha,01,,-15.05,50.35
mg,tembizoka,Tembizoka,02,,-23.6,47.0166667
mk,dlabocica,Dlabocica,52,,42.1986111,22.2413889
mk,dzepista,Dzepista,21,,41.4427778,20.5327778
ml,riziam,Riziam,07,,14.4833333,-5.9833333
mm,chaungnakwa,Chaungnakwa,13,,16.1655556,97.9675
mm,hwehawn,Hwehawn,11,,21.3666667,97.5
mm,kamahta,Kamahta,05,,17.3333333,97.8666667
mm,laikawng,Laikawng,04,,25.7666667,97.8
mm,loi-mawt,Loi-mawt,11,,23.4,98.1666667
mm,makaukpat,Makaukpat,10,,24.8,94.7833333
mm,natchaung,Natchaung,05,,16.0,98.15
mm,suangphei,Suangphei,02,,23.15,93.8333333
mm,ta-ni-la-le,Ta-ni-la-lè,06,,19.5166667,97.2333333
mm,thedangyi ywa,Thedangyi Ywa,12,,11.55,98.7333333
mn,baatar uan huryee,Baatar Uan Hüryee,21,,49.4166667,102.7
mw,muyala,Muyala,11,,-13.9833333,33.45
mw,zebediya kaunda,Zebediya Kaunda,21,,-11.5,33.7
mx,alonso osorio,Alonso Osorio,20,,16.183333,-95.166667
mx,amatitan,Amatitán,14,9791,20.833333,-103.716667
mx,capulhuac,Capulhuac,15,20517,19.191389,-99.466944
mx,casitas,Casitas,30,,20.25,-96.783333
mx,el botadero,El Botadero,18,,21.75,-105.266667
mx,el coacoyul,El Coacoyul,12,,16.766667,-99.5
mx,el coyul,El Coyul,20,,15.926389,-95.8125
mx,el laberinto,El Laberinto,27,,17.75,-91.3
mx,el limoncito,El Limoncito,15,,18.433333,-100.233333
mx,el lindero,El Lindero,11,,20.95,-101.633333
mx,el sonoreno,El Sonoreño,06,,28.433333,-106.783333
mx,hacienda san jose,Hacienda San José,07,,25.7,-101.716667
mx,la palma leada,La Palma Leada,12,,17.9,-101.733333
mx,la pinuela,La Piñuela,16,,18.736111,-100.994444
mx,la zarza,La Zarza,20,,16.118056,-94.195278
mx,los acosta,Los Acosta,27,,17.945833,-92.9
mx,los alisos,Los Alisos,11,,21.210833,-101.519444
mx,playa lauro villar,Playa Lauro Villar,28,,25.833333,-97.15
mx,polvoron,Polvorón,26,,27.366667,-110.2
mx,providencia de la parrita,Providencia de la Parrita,19,,24.9,-99.283333
mx,rancho el veracruzano,Rancho El Veracruzano,10,,26.65,-104.216667
mx,rancho rosas,Rancho Rosas,06,,28.35,-108.3
mx,rancho san jose de los brazos,Rancho San José de los Brazos,28,,26.816667,-99.366667
mx,rancho virgen,Rancho Virgen,26,,29.083333,-110
mx,ri playas,Rí Playas,30,,17.175,-93.841667
mx,rio del trovador,Río del Trovador,20,,16.55,-95.466667
mx,san esteban,San Esteban,21,,18.45,-97.283333
mx,san juan de amargos,San Juan De Amargos,07,,25.933333,-101.016667
mx,santo domingo,Santo Domingo,03,,25.491111,-111.917222
mx,santo domingo tonaltepec,Santo Domingo Tonaltepec,20,,17.606111,-97.361389
mx,yervanis,Yervanís,10,,24.737778,-103.843056
my,kampong batu sawar,Kampong Batu Sawar,06,,3.49,103.1245
my,kampong long kiput,Kampong Long Kiput,11,,4.016667,114.383333
my,kampong machang,Kampong Machang,03,,6.066667,102.15
my,kampong nangar,Kampong Nangar,11,,2.566667,111.416667
my,kampung menawo ulu,Kampung Menawo Ulu,16,,5.318,116.2017
my,kampung sama,Kampung Sama,06,,4.0345,101.9627
my,kampung sungai karas,Kampung Sungai Karas,01,,2.357,103.039
my,plaman buta,Plaman Buta,11,,1.35,110.1
my,taman perpaduan,Taman Perpaduan,07,,4.6361,101.1517
mz,alcolete,Alcolete,10,,-16.8138889,34.5558333
mz,chicoma,Chicoma,06,,-14.8611111,40.7625
mz,machachan,Machachan,02,,-24.4083333,33.1302778
mz,meteva,Meteva,06,,-14.4705556,40.4286111
na,epoko,Epoko,36,,-17.4666667,15.2
na,okahwa,Okahwa,32,,-18.7333333,13.8833333
na,tamsu,Tamsu,34,,-18.5833333,20.5666667
ne,dibilo,Dibilo,05,,14.2,0.7833333
ne,ouinditene,Ouinditéne,05,,13.7666667,2.9
ng,ajavwuni ugbevwe,Ajavwuni Ugbevwe,36,,5.878651,5.81072
ng,jauro tuku,Jauro Tuku,55,,10.067848,11.203937
ng,jegan maunde,Jegan Maunde,27,,9.283333,11.516667
ng,maidontoro tsofo,Maidontoro Tsofo,49,,9.716667,9.066667
ng,ndiavu,Ndiavu,45,,5.529419,7.752538
ng,rafin baura,Rafin Baura,40,,12.1,4.233333
ng,rikaka,Rikaka,51,,13.207485,5.667091
ng,sufa,Sufa,46,,12.1533,10.4289
ng,yaza,Yaza,35,,10.294981,13.257877
ng,zhirangwa,Zhirangwa,27,,10.436328,12.266269
ng,zideyeregbene,Zideyeregbene,36,,5.063034,5.764124
ni,el coquital,El Coquital,04,,11.9333333,-85.2
nl,overlangel,Overlangel,06,,51.776131,5.673488
nl,peelsehuis,Peelsehuis,06,,51.622605,5.666831
nl,vorenseinde,Vorenseinde,06,,51.533333,4.583333
no,bentsjord,Bentsjord,18,,69.533333,18.633333
no,bergby,Bergby,05,,70.15,28.916667
no,bo,Bø,20,,59.366667,10.283333
no,hareton,Hareton,01,,59.966667,11.5
no,hauskje,Hauskje,14,,59.216667,6.133333
no,mo,Mo,14,,59.483333,6.3
no,nore,Nore,04,,60.166667,9.016667
no,ristveit,Ristveit,04,,59.866667,9.833333
no,stordalsbugen,Størdalsbugen,16,,63.566667,9.783333
om,as samidah,As Samidah,02,,24.015,56.7527778
om,sur al `abri,Sur al `Abri,02,,24.6566667,56.5152778
pe,antashallalle,Antashallalle,08,,-14.6202778,-71.8877778
pe,cachipujo,Cachipujo,04,,-15.6033333,-71.0658333
pe,cauta,Cauta,04,,-15.5380556,-71.7955556
pe,chaclaya,Chaclaya,18,,-16.1666667,-70.55
pe,cruzcag,Cruzcag,10,,-10.3491667,-76.175
pe,hacienda casa blanca,Hacienda Casa Blanca,02,,-9.5,-78.1333333
pe,hacienda pabur,Hacienda Pabur,20,,-5.2197222,-80.0283333
pe,la palma hacienda,La Palma Hacienda,15,,-12.1166667,-77.0166667
pe,morullo,Morullo,21,,-17.15,-69.6166667
pe,ocona,Ocoña,04,,-16.4330556,-73.1077778
pe,san cristobal,San Cristóbal,10,,-10.2888889,-76.1919444
pe,sisiccaya,Sisiccaya,11,,-13.2,-75.75
pg,badilu,Badilu,12,,-4.8,146.2
pg,karum 2,Karum 2,08,,-6.3333333,145.1666667
pg,kokun,Kokun,12,,-4.3166667,144.6333333
pg,murupukio,Murupukio,06,,-7.6,143.2333333
ph,anibong,Anibong,33,,14.2149,121.4662
ph,bantolan,Bantolan,49,,10.787161,119.587405
ph,botong,Botong,11,,9.72256,124.212958
ph,katongkolan,Katongkolan,21,,10.7035,123.8079
ph,lambac,Lambac,33,,14.2569,121.4572
ph,lombayan,Lombayan,30,,10.6019,122.046
ph,ma-alan,Ma-alan,18,,11.35,122.883333
ph,manaois,Manaois,63,,15.6781,120.6004
ph,mocpoc norte,Mocpoc Norte,11,,9.883333,123.8
ph,munguia proper,Munguia Proper,48,,16.2971,121.1564
ph,oyungan,Oyungan,30,,10.625,122.1868
ph,san roque,San Roque,67,8637,12.533333,124.866667
ph,santa nino,Santa Niño,28,,17.9585,120.7145
ph,sominabang,Sominabang,16,,13.5223,122.9851
ph,tanawan,Tanawan,G8,,15.4017,121.3832
ph,tigbauan-maasin,Tigbauan-Maasin,30,,10.9197,122.4806
pk,al-flah town,Al-Flah Town,05,,24.8737,67.1635
pk,al-nur town,Al-Nur Town,04,,31.4762,74.3625
pk,badiana khurd,Badiana Khurd,04,,32.381462,74.6228
pk,badoke,Badoke,04,,32.266667,74.133333
pk,basto bagley adel khan,Basto Bagley Adel Khan,04,,29.365779,71.086617
pk,baware nau,Baware Nau,04,,32.121188,73.619848
pk,dansai jo,Dansai Jo,02,,27.253188,66.58141
pk,dhok kufri,Dhok Kufri,04,,32.927387,72.334282
pk,faqiran,Faqiran,02,,30.573992,66.641243
pk,firoz kirio,Firoz Kirio,05,,26.12071,68.501826
pk,gaufgarh,Gaufgarh,04,,31.139443,72.651883
pk,goth haji shahdad khushk,Goth Haji Shahdad Khushk,05,,24.552889,67.951628
pk,goth iqbal memon,Goth Iqbal Memon,05,,26.872022,68.371149
pk,goth kativar,Goth Kativar,05,,24.893837,68.049333
pk,gujarke,Gujarke,04,,32.187504,73.455955
pk,hafizabad,Hafizabad,05,,27.869444,68.419444
pk,imamwali,Imamwali,04,,31.537373,70.961926
pk,kachchi kothi,Kachchi Kothi,04,,31.313889,72.923611
pk,kamand rial,Kamand Rial,04,,33.352188,73.298559
pk,khadang,Khadang,03,,34.539048,72.812872
pk,kormal,Kormal,03,,34.930182,72.533297
pk,kriplian,Kriplian,03,,34.290278,72.848333
pk,makaure da wahan,Makaure da Wahan,04,,28.672222,70.214168
pk,mamiam,Mamiam,04,,33.510432,73.502667
pk,mulla katyar,Mulla Katyar,05,,25.113501,68.364715
pk,nawi lali,Nawi Lali,05,,27.982468,68.666921
pk,nola mela,Nola Mela,03,,32.95,71.15
pk,pathai,Pathai,04,,32.495748,74.638372
pk,qadiwind,Qadiwind,04,,31.168333,74.481389
pk,ribatdeh,Ribatdeh,03,,36.223536,71.709809
pk,salem khan jatoi,Salem Khan Jatoi,04,,28.743112,70.397122
pk,sarangpur,Sarangpur,04,,32.320997,74.682243
pk,sodawal,Sodawal,02,,29.482936,66.533716
pk,taralai,Taralai,03,,34.502976,72.713994
pk,wari,Wari,05,,26.891045,67.772507
pl,alt proberg,Alt Proberg,85,,53.829783,21.369387
pl,brzostow,Brzostow,86,,51.964954,17.430097
pl,dabrowa rzeczycka,Dabrowa Rzeczycka,80,,50.656578,22.0443
pl,drawsko kreuz,Drawsko Kreuz,86,,52.853788,16.030228
pl,giecz,Giecz,86,,52.320284,17.37095
pl,greulich,Greulich,72,,51.360189,15.764806
pl,nowinki,Nowinki,75,,51.735047,22.337006
pl,rabka,Rabka,77,,49.616667,19.95
pl,rumunki orlowo,Rumunki Orlowo,73,,52.766667,19.3
pl,schmolz,Schmolz,72,,51.072615,16.881818
pl,slawa,Slawa,87,,53.779732,15.897946
pt,aves,Aves,17,8506,41.370339,-8.410104
pt,paul de cima,Paul de Cima,14,,38.957757,-9.380862
pt,tojal,Tojal,14,,39.083333,-9.083333
ro,banesti,Banesti,42,5636,45.1,25.766667
ro,buzahaza,Buzahaza,27,,46.6,24.833333
ro,durnesti,Durnesti,07,4144,47.766667,27.1
ro,fundu lui bogdan,Fundu lui Bogdan,04,,46.516667,26.966667
ro,kisseten,Kisseten,36,,45.75,21.733333
ro,lacu baban,Lacu Baban,40,,45.6,26.966667
ro,majdan,Majdan,07,,47.666667,26.833333
ro,poiana marului,Poiana Marului,34,,47.4,26.016667
ro,urcu,Urcu,12,,44.835,21.833611
rs,brezani,Brezani,00,,43.324167,21.339722
rs,brvenik,Brvenik,00,,42.811111,21.426389
rs,donja susaja,Donja Susaja,00,,42.348889,21.689167
rs,kosavc,Kosavc,00,,42.089722,20.702222
rs,novi varos,Novi Varos,00,,43.474444,19.287778
ru,abazinka,Abazinka,43,,52.371111,40.196667
ru,antoshkovo,Antoshkovo,60,,56.983154,28.044173
ru,arapovka,Arapovka,09,,50.516679,36.291054
ru,balabin,Balabin,61,,47.418038,40.905633
ru,bobury,Bobury,69,,55.652199,34.03744
ru,bogatyye ploty,Bogatyye Ploty,43,,52.2047,38.2142
ru,borovskaya,Borovskaya,85,,60.587912,41.427837
ru,bukharevo,Bukharevo,21,,57.556909,42.273752
ru,buolkalakh,Buolkalakh,63,,72.933333,119.833333
ru,chernaya,Chërnaya,25,,53.982356,33.865987
ru,chertushkino,Chertushkino,73,,55.158716,51.016071
ru,farkovo,Farkovo,39,,65.716667,86.966667
ru,feklino,Fëklino,13,,55.5303,62.5169
ru,goloshchapovka,Goloshchapovka,41,,51.8762,37.6731
ru,gozhan,Gozhan,90,,56.524983,55.197102
ru,grobovo,Grobovo,71,,56.821483,59.53978
ru,isavshchino,Isavshchino,62,,53.716667,40.683333
ru,iso paljarvi,Iso Paljärvi,85,,60.983333,36.35
ru,kashino,Kashino,76,,53.929543,38.850109
ru,kazarevo,Kazarevo,69,,55.563611,33.346667
ru,kishkinskaya,Kishkinskaya,88,,57.9084,38.3193
ru,"kolik""yegan","Kolik""yegan",32,,61.722778,79.123056
ru,komlevskaya,Komlevskaya,85,,60.485293,43.056553
ru,kozlanga,Kozlanga,85,,59.3,41.083333
ru,krasnovskoye,Krasnovskoye,39,,56.4273,90.4276
ru,krutoye,Krutoye,56,,52.128106,37.190897
ru,kurovshchino,Kurovshchino,72,,52.895727,42.422968
ru,larina,Larina,40,,56.3599,63.5814
ru,lavyn,Lavyn,60,,58.670289,28.513795
ru,lebyazhye,Lebyazhye,75,,58.4879,82.7014
ru,luch,Luch,62,,54.447421,41.652052
ru,lukhino,Lukhino,52,,57.8073,30.8975
ru,lysmanovo,Lysmanovo,90,,57.976,56.8267
ru,malaya buinka,Malaya Buinka,73,,54.952973,48.238905
ru,mamosovo,Mamosovo,62,,55.064485,40.754622
ru,maslyanovka,Maslyanovka,54,,55.200385,72.659897
ru,molotovskoye,Molotovskoye,70,,45.846038,41.518852
ru,moma,Moma,63,,66.45,143.1
ru,monastyrka,Monastyrka,31,,53.217778,91.758889
ru,munay,Munay,29,,53.2134,86.9009
ru,nizhniy matveyevskiy,Nizhniy Matveyevskiy,61,,49.5868,42.1059
ru,ogarkov,Ogarkov,84,,49.483333,45.733333
ru,orelye,Orelye,52,,58.9,31.7
ru,osintseva,Osintseva,13,,54.916667,60.466667
ru,pentilia,Pentilia,42,,60.347091,28.678761
ru,podskalnoye,Podskalnoye,27,,44.0828,40.9664
ru,pogost dolgiy,Pogost Dolgiy,52,,58.46124,34.89096
ru,polyanskiy,Polyanskiy,81,,53.4443,47.8257
ru,prigorki,Prigorki,77,,57.758729,37.034264
ru,rodniki,Rodniki,90,,58.483585,56.754205
ru,rudenka,Rudenka,25,,54.831648,35.471399
ru,rudnitsa,Rudnitsa,77,,56.484784,34.268454
ru,rutaka,Rutaka,64,,46.714445,142.529389
ru,safronova,Safronova,32,,60.516667,65.316667
ru,sashino,Sashino,85,,59.702724,38.141314
ru,shalava,Shalava,88,,57.338865,39.817663
ru,shuklino,Shuklino,10,,52.761042,33.805808
ru,shumilovka,Shumilovka,42,,59.267471,29.936852
ru,sormovo,Sormovo,16,,55.6239,46.1939
ru,sorokino,Sorokino,69,,54.746293,34.231718
ru,suchki,Suchki,56,,52.798474,35.989271
ru,sukhona,Sukhona,06,,62.4237,40.5835
ru,svetlovo,Svetlovo,23,,54.897796,20.175694
ru,sysoyevka,Sysoyevka,69,,53.8527,33.0811
ru,toroetsukoe,Toroetsukoe,64,,46.920574,142.636883
ru,tretya alekseyevka,Tretya Alekseyevka,56,,52.575376,36.620852
ru,troitskiya ozerki,Troitskiya Ozerki,47,,55.091123,38.944901
ru,tumleyka,Tumleyka,51,,54.8166,42.5546
ru,turayki,Turayki,88,,57.8125,38.212
ru,turdali,Turdali,73,,56.15,52.916667
ru,turgenevo,Turgenevo,47,,55.33928,37.710371
ru,tyngiza,Tyngiza,53,,56.919812,76.719245
ru,ulazy,Ulazy,61,,48.846907,40.728971
ru,uspenska,Uspenska,78,,57.07454,65.063022
ru,voybokala,Voybokala,42,,59.881947,31.830927
ru,yakovlevskoye,Yakovlevskoye,88,,57.436263,39.383855
ru,yesino,Yesino,21,,56.723854,41.032016
ru,zaragat,Zaragat,08,,54.0386,54.6116
ru,zilanovo,Zilanovo,73,,55.416667,53.866667
sa,al-rawshan,Al-Rawshan,11,,20.01904,42.6168
sb,komurarata,Komurarata,08,,-9.4333333,160.2833333
sd,gebbid,Gebbid,33,,11.3833333,26.6333333
sd,korare,Korare,33,,12.9833333,23.4166667
se,ekeby,Ekeby,27,,56,12.966667
se,fansen,Fansen,03,,61.116667,16.233333
se,granberg,Granberg,10,,60.5,13.45
se,hermanstorp,Hermanstorp,06,,56.966667,12.583333
se,humlegardsstrand,Humlegårdsstrand,03,,61.35,17.166667
se,kvarnehagen,Kvarnehagen,28,,58.1,11.566667
se,salto,Saltö,28,,58.866667,11.116667
se,vahavaara,Vähävaara,14,,66.916667,21.9
si,brezovica pri gradinu,Brezovica pri Gradinu,04,,45.4511111,13.855
sk,novy svet,Nový Svet,04,,48.1,18.15
sk,svaty dur,Svaty dur,08,,48.95,18.85
sk,tura luka,Turá Lúka,06,,48.75,17.5333333
sl,allen town,Allen Town,04,,8.4136111,-13.1602778
sl,tefee,Tefee,01,,8.7,-11.2166667
sn,kolobane,Kolobane,07,,14.8191667,-17.0433333
sn,ndioubene galo,Ndioubène Galo,10,,13.9,-16.0
sn,nionaga,Nionaga,05,,12.4166667,-11.7166667
so,boosaaso,Boosaaso,03,,11.2847222,49.1825
so,ganbar,Ganbar,14,,1.45,43.9833333
so,marca,Marca,14,,1.7166667,44.8833333
st,germinia antonia,Germinia Antónia,01,,1.6333333,7.3833333
sv,ingeniero san andres,Ingeniero San Andrés,05,,13.8183333,-89.4044444
sy,ain daqne,Aïn Daqné,09,,36.5252778,37.0783333
sy,kawur kuy,Kawur Kuy,12,,35.95,36.25
sy,kharfan,Kharfan,09,,36.5833333,38.1666667
sy,mashat at turn,Mashat at Turn,04,,35.9416667,38.0805556
sy,tall basirah,Tall Basirah,14,,34.9833333,35.8666667
sy,tell el khodor,Tell el Khodor,14,,34.7333333,36.1
td,moundou,Moundou,08,135167,8.5666667,16.0833333
td,retgi,Retgi,04,,12.1,17.0
tg,amedjonokou,Amédjonokou,02,,6.2666667,1.5666667
tg,avedjikpodji,Avédjikpodji,10,,6.1952778,1.17
tg,lama tessi,Lama Tessi,22,,8.8333333,1.0833333
th,amphoe nakhon luang,Amphoe Nakhon Luang,36,,14.462807,100.608315
th,ban ba na ha ro,Ban Ba Na Ha Ro,70,,6.349806,101.272944
th,ban baek,Ban Baek,72,,15.833333,104.1
th,ban huai hua chang,Ban Huai Hua Chang,10,,17.707361,100.318334
th,ban huai po,Ban Huai Po,03,,19.992667,100.528389
th,ban kaeng noi,Ban Kaeng Noi,48,,12.907778,101.885111
th,ban khok chong,Ban Khok Chong,68,,6.586,100.700167
th,ban ko thak nuea,Ban Ko Thak Nuea,68,,6.895695,100.693778
th,ban krabuang,Ban Krabuang,16,,15.9,100.033333
th,ban mae ko luang,Ban Mae Ko Luang,03,,19.483333,99.6
th,ban map hua thing,Ban Map Hua Thing,68,,7.910278,100.289111
th,ban muang chum,Ban Muang Chum,03,,19.904917,99.952695
th,ban na muang thung,Ban Na Muang Thung,73,,17.054695,104.591222
th,ban saba yoi,Ban Saba Yoi,60,,9.183333,99.35
th,ban soi suk san,Ban Soi Suk San,48,,13.221806,102.169333
th,ban talad mai,Ban Talad Mai,35,,14.553,100.32175
th,ban thung sai,Ban Thung Sai,58,,10.533333,99.266667
th,ban wo,Ban Wo,06,,18.390833,99.346111
th,bang phae,Bang Phae,52,29548,13.691568,99.929816
th,king amphoe non din daeng,King Amphoe Non Din Daeng,28,,14.313529,102.748145
tm,esenguly,Esenguly,02,,37.4655556,53.9725
tr,abdicikmaza,Abdiçikmaza,04,,39.413072,43.132358
tr,aratepe,Aratepe,52,,40.684467,37.851923
tr,asagi kayi,Asagi Kayi,82,,40.566667,33.1
tr,bagbasi,Bagbasi,64,,38.746944,29.482222
tr,carikbozdag,Carikbozdag,45,,38.433333,28.65
tr,dokmetepe,Dökmetepe,60,,40.312245,36.291795
tr,durulova,Durulova,44,,38.35421,37.835367
tr,gullu,Güllü,72,,37.250822,40.715768
tr,guzeres,Guzeres,70,,37.324108,43.746803
tr,hacialiler,Hacialiler,43,,39.044444,29.504722
tr,hop,Hop,13,,38.147242,42.370205
tr,kekerli,Kekerli,49,,39.065834,42.347946
tr,kurugol koyu,Kurugöl Köyü,50,,38.433333,34.533333
tr,ortulu,Örtülü,03,,37.902967,29.781803
tr,parcinik,Parçinik,63,,37.418206,38.934371
tr,sorkun,Sorkun,03,,38.444722,30.054167
tr,susuz,Susuz,71,,37.305844,31.942972
tr,yasikaya,Yasikaya,04,,39.643615,43.388898
tr,yulari,Yulari,07,,36.359782,32.227241
tw,luku,Luku,04,,23.7463889,120.7547222
tw,putoukeng,Putoukeng,04,,25.25,121.5166667
tw,tingwutso,Tingwutso,04,,24.0666667,120.4333333
tz,kimu,Kimu,18,,-5.45,38.9666667
tz,magogoni,Magogoni,20,,-5.1666667,39.7833333
tz,mwakinda,Mwakinda,15,,-4.2166667,33.45
ua,beresna,Beresna,02,,51.571605,31.784558
ua,derebchin,Derebchin,23,,48.75559,28.335622
ua,klinki,Klinki,05,,47.290027,38.248897
ua,kommunary,Kommunary,11,,45.547881,34.222914
ua,kudobintse,Kudobintse,22,,49.701545,25.170552
ua,lidykhiv,Lidykhiv,22,,50.012626,25.393697
ua,maryevka,Maryevka,11,,45.114372,36.241136
ua,myslyatin,Myslyatin,09,,50.089091,26.713
ua,novoestoniya,Novoestoniya,11,,45.499596,34.242982
ua,stefanesti,Stefanesti,03,,48.616667,25.65
ua,yuryampol,Yuryampol,22,,48.739881,25.943217
us,allenfarm,Allenfarm,TX,,30.3991667,-96.2436111
us,bartholows,Bartholows,MD,,39.3738889,-77.2322222
us,basking ridge,Basking Ridge,NJ,,40.7061111,-74.5497222
us,belleville,Belleville,IL,40919,38.5200000,-89.9838889
us,bonny kate,Bonny Kate,TN,,35.8833333,-83.8997222
us,cedar mill,Cedar Mill,OR,13975,45.5250000,-122.8097222
us,cobham park,Cobham Park,VA,,38.0583333,-78.2619444
us,coldstream,Coldstream,MD,,39.4130556,-77.3097222
us,colony woods,Colony Woods,GA,,34.0988889,-84.5019444
us,de kalb,De Kalb,MS,,32.7675000,-88.6508333
us,diamond,Diamond,IA,,40.7630556,-92.9655556
us,elmwood,Elmwood,ME,,43.7225000,-70.5672222
us,etter,Etter,TN,,36.5613889,-85.0980556
us,fairbanks,Fairbanks,CA,,38.8658333,-120.6691667
us,fairthorne,Fairthorne,DE,,39.7727778,-75.6036111
us,gettysburg,Gettysburg,OH,,40.1113889,-84.4952778
us,grandview,Grandview,ID,,43.0530556,-112.7875000
us,high point,High Point,MO,,38.4844444,-92.5905556
us,hillsdale,Hillsdale,LA,,30.7444444,-90.6208333
us,huron,Huron,KS,,39.6383333,-95.3513889
us,jefferson center,Jefferson Center,PA,,40.7755556,-79.8350000
us,johnson,Johnson,ID,,46.4269444,-115.9155556
us,jonesboro,Jonesboro,AR,58619,35.8422222,-90.7041667
us,kimball,Kimball,NE,,41.2358333,-103.6625000
us,kinder,Kinder,LA,,30.4852778,-92.8505556
us,la push,La Push,WA,,47.9088889,-124.6352778
us,lambert,Lambert,AR,,34.3080556,-93.2063889
us,moscow,Moscow,AR,,34.1463889,-91.7950000
us,mountain park,Mountain Park,AL,,33.5650000,-86.7713889
us,new baltimore,New Baltimore,VA,,38.7672222,-77.7286111
us,newburg,Newburg,WV,,39.3883333,-79.8530556
us,oakway,Oakway,SC,,34.6011111,-83.0258333
us,palisades on the severn,Palisades on the Severn,MD,,39.0427778,-76.5777778
us,park view,Park View,IA,,41.6941667,-90.5455556
us,peola,Peola,WA,,46.3091667,-117.4819444
us,quincy hollow,Quincy Hollow,PA,,40.1536111,-74.8625000
us,saint francisville,Saint Francisville,IL,,38.5911111,-87.6466667
us,scoville,Scoville,ID,,43.4805556,-112.9952778
us,thomaston,Thomaston,NY,,40.7861111,-73.7141667
us,travis bridge,Travis Bridge,AL,,31.4541667,-86.7888889
us,verona,Verona,CA,,38.7861111,-121.6175000
us,wallingford,Wallingford,PA,,39.8908333,-75.3633333
us,wardlaw,Wardlaw,TX,,31.5463889,-97.0644444
us,waterway estates,Waterway Estates,FL,,26.6408333,-81.9091667
us,whitney heights,Whitney Heights,SC,,34.9777778,-81.9250000
ve,cachito de venado,Cachito de Venado,18,,8.8822222,-69.0155556
ve,el guarico,El Guarico,02,,9.7416667,-63.9958333
ve,el paradero,El Paradero,16,,9.7666667,-63.3166667
ve,jebucabanoco,Jebucabanoco,09,,9.2311111,-60.8444444
ve,la florida,La Florida,20,,7.7861111,-72.0466667
ve,las trincheras,Las Trincheras,06,,6.95,-64.9
ve,oficina guamal,Oficina Guamal,15,,10.35,-66.9666667
ve,palmira,Palmira,15,,10.0461111,-66.2775
ve,rancho chico,Rancho Chico,02,,9.2955556,-64.6661111
vn,ap go dat,Ap Go Dat,21,,9.866667,105.2
vn,ap hung hoa tay,Ap Hung Hoa Tay,03,,10.083333,106.483333
vn,ban suk,Ban Suk,07,,12.8,108.466667
vn,dam ngoc bai,Dam Ngoc Bai,37,,10.433333,106
vn,loung than,Loung Than,17,,23.116667,105.5
vn,luong ma,Luong Ma,31,,11.666667,106.733333
vn,mao-sao-phing,Mao-Sao-Phing,22,,22.316667,103.25
vn,muong te,Muong Te,22,,22.466667,102.616667
vn,phu dien,Phu Dien,28,,13.216667,109.25
vn,ple brang tpe,Ple Brang Tpe,10,,13.75,108.4
vn,som quc,Som Quc,19,,21.716667,104.9
vn,tien loc sach,Tien Loc Sach,26,,19.416667,105.15
vn,van truong,Van Truong,15,,21.016667,105.833333
vn,xom gai,Xóm Gai,86,,21.246882,105.202138
ye,`uruq,`Uruq,16,,15.4933333,44.2561111
ye,al jinnat,Al Jinnat,16,,15.6844444,43.9427778
ye,al-buqairain,Al-Buqairain,04,,14.5525,49.1255556
zm,mwambula,Mwambula,06,,-14.1333333,31.2833333
zr,bengengai,Bengengai,09,,4.8333333,27.6833333
zr,bingo,Bingo,09,,0.5333333,29.3166667
zr,ileo,Ileo,00,,-0.45,20.4166667
zr,kimwanga,Kimwanga,10,,-3.7166667,26.6833333
zr,lieke,Lieke,09,,-1.2833333,23.85
zr,nkame,Nkame,08,,-4.7944444,15.0819444
zr,tshimpuki,Tshimpuki,03,,-5.9786111,22.5275
zw,mtoroshanga,Mtoroshanga,04,,-17.15,30.6666667
gowid-1.4.0/examples/gowid-table/statik/ 0000775 0000000 0000000 00000000000 14262344540 0020144 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-table/statik/statik.go 0000664 0000000 0000000 00000202024 14262344540 0021772 0 ustar 00root root 0000000 0000000 // Code generated by statik. DO NOT EDIT.
// Package statik contains static assets.
package statik
import (
"github.com/rakyll/statik/fs"
)
func init() {
data := "PK\x03\x04\x14\x00\x08\x00\x08\x00w\x8fqM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00 \x00worldcitiespop1k.csvUT\x05\x00\x01\x13W\xf0[d\xbdOo\xe4\xb8\xb2/\xb8\xf7\xa7\xd0\xeel\xc2\x82\xf8O$\x97vUw\xd5i\x97\xab|\xca\xd5\xdd\xb7{\xd5\xccL:E\xa7$f\xe9\x8f\xdd\xf6n\xbe\xc5`v\xe7\xe1\x02\xef\xf6\\\x1c<\\\xf4\xe0\xe0apw\xfeb\x83\x08RJ\xd7\x19\x03\x16\x7fdFP\xfc\xcf \x19\x0c\xbd\x89s?\x0dO\xf0&LOp\xb1\xdd\xfa~\"\xf8\xd9\xefC\xec\xe1&\x1e\xe7\xd6M\x08?\xb8)L\xf3\xce\xc3\x87\xd8\xef \x9d\xb9;\xf8\xcd\xb5\xa184\xfe\xa9\x85\xdf.\xdaP\\\x11\xe4\x12@\xd4em5c5\xd4\xa64\x8a\x89Z!\xc3\xc65n\x17\x87\xbd{\x82\xcb\x13\xac\x0c\x80\x10%3\xa2R\xc4P\xa9\xdaJ\x99\x18\x86\xf3C\xe3zz\xc9_\xe0\xd2\x0d\xe7W'\xaf\xd0\xc4(*^1\xa8m\xa9\xb5\x16\xb2&\xbeyr\xcd_\x02\\f\xb7R\x00B\x96\x86I\xae4\xd4ui\x94b\x9c^\xb1u-\xbcq-pM$\xaaR\x15\x92\x98\xb2\x12Z\x1b\x8d$\x87y\xe7\xda\x00W\xc9\xe1\x96\xde\xca\xeb\x9a1F\xf9\xab\xa5\xe6\x1c ;\xd7\xfaC\xf1\xf5\xc9\x8d\xc5\xc1\xb5\xee \xae)\xe0o\x18pE\x01\xcc\xd0[\xb8V\xaab\xa0\xab\xb2\x12\x8c1A\xcc\xf3_:\x8fYk\xe1\xfa\x04\xa9px\xa9\xad\xd5L@\xadK\xad\xb8\xd6:1\x1c\x1a\xd7u~\xe7v\xcf\x8d\xeb\xcf\x0fq\x82\xeb\x7f \xbb\x8aSJ0/-\xaf\xa5\xc0b\x92\x9c\xab\x9a\xd2\xfb\xd5\xb5\x7fq\xe7\xe1\xbcw\x8f\xf0\xb7\x8c?\xbaG`\xc8\xc1J%j\xce%\xd4\xbc\xd4\xcaTJf\x96\xdf|s\xfe\xe4\x8b\xaf\xf1\xb1=\xf7E\xe3\x9fvn@\xf6\x1c\xfe\xb7\x14\xfe>\x85/\x95+\xeb\x8asL\xbf\xaad\xc5\x18F56\xa1\x7ftC\x80\xdb\x05T\x82\x9aNUUFP\x1d(]\xd5\x8c2\xfb\xe4\x0e\xc5\xd15\xed\x0c\xbf\xb8CqC\x881*L!\xa4\xac\x19\xd4\xa2\xd4\xc2\x08N\xe4\xcf\xf1\xb1\x0d\xf0+=E\x95\x1a\xa4\xaa-\xa3\xca\xd7V\x19s\xe6Z\xd8\xbb\xdd<\xc4\x07\x0f\xef\x12x\xf9O\x90\x12@V\xa5b\x86\xaa\x97\xd9\xd2Hc\x8c\xb1H\x7fpc\x13\xc6f\x82\xab\x05,\xe4\xd6\x08!\x04\x92+Y)\xa5j\"\x8f\xc76\x1c\xe6s_\x1c\xe3\xd8L\xbe\x83\xab5\xe4\x06C^\xfe\xb3\x03i\x01$/9\x13\xcb\x0b\xa5\xa8E~a;\xf7\xe10\xc3\x87\xe4H\x01 Y\xc9\x8deu\xad\x81W\xa5\xe0\xc4\x85\xa4\xfd~\x1e\xfc\xb3+|\xd1\xb9]\xe3\xe1\xe3~\x1e^\xfe\x93\x02\xae) '\xd5p\xe4\xa0\x17i\xb6\xbe\xe8\xd8\xfa\xb1\xf0\xc5\xb3\xdb\x0d\xa1\x1b\xe1&y\x7f\xcd^J#+mm5\xe7\x9cr\xa9\xc4R(\xd3\x10\x1fz\x0f_\xd0\xc1\x02\xcc\xf9\xe1\x95\xc9%b9\xb1!\xed\x83\x1f=\x96\x04\xfc\x94\x00\xd2W\xb9\xc0\xeb:g\x8a\xd79Y\x1d\xf8a\n\xdb\x06\xbeK\x0e\xc3\x8a\xb4\xa5N%\x0cR!)r!\xe9\xd88\xb7\x81[zbw\x97U\xc9\x15\xc7?\x90\xb2\xac\x05U\xd0\x99\x8b\xb0q\xfd>\xc2%=+\x0dp\xce\xeaR\xe4\xea\x13e\xcdR\x84\x11\xb6\xae\xdb\xb8\xfey\xee6\x1e\xde\xbc\xc2\x0cyL\xc9\xd9\x9a^\xb1D\xbdu\xe3\xe8\xc2\xf9\x9d\xeb\x1d\xbcI\xf8{\xc4\xd8\xeb\xcfYU*c,\xe3\xc0SQJE>8\xf8\x94\x1cY\xd3[\xe4\xf2\x96\xb5\xcd\x12\xe9\xb0m|\x8f1~ZQN\x16_\x18\xeaR\x9e\x18\x06w\x88\x0f\xe3!>\xe0@\xbeB\x91f]\xb3$\x07\xe5D\xa2\xf6\x9b\xc1?L\xe3\x0c\x9fWD\xe9\xe1\xa5\xc5D\xebSQ\x8dn\x13[\xb8\xa5\xa72):\xc1\xac\x94\x12;@\xee\x90g\x9b\x00\xc3<\xf8~\xef\x07\x07\x9fW\x84\xa2\xd89\xca\xf0\x9af\\Q\x955\xd1\xb3\xb3\xcd=\xb8\xd6\xdd\x057\xc4\x19.V\x84\x13\x8f-%\xf0\xb2^\xf2\x16a;o\x9b\xd0\x84v\xef\xe0\xcd b\xab9g:\xcf&8S\xa9\xa5l\x90\xe90\xb7\xed\xd1m\x1b\xb7up\xf5\n\xa7\xb9\x1dG\x05\xcc\x83$6\x89\xb2\xb36\xc8\xd6\xbab;\xfb\x07Wl\x9b\xb0\xc51\xabxC\xde7\xe4\xc5~p\xce\xabutAf\xa1\x12\xdf~\xeeq.\x81\x0f+\xa2Wq\xb6\xd6\x18R\xd7D=\x86\xc1\xc5.\xf6S\xec\xe1\xf6\x15\xa6\xf8Q\xb8\\\x9ay-Js\xb6\x19\x80\xe6\x1c\xb7\x0b}\x13\xe1\xe2\x15f2I*V\xc1\xb9\xd2\x94\x96\x01\xb6\xee\xde=\xfb0\xb8\x11\xde\x9c \xc7\xf2Z\xda\x0c\x9c\x0bS2\"\x9e\x87\xd0\xb9\xc9\x05x\x93\xd1\xcb?\x80\xa9T\xb6F\xc1\xb9\x14\x8b\xb0\xb1\x19Vac\x17\x8b\xad\xeb]\xbb\xca\x1a\xbbX\xbc\xa1\x80\x8aXY)\x90\x95\x95KC\x1a L\xee\xde5\x01\xfe\x9a]\x9c\xe5\xcey]\x9a\x9c i\xa8p\x06\xe8\xdc\xb0\x8d\xc5q\xf0\x93\x87k\xc27\x84\xab\x9a\x1a\x93]\xe8\xabR\xb0%\xf2#\x8aU{\xdf\xc1\xcd\x02R\x16l\xc9\x17r\x91\x05 \"\x8f\xc3\x14\x0b\xd7\xfa\xfd\xe0\xe1\xe6\xe5\xffA\xdfE\xf21|\x8b,\x85\xaa\xb86p\xaex\xa9Ej\x1c\x03|\x9dC\xff\x1c\x8f\xb1\x0d#\xfc\x0d\xf1\xcb\x9f\xc9C\x92\x1c\x17e\x95f\xd5sU\xa5\xe1\x89^6\xcd=\xd5\x00\x96Q\xf4\xe3\xe4\xe1\xcb\xab\x90O\x14\xb2D`\x96\x08xi\xd7\xd4\xceG\xd7\x85!t\xf0\xe3\x02R!W\xa5\xcc\xcd\x04\xc5\x7f*\xbby\x98\xb7\xf3y\xa6\x1e\xe6\x97\xff9\x9f_\x93\x07W;\xe7\x06\x05:\xa1\xca\xfal\xf3\x04\x1b\xf7\x1cZ\xffppp\xb9\"l\xb1J\x95\n\xb8E\xb9`\xf3\x04G?\xe2\x8ctpp\xb3\"\xa2\x92\xa5\xc5\xfe\xbc\xf6\xb8'x\x88\xedS\xef\xa7\x11~Z@\x8eN\x037\xa7Y\xf8 \x1e\x83\xbf\xefQ6\"\x07\xabU\xc9R)\x1a\x9ert[\x07;\x1a\x03\xc7\xc9\x0f\xc5v\x88\xe3\x88\x12\xe1\xdbS\xd8\x9b%\x8c\xa4\xc4\xbadK\x8f\x91K\xd1o\x1d\x8c\x8e\xa6\xa8\xd8\xcd\xae\xdd\xc1-\xf9>g\x1f\x0e5\xb2\xc6Y\xe9\\/}\xf4l\xbb\x83\xcd<\xce\xe7\xfbMlc\xbfwp\x89\xbew\x8b\xaf\xaa\x00\x18&\x15\xd7c\n\xa9w!n\"\xbc\xa5'\xfe\xca\x17\x11\x8fW\xb9\xab\x11\xd5}pG\x92\xe2\xdf\xae\x08\xe5y\x86C\x9d-e\xaa\xea\xed\x0e\x0e\xae\x9b\xc7y\x8c\xdd\xc6\xc1\xd5+\x8cQ\x9f\xd7y\xac\xa3\xd5\xd3\x1a\xf7\xc1w\x9b\xf8\xe8Z\x07W+\xa2\xb1\xb1&!l\xcd\xd7!\x06\x8f\xffp\xb5\x00\x8aT,2\x00\x17\xd8\x86\xb6(\xf1\xa4\x04^/ M\xef\xceC\x12\xd3yh&\xd2\xb9\xf5D\x9a]j\x97j\x11.\xb9)\xed\xd9\xf6\x8e\x96V\xaeu;\x7f\xf0\xb4\xc0B\xfc\xf2\xc7\xe1\xe5\x0fZ4\xc9\xb2FyN,5\xdf\x00\xf6\xf9~\x1b|\x0b\x17+\xc2V\x82\xb5%k!\x0d\xe8\x92qY\x1b\x8d\xd4\xf7n\xbf\x0f\xe3\x06\x87\xf7\x19~x\xed\xa1\xf5^]\xdaZ\xd8\x1at)\x98\xacku\xb6\x0d\xb0\x89w\x01E\x0b|\xa2HV\x97\x9aW\x95\xc41\xb4\xac\xaa\x9a\xd7\x12\xa9v\xc1u\xae\xa7\xda\xcd\x00\xe5/\x8d\xe5\xc5\x85DZ]\xab\xda \xe9>LC\x80w\xf44\x06@\x95\x8aY\xc5p\xfc(\xb9\xac\x84\xb6D\x15\xe7>\xc4y\xe3\xe1]F/\x7f\x90$\xa9Jaqm\x8b\x03\x0f\x934\xa1n\x03\x1c\xe2p I5\xbbF\xe3\xdc\xa8\x8c\xa94\xa3\x94\xb2\xcap\x93(g\xdf\xc7\xf9n@\xe2\xf9\xe5\x8f\x8c1\xb95\x0e)F J\xae\xd6RQ\x01t\x9bxpE\xbf\x9f\xfd8\xba\x1e\xae\xc9\xfbq\xf1j\x89I\xaamm+Mc\xa1\xb5\x82q\xe4\x8bs\xec\x8e\xad\x9f\xf1\x1f>\xbd\xf6h\x83E\xc3\xb5\x128g\x98\x92\x1b\xc9+\x99y\x868\xba>\x8c\x11YV\xac\x15\x80)\x0dSV\xc3\xb9.5\xb7\xbc\xa2|\x8fn\n(6?\xc1\xed\x8a\xb0\x96tYIMu\xc4I\x06=\xdbv\xb0\x9f}\xbf\x8b3\xbc\xcb.\xaesYUV\n\x17\xed\x82\xe5\x81\xa0\x83&\xf4\xee\xfc\x91d\xd9\xf7\x08\x7f&\x98\xa9s\xd3C\x96<\xf1\"K\xe7\xfa\xbd\xa7\"\x82\xeb\x0c_\xfe\x18\xd3\xee\xd2\"|\"7\x11\x0fq\xee\xe03>\xf0\xf7\xd3Z\x82\xb1Uv\xdc\xf6\xb0\xf1\xe1\xb9\xf1p\x99\x1c\x1c\x1d\xb9(m%\x15.\x98\x98.\x99\xa9\x146\x95\x1e6\xb1\x0f.\xb6\xc1\xf5\xfb\xe7\x00\x97\xdf\xf8\x04\xae=P\xee\xa9-.\x85*^*Q\x0b\x85l[\xd7\xef\xbf\x06\xd7\xc3\x9b\x05\xe0r\x8f\xd7\xa5\xae\xb9e\x06\x18\xc3\x96\xc9\x85\x90D\xdd\xb8~\xff47\x11\xde\xac\x88\xb6iM^\\\x00c\"\x0f\xdc\x89>\xf8\xfe\xc9\xf5\xfbi\xa6\x7fx\xf3\xaf\x01\xb4C\x88\x83\x13G\xb9[\x96\xbcb\xc2\xd8\xcc\xdb\xef\xb7\x0df\x01\xd9VL\x991\xeb\xe0R\x89\xd2\xe6\x1a@\x9ey\xdb\xb8\x80\xf2/\xbcy\x85\xb9\xa0}H\xce\x80\xa1tehzN\xf4m RtP\x96\xa2-F\xda\x15c\xac.%m\x90!\xe5\x0e\xc5\xa7}\x17\\\x84\xb7'\xc8\x15\x80\xd0\xa5a\x96\x11\x87.\x85\xa4-\xaa\xccqL\xd4\xc7\\J(C2\xc9S)\x99\xaaf\xa9\x0ev.N\xc1\xdf\x87\x1e\xde\xae\x08e\x1d!Jmk\x8b\x93GU\x97\xd6\xeaJf\xfa)\xe0\xb4\x8a\xf4\x0bZK\x85\xb3J\x02\xabT\xa9\xb8\x94)\x97\xbb\xe0\xe2\x93\xc3A \x9d*m;\xd7\x15cB\x02c\xbc\xac*VY*\xf3]\xec\xf7\xcd\xec\xfa}\x1f\xf6\x8ef\xcfo\xfd,\xed\xdf1\xdac\x04\xc6E\xa9\xb4\xa9T\x8d\xbcw\xf3}\x9819\xf0\xfd\x8aP\x86\xe0\xac4\x8a\xd9\x1as]\x95\x0c'$z\xd7\xde\xf5\xc7\x08\xef\xe8\x89\xa2\x97@\xc1\xb82LS\xea\xa5\xd2\x8c\xa9D\x17\xbf\x06\x17q\x85\xf6\xee\x04\xa9|d\xc9\xb5f\x9a\x03\xabt)mj\xa3\x8d\x0f\xd8R\xe2\x84 ~\xff\xda\xc3+J\xbd\xb0\xb5\x95XF\xb6\xb4\x1ae\xb6\xc4\xd5\xefw.6\xfe9\xc0\xfbW\x98f\x04VZ\xa3*\x0d\x0c\x17vL\xeb\x940d\x19\x1b\xd7\xff\x1e\x1c\xb1,\x18\xa7RQ\x95\x8cI\x83UA\x9b\x9c\xc6(jFM\xbc\x0f\x0d\xbc\xa7'NP\x02W L\x1b`\x15\xae\x11jS\x8bD6\xe3\xac\x8f\xc2\xca\xfb\x13\xa4R\x12%\xb35W\x98~Y\x1ac,\x15~3\xba~\xdf\xb8Gd8Ani\xf3\x80\xf3\xca(\x0eV\x97\xda\xd4&\x97\xd2\x1c\xf7\xf3\x18\xe0}vy\xaa\x81\x8a\xd3:\x0d\xdb>\xd7\\e\xca\x0eK1b\xdc\xaf0\x0e[\x1c\x97'\x86\xda\x02\xe3e.\x96\xf9\xf7\xb0\xf1\x18sri\xd0\x92\xa5\xb1\x95U\x14\xb1\xae\xb5d\x94\xcd\xfb\xe00o3\xfc\xb0\x80\xdc\x10j\x83Bxj\x08\x95\xb2\x15\xcf\xd4\xfd\x9e*\xf2\x87\x15aY\x93\xe4i9\xc5\x8eBVe\xa5L\xf4s\x1b\xe0\x07zRbmY[Y\x1b\xa4c\xa52J\xe7x\xa3\x1f\x9a\x83\x0b\xf0\xc3\x02\xb03 l\xdb\xdaRA\xf3\xd2\xd627\xc7C\x98qH\xd9\xc3\xd5\x02h7\x19\xc5U\xac\x8d4\x08\xa4\x9d{\xa4\xc6\xce\x83\x0dc\x1a\xe7\x1e\xae\xbe\xf1\xb1t\x9c`\x18\x0e\x19\xc0\x98,\x99\xc6\xde\x81|\xad\x8b\xf7sOc\xcc\x87\x13\xcc\x83\x08\xb7ue9\x15\xb9RU\xc5\x13\x075\xf4&\xc2\x87\x05\xe4.\xc2\xb2\x04\x88\xd3V\"\x9c;\xdf\xef\x8f\xd8??\xbc\xc2\x82\x01\x8a\x89\xa2\xd6\xc2\xb2D.*\x95\x9aW\xe7Z\xd7c\xb7\xbb^@\x1e\x9f\x84\x94\xd4aQ\x1e\x12\x86\xc9D\xdc\x8f\x18\xe1uvY:3\xabkS\xe9\xd4l\xa5\xe153\x896\xd2\x9c\x02\xd7\x0b\xc8\xc9\x90\xc6Z#\xd3\x90a\x84\x96\x8c\xa8}\xf8\x1d;\xc2uv\xa9\x9e\x18\x8d_\xb5\xa5z\x12\xb5Lcj\xef\xba\xa7\xb9\xdf\xc3\xc7\xec\xd2H\xb4\xca\x99X\xd8T\x18\xbd\xefS%~\\@e\x00\xa4]K\x8d\xabe\x11\x88\xd4a~t\xfd\xef3|\\\x00E\xcbK)\x0c\xf60\xac\x11#y\x9d&\x80\xa3\xeb\x8f\xae\xa7\xe1\x07n^\xe1<>\x08\x93\xeb\xdd\x96F/\x93\x1e\xf6\xf3f\x0cM\x84\x9b\x15QQ\xf3\xd2\xcaJ1Ie-\x85b\x8c/\xf4\xad'\xe2\xd6\xa7I\xc0\x96\x15\x13\xccZ\x9a\x1a5\x8a\xbfT\x85y\xe0\xdc\xce=\xfc\xed\x04s\x1b\xa9\x84a\x8a\xd1\x90XW\x95I\xfdg\xf0\xfd}p\xf099$\x16\xe0\x04\xc3*&H,\xa8\xb5`\x15\x1566\xe9m\x833:\xb6\xeb\xdbo|\xf9\x147U\xa5\xcd\xe4\x1e\xa9|\xbft\x9d\xaa\xca]G\x95J.\xf3\xc9\xd8\x84f\xdb\x04l\x15p\xfb\nSW\xc6\xe1I\xe1r\x1b\xbb\xb2\x148\x1de\x9e\xdf\xe9\x18\xf0\xf7\xb04$.\xe94\x18\xb3\xa6\xa4\x96\xa9\"\xc74!\xdc&\x87I\xaa\x90,\xc1XU\xa6\x81o\x1a]x\x9a]\x0f_\x16\x90'\x03!\xb4b\xa9mh\xc6\xad\xe6\x99:\x1e\xe2\xe4\x90:\x01\xca\xb9,\xed\"\x1cTe\x8dC\x13\x95\xed4\xc7n\x86/\xf4\xccS\xb7dB\xa3\x80R\x89\xb2\xaam\x8e\xf5\xd1\xf5X\xb8\xf0svi\x98\xaeK\xa5e\x8aS\x95\x92\x99,l=\xfa\xf0u\x86\x9f\xe9\xb9\xf4}\x95\x8e\xe8*S\xda$;<\xfa\x1e\x17\xa6\x1b\x17\xe1\xe7\x13\xcc\xe4\xdarMs\x91)\xadZd\xa4\xc7y?\xbb\x9e\n\xea\xe7\x13\xc45\xb4`\xd8\x1c\x8cI\x03\xaf\xa53\xd6\xc4\xf1\x1c\xe0g|\xe4\xae\xcfja%\xa7\xacq\xcd\x85&\xaa\xdf\x83\xf3\x03\xfc\x1b=\x97\x86[)+P\xdaa\xa5bRX\x99\xe9\xe2\x1e\xe7\xc0\xe7\x80\xd4+&Y\xa4*\xb5\xae*\xec\x16\x9c\x97\xdc*aD\xe6\x19\x03\xb6x\xe4\xc8hMqUil\xc0\xa6\xb4F\xd3\x02\x02\xe9\xfb\xfd}pX\x1c\xf0o\xaf0K\xe92\x96\xd7(\x9f2U\n.\xab4\x95\xa3\x14{\x87B0\x8eD\xbf\xbc\xf6P\xc5\x93@\x92\x06^\x1c\x96\x96\x0e\xfe\xe4\xe2}p\xbf\x87)\xce\xf0\xcb+\xcc\xd2\xa0P\x9b\x9aKM<\xb6\x162IJO\xf3\x9d\xef1v\xf8eE4L\xc9\xd2Za\xb8%\xc1D\xd6F\xd6D\xff\xdc\x90,\xdf\xef\xe1\xd7\x15\xe54\xd5\xb2\x16\xd4\xd3xY\xd3\xa1f\xa2\x8f9\xe5\xbf\xae\x88\x19\xeae\xd5\"e\xab\xb2Z\xa5\xfa\xe7$\x9e\xe04\xf0\xeb \xe6\xfa\xa8E\xea\x978\xd8R\xd7[8\xda\x90\x06\xf8__a\xb1\x94\xaf\x90\xb8\xa0\xa9T\xc9\x04\x1d\xd4\xaf<8\xf4\xfc\xba\xa2\\J\x95\xb1\xaa\x96i\x14\xaf\x8c\xc6\xf6\x14\xc1\x0d\x83{r=\\\xa0\xfb\xf2\xf7\x1e\x84\xc0u\x95Rumj8\xd7\xa2\xb4B\xd4Z!\xedf\x98G\xdf\xba\x11.\x17\xc0j\x00\x86\xc3%\x13p\xae\xeb\x92i\xce2\xed\xec{7\x16.\x0c~\x84\xcb\xe4\xb9 \x0fO{;\x861 \xe7Z\x97\x15.\x979\xf2l\xdd\xe8\x87\x10\x0b\xdf\x16\xdb\xb8q\xbb\x08o0\xe4\xe5\x1f\xb1\xf8\xae-\xde\xa4 \xa1h-\xc9\x14\x9ckU\xd2\xbb\xee\xda\x88\x11\x7f\x9f\x1c\x9c(\x0c\xc9\x91\x12id\xa9\xb5Q8\xdbG\xd8\xcfn<\xce[\xd7\xc2\xbb\x15\xa5\xe4TR\xd4\x95\xa1\xf4HYU\x86\xc8\x9b8L\x0e\xde\xd3\x13\xfbz\xbd\xccoX,\xf9\x80e\x1bq\xe6\x9e}[\x1c\xdd8\x0d\xaew8\x83\xa3\xfff\xf13J\x90\xa9\xd2\xbe\x99\xaeKQ%\xf99\xc2\x14\x8en\x1b\xbf\xce\x1e\xbe\xacH\xe0\xb0+j\xa8K\xc9+\xc1\xe0\\\xf3\xb2\xb6\xcc`\x19\x0d\xe0\xdb\xc2\x0d\x9b\xd8\x86)\xc2wmq\xb1`\x9ch\xec\xba\xc7kT>\xef8\xdb\xce\x94\xeb\xd0\xe5L\x13\xc0\x11\xf9\xf5\xee\xba6%NI3\x84\xb1\xf5\xbd\x83\xbf\x8e\xad\x7f\xf9\xaf\xb4\x0d\xcfy\xa9\xe0\xdc\x9c\xce\x81\xb63\x1cg\xbfic\xd1\xcf\xfe!\xc2M\xf2|$\x0f\x89\xad\xac\xacI$K1\x1b\xa3)\xee\xc1\x1f\xdd0\xc5bt\xfdX\x8cq\xde\x06\xf8\x9c\x83n1\xe8\x96\x82H4\xc5\xb1\xbc\xa2A\xf5\xdc\xf0R\xe2\xe4\x86=\xe2\x01\x06\x94\x8d\x07W\xec\\\xb1q\xc3\xd6\xc1\xe7S\xc0%\x050\x0d\xc0\xd4\xb2\x19@{\xbd\xcb!\xcb\xf6 \x8e~pM\x1c\x9e\x1c\xdc\xac\x88F8\x95\xb6\x94kM\xe2\x19\xa5\xf8\x19\x0e$\\\xc5\x07\xb8Z\x80Ib\x8e\xb6R\xa2\x98\x85m\xcb2\x94\xb2\x9e\xa1u\xcf\xbd/\xda\xb0y\xf0\xc3\x8e\x0ea{_|X\xbcF\xd0\xd1\x9b\xb1\x0c\xab\x94\xa9\x92W\\\xd1K:\xbfki\xb7\xf7z\x01:\xbd\x84K\xcep\xbe\xc4y\x8bIK\xc4\xe3\xbcmv\xb1\x85\xdb\xecjK'\xf7(\xf8+\x8b\xe9\x91\x12'\xd9\xb3\x9d\x87\x8d\xdb\xb9\xbeq\xf3\xe8{\xb8|\x85\xe9`\xd8.;\xe8(\x07($\xdf\xcdC\x1fw\xf0\xf6\xe5\xbf\x87\xfe\xe5\x9f\xe9\xacP\x9aR\xa4\x0d\x1e\xa4\xf0;\xdf\x8f~hc\xdc\xc3w\xaf0\xed+\x8bega\xd9Z&\x0e\\Q\xd1K\xbf[Q\x8eW\xad\xdb66S\xb6\x03-:\xfb=\x12\x9f<\x99^S:\xf2\x16\xc9\xce\xc3]\x1c\xc6\xc9\x0f>\x14\x07?\x8e\xbe\x1dc\xd3\xc2\xf7/\xff\\B\xafN\xa1(\xa7(\xbe\xec\xbe2\xb6\xe8\x98`,\x83\xef\x0f\x1e\xbeO\x0ee\x84/%cs\xc1\xecc\xbb\xdb\xb8m\x03\xef\x16\x90\xd50\xd2\xf9,X\\\xc0\xed<4n\xd8\xf9q\xf3\x04\xef\x17\x80b\x9a\x92\x98t[\xd6\x89\xc6\xb7\x1dF\xe1\x07x\x7f\x82Yv\x16\n\xf4\xb2\xdd\xbc\xf3p\x88C\xeb\xe1\xea\xe5\x9f\xe8`\x87Ul\xd9{\xb7I]\x87\xb2\xd0\xfaqt3|HN\xae\\\xbbVn\xa6yj\xe0\x03>\xf2\xef\xb4\x0d\x963\x8a\x04\x9d\x1b\xc6\xce\x87\x01\xae\x17\x90\x8b\x9d\xe5j\xe2%C\xba\xde\xcf\xbe\x8fS\xe3\xe1\xe3\x8a\xe8\xeca\x89\x0c\xab\x7f\x8d\xf5\x18\xdb\x870=\x9f#W77(p\xe7\x10\xe4\xee^\xfe\x1b\x83r\xed\xc8e\xb9\xb5\xec\xd1\xef<\x8c\xdb\xc6\xb9v\xf3\x04\xb7\x0b\xc8%\xaa\xa8D\xd7f6n\x9bG\xdf\xc7G\xa4K\x00\x85]\x85+k\xec\x12\x15\x15\xfd\xe87\xfew\x14\xa0\xb3K\x95\xcd\x96s \xb6\xc8\xb1\x89\xd4\xc7\xfe|\xf4~3\xcc\xdb\x03\xdc\x92\xf7v\xf1R\xc9\xe8\xbc{\x86%\x93\xce\x86\x90q\x8a\xc3\x80m\xf6\xcb\xcb?\x13\xc8\xa5\xb8\xf45^jJ\xcb4\xb7m\xe8\xf7\xbe\x87//\xff\xbd\xc0\xdc\xaa\xea\x8aW5\xb6\x84ZJ-\x91\xfa!\xf8v\x9c\xfcn\x82\x9fV\x94\xfb\\\x12.\xc0\xbcJ\xc3\xa3k\xdb&\xdey\x14~\xdb\xb6y\xf9\xe7\xdd\x92\xd9\xbcO\xabq.Z[\xd9\xa3o<\xfc\xec\x97z\\r\x03\xcbN\xe1\xd9\xee\x00\x91\xce\x81\x92Z\xc3\xe6 ^\xfeO\xf2\xbf[\xfc\x9c\xa5S*\xab\xa4\x01\x83\xa3mU\xd5\xc87\xf6\xfea\xf0p\x9b\x1c\x9cg\x95*ki\x04\xa7^\xc8Y-\xe5\xd9.\xe2\x94FE\x10b\xe16\xee\x9e\xe6\xb5\xbf\xe6\x80\x0b\n@\xf9\x9dY\x9c\x87tEe\x18\xa1uc1?\xb8\x11>\xb8\xb1\xf8\x11\x01.*q%\xbe\x1cP\xe9|l\x87\x99x\x06\xb7\xef\xfd\x06.\xe8\xc9\x04\xad\xc2\x97\xe9\x8f-G\xdbH\xb7\xbb\x0fm\xeb\xdaPl|_\xb8\xce\x0d\xf0v \xb9\xf4}q\x81!\x8a\xd1\x8c!\xa5\"E\xb8\xaa4\x8aj\x82\xf8\xe3\xec\x86\"\xce\xad\xdf\x15\xae\x0d\xf0\x96\xfc\x9f\xc8\x7f\xd1\xa6\xd3X\xa1J\xa1\x93\x82MU*\x85lw\xad\x9f\x87\x19e\x98\xe4\xd2v#.\x94\xe8\x0f\xce\xabRJR\x06@\xe2\xde\xcf\xc3\x01\xc7\x0e\xec\x86\x19\x89\xbc\xc4\xc9y\xe2\xeb\x81\xe1\xee\x19\x9e}\x17\xe7\xc1\xc1\xaf\xd9\x15\x96hy\x9dN\xe6eIkj\xce\xcf\xbc\x87c\x08\xb1\xf3\xd3\xe8\xe0fE8C*l5I\x9f\x0f\x17\xf7\x86v{\x91a\x0c\xbdk\xfd\xd1\x15n\x9c\xfb\xdd<\xc2\xed\x12p\x91\x03\xa8e\x99\xd2\xf0\xf5\xfcK\xd9\xb4\xdf\xeb=\xe0p}\x0c\xf0%9(*)S\xb2\xdc\xbb\xb8\xc6\x95\xcf\x92\xb4\xd9\xbba\x86\x1f\xe9I\x8a28G\xd6ICB\x95\x82\xb4\x96\xe5\x99\xdf\xc3on\xba+\xdcf.\xf6\xa1\xdf\x05\xf8\xedb\xba+.6s\xf1\x8e\xbc\xa4\x8c\\\x95\xb6\x92\x94{\x84\x9c\xad\xac\xe1y\xe3\xa6\xa2s\xe3\xe4\x86\xb9\x81\xdf\xfeJ\xfe\xeb\xc5\x8f\x8d]\xb0R\x9a$\x9e\x88\xaa\xd4\xd5\xca\xec\xdab\xe3\xee\x9by\x80\x8b\xb6\xb8L\x08\x87,n\xcaZ'=,dP\x8c\xf4+\xfd\x1e\x0e\xeen(\xa6\xb0s\x0d\\!\xfcB0\xbf\x83\xb35\x81Fq\x85\xf4\xbd\xdb\xffV\xf8\xf6\xfcy\xfe\xeaCWts\xe3\xee\xdc\xb3\x9f\n\xb7\xc1\xd6\xd6:\xf8\x98)~M\x14\xd7+\xc5\xc5\x06\xdb_\xebhS\x9f\xab\xb5\xf1\x0b\x9e\x8f.\xc5\x99\x1fa\xe3\xda\xd6\x8f\xd3\x10\xe1rE*\x9dT\x18\xc9\xb4\xb1p\xceK\xa9\xea\xca(\"\xf7cp\xfd\x08\x97~\x0c/\x7f\xef\xc7\xa4:\xc3Kn8cP\n%\x05\xaf\x91p\xeb6\xfe\xd9\x15;_l]wD\xb9~\xf1\xbf!\xbfR\xc4\xa7$\xb6_8\xafKc\x18\x13,\xb1\xa6\xb3\xa17\xd9\xcd\xba1Rs\xc9\xe9\x04\x0c\x975V'\xd2\xb4~X$\xf1\xf1\xb4~\xb8Y\x83\xa8\xf3\xd6YJ!E\x0dd\xdd\xf9>8xK\xcf\xba\x02a\xac\x129\xd7BJ(Y\xa5\xd3;\xda\xd9u\x0e>\xd0\x93\x92\"\xca\xda\n\xa3Q\xd6-\x8d2\x1akv\x84.\x0e\xbe\x9f<\\gWH\"\x15\x82W\xd2\xc2\xb9*\x8d0Z\xd3\xabIU\x92\xb4$\xe1\xa7\xd0\xb6/\x7fOX\xa5]\x13\xc3\xac\xb45r\xc8Z\n\xcb\xce\xfc\x04n\xb7\x0b\x85\x1b\"\\ \xb8\xc0:\x12\x80\xf2\x11\x0d\xa95mv\xaaZkN\xc4\xc3\xdeo\xfc\x04\x17\xd9\x95\xb8dc\x8b\x00&l\xde\xdc#\xd2G\xd7\xa2`=y\xb8 x\x89\x10\x0b\xcc\x96\xe6tZ\x85\xa4;\xdfm\xfcvKe\xb6 \x8a\x19\x074Z\xc6\x9b\x13\xe9\x10\xe7\x9d+Z?\xc0\xdb\x8c?\xf8\xa4f\xa5\xcb\x9a\x1a\"\x1d\x1f\xd36\nr\xe0\x88\x16\xc6\x00W\xd9M)\xa8\xe9\xcchMA\xe7\x9e\x8a\x9d\xefp\xb9\xf5T\xbcE\x90\x0b\xa1\x12\xcah\x869\xab\xad\xd0\xcc\x9e\xdd\x05h\x9e\x86\xf1\xa9u\xf0\x9e\xdc\x97\x7f\xa7)\xa0\xae\x16\xc5c.\x97\xac\x11\xedCDA<\xd1?\xbc\xfc\xb3}\xf9w\xf4!\x97\x04\xa8\x17\xcd0\xec\xd6Y\xad\xf9.\x00\xb6\xb9\x087\xf4d\n\xa0\xe6Y\xce\xc4\xf1\x8e\xad\x91\x1f}{\x88\xa3\xef\xfb\xe0\xbb\x007\xdf\xf8p\x96\xaeu\xc9\x18)\x82r\xd2\x91\xc5u\x0f\xb2\x05?\xb9!\xf4\x9d;\x04\xb8Y\xff~\x08kN4\x0dH\x1c\x97$\xb4\xafw\x17`\x9c\xe20>\x84\x03\xdcN(\x19#\xca\xd4y\x83\x82\xcb\xac\xb6sv\xd7\xc1C\xe8\xdd\x0c?\xd1\x13G|[*\x9e\x15\xce\xb1\xeam\x1a\x87\xef\x06p\xed&\xb8-\\$\xe7RP\xcbV\x8a\xe9\x8a\x01+\xb5\xa9\xb8D\xb2\x8dw\xf3\x83\x0b8:dpaH\x1c\x92\xb6f\\\x03\xca\x1a\xda\x9a\x9ah\x87\x88\xc3\x0d\\fw\xa1\xd4\x8aW\nh\xfd\xab\xa5@\xcam\xeb\xc2\xe0\xcf\xefb?\xb9\xd0{x\x93\xfc\xdf/\xfe\x0b\x99$\\\xae\xb5\xaa\xe9\x80\xd8\xd4Z%N\xbf\x9f\xfd\xe0\xb7\xf0\xa6}\xf9c?\xbf\xfc\x81\xf8\"\x0b\xba\\i\xad\xe0\\\x94\x95f5\xa3Dm\xe3\x9d\x93v\x94?\x1f\x9f\x06\x0f\x9f)\xe46\x85\xdcb\xc8E\xd2\xbe\x97R\xea\x9a\x81(\xad\xaa-\xa3\xb2\x18\xe2~\x0e\xbd\x9f\xe0\xf3\x02.\x92\xda=\xafLe\x05\x94\x8ckE\xb5\x94\xf4\xaf:7\xec\xc6\xf3\x9d?\xbf\x1b\xfc\xd8\xfb\xac\x87u\xbd\x84~\x9fBsb+\xadk\xad\x00;a%R\x1c\xfd\xe40\x8e\x80\xcf\xdd\xce\xd1V\xd0-\x85^S\xe8\xf5\x1az\x99\xf6Q\x19\xe8\x92\xf29\xce\x9b6\xec\xfb'\xb8]@N\xa9\xac\xb8U\x0cp\xe5\xa3*I-\xe3\xc1\xcd\xdb\xe0\xfb\xde\x8f\xf0\xd3 \xe6~[)\x10\xcb\xf2d\xef\xc0\x1d\xe2\xdc\xc5\xb9\x8f3\\\x9c`E\xf79V\xa5p\\:\x9f\xed7p\xe7\x86\xbeq\x1d|\x9f\xdd\xef$-\xf1,\xe0\n\x1d\x7f\x8f\xeda\x8a=|\x9f\xdd\x1f\x92z\x1d\x87\xf3e\x1c\xdfo\xa0m]\xbf\xdb\xc5\xc7!\x0e\xf0\xe1\x15\xfe\xb7\xb4\xcc5\x15K\x8a\x80\xa5\x12\xb49\xbd\xdf\xc0\xd1M[|\xdfMv/\xeb\xb4\xd7\x83-.\xbd\xf9\xe8\xbb\xcd\xe0\x9f\xe0&\xbb9\xb2\xda\xd0\x19\xc79\x1d\xc8\xe3\xe0\xba\xdf\xc0\x10\xb7\x0d|\xc6\xc7/\xf9\x8d$\xe2\x92\x1e\x92\xa1\x89z\xbf\x01l\x81}1\x06\\\x88\x8dpK\xbe\xdb\xec\xfb%m\x15\xe8\xbc\n\x12\xcb\x82|\xbf\x81\xa9\xc9)\xfc\xb2\x80\x1b \\r\xad\x90Cb\x99&\x19\x1d\x89\x1f\xfd8m\xe6\xe1\xa9\x88}\x81\xef\x1bz\xf8\xf9U\xd0m\n\xfa\xee\x9be4\n\x87\xf9m\x1eF7\xec\x1f\xfc\xd8\x04\xb8]\x11\xd7$\xbcUZTt\x85H\x94\xbc\xe6\x8a\xa8\x9b\xd0\xc5C\xe7wt\x10\xb5@\x99t\x0f,\xe3\x8a\xf8,\x12\xdf\xd1\xc8\x1c\xfc>\x0d\xcc\xe1\xe5?\xf6I\xc7N\x96\x06\xce\x95\xc8s\x9a8\xdb7\xe0v\xdew\xdd\xe0\xe0b\x01\xb8&\xaf\xcb\x1a\xd3Z\xa9D2\xef\x9f\\?\xce\x85;\xc4\xce\xed\x90v \xb9\xc8!\xd5\xa2\x90\xb6ds\xb9\x14\x80\xfc\xad\xdbD\xfc\xf7pq\x82\xd8F\xf3\x9d\x95ZC\x95\x8eC\xb1`\x1a\xd8\xcfG\xd7\xfb0\x84\xfd\x0c\xef^a\x9c\xff\xed\xa2k\x8eK>K\xe9\xdb?\xf9\xdd\xec\xe0]rpa(\xa9m\xf1\xb2\xc2\x9f\x0f\xf3~\x1e\x02\\%\x07\x97\x1b\xac\xca{S\x14\xc9\xab\xc2\xe8\xc3]\xeb\x8e\x11>f7\xa71e\x05N\xb71\xf6\x0dL\xee0?x\xf8\x92\x9cD(\x15T\xab\xe2(\xd2\xc4}\xdcE\xf8\x92\x9cDS\xd9,2\x948\xddQ\x9bn\xe0\xd9=\x86\xc1\xc3\xaf\xc9\xc9\xd9\\\xca\x12;\xca\xd9\xbe\x85p\xf0\x83\x1b\xdd~\x1cg\xf7\x15\xfe\xfa\x8d\x8f\xeaL\xad\xca`\xe7B/%{\xb6\xef\xa0s\x07Gr 9\xb8\xbcc\x02+\x98\xa9U3m\xdf\xc3\x8eN\xe0\x07\x07o\x17\xc0\xd2U\x87\xbc\xfc\x81s[*$\xec\xdd\x9d\xdb\xcf\xa1\xf5\xf01\xa3\x97?h\xd9\x8e\xb4\xb8\xe0\xd0\xeal\xff\x15680np*<\xfa\xaf\xb3\xef\xe9N\x1b\x85\xbc\xfcQ\xdc`\xd0\xcb\x7f\xc5\xb4\xd8\xc4\x923K\x9d\x10\xaf\xdf\xc5\xb9\x83\xcb\xe4\xd0\x9e9mV\xb1\xbc\xa3\x93\x88\xe2!\x0ea\xdb\xc4\xf3\x8dk\xdd\xb6qp\xb9\x86\\R\xc8\xcb\xdf\x97\xf8\xc1\xac\xdb\xf3\xfb\xaf\xd0\x8f\xae\xdf\xbb\xa7\xd8\xef\xe1\xe3 f=\xd8\xdc\"\x19+\xc5\xd9~\x00\xd7\xc7\xe2\xc1\x8d\x87\xd0;x\xf9?\xfaX\xfc\xe4\xc6\xc3\xcb?z\x07\x92\xd1\xd9\x17\x07\xceOi\x1f`\xe3\xba\x8dk\x03f\x97\xc0\xcb\x9ftf\x8ck\xa5\x1c5g\xab\x92\xe9\x1e'\xf4!\xf4\xfb\xf8\x10\xe0\x03\xa1\x97?\x1f\xd2 \xa7\xacJi\xf2\x05\x07Q*\x94\xfe\xb5A\x8ec\x18\xf6q\x84\x9b\x97\x7f\x90+\x0d\xa5C/\xca\xaeui\xaa\xb4\xfb\xb0'\x05\xf3!\x16\x9d\x1f\xe3Spp\xf3\xf2'z\xaf\xfd\xf8\xf2'\xfa\xa5H\x1b5\xf5\xba\xf3P#\xd3\xe8\xa6\xb8q#\x0eL\xd3\xcb\x9f\x84h\xcdm\xd7~\xc3y)\x91\xf2\xc1\x1d\x0eM\xec\xe1\xa7\x97\xbf'\x90\x0be\x8d\x91\x97\xfcl?\xa1\xb4\xd38\x9c\xcava\xa2\xcb\x0c\xefO\xbeoN$\xe0\xdc\xf2\xe5Lp?\xe1\xd41O8q\xccS:\x88\x928\x0c[\xb6l\xf0\xee'\xf8:\xd3\x91\xcd\xdf\x92\x83\x03\x11\x93\xe5\xd2\xbdm\xb5\\(\xdaO8\x81\x17n?\x8fS\xe8q\xda..\x10\xbf\xfc\xa3_R \x89\x9e\xaf/'\xfa~\x8a}\x88\xc58o\x9b0yj\xca\xcf\x89;\xffr\x9b\x7fy\xf9\x83~\xe2\x15\xb0J\xf3\x9a\xee\x17.\xa9Xwm\xf7\x8f\x80}a\n\xdb\x08\x7fK\xe0\xe5OZM0^2\xea\x92\xcb\x9d\xd1\xfd#\xcc\x9d\x1bb\xb1\xf1\xad\x83\x1f ^\"\xa4!\x8c\x9f\x06\x08&W\xd5\xce\xe6\x00c,\xdaX\x1c\xe7\x1enc\xf1!\x167s\x9f\xf4\xc1yN\x8e\xa0\x03N\xae\xce\x9a\x1e\\;\xc5\xe2\xc1\x0f;\x1c\x9a\xa7X\xfcD\x90\x8aC\x942\xdfc=7\xba\x94\xb9\xbc\x9b\x1e\xb6n\x18\xc23\xb5\xf07'\x88\xa3\x1b\xcbs)&\xca\xe8u`mz\xf0m1\xb9M\xebv\xb4w\xf9%C\x1c\xb5\x98(\x95\xa6\xbb\xba\xc4#d\xba\x95\xd3\xf4\xd0\x85\xc1\xe5\x83\xcb\xeb\x13\xcc<|\xa9`S\xe3p\xd3\x0c\xb0\x99\xa7C|\x08[\xbaT\x9f\x11i\xe4\xab\xb2\xaa\x96eZi\xab\xa4z\xd9\x0c\xd0\x85\xf6~\x1b\xe0:9\xd4\xe1\xb0\x97\xa5<\xb3\xba\xb4i\x0b\x8bH\xe3\xe0\x1e\xc2\xd6\x17\xe3p\x1c\x0f\xb4i\x91\xfc\xb7\xc9\xcf8\xbdg\xb9A\xc5\xd4:\xfd5\x13l\xdc\x13f\xf629TN\xe64\x89i\xb1^\x9ej&\xb8\x8f\x8d\xeb\xc38z\xf8aE8\x1d0\xbb\xea\x06\x9fk\xben\xc76\x13\xb4q\xdb\xb8a\x07\x1f\xb2\xcbX\x8a\xff\x15u}JK\xef\xfa\xe2\xe8\xda\x0eE!\xf8\xe8\xfa\xe2f\xf1\xe0\xba\x97\x99\x92\xd7\xb9.4\xf6\xc2\\\xe7\xd8\x13\xda8\xc0-=\x97\x1c\xac\xdb\xc8\xebM\xb0\xb3f\x06\xd7\x8eq|vm\xebF\xb8h\xc7\x97?\xc7\xe7\x97\xbf\xb7\xed\xcb\xdf\xc7t\xcfA\x975\x8e\x7f\xb9S63\x1c\xe6v\x8cw~;\x1e\xfcx\x9c\xc7\xe7\x89nI\xfdk\x10]\x16]\xefA\xf3\xaa\\\xd26C\x17\x0em\\\xe8\xae\xc3\xa1}\xf9s\xe5J+\x87\xbc\xad\xcf\xabR\"C\x1f\xc7\xe76\x1eC1\xb9\xfe\xc9\xc1\xc7\xc5\xfb\x85\xbc<\xc9\xe5\x8b\xa2\x96^\x8e\x0e\x9a\x19\x8e!\x9dE\x0c9\xfa\x9b\xe4\x7f\xf9c \xd9C\x97t7A\xadE2>\xe3 2\xf8\xf1\x19n\x9f_\xfe\xdeO/\x7f\x0e/\x7f\x8c\xcfIq\xb4.\x0d\xa6\x8c\xad\xe4\xf3\xfd\xd1O\x83\x87\x97\xff+\x83*\xb5.+\xd2\xbcdJ\x81\"a\xd8\x81k\xe7G\xda\x8d\x9a\xe1\x02\xe1%A:')\x8d\xb6\x06\xac.\x8d\xb0\x1ai7\xae\xf1~\x0c\x91\x16'\xaf0\x8e\xeb\xe7\xa5V\xba&]\x17[W*\x91\xb7\xf1\xe0z\xb8\xcc.\xad(L)\x8c\xb1\xa4\x97#K\\\x90\n\x9bh\xfb{7\x14s\xe7G\xd7\xba\xc4E!?\x9eB\xe8\x1e\x88\xa1k\xf9I\xdf\xc5\xc8\x9c\xac~r}\xff47\xc5\xc1\xb7q\x97d\x84\x14pE\x01\x99S\xf2\xac\xc9U3aV\xce\xe1\x90\x14j._{D\xba&bT*\xafJ\x97\x82\x931\x00\xe2\x9a\\\xbf\x7ft\x83;\xc0\xe5+L\xf9#\xe3\x15I\x8dSh\x9cx\x89\xc3c^0S\x0b`\xe9\x06S%\xd3\x0d`V\x95R\xd2\xd4\x8c\xd4m<\x14\xad\xdf`\xf4\x08?\x10\xcc)\xd2\x95\"%(\xa1he\x14\xe8\x8a\x8f;\x14\xa3\x9bf\xb8$x\x8b\x10\xeb\x84\x95\x9cN\xd0QVP\x96\xe2\xde\x06?\x0f\xa1I\xe4o\xb2\x87\x18(~]r&M~\x81b\xc8\xb1\xc7\x16B50\x04x\xf7\xdaC\xd7\xbcl\xa9\xac\xd1I\x95XpJ\xd0\xbe\x8d\xbb\xb8w=\xbc[\x00\xdd\x1d\xd4%\xe7U\xd2\x8b\xabJ\xc94\xb5\x91\x83k\xdd>\xe9\xac\xae\x88$\x9dRZ\xcbIU\xa7\x16\xba\xce\xa4a~\x0cHH.]\x01\xc4IH3jv\x8c\x0b\x99\xe8\x9e\xe6 \xae\xe8\x99_\\Yi\xa9J\x98b\xe9\xb5~\xe7\x1f\xb1\xea\xe0jEk#\xa9S\xe3\xb4\xaa\xaa3\xed\xdc\xefw\xee\xd1!\xf1\x02)\xe6\xba\xb4\\\x93\xfe^%y&\xee6\xae\xdf\x0f\xfe>\xd2\x0d\xa6\x05\xe7\x84\xa8:\xcd\x0c\xac*\x15#\xfd\x14\xe4\x99\xfbm\xc0Fq\xb5\x80\\\xd7\xe9\xc4\x84\xd4*\xebzi\x1dm\xe86\x11\x8b\xe9\xc3\x02\x04]\xb7+\x95f6i\xbb1A\x84\xf1\xd1=\xcd\xf0!9\xd48\xeb\xd2j\x91T\xb8$\xb3\x1c\xa9:\xd7\xef\xf7np\x8f\xae/8\xdd\x0d9\xf9X\xbe\x00U\x1bAj\x8aX\x17\xb2N\xa5\xdcE\xef\x06Wl\xa2o]\x0f\xd7\xc9w\x99|LR\xdb#}cE\xe5}t\x9b\x19; \xb5\xfe\x03\xdc|\xeb\xcd\xd9\xad\xd2.5fWYS3\x968\xef\xe9J\xfe\x14\xfb\xfd>6p\x93\xfd_\xb2?\xf3Z\xc6Hy\x0c\x8b\xaa\xa2%\x11\xf1\xf6;\xba\xa1\x15\x07\xb8y\x85\xb9\xc4\xf1J\xda\x8akK\xda\xed8P\xa7\xca>:\x1c\x9b\x83\xeb\x91!\xa3\xdc\xaf+e\xd2\x9d\x04\xa3\x95I\xb4\x93\xeb\x8bMh\x03\n\x97\xae/.\x11\xd2M\x0d\xba+\x82C'7\x96\xfa\xc4\xd1\x1f\\\xbf\xa3\xd2\xb99\xc1\xdc\xe4\x84\xacM\x1e\x97R\x9f;\xfa\x8e\xc6\x95\x83o\xe7\x03\xca\xae\xff\xe2O\xefP\x9ai\xb0\x96\xcc\xeb\x10Wh\xdd\x90z\xf6\x0d\xc1S\xbf\xaeq\xe5bs[\xd24\xa8!\xc7\xe0\xba\xb9\xdf\xc3\xe7\xe4\xd0\xa9ii\x14\xc3\x84\xd3\x8e+\x92\xcc;\x07\x9f\xf1\xc1\xd2\xf0\xadY\xcdiH\xa9\x0c\xa7HF\xb7\x19\\\xbf?O\xb3\xfb \xe7&/\xb5VIe[\xb1\xfc\xda\x91n \x15\x89\x81\xe0\x87W\xf4V.:\xa9\x9a-C\xee\xe8\x8fs?\xcd\x07\xb8]\xc0\xd2\xa1\xd2P[\x19\x14\xf7\x13\xe54\xe3H\xb1 \xc3\x0c\xb7\xaf=k\x8f\x15\xe9\x82\x05S\x92\xaa|\x0c\x9b\xb8s\xbb\x00\xb7\x0bH\xb3\x9f\xa8j\x06\xd6\x94\xcc\xa4am\x9c\xbb\x8d\x1f\xa8\xbf\x84\x11n\xbf\xf1\xe5&b9K\xe91\xcaP\xdc\x93\xdf\x87\xde\xf7\xd8K\xbf\x9c`\xeeW\xbc\x12u\xd6,\xd45K3\xc5\xe4\xbb8\xb4\xfe\xe0\xe1\xcb\x8ar\xe4\x8c\xc9t\x97A\x9b4\x12L\xf7\x01\xdbs?\xdd;\x8c\xff\x1b_\x1e\xcd\x05\xe7\x92F\xf3\xaaR\xd4%\xe6a\x9eF\xff8\xc3\x8f\x0bX\x8a]\xe4\xdb\x15\x15]\xc0J\xc5N\xa7f\x1blr\x1b\xd7\xe3\xd0G\x87g\xdf\x86,\x1d\x90\x9b\xa4\xcb\xacKM\xc2(\xf2?:,\xa1]\x80\x9f\x17 ,v\xbdEMH\xf0\xb2Vg\xc1\xc3f\xee{\xb7\xf7\x1e.\x17\x90/\xd2\xdat \x8f)\xacmZ\x90\x06\x0f\xbb\xb9/Z\x1f\x06\x0fo_\xfe\xdf\xbe\xf8\xf0\xf2\x07bfI\x01\xc3\x08\x85 \x126\xad\x02\x82\x87Ch\xb1Y\xfa'\xb8Z\x11&[\xf1Rp\x99/\xce\x97&\xdf\x8f\n\x1e\xba\xb9\xc5\xc9\xa8w\xbdw\xbd\x87\xebo\xbd\\Q\xd2\x84VyiS\xaa|w;xl\xdb\xbb\xa7.\xce\xfd\x84m{\x81t:/J\x91\x92\xc6Y:\x9c\x0e=\xb8qr{\x17{\xb8X\x00K\xa2z^\x98iY*\xa4\xdb\xb8\xfd\x80B\x17>q\xf8\xe2\xaa\xe4\xb0J\xf1D\x11z\xff\xe8Z\xb8\xcc\xeer\xaf,\xc9\xa0\xba\xc6\xd5Z\xa0\xabM\xfb\xd8\xb68\x95g@;4\xa2\xac\x15\xbe,\xaf\x1bC\x0f\x8d\xdbaz\x8a\xcd\xfc<\x0f{x\x9f\xbd\x97\xc9\x9bS\x99\xa5b]\xe7\xa5Eb\xbc\xc3\xe9\xf4}rH-V\x96\xa2\xa6\xe6\xa1q|A\xa2\x03N\x8c\x0e\xae\x92\xc3\x18X\xa6,\x08\\\xbfbR\xd7T\x1c\xdc\xb4oh\xa2\x19\\\xd3\xb9\x9e\xac\xc5\xe4\xa0\xcb5H\xd4\xa4\x11,\xb56\xa0m)\x94\xa6\xc2\xed\x1cN\xd5\x83\x83\xeb\x05\xe4\xa2\xcb\xedO\xd7%\xa3\xd4\xf4\xae-\x0e\xae\x98\\\xeb\x1e\xe0c\xf2|!Of`\x82n\x12i\x1c\xa2\xe9\x84.\xf4pt\xe3\xe0p\xe8'\x87\x92\xa0JUq\x81I\xa8xMI8\x0e\xaei\x1d\x99K\xb99\xc1L\x9c\x0d\x18\x19QJJ\xc6\xe8\x9b\xc1\xb5p\x9b\x1c\xa1\x92\xed\x17Y)\xd0\xa64\xda\xd0k\xc70<\xb9\xd1\x0dp\xbb\x00J\xa3)+\xad)\x85\x82I\xa4{t\xd3\xa3\xeb\xf1m?\xaf(W\x06\xab9U\xb6J6\xa8\xc2W\x94\xec\x87\xf0\x8cB=:t\xf5A\xa0\xa8\x99lP\xc9\xb2V\xe9\xdaP\xf8\n\xdb\xc6u\xa3{\"cN\x0b\xaa\xd2\xee\x11\xb3\xd4\xae\xd3\x8es\xb2\x83\x13\xbe\x02\xcaW\xbd\x83\xb7\xc9\xa9\xd2-*\xa6\xd3\x95$\xa9J\xa5\xb1e~\x85&<#\xc1\xfb\xe4P\x94(\x80\xa7\xf6K\x1a\x00i-\x8d\xa4s\xfb\x88M,9\x94\\\x89-\xb0ZLfUIU(|\x85\xd1==\x85]\xf1\x9b\xdb\xe0\xa3\x0dp\x9b\x03.0\xe0\x02'l\x9bTU\x92\x991\xba\x97\xcd\xd5\xca>\x8f\xfe<\x14\x87\xd8\xc3m\x82W\xb1_2\xa1\xebd D\xaa\x92\xe5\xed\xa6\xf0\x15\x1eC\xe36\xf03=\xf3\xb5M\x91T\x97\xe84g\x19e\x06\xf8\xcdm6nt\x1b\xb7;\xf7\xc5\xbd\xdb\x85\x1d\xfcv\xf1*\xe8\x07\n\x929dL\xbeO\xe8\x93\xe9N\x9e\xb0R0\x8dYg\x9aqe\x91\xcdw\xaeK&\x037\xaeu\xf0\xdd\xea\xbdD/\xe9\x88\xc9\xd2\x82b\xebH>\xc0\xde\x85q\x1e\x12\xc3\xbb\x84\x89Z\xa6\xde\xcb\xeb\xda\x08K-\xa1f&\xbd\x06e\xf6~\x0f\xef\x92\x93K-\xeb\xce\xaaz\xd9\xe8@B\xdf>\xb9a\x07\xef\xb2\x9b5\xda\xb0\xe5[\xac\x0e+E\xa5\xa8J\xf6\x8d\xeb\x8a)>\x0e_}\x03\xef\xd0\xf3%{p\xec\x17u\xc955\"\xc3Y\x8a9<\x8c\x8d\xdb\xc1\xbb\xec\xd2&.\xce\xefRij\xb7\x95U\xa2\xa2j\xd8\xc7\xc7\xc6\x0d\xf0.9\xb9mU\xbc\xaei{\x86S\x8e\x1aw\x7f\x1f\x8a\xd4E\x02\xbcO\xbe\x8b\xe4\xa3kV\xb8\x9c\xa4Q\\\xa1h\x9eV\xda\x03\xdc\xfb\xdem\xda\x19~\xc8.]\xc338\xb9I\xb3(\xe1\x84\x81\x96\x9dS\xaa\x08\xf7\xe0\x9ei\xf1\x99\xfd\x17\xe8\x97I;\xac\x96F\xe2\x1b,.\x97Dj{\x07\xd7\xb632`\xec\xa9$*&\xc8Z\x99\x15\xcc&\x92\xa9\xd86\x9e\x9a$NW\xc5\x9b\xc5\xb3(\x10\xca\x8ac\xcd\x08\xba\xcbdTbzv\x1b\xb8\xa2g\x95f\x9dd\xfa\x03j\xb6\xd8\x86A\xb2\xc6\xf5_\xa9\x83\x1c\xdd_B\x0fW\xab\xff\x86\xfc9\xc3\x92\xf6\xac\xc4\xd2\xa4\x0eqp\x7f k3\xbcZ\xbd\xd4\xb0\xe8*\x14+M\xa5\xe8z\xa1\xcd \x9aqmvEOjz\xba\x14\xca0#\xb0<\x98\x14\"\xa5\x07;\xf5\xb9/\xfc\xd8\xb9\xdfB\x8bC\x17\\\xe5\xb0\xef^\x85\xd1\xf9\x0b\xdd\xff\xc3f\xa6jMC`\x17\x86\x02\xfbn\xe8\xe0:\x0c\xc5\xfb\x04e\x95\xf4\xffj\x94\xff\xe9\xf4X i\xa9\x1bv\xf1/n\x1c\xfd\x98\xea*5\x12,\x92b7\x8fS\x80\xebo~N\xad\x06K\xa8xK?\xe7\x8c*\xb2\x81\x83%dlZ\x84\xa7\xed\xd9]1\xb9\xe3\xd17\xb47\xbb+\xbe$\x8fL#\xab\x16\xa2\x06U\x95\xdc\xa6J\xee\xe2c\x87\x8d\xcc\xed\xe0\xfa\x04\xf3]e%\xa5\xa8%V0\xb7\xda\xd6)\xaf\xf1yG{?\xd7\x0b\xa0RE\x19\xa4\xae4\x0d\x99JT\x96\xca>>\xb6\xa1\xf8\xea\xc7\xa6u_\xe1\x13z\xfe\x96=4\x84\xdb\x92\xcejq\xc6U\x94\xfa\xc1u\xcf\xfe)\xe5\xfa\xc9\x1fpE\xb7\xfa\x7f\xf1\x87%\xe3X\x8e\xa4\x06!j*\xcd\xd1\xb5\xbe\xa1t\xdf\xaeH$\xddYkm\"\xe5\x92%\xd2a\x13p\xbdFN\xbe\xe8\x98\xe5(\xb5*\xad%\xc2\x9do\xdcT\xdc\xbb\xdf\xee\x1cq$\xff\x0f\xc9\x9f\xfb\x8c\xad\xa5%\xb5m\xa1\xa8\x1d\x8d\xfe0\x17\x9dk\\\xdb\xfa\x06n\xd1w\xbd\xf8(\xf5<\x9f\xb1\x93\xbe\x0fq4\xae)\x0e\xb4\xaa\xb8ExE\xb0JCj-\xad\x16t\xa7\xc1\xaaJg\xf2n3\xd0F\xe2\xed f=\xd5\x9a+\x85\x19\xa9p\xee\xd7l\x89\x7fpw\xa9tNpa0\xdc(N\xe3\xb6\xe2V\xa6\xd2l\xfc\xc1\x0d\x0776\xaf2r\n[\xb3\x93\xa5\xa5\xca*\xce\xa9E\xa94[O\xee\xe06\xf0\x85\x9eK\x96\x85\xb5\x96\xb2\xcc\x8d\xd0\xd8\xe3&\xd8\xc4a\x1f;\xd7z\xb8\\\x11\x1d\x1e\xc8E\x19n\xb9R\x90\xa8\x1f\xc2\xe8\xe029\xb4\xb9\xacJ\x95.T\x84 \xb6n\x9c|\xdb\xba\x01\xde\xac(Gf\x97\xc8\xb2\x0d\x8b\xfb\x8e\xcc\x85\x0ea\xb7O\xc6B\x13\xc2\x94~sX\xa0\xcb\xfa\xec>B\x17I3s\xf2\xa9J\x18\xe9\x15\xd0\x05zR\x88>\xbb?\x82;\xb8\xbd{tp\x91]\xc6\xb3\xe2\x020aK\xa9\x14\x125\xe1\x10\xba8Ex\xbf\x00\x9e&\x0c\x06L\xacf1\xef\x8fpp\x07\xf7\x1c\xe0*9\"\xc9\xb0\xf5\xb2\xd2\\O\xfb\x88\xb2\x0b\xe7\x07\xd7\xa7\xb7^}\xe3\x93u\xbe\xe5\x9f\xb6\xeeq\xee0\xa77L.\x1e\x90#\xb9\"\x1b&P\xc0\xb0\xc7\xbcJ\xc94\xce\xb4\xbaI.O\x13\xf7b?\x17\x07\x93DFk\x92\xab\xe4\x90\xd5\xe4\xc5|\x0b0\xb1\xe8M\"e7\x1f\\8w\x8fn\xefFw\x08p\xfd/~\x96g\xf1\x85w\x81\xc8\x1b7nr\x07\x0f\x9f\xb2\x9b\x0d\nc\x99\x90\x8d$$\x19]\xe7\xe1\x13=\xb3E\xcd\xe5\xe6\x85d\xa5\xd0kT\x98\x99O\x94\xa34\xb9\xa7\"M\x89\xad\x97\xd2}\x9a\xfb\xb8\x8f\xf0Kr\x04\xfb6\xef\xcb9\xe3\xd9\xa1\xc1E ]\xc6\xc6e`\x02t\xf2\xcaN\x97\x06\xaa\xd5\xbc$\xd17sWLM\xe7\n\xbf\x9b\x1a\xb8B\xef\x17\xf4~\x87\xde\x85w\xa9\xf2J\xae\xf6(\x91w\x08\xb4\xcb\x9c\x1c:\xa6_O\xb5h\x0ff\xb1Wxh\xe0\xd8\xcc](\x0e(K\x15\x878\xc0\x0d\xf9\xaf\xc8\x7f\x15\x87\xb4bf\xa7\xa3\xbaJ\xae\xa6vV\xee\xf1\xc1=\x15\xc7\xc6\xf9.\xb3\xdfb\xc0\x0d\x05TI\xcfA+b\xcdV\x81\x91u\x1a\xdc\xd1\xe3[\x06O[:\x8b\xf7\xb3_\xee\xd52\xdaW\xadN\xf7\xed\x0eG \x9b\x17\xf7n\x17Ix>a2\xe2\x91o\xb0\x1b\x0b\x8c\xd7%W\xc9\xce\xf3\xe1\x08\xcd\xa3\xbb\x8f}\x1f\xe0\xfd\x02X2\xf7(U2\xbd\xcc\xb8)\xb9H;\x15\x87#N\x90q\xc0\x19\x96\x9clOU\xc9\xdc\x02\xb8*\xeb|<}8\xc2\xe8|\xe7\xe6\x16n\xb3[\x89o\x0e\xfe\xe9Jp\xa6\x9b\xe2B\x99\x11\xa5Z\x93U\x8b\x14s]2\xcb\x15\x91\xc7n\xee\xc7\x00\xb7\xd9eY\x9f \xdf\xdf`\x9cj|I\xf0\xe4\xc2\xc1=\x91\xf2\xcc\x8a\xaa\x94\xc5\n\x8b\x9e\x9b\xd2Vi1F\xd4\x87Clq\xf4E\x87\x96(d\xcd5\xed\x8aa\"\xb25h\xa2\xed\xf7X\xe8\xf0e\x01\xb9\xa8E\xbd\x14\xb5\xc2\xe1\x8e\xb6\xec\x0fG\x98\xfb{\xac\x8f\x1f\x93\xc3\xb2\xaa\xffb\x8f\x99+\xd2J\xc5\xd5\xf2\xe1\x08\x8f\xb1=>\xc5~O5\xf8\xf3kO.rSg\xab\xb1\xbc.+\xbe\xf2=\x91e\x95!\xd0u\xeff54\x8e\xc3\xe8\x92\x07\x94\xdfr\x1bO\xf4\xa3\xeb)\xe6_^\xe1\x9c\x13n\xa8\x88p)\xad\xa9\xec\x9f\xe2a\xdb\x84D~\x82,\x9b\xd5\xd5b\xcd\x0b\x19?\xc0|\x0f\x90\xa2\xec\x03\\,\xa0JF\x89*\xa1\x04U\x80`5CJ,\xc4G\xda\x8dz\xb3\xa2*\xe9\x95\xa8*\xed\x07\xf3\xba\xd4\x96\x94\xae\x0f\xb8\x8c\x19\xc7&\xc2\xbb\xe4\xb0d\"@\x99t\x06\xc0\xeb\xd2rR\xea>\xe0:\xa2\xdb\xa4:}\xbf\"^\xa5\xb52\x97\xdaZJ\x87T\x9c)\x9d\xe8\xfb=\x19\x81J&7\xde\x7f\xebeI3E\xd1\x01&\xbeG\x1b\x91\xb8\xc6\xd0Q\x89\xbc_\x11\xd3I\xba\xcb\xc3\x11\xaf\xcbZ!\xed!v\x1b\xf7\x18\xe0*\xbb,\xc9\xd6\xaa^\xab\xb5&\xfb\x11H\xdb\xb9{\xe7\xe1\x9a\x9e9>ai\xff\xbd.\x8d\xe5\x94\xe2\x0e[\xc8\xfd\x8c}sEK\x89d\x038\x9cN2q\x89t\x18\xa0w~L\x03\xc4\xc7\x15\xe5N\xa7\xb5\xa1\xf3BS2+\x19\xc5~t\xbew\xdd\x8c\xc5vs\x829-u:^\xacKS \xca\xdb\xe4\xfc>\x1e\xe0Krr\xac\xbcb\xd22\x8a\xb6R\xd8?\xcf\x0e\xcfp\x88\xfdnp\xd3_\x9e\xfcC\x84\xab\xd7\x1e\xd2\xa2\xb0\xb8d3b9\xe3?<\xc3\xb3\xa3\xeb\x1e\xf1\x01~]Q\xbe\x16\x99\xaf\xd3!JSf\xeb`\xe3\xfa\xa2u\xdd\xb1\xa1\x9b\xb9}\xf1\x01\xf1\xcb\x1f\xa9\xab\xf2\xaa\xacx:\x89\xab\xaa\xe5\xc0(3\x8d\xae\x1b],\xfa\xf8D|\xb7\xc9\xfb1>\xa5c\xadtK\xb6\x12%\xbd\x05\xa7\xccbG\xaa@\x9d/\xde\x86l\xa2\xacZm\x1bV\xb2dg\xed\x06\x1a7\xf8\xa9\xf0S1\xb9fr\xf0\xfe\xe5\x7fd\xff\x17\xf2W\xe2\x95=\x1cA\xf2\xf7Y{ \xb5\x8f\xbd\xeb\x1c)~\x10\x10t\x9fC\x83\xa9V\x1d\x9f\xf6\x00\x81\xb6w\xf6q\xe7\x8a\xd0\xe0\x8a\xee\xaf\xa7\x80\xbfR@\xe2\xab\x89O!K<\xba\xc9\xc1'z\nI*\x8a\xf4\xe3Y\x8b\xeb\xf3\xa6\x1fg\x07\xef\xb2\x8b\xbd\x16\x1bG\xben\xce\xaa\x92i\xfat\xc0Y;A\x17\xda\xc3\xdc;R\x82I\xa0N\x97\x10\x97\xcc\xf0z\x99)\x91\xfc\x18w\x0fq\x08cs\x08p\xf3\n\xab\xb4\xdf\xff\xca\x80\xf0\xa2\x8f\xd4>\xc0n\xde\x06x\x8b\x0f\x94\x92\x94.EV+\xe2\xb2\x94r%\xdb\xc7\x07\x1f\x0e\x01\xdee\x97\x0e\x04\xeaU\xcb\x84\xebU h\x1f\xe0\xde\xcd\xfd~n7\xbe\xf7#\xfc\xf0\xda\x83\xc2\x81\xd2\xe9\xfaF\xd2\x85\xab\x91\xa1s\xcf\x9b\xc1#Y\x80\xebW\x18%\x1c\xa5O\xc6\xfd\xd5\xeb\x94\x8f\x13v\xc7[zrM\xc9\xa9I\xd7O\x9du\x0e\xdc\x9d\x9b\x9f\xe3\xec\x86 .NP\xa5%\x86\xac\x93\xb6$\xd2\x85\xa9pm(\xe2\\<\xc5y\x1c\xfd\x1d\\\xbc\xfc\xaf\xa9\xb8hC\xf1i.~\xc9a*Y\xe3a\x954\xcc\xc0y]2SW\xb5Yc\x186!\xf3!R\xe93\x1d\xb6N\x8a\xd6'\xaa;\xb7P\xdd\xb9%\xce\xaaR\xbc\xa6\xc3\x8bZU\xc6\xf2\x85\xfa0\xb9M\x11\xe7;\\9$\xae+\x0c\xf9\x94Cd\xda\xea3F\xb1\x94\x9d\xca\xdaJ\xd6\xc4\xdd\xc5y?\xfb\x01.\x16 \x93\x04\xac\x8dd\x95\xa6\x032!\x0cgD\xdc\xef\xe2\xfcL\xf3\xc8\xfc\x9c\n\x88\x95\x153V\xd4t%\x8c\xf1\x9a2\xb0sq\x0e\xbd\x83\xb7.\xce/\xff\xabwK&\xa5\xc21\x95\xe2T\\[*T\xdf\x16\x87\xc6On\x98|\xd1\xff%\xce\xcf\x83\xdf\xc3wmqu\n\xfc\x94\x02)a\xac\xe4\xdcT\xd9d#c\x96Y\x8c$t\xcf\xa1\x85\xbf\xd23\xd7\x9b\xe2\xac\xe6\x02\xce\xa9\xdc'7\xdc\x87m3\xc1\x97\x05(\x91\xcd\x82\x19\xaeI\x1fVr\xa3Ss\x98B\x9c\x0fn\x08\xbd\x87/'\x98\xa3\xad\x05\xab\xad\"\x05\x13\xc6\xb4Qg\x1d\xa9~l\x9b\x00\x97\xc9\xa1\x9br\xf8\xb3N&o\x93\x19\xe8n\x07C\xec\\\xef\xc7)\xc0\xe7\x15\xd5,)C \x12\x9dP\x16\xa2\xbbMg\xdd\x1e\\\x7f\x08\xb8\x08\xf5p\xb1\"\x1c\xa0\xce\xe9\xa3\x05i\xa3+5\xf0n\x0f]\x98\\\x1f{\\\x0e]\x9f }!\x80\xe9\xd56<\xf2 \xf5\xe8\x1a\xd7O\xaeqp\xbb\xa2d&\x9a\x96Q\xaa*\x85B\xba\xc9w\x9b\xf0\x8c1}Y\x11\x1d\x96sQ\xd6d\xe7?\x0b\xcd\xdd\x01v\xad\xdb\xc4m\xd8:x\xbb\xa2|E\x92\xd9\xf4\x85\x13\xceK.\x93$\x8b\x0c\xcf\xfe\x18\xc6\xc9\xc1\xdb\x05\xe0r\x06%_2\x9dM\xaaTJ$\xe9\xadka\x08\xcf\xc1u\xf0998\xe50y\xd2\xbd;-\x1c\xba\x0e\xb6\x0d\x8e#\xbd;<\xd2\xe9\xc8\x8aI1\x0eE\x81t\xa0bQ\xbc\xc5*\xef\xa0y\xf4\x8d{\xec\xe1}vic\x85\xadJ\xf0V\x97Dvp\x1d\xcd\x13W\xd9%\xfdk\xbd\xea\xf3Y\xbd.@\xba\x0eZ\x14w\x1f\xfb=|X@\xdeO\xd4\xa7X\x0d\x11\xc6p\xde\xb9\xc7 >,\x80^/JI\xe7\xde\xa7\x18;wp\xf3\xe1\xe8&\xd2\xffN\x88\xee\xdc\xca\xd2\x80\x95\xab\xe6r\xd7AO\x97\x1e\x92y\xb3\x05Qj\xeb\xb2\xa2X)?\xe3\xec\xfa\xfd\xb1\xf1\x01nW\x94\x8d\x920\x05V\xac\xd7e\xbb\x0e&w\xde\x87\xf3\xd6\x9d\xb7\x1e\xbe\xac\xf8\xe5?\x16\xa5\xc6e\xe0\xb5z\xd5!D\xae\xc6\xef\xc8\xe8Q\xf1\xf4\xe8\xe0\xcb\xea\xfb%\xef30\xb2|l\xcd\xaak\xdf\xf5\xb0qnrC1\xbb\xbeh\xe6\xe1\xc9{\xb8L!?\xba\xbex\xff\xf2\xdf\x14Dm\xc5\x9eT\xbc+^\xea\xb3\xee\x11\xba\xf9 '\xda\xeb\xe4\x90\xadxv\xba\xfe.\xe8\x8c\xaf{\x84g\xbf\xf1\xbb\xf0\xe4\x8a\x83\x9b\xfb\x9d\x83_\x17\xffU\xf2\x93.\x1d&\x8eN\xe0\xce\xba\xdf\xc1\xb5\xb1\x1fc\x11\xc78\x84\x08\x17\xc9\xf7)\xf9H\x9d\xf9d1\xc7\xaar\xe9\x17\xbf\x83\xeb\xdc\x84=\x12.\x08\xbc\xfc\x9d\x14s\xac\xb6\x8ct\x1fs\xfb\xa5\xb5$[x\xb6\xee8\xb7\xcd\xec\xb6\xf0fEL\x01\xaf\x14\xd3X\xd6t\xc2g\xe1\xdc\xd2\xdd\x02\x14(\x89i\x0cS\xba\x07L.\x19\x9e\xab\xe8;\x05\xb6^\xbe\xb0\xd0\xfd\x8e\x03\xee&Nn\xe7\x07\xd2\xe0\xbd\\0\xd9sbd\xbb\x1a\x17\xe2|\xcd\x01\x99(r\xdb\xf84\xb7@\xb6\x892\xa6\xfa\xab\xd7\xdb7\x96f\xc9L\xbe\xd2\"\xa0\xe2Q\xa5\xe5uJ\xb5\xc2\x99b\xa1m\xdd\xc6\x0f\xa1O&~>\xac\x1e\x9c\xa5q\xe4R\xa4r\xbd$\xbc\x0d]\xec\xb7\xd9\x1e\xd0\x87\xd5\xf3\xcd\xe6\x17&\x7f\xfdR\xce\xc2\xd6/\xd9\xfd\x90!u\xb1\x8al\xdc\xb3\x8a-\x1b5\x89|\x8c=I\xb6H\x7fK\x98\xae7\xd4\xc9\x08\xdb\xfa\x92\xd7\x85\x9a5\xe2]1\xba\xbe\xb8\x8f\xa3\x87\xf7K\xc8\xad\xeb\x8b\x1f\xe2\xf8\xf2G\xda|W\xa5N\xaf\xa7\xd0\xef\xda\xe2\xa7W\xa146\xa3\xa8\x86\x19\x97\x8b\x95\xc8S$C\x1c\xdd\xb8\xf0~&Ond\x82XL\xf9\xfa\x95K\xcb\xa2t\xc6\xb1\xd8\x0c\xee9\xae\xeck+[~\xbeL?\xf3d\xa5m\xb9\xb4f\xed\xb2Ex\x8a\xf8!\x0c{\xdf/\x11\xfd\x94|T\x94v\xb1\xba\x82EI\x1c\xa1\xa0J\x1b\xe1\xf3\xcb?\x8a\x9b\x04\xc9\x18\x89.\x19\xf5UQfs\x19D\x1d\x8b\x9do\x8bi\x88\x0fn\x17\x07dJ!_\x96\x90\xdc\xfc\x94\xa2aA\xaeI\xc3\xcc\xfaq\xf2\x1b\x97\xeew|\x971\xcf:\xf1\x8a\x1a\xc2\xa9Z\xa8pp\xc6\xd8y\xb2d\xb2\x8f#\xf1\xfd\x80ao=\xd92\xc1\xb0\xdc\x05\xed\xda\x89W\xd3@)\x92)\x16\xbb\xd8\x85~\x1f\xe96h,\xdef_\x95LIH\x9bl\x940\xc6J\xcbh\xc1\xfa\xaf\x8c\xc5\x14{\xd7N\xfe\xe8\xb7\xdf\xc6Q|9\xfd\xc0S\x99\xd5U\xea\xa5V/\x06\x00\xbb\xdf\xe1\xc9\x0f\x0f\xae\x0f#\xfcB\xe0\xe5\x1f\xe32\xcb\xa7\xafF\xa4\xd9\xc2$\xd3y\xdd\x13J%\xc7\xd8\xef\x8b\x8d\x9b\xe6bt\x8fn@\x01\x85\x82.1\xe8\x96\x82h\x17\xa0\x94\xc9\xe8'\xe38\x0d\x9eX[|\x1c\xc2\x914vS\xd0\x07|\\QP2?\xbeZ\xd1\x92\xcb\x96\xf8\xab\x08:\x97\xcc\xf5-\xdc\xd7\xd9_\x91\xb9\xf8\xea4I\xb3o\xde\xdb\xbb~\xff*\xb9\x1f\x93\x97\x06\x8d\xe5\xd8\x95\x9d\xec$e\xc6\x19\xdf\xe7{\xf7\x18\x8b\x99\xce\x80S\xd0u\n\xfa\xb1\x9di\x0cT\xa5 \xe3\xe4u\xc9+\xf6\x0d\xef\x88+\xf8\x85\xeb\x96\xee\xd1\xd5\x94A!\x93eI[\xf3o\x19\xe6~\xefBqp\x83\x1bO\x8c)\xf0\x8a\x02\xc9x\\)T\xb26^ \x8b\xec\xc7\xd6u\xae/63\x8e3 _\"\xa6{\x1ddm\x0c\x97\xf0H9\xd1\x8fG?\x1c\xdd\x8e\xcc\x89\x92\xfff\xf5W\xe9\x93\x06\xa2f\x94>\xa60C\xcf\xe0\xdaml\xfd\xe4\xe1b\x01\xa4\xcf\xce\xb0\xbb\xa7\xbb\x1f\xb4\xf9\x94\xb4h\xbag\xd86a\x1b;\xfa\xfc\x1b\xb9\xf4\xf1\x13&K\x93\xb4\x84\xc8Vf\x8d\x13\xf33P}\x921\xd5\xeb\x15%\xc9_\x962\x0f\nB\x94L$e \xe4\xf0\x93\x7fpp\x9d\x9c%j\xa9\xf3\x07\xf4*\\`\xd1fE\xef\xc0\x1f\xe3!\xc2w\xf4\x14uZ\xa3,w\xa0\x98*9\xd2\xc4\x83k\x1e\x1d|J\x8e\xa0\x0f\xf3\x98\x93\x0d\xfc\xe5\xe6\xb4@\xda\xc9u\xe3\x8c\xa56\xce\xb4\x8b\x82\x94\xabu\xa0j\xbd.\xd8{\xd8\x85Mh#\xbcM\x0eI\xc4\xb2\xe4\xb0\xde\"E\x1a\\\xbb\xee\xc2\xe4{\x0f\x9f\x12|\xf9\xa3\xf7\xcbe\xcbEz\xc7i\xaf\xdf\x83\xbbw\x0f\x8fs\x1f\x8ay\xbf\xf1\x0f\x8f\x1e.\x96\x80\x1fs\x80\xa0\xc6h\xb4\xa9\x15C\xc0*\xcd\x91\xf3\x9e&\xb9i>\xcc\xf0\x03\xc1/\x08U2\xf9X\xd5\xdaHC\xe7\x0c\x95\xb0B\x13\x83\xdf\xbb\xbe\xe8P@\xf5\xf0\x03y\xae\x93\x87\xd3W\x1c\xb2\x8e![\xcf\xd8\xfa=t.\xecb?E|\xd3\x18\xef\"\\\x9f\x02\xbeP\x80\xb4\xc8\x9b\x0fu\xed\xf2\xb1\xac~\x0f\xfd.\xb8\x87\x19>&G*\xfa\x02\x05\xb7\x92YH\xb7+\x84A\xb2\xc1\xdd\x85\xbe\xd8\xb8yp\xf0\x99\xf0%aY\xe5\x8bp\xeb'\xec\x908\x1c\xdc\xc1\xc1\xe7\xe4(\x96.\x80UZ\x1a\x05\xaa\xack]Y\x86t\xe3|\xe7\xe0\x16\x1fdh\x05\x87\x0d\xda\xc6\xa3\xbb#H\xf0\xe4\x9e\x1d\xfc\x82\x8fl#\x93[i\x0d\xa3\xe8\x946\x9ar\xf0\xdc\x84\x81\xae\x85\xc0\xaf+\"\xe9\xa5\xa2\x0f_rC\xdf\xf1\xaak^S\x9c\xcfa\xe7\x9f\xfc\xe0\xf7\x1b\xac\xfa_\xbf\xf1\xa5J\xacjQ \xb8\x84\x93\x8c\xcb\xb3>$i\xf7\xeb\x1c&\x97\x05\xde\x8c\xe9\x1e [\x96\xe8d\xa5\x92\x9f\xf5-\xc4\x07?\xb4\xae\xdf\xfb\x16>\x9d`\xb6\xd0\xa6u\xcd\x046\x92Z\x0bi\x0c\xd2\x1f\xbdoG\xdf\xcca\x84\x9b\x13\xcc\xf45\xe7u\x95J\xae6\x82!\xfd\x03J\xaec\xfa\xc2\xdaO\xafp\xe6\xc8\xd7\x18\xd7\xef%\xf6\x116\xbe\x9f\xc6\xfb8\xec\xe0rE\xb8\x16\xa8\xedj\xb9\xd0,\xf21\x91\x0f\xfb\xcd\x13\\&\x87\xf6$\xc9B)7\xcb\xd7\x85\x90(\xc2\xe5\xcb\xffNv\xd0\xec\xaag\xbc^\xc8\xecc\xda\x89\x8d=\xbc\xcfn6r\xb5\x1cx\xb2R%\xaay<\xdc\xa30\x9d\\\xb2\xa0g\x97\xaf7.\xf7\xb9\x91\xb2\x8bp\x1d\x97\xdf\xb3\x85\xbd\xba\xa4\x9fP\x80\x87\x8f\xf8\xc0J\xa9\xab\x93i\xc1jM\xf0\x10\xc6\xe9\xc1\x87 >/\xa0JQ\x99\x85\xd6\xac\xaf\x1a\xa78\xec\\;nf\x14\x94n\xa7\x97\xff\xfd\xca\x8bSO-\x96\xc9k9l;\x8b\x1d\xb8\x11g\x1e\xb2zu1\xe2\xbcC\x90\x96\xdb8\xb9*Pu\xfe\xe2\xaeA\xf2q\x1e\n\xd7\x16\xbf\xb9\xcd\x80\xcb\xf3\xe4\xb9@Of\xa9\xf3;T]*\x96\x0e{\x8e\x1e\\?\xb9\xb1q-\xd9\x16\x81\x8bo|\xc9\x96\x84,k\x9e\x0d\x8ejV\x1a\x93\xf6\xa7\x8e\x1e\xb6n\xdb\x84\xe3|\x1f\xe1\xcd\x8a\xd2\x0e\x92*\xebl\xfa\x1aY\xaa:\xcd(\xc4\x82\x13\xda\x1bz.\xa4J\x90 S\"\xd5\x96\xb6Z\x88\xb4q[\x94\x17\xe1\xcd\x02\xd2\xa7J\xebe\x87\x83\xac\xd8)E\xb4\xc3\xfc\xbcu{x\x93\xdd4\xadU\xa5H_\xdb\xc8\xe6w\x89t]xm\xdd\xe8\x8aMK\x1f+]\xd7^o0\xf02\x05V\xe9s\xa5\x8a\x0c\xb5.{\xc3\xafc8\xba\xcd<\x9cxo\xc8\x8bM\x98\xb6\x1a\xd3\xbd\xd3sS\x95Un\xc5G\x7fZ\xb9-\x91\x9c\x16oK<\xf9\xa3a|\xb5\x8f@v\x80\xf3\xfe\xc0\xd1C\x17\x87\xb9m\xb1\xf5&7\xed\x84\x08\xd7+\"yC\x975}eu\xd9\x7f:6\xe0\xfa\xb0\x89\xfd\x1e.\xb2+\xd2\x9dr\xceP\x10\xe6tf\xc5\x91n\x83\x82z\x9b\xee;& m\xb6.\xa1\x19\x8a]\xcc\x96\xcahY)\xa2\x8e\x13]UL\x0e#\x93]\x9asU\xa7\x0f\xd40n\x95A\xba\x83C\x82CL_W=aZ\xc5T\xa5\xaeP\x08\xe4\xa24\x95\xb6H\xdf\xban\xe3\xb6\xf0!9KZU\x9d\xd3\xaa4\xa5\xb5\x8d\xdd\x86\xecX\x7fX\x80H\x960\xea\x8aY\xba\x83W\xc9\x1a ;wN\xdfs\xbd\xce.K\x9a\x0b\xf4R\xbeX\x9e!\xba\xde\xc5@\x9f$\"\xb7\x16\xb4_Sk\x9cP9\xc6[I\xa2\x8b\xdbc\xdc\x16}\x1cH\xa7\x88<\x1f\xc9\x93\x8a [d\xa6\x1c\x11\xfd\xdc\xef\xe7\xe0\x8a\xe3\x10\x8f~\x80\xeb\xec\xbdI^ih\xe9\xc7\xadN\xdf\xdca\xaa\xa6\xb7\xc4'\x94\xac{\xf8\x94\xdd%s<%\x9b\x99\x9a\"\xc7\x16>\x90\x11il\xdd\x9f \xd5\x1aL-\xc8\xfa\xe82u\xf1\xf5\xf3\xa0\x89grE\x1f\xfa\xb4\xbas\xc5\xc7\xf0\xf2_\x91\xd6\xc8L\x97V\xd1qrUj&\xa9\x9aG\\\xb7\xb9M\xfa\x0e\xc0\ni\x83E\x94\x8as\x91\xee\xd9\x1a\xc5\x90zr\xbd{$\xe1=\xb9\xefL\xb2DP1\x9d\xee~\x1aA\xd57\x85\xfd\xc6\xcd\xae?\xef\x9c\x1bC\x0f_\x16\xffu\xf2\xe7\xfcZf5\xc5/MU\x9f\x1d\x0f\xe0\xda\xf3\xbb\xd65\xc5\x14\x1f{\xb8h\xcf\xbfG\xcf\x17\xf4T\xe9\x8a\x82\xd1B\x93Q\xb5Z\xa8\xcc\xd0\xcf\xc3J\xffq\x1e2y2s/u\xcdA\xcbt\x05\xf9x\xa0\xee\xeczW\x1c\x9a\x19e\x81\xec\xbb\"\x1f\xb1\xf0R\x18&\x13S\xcd\xb9\xc9L\x91\xbe\xc0FN&\xcb\xc6/\xf5\xaadD\x84\xe3\x14\x8b\x8d\xdb\xb7\xfe\xa9p;:\xf5\xa1\xee\x86\xc1\x97)\xf8\x02\x83\xafh\xa9!i\xf7A\xd4Jk\x0b8\xe7\x98\xbaf:E\xf4\xe8\x06_\xf4\x0eG\x1e\x82\x1f\xb3\x117\xc1K\xc6\x193\x06\xb4(kf\x8d\xa4$\xee\\?\xbaP\xdcGx\x9b\xd0\x0f\xc9\\\x01\xd7%W\x02\xc9\xeb\x9a\xace1\xa2n\xe2\xa18\xccwC\x80\xb7\x08\xaf\x08\xe6\xe8-\xd7\xc2h\xd0\xbc\x14Br\xc3\x91\xe1\xce}\xa5\xcb\xa2\xdfg7\x7f\xbbCia-\xc7\xa8IT\xa42\xb8\x0bC|.\x0ea\x08\x11\xbe'|E\x98\xea\xaf.\x19\xaf4}\xe5_U\xccp\xaa\xf3\xbd\x9b\xef\xf6nh\xe0\xdd\x02r\xe51a\xa5\x14\x98\x92Z1c(\xfa}\x9c\x9a\xa2q\xf7\xa1\x18\x1b\xd7\xec\xdc\x0e\xabrl\x0e\xf0\x0e\x7fx\x8f?\xdc\xe6\x1f\xae\xd2\x0f\xb9\xe1(\xc5q\x1a\xa8\xb1\x0b\xb0:U,E\x16\xben\\[t\xbe\x8b}\x8a\xe4\xaf\x14pM\x019\xd5Fs\xfa\xdc\xbe)\x85fL\xda\x95\xf9\xe0\xa6\xf0@\xfa\xe5SS\\e\xcf\xd2T\xad\xc8\x9f\xdd\xaf\xe8\x1b\x08\xc44\xdf\xbb\xe1\xe0\xe1]v\x97*5ZU\x12\xabT*e\x155\xd5\xc6\xdd\x85g\xd2\x03}\xbf\"\x8aY\x97&\x19I\xadM\x99m\xc8\x1e\x0f\x10:\xd7=\xba6\xc0_\x17\x90KQ -\xb4@\x01\xd6\xd6\xcc\xa6\"?\xb8m\xb3mBq\x88S\x93\xcc2\xa2\xef\x8a|\x99-ica\xe1\xa7\xdb*\x89\xads\xfd\xae\x18\x82k\xe9x\xa8\xdf\x15\x9fC^\x07\xd0\xd5\x13\x9e\x1b&\xb7F)*\xa5C\xe3v\xb4g\x92\xdd\xac}\xa0\x84\xad\xa4\xc1\xd8\x0d\xe3FS#;\xc4\xa1\xc3\x88\x93\x93 \xad\xc0vB\xd7\xcf\x84\xe0\x96z\xc7a\x08\xc76\xe0t\xb3\x80L\xccI\xc5\x9abM\xf7\x02\x8f\x87t\x9e4\xf8b\xe7\x8aG\x97\xd6\xfak\xc0\xcfn\xed\x87\xa6\xcc\x17lt\x85\x13'\xabMb\xee\x82\xeb\xe0:99\x9b\x8aURp\xcc\xa6\xaa8\x0d\xba\x87t\x8b\x0f[\xc3\x93\x1b\xd2\x1d>l\x0dOKcP%cBU\xd4\xf0E-5\xa3\x1a\xee\xddc(Z\xac\xaa\x8f\x88>P\xa5\xa5\x1a\xb6\x86\xcb\xda y]\xd7\x96S\xe9\xf7\xb1uE\xe7[\x07\x1f\x11]#\xa2\x8c\xf3\xd2*\x1c?R\xacG75tW\x8c\x9c\xdc\xc0\xa4U\x1a\xcb;}\xb9?\x95\xf7W\xb7\x0b\x8f\xa1\xdf\xc1\xdf\x16\xb0\xf4;RCEbIjNH<\x84\x8d\x9bv\xbe\x81\xcf\x0b\xa0W\xd7%\xe7B\x89\x1a_\xaf+k*\"\x1e]\xeb\xbb\xa4+\x7f\xef\xa6\x18H\xe5\xbbK\xda\xf1?P@.s-\x05cT\xe6\xc2j\xc6yb\xc6U,}\x04|E\xcb\xd0\xcc+ki\xcc\xad\x0d\xcf\xe3\xcd\x18w\xee\x11\xc5\xbf\xec\xe6OaI\xc3\xad\xa8i\xd4\x13B3j\xf5\x93\x1b\\\xeb\x02|\xc9\xee\xd2\x16+nu\x8d\xadF3a-\xf5\xa6G7\xd0M\xd2\xb0\x8e\x00\x96UR\xe1\xf0\xa15W\x95>;\xb6\xe0\xda \xa7}\\,\xc2E;\xe1\x9cO\xd8\xa8t9\x94[mD:b\xb5\xc2\x10\xcbfx\x8e\xe3\x14\x1f\xe1r\x01&\xadYm-\xe9#[\xba\x94\xa2\xaa,\x11\xef\xdcf\x88\x8f\xae\x18\x9e\xfd\xf6\xf9i{p\xf06\x87|^CLE\xb6\xe9jU+m\x80\xc4\",\x97\x16v\x83{\x1c\x0f\xb18\x0c~~\x86\xb7\xd9wE>\x93\x0c\xe3\x1b%\xb41t\\**\x9a\xebZ\xd8\x07\xbf}\x86w\xf4\xccd\x82W\xdcP\xd2\x84\xae\xac\"\xaa\xc1\xd3\xc7K\xe0\xdd\x024\xcf\x9fE\xaf\x98\xb1(\x12\xe8:\xcd\xe9-\xf4\xf11\xf4\x87\x00\x1f\xb3\xab\xb3A;\xa1*I\x16\x97\x84\xd0U\"\x1d\xdc\xe6\xe0\xe03=u6D\x99wdm\x99^=\xcc\xdd\xdc\x1fB\x11\x876>F\xf8\x9c\xbd\x9f\x92W\x0bJr>\xaec\xb6\xa4\xb2\x18\xb7M\x17\xdbg\xb8\xcdnNl\xa5y\xcd\x14\xe6\xdf\x18f\x18\xe5\x7fl\xdd\xa3\x83[z\x9at\x93Vk\xab\x05\xc7<\x19\xab-J\x9d\x13\xb8\x07?\xc2\x05>\x98\x06\xa3\xaa\x9a>\x88\xa4+!,\x19v`\x15Cqr\x82\xa3\x9b[2\x93\x1c:\x077\xd9\xf3\x06=Y7\xd3*\xad\x95\xc6E\x9b0\x95AA}\x82)\xde\xbb\x16\xbe\xd0\x93\xa5\x8f\x9d-g\x14\x0b:\x1b\"\xdd\xa5\x1b\xa7@W\xe8\xd0\x95\x1cT-\xe8\xd6\x1c[\xcf\xe05Q\xce\xcf\xaeq\xcf\x0e.\x17@\x86\xfbHAh1>\x85t\xbbyHQ\xbd]@\xa5A2)I\xd9#\xef \xea\x92!\xed\xdd\xdc\xef\xe6\xa2\x9dC\xb1\x89\xfb\x1d}\xefp \xb8L\x01U\xfa\xc0p\xd6\xdc\xe6\xf5\xa2\xdd?D8\x84q\xf4\x93\xef\xe1j\x01\xa2\xa6\xeb\x02Z\xd1\xf9\xec\x9a\xa2\xd6m\xe7b\x83B)|@xI\x90\xcc\x08*L\xfd\xebH;w\xbf\xa3\x01\x9e\x1c\xdaO\xd6\x8bV>v\xe15\xd2c$\xb1\xafs\xc3\xdc\xce\x01n\x92\xf7:{\xc9\xb4\xb3.%}\xf8\x87-\x91\xcf\xc3v\x86\x1f\xf1\x91\xef,\x18AI5\xe9\xab[\xc3\x08\x9b\xc1?\xbb>\xc0ev\xc9\x82\xa1(\x05'\xa5R2\xa2\x86\xeb\xebD\xfa\xe0\xfbp\x80\xcb\xec\x12)\xce\x85IC\x86\x95\x92\x8e\x91\x91t\x17\xfb{W\x8c\xf3\xe8\xee\x1d\xbc%\xcfm\xf2d&!i}\xcb\x17\xf3\x93\xc8t\x88\xa3{\xd8\xc2Ur2ae\xf0\xf5\x80\xb2\x7fE\x17\x8b\x87\x11\xfa\xf8\x10\x8a\x077\xc4\x11>\"\xfc\x89`N\xb9\xd4t\xa7\x91\xbe\x05Nk\xeea\x06\xb7q\xcf\xa1?8\xb8X\x80L]N\xe8e{\x9d\x8c\xbdk\"\xee\xa786\xf4A\xfd\x8b\x13$\x9d\xbd\xba\xb4F0%\xe96\xb1\x94L\x0bb\x18\xdc1>`\xec\x0b \x05\xb9\x8a\xda\x90\xb64\xdfXV)\x89\xc4\x1b\xd7\xbaM \x9b?\xe4\xd6,\x9b\xdd5\x950t\xb1\xa0R\xb5\xa0x7q3\x0fOp\x99\x9c\xda\xa6/\x17(\xce\xacM\xdf}\xd32E\x19\xf7nzz\xf2\xc5\xb1\x8d\x13\xd2g\xef\x0dysN9\x0e[\xc2\xa0\x98\xc0\x13\xd3\x10\x1f\xc6\x83{rpy\x828 \xd4\x15\xae\xa7-\xe3\xa4\xf1\xc3\xb5\x11T,\x9b\xf9\xd0\xb8\xc1?D\xb8\\\x11\xcf\x9a}\xaa\xb6\x95%\x93\xebZh\x95\xe2\x9fc{p\xad;4py\x82\xb8~E\xe1,/\x01\xd9\xff\xc7\xd5\x974G\x8e#\xe9\xde\xf5+hu\x86hXI\xe0(\xe5\xa2\xcc\xd2\x92\xea\x94:\xb3\xabO\x8d\x88\x80\"\x98d\x10Q\\B\x8f\xfa!\xef\xfe\xcc\xfaYO[\x1f\xe6\xd0fu\x18\xab9\xe9\x8f\x8d\xb9;\xc8P\x8d\x0e\x01\x07\xe5\xce\x05\x8bcs\xff|\xd9\xbc\xebF\xb6\xde\x85\xae\xa5m\xa8\xd7\x7f\x11%iLrV*S\xc0,\xc6\x16\xc6\xd9r\xe6\x1e`\xaa\x0cK\xc6wo3\xa8MM.\x8c-E\x91\x82\x82\xf0R\x80\xd0\x93\xef\xb02?\xa6T9DtL[\xec\xf6\xd4-G\xf6\x14\xea\x06\xee\xf6\xf1\xf5_D\x08\xba\xadQ\\\xb1\x02=;\x1c\xf0mc\x13\xfb\xddz\x97j\xfe\xea\x8fY-\x08\x98\x14\x16t\xaa\xc4\xa8{$\xf4\x02s\xb8+J\x1c5-#\xb5\xb3\n_\xdd\x95\x82c)n\xbb\xb8\x82\x17\xbdJi)\x90\xd5J\xa1\xc9\xcd\xd0(GM\xbc\xea\xfd\x11\x1e\x0c\xaf\xfa\xf9\x0d]H\x1a\x0e\xe8\x135\x9f\xbduP$f\x07\xdf\xfc\xf0\xdd\xb1b\x9f\xfb\x98\xdd\xfb\xe6\xc7\xeb\xdf!\x97\xdaArJQE\xae\x0cH\xd4\xbe\xc7\x9b^\xa7\xb4\xa4\x88\x1eN:\xa3 \xa8\xdep\xc1\x1d\xb1\xbeP\x1b\xb9\x9e\x89\xd4\x80\x0d\x9a\xe7S\xfc\xc9\xb9\xb0k4qm\xa9\x0d^\xbf\xcdX2gu\xdcj\xf4\x1f\x10\x0e_\xfd\xa7:6U\xfd\xd3OS\xd8\xfa\xf6'\xf6\xd3\xf5\x1f\xb2J2V\x88\xbc\x942\xe1\x1d\x08\x89'\xb2\xf0\xa4\xb8oBj\xec\xd7o\xe8\xf4\xc1\xda\x1a\xe9\xd0\x99\x9e\x9b\xc2\x18E\"/\x8do\xb7\xc0\x9f\x08\x9c*\xb9\\\xa1\xa7\xc4R\x9cu\xe7\xfb\x16:R\x9c\x02\xbb~\x9bQ\xc9\xccV\x96\x8a9\x0e)\xbdK7\x0e\xc4K\xa9\xa1\x99\x8b\x90V\xf0\x82\xe0\x02@\xfb!\xeb\x08]4U\xe9\xf5\xdb\x0c\xce\ndn\x9d)e\x89! \xa4t\x056 \xc4\xbc\xf4\x04xI\xa7>\x06*\xd29\xdc\x06\xb7B\x13\xd3q\x82Q\n~Q\xc7a\x94\x08 \xba\xd9\xe6F\xa8\xd2a\xcd7a5\xf9\x97\xdd\x14\xd8\xcdB\xe1\x94\xc8\xe6\xda\x96\x8eY\x99\x97<\xddq\\\xef\xd8\x0d\xfc`\xdb\xc3hPZ\n(\xac\xc2HN\xea\xa1\x19k|\xfd\x9b\x94\x1a\x89\xf5ly\x89\xd1)\xac+\xe9\xb1S\xbf\xf7-\xb4\xfc\x9b\x85\xc2\xeeR\xe60\xfb\xc5\xae@Mh\xef\xd1>e5\xa2z\xbf\xa5\xdc%\xe5P#\xe8\xdc!\x049\xfa\n*\xeb\xb8!\xb1}\xec\xe1\xae\xb73\x81/mr^hm\x0d\x1e\xbc\x1a]HI\xcc}3\xc1K\xe0\x13N\xb4\xd1(\"9W\xd6\xd0\xca\xdf\xa5\x8a\xdb\xc7&\x0es;\xb8}\x9b)i6`u\x81\x8a_\xe4FXK\x85\xb3\x8f{\xcfn\xe1\x07\xf4e\x81\x98\xb7B\xab\\\xd0?[\xdf\x0fS\x07\xefp\"\xd1\xc6Z\xe5R\xa0\x19\x82\x13yi\xd0\x19\x07\x04\xc6\xd6O\xec\x16\x7f\xa5KlJ\xa3\xba\xe3\xd4Y\xdb\xeae\xd7VS\xb6\xf7\xc31L\xd0'\xaa\x89\xdd\xa5\x8b\xb7o/\x16d\xdcgla\xd1~\x94\x1b\xbcA\xdc\xa2Fe_Rj\x13\xfe=\xe9\x0fm\x96\xb9\xd1\xc8b\x17\x9a)\xb0/\x94\x18\n\xb5\xe2`\x05\x87\x05\x16\xfb\xaa\x1d\xfap\xf4\xec\xcbB\x89T\x7f\xe9l\x89\xcf\xd6(\xdd\xc8\x0e\xa1\x1d\xaa\xa6\xf2\xec~&\xb4\xa4H\x04\xba\xe4N\xd0\x12\xd9\x96\x05\x16\xdd!n\xfa\xda7-\x14\xff\xfd\x1b\x1a\xa7\x95:\xe7V\xda\xe4\xda\xa7\x89}\x1b\xfb!\xdb\xc4f[M\xec\x9er\xef)\x97\xde[\x17Bj\x18\x8c\xad\xe3\xae !h\x16XV\xf7'\xd2R\xf5h\xad\x15A\x02\x18z\xfb\xae\xda\xc6\xae\xae\xd8\xfdL\xc0\xfa\xc1\x94P{\xa5t\xe4 \xa4%\xbdN\x177mUW\xeckJ\x1dO\xddO\x19\x9b\x8e\xa8\xb4\xa4V\xdd\x8d\x9b\x00\x0d\xffkJ\x13 \x8fU\xa2\xd0\x16\xb1hq\xd1\x98X\xdbj\xe8\x91\x97\x88\x92\xac\xf0\xb5\xd5\xa5\xd5\x84\xa7`5M^\xbaq\xc0CbJ\n\x9a)\x97Bc\x00H-sP\x9e\xd4\xe8z\xff\xd4\xc56\x1e={X(E5\x93f\xd6\x85\x99\x9d\xf6\x91\x9d\x06\x94\x87\x94&\x0d[rYJT\xfcB\x0bE\xea\xa5\xdf\xf9\xc6\xc3}S\x9aF\x08\xa5\xac-\x0c,:\xac(\x8bB\x11\xebHc\xf7\xc3L\x08\x9eVZ\x82k\x89\x13\nn,\xb7\x89y_5\xd8\x9f\x1fN$\xb4&\x04\x92(u)`\x89\xeeT\x91zi\x1f\xbb=(\x8c\x87\x94\xa2i\x8b\xc9\x0b\xa9\x1cF p\xca%\xb6\x88\xf3\x92\x87\x99\xc0\x81P\xe7\xa5.`\xac\x81\x02V\xa2\x14\xf4\x12\xe3zWW\x18Xov\xf9\x90y\xe9\xac.5\xfa\xbcX'i&\xd3\x8f\xf5.\xb6\x9e=\xa4\x14\xa3v\x80\xf2W8\xc8\x1bK\x03v\x7f\x0cC\x83/9\x13\x92\xfa\x92ue\xe9\n\xc4\xc4,M\xe1\xa8d\xa7>N\x01\x0b`\xa1\x8a\x04\x19e$\xc6$\xe4V\xe0\xe3\x87\xd8\xc50\xf4c\x1d\x03{|C\xa7&\xe1$7\xa5\xc6&Q\xa8\xc2Z\xac\x8d\xa1\x0b\xc3\xe43\xdf\x84\xba\x0ft\xf7G\xbat\xf1\xe6R\xfafS\x1aU\x16\xe8B\x85\x01\xd0\xe9\x06\xb1\x1a\xa0;\xf9,\xbe\x04\xe8-\x8f\xa7+_\xe8\x8a.I{;!$\xceH\x9c\xd6\x8e\xd3;\x8f\xfb&L\xf0\xd4\x990\x143\xcc\x8a\xa2\xc0\xb8?F\x17\xc4\xd8\xf9 \xee\x9e\xd2\xd4\xc2\xac\x90\x86f\xd02qm|\x83\\\x98\xe2 S\xe4\xc2 J\xc8\xd2\xb2\x87\xb1\xdb\x86\x16\xe6?\x8f\x0b\x95^R)'-\x81JsE\xd5:L\xed\xb6z\xf1\xec1\xa5\x86n\xea\x84\xb3B\xb2\x12:\x9b\x93\x1a\xabvl\xfc\xcb\xc4\xfe\x8c\xbf\xa8\x95-\x82\xf4s\xac\xffRZGw\x1c\xfbC\x80\xb9\x14\xfb\xf3L\x94\xf49\xbc\xd4FC/\xe4\x85\xe24\xbe\x1d\xe3\xb4\x8a0Sg\xdf\x16*u\x00k\x85\x83\x05\x04,\x1d\xb9\x93\xf8i\x93\xaf\xe3\x11gP\xa0D\x7f\xf9C.\x95\x99V\x85,0\xbe\x8f\xb2\xca\x1a|\xf1)\xf4\xd0\x0b~\xa1D\xd2\x8c\xb6\x94\xca\x1a\x8d\x13*%\xb9\xc0zx\xf1\x9d\xdf\xfa\x81\xfd5\xa5\xdcbuqe\x0bH\x0b\x91\xd8\xaa\x86&\x06\x7f\x9d\x89\xb4\x02H\xae\xb7f\x89\x92\xda{\xe6\x9b\xf3\xce?c8\xe6\x8b\xe6\xfck\"\x93\xc91\x17\x8ekh \x85(\xecY\xbf\x82\xd9\xe2\xd8\xf9\xce\x0f8Y\x9cIN\xb0\x9e\x8bwsq\x82\xb7\xef7l\x1bV\xabj\xc3\xae(Q\xe4s\xadf\x93\xa7b9.\xed7\x08:\xd2\x05\x04\x1b\xe9\x02\xb1\xca\xc5 ^\xaa\xd9\x84\xfe\xac\x0f,\xd4a5\xb1\x0f\xf8KnR\xc8\xbb\xfc\xff\xc9\xb7}h\xd9GJ\xd0\xb6O,\x01\xd3\x17\x10\x80>\xb0m\xe7[\xdc\xef\xbb\x9a \xd0\x89\xa0\x96\x11\x8d\xda\x00\xcf\x0eq\xfb\xfa!v\x07\xf6\xe9\x0d\x8d\x96*\xc5\x9b\xa0\x83\xc9T\x05D\xa0Om}\xb7\xe91\xc2\xd5\x86}\xc2\x0b\xaf\xff\xfft%\xbd\x942h\x92\xba\xbcy}\xf4]\x1bv~\x1bZv\xfd\x86\x96\x96\x82\xb0\xa1\xc5\xc9\xc2\xdd\xfbf\x80a\xa2\x19^\x7f\x9bY\xecb\x99\x92\xf0k\xfa\xc0\x8e~\xe7\x8f\xdew\x9e}{\xfd\xfb\xee\xf5\xefDc\xe4\xa4b\x9eHH\x91\xbb\xb3\xbe\xc2-\x90x\xac\xd6>;tU\xb6\xed\xfc\xa6jG\xdc\x109]\xbdJW\x13\xd8\xb56\xc9\xa3\x1aT\xa39\xebk\xd6\xc6\xe3\x94\x81\xbaew\xf1\xf8\xfa\xdf\x19(\xdc\x19\x1aY0\x81~\x1d}\xcd\xfa\xa3\x1f\xa6l3v\xeca\xa18\x85\x19r\x06\xd8,\xb2\x81\n\xca\x9a\x11\x95U\xf7\xfa\xff\xb2\x9b\xd7\xff\xaa\x931$\xf9W\xce\xd0\xc7\xea\xaco\x98o\x9a\xd0\xce\xa7\x89@.\x87\x89\x18\x99<\x99\x9b\xab\\P8[\x10\x19\xc2S\x08\xec\x11\x7f\xb9\x00F\xb4\xca^\x02D\xf4-\xabc\x13W\xbe\x85\xf6\x99\x88\xe4\xdacE\xb2\xf5\x10e\xce\x93sa\xdf\xb2vS\xc5q\x15\xda\x90m}\x13\xd9\x1df_\xff\xa3\x0d\xd9\x15\xe4\x11P]\xa1\xb1|\x91s\x14\xa8b\xeb\xb7\x9e\xdd\xa5\x14\xcd\xf8\xe4 \xf3Z\x88\x19\xe9\xfc\xac\x8fl\x15c\xef}\x1f\xd9\xe5L$<\x03Iq\x03)\xf0\xbf4\xc0\xbb\xf5\xed\xcaw\xec\x8a\x12B[\xd5\x06\x96u\xb3/R\x1f\xd9\x1e#\xf5\xdeR\xbc^d\x99q\xd5\xb5^L\x18\xfb\x81mC\xb7\xaf\xda\xca'\x94z\xcf\xae\xe6\x0b\x17\xed\xf0\xfao\xb8\xc2\xd1nt\x06K(\xe7\xee~\xd6\x1f\x19\xc5\x94\x0c\x1dY\x89\xfbv\xd3\x85\x9e}^.\x12\xc6\xfd\xa6{\xfdg?\x9b1\xda\xd9Y\xc5\xba\\Sx\xc0\xb3~b\xbej\xb3\x8d\xff\xb5\x0d\xec\xe2\xf5?\xdb\xec\xbd\xff\xb5}\xfd'A\x87\x14\xb9\x91\x86\"\x01\x82^O\x9drb\xb5\x7f\x1e\xbb\xac\x1e'v\x8d\xd4\xf58\x913\xb5\x81\xc6\xa6\x10\xf1\x10\xd8v\xbe{\xf2\x88\x94\x84\xe9|\xcf\xa4\x88\xd4\xc9\xd3\xa9\x9f`\x0d\xb4\xf3C\xe6\x87l\x18\xbb\x16\x96A)\xfb\x08YNP@.\xa9^esn \x18\xaf\x9f\xd8\xe0\x9b&[\xf9\xbe\xea\xfc\x8e=B\xe62e\x84~\x83\x81\x81\xde\xbd\xf6\xf4\xc4!4M\x86\xa7\xd2q\x13;\xf6\x98\xb2\xd7\x94M\xa2\xb3\xf5\xa9*rq6l\xd8>\x8e\xed&\x8e\xec6\xa5\xdc2\xa1\x8c@\xb8\x969\x80\x07\xb4\xc3TQ\xc3\x86ua\xd8V\xec+\xfe\xf29T\x00\xb4\xf0\xb3a\xcb\xfc>l~\xc46\xd6qd\x17\xfb\xd7\x7f.\x19\x8a\xe3\"\xe7;.\x06\xad s\x0c\x9b\x1fU}\x88\x9b\x1f\x15\xbb8\x82\xd0\x9cC\x95K\xee\x1c\xa5e\"\x17(\xd0\xf8\xbd\xcf\x86\xd0\xf7\x15\xbb\x01\xf2\x11IIq\xbb\xd3\xf7\x9dbs\x0c;\xe6\xf7\x87]\x0cY\xeba\x0e\x985\xa3o\xb7\xec\x82\xae\xdd\xd1\xb5\x1b\xbc\xa6\nr\xc9+\xa4\xe5e\xf2\xe8\xb6J\x18\xb8\xc7\xca\xb7\xd9\xcag\xad\xcfv>\xc3\x88\x8bmv\xe9\xb3;\x9f}\xf2\xd9\xd7\x88\xcb\xd4\"W\xdaY^\xa0\x89\xb3,\xa5\xd3\xfa$\x1a\xea$\x13j\xdc\x8c\x10\x8bC\x08\xf9v'\xc6\xdd\xe8+\xf8\xc9\xc8\xfa\x1cD>\xc1\xa5O\xa3\xcf\xde\xe1%A\x86\xf7%/\xc9\x9a\x9a\xe7\nz\x82\xfe\xc3\x1d\x0e\xf1$z\x9fT\x80\xcb\x9d\x93d\xc6\xc8s#-,A\x92L\xedC\xbb\xcd\xda\x88\xc7\x0d\xd95\xe6\xeebE\x16.2w\x1c\x97\xcb\xf0Y\xd6\"\xa0\xef,\xb7\x8bu\x86\xb0!$\x08YD\x0ea\x05\x06\x981\x96<\xe3K\xce\x05\xd4v\x92\x8a\xd9\xb0\xf3u\xd6\x8e\xc1\x93\\\xcc\x1e\xe1\xc2\x1d\\ I\xebL\x01\x9a\x1e*\xc1)\xd0\xc6\xb3p\xe7W\xe3\\4\xd7s\x06MZLN(\xcc|\xa9\xf9\x15\x9a\x1b\x07xb\xb3\x08\xdd\xfa\x00O\xa4:O%\x93V\xe0\xce\xe5\xc5I\xee\x80\x151`dk\x12<`=<\xe2\x15x\xcf2w\x82\xa0\xc89Ln\xdc\x9b\xa2\xd9\xc3\xed\xb3\xf5n\xdc\x93,f\xdfAv\xae\x0c\xae\x1d\xe1.;#\x0b\xb7\xb4\xb2\xd6'\xd9\x01\x91l@\xf8\xce'\xf9G\xbc\x04\xf36\xe8qFS\x11\xe9\xdc8!\xa5\x9c\xef\xd0\xfb\x95\xcf\xa6T\x99\x0f\x90\xf9%\x927\xbd\x9b=\xfc\x1c\x86\xe1\x9a\xf9c\x95\xf5c\x0d\x9a\x97Db\x95=\x8c5(]j\x01*\x97RP\xc3\xc6xwoJw\xf0\x8d\xdfd{O\x0f{\xc4\xdc\xad\xaf\xc8\xa8Y\xe7\xc6(j\xa2R\x94\xcb\xf3\xf0\xc3\xb2~\x96\xd9\x91\x17\x03\xf9\x1d#l\xf0\xfc\x8a\xc9\x83/\x89=S\x9b\xfeN\xb6r\xc2\xe6\xca\xa1O\x16|\x0b\xc2\xdf'\xc6-\xc2\xe5\x00\xef\x16qr\x98\x91Lb\xbcc\xa1\xf2\xc2 SX,u\xe9\xac\xc0\xda\xae\xabv\x9b\xcdJ\"\xb6\xd9\x06\x87\x10D\xf9\x81\xff\xcc\xaa\"\xb6\xd9\xfb\nF\x14\xf8\x0fZ\\\xe9\\ e\xa4#\xe7Mm\x856g\xc3\x9e\x85\x1e=\xfa'\xf6a&\xb8L\x81h\xc8o\xd7\xa8\xdc\x95\xd2\x9c\x0d\x1d\xf3\xabM\xb5\xae\xea\xbd\x7f\xf1\xecb\xb5\xa9^\xff\x912\x9c\x8e\x06\xb5P\xbc\x940$\x0b%\x95\xb1(\xd3\xf9!\x1c\x02\xbbH):)\xf3\xbc\xb0Z\xc3`R\xe6\xd6\x08'\x15\xb2\xf6~[e\xb5\x9f*v\x81\xe45\x90\x96\x04\x92\xf9\xadR\xa0~:\xb6\xf2[DE\xbcLiAg\x98%\x854\xa3cwle\x1d[\xfb\xae\xaaW\xf1e\xe3\xb7\xec\xdd\x1bZ\x1b\x02\x0d\xa4\x1a\x946/\xf0#7\xb1\xde\x07|\xd5\xf7\xaf\xbf\xcd$\xb4G\x0d\xcaKJm\xd2\xe1O\xe9\x88\x7f\xec\xc6&\x1e={?\x13\x9a\xdeE\x19-\x05E\x165\nZF\xc7\xb6c\xd3\x8c\xec\xea\xf5\xf7\xa6y\xfd\x1d5\xab*si\xb8\x85\xa9\x0e\xcfKa\xca\xc2\x12\xe3\x0bF\x0f\xbcJ)hlU\xe2 \x1e\xb7P\xc0\xa5.,\xc7R\xdb\xf9u\xe5\x9b\xaa dL\x9bHM88)\x12\xb1t\xb9A\xd4\x1f\x14\x88\x07\xf6)\x1e\x08\x82\xd2\xe6\x02\xe3\xdd\xe2\xf9]\xc9%\xc7o\xaaC\x1d\xba\xa6b\xd7)\xd5\x8e\xeeV\x18\xab4\x1d\xf5\xe1\xf9\xf3\x80\x80\x89\xe366Y\x1d\xa7\x91]C\xe6\xf5\xb7&\xbb~\xfdmz\xfd\x9d\x19\xfe\xb6\x88\xd5\x1c\xd7\x07\xe4b7\x8c\xcd\xc8^\xffo7\xbc\xfe\x0e\xa5\x816\x10e\xee\xb8t0\x8fwyiE\xfa\xc4\x83\xef\xd6U[\xd5\xec\xdew\xaf\xff@\xaa n-\xac\xe4\x05\xee (\x0d\xeb\xed\xa1c}\xecj\x0c\xe2\x83 \xa7\xaf\xd4\x1a>\x1f\x11X\x8d\x16T\x1b\xfd\xd8\x8f/\xec\x01\x7fK\xc2\xb8Q\xdcX\xadaI\xec\xb4t%\x96\xd7\xe4\xfb\n\xcf\x13~\x99\x89\xd4\xdc\x0b\xad\n\x813Pt\xff\xc1\x8a\x9b\xc6\x06\xf1\x88(\xe1 \xbd\xc6\xb8\xd2J\xb4\xd5\x93\xa5\xd4\xe2lxf\xcdX\x8f\xec\x06~\xd0\x02\x05kT\x11\x8c\x12nO\xa3\xb3\xdb\xf0\xcc\x0e\xe3\x10\xc7\x1az\xf1\xfdB\xf1\x84\x85m\xd0\xd2q\x86\xd2\x1a\x9e\xd9P\xb5\xdb\xe7q\xe8#{\\(d\xd6\x0b\xb0\x07\xdc~F&\x19^X]\xedGv\x0d?h\x1f\x0e\xab ,\xcdy\xca\xf3\xc2\xf6~\x1b\xb7\xb1\xad\xd8\xedL$#\xed\xd9\x90\xfc\x84q\x85\xec\xcf\xbe\xae\xda\x8dg\xb73\x81\x16\xd9z^\x85$\xa7\xf0\xd1\xb3\x15\xb4\xec\xd6\xb3\xcb\x94r\xb2\x850\xa5(\xb8\xc1\xbdc\xab\x8d\xb1\xc0\xba ]X\xadwU\xcb\xde/\x94Ti\xe1d\x0c\x9ek(e\n)\x81\xbbn\xd0\xba\xe3\x9a\x12N\x11H\xa5\xe3\\\xd2i\xa9\xb6\xd6\x95\xc8\x18\xf7\xfb\xb1\xf5\xdd\xc4\xae\x17\n\xbd\xefLnti\xad\xc0\x8d<)\x9d\xd0\xc8>n\xe2\n\xf7\xaf\xd9\xf5\x89\x84\xb9\x9cvy\xc9\x85\xd1\x06\x0d\xacJn\x0c\xbeHSm\xa6zW\x1d\xd9\xcdL\x00\xb3\xe19\x17\xb2\x90\x05\x82r;U\xd0\xcb\xec}G\xbbe\xb73\x91^E\x08\xadJ\x89JG\x0b\xa1\nd\x9e\xfaf\xf2C\xd5\xb2\xdb\x85J\x07\xd4\xdc:\xdc$/\xf2R(\xe0m\xe31\x86\x1e\x969\x18\xb3\xe7M&=@;g\\\x81\xdf\xaa\xa5\xb3\xf8\xea\xfd\x10\x9e\x921\xc7\xc3\x89\xe4T\xe6s\xf8a\x03*s\xf4l\x1a\xbb\xc9\xef\x0f\xb1a\xbf,\x94\xa4\xe8\xb7\xa5rP\x8e\x12\xd6\x0f0\xae\x9e\x8d=-m\x9f|\xb7\xa7\x95-R\x8f\x7f! @\x97\x96\xa1\x0e\xbe\x16W\xb9 \xb1\xf2\xdd\xb0\x8bM|\xee\xd9\xe5\x89\xbc}\x8f\xddP\xcdQ\xb0\xca2\x97\x8ab \xa3L\x8fc%\x016^\xa6\xdcW\xcc\xdd\xfd\x8c\xea\xbcD\x7fP\xc1\xceK\x9d\x1bMA\x05A24M8VM\x13\xd8\xe5\x89\xfc|\xc34w\xc2\x11D\x1a\x87?\\\xce9K\x10i \x18\xdbv\xcaj?\x04v\x89\xe45\x90\x8fw\xb8~J\xabOvnUn\xdd\xf2\xb0u |\xe6\xa6a\xef\x90\xbc\x05\xf2\xcbW&\x94+\x0d6Ei\xe8ahF\xceO\x92q\xb5\xf3\xfb\xec\xe0\xbb\x9a\xbd#\xfa\x1e\xe8o\x17\x84\x80\x97\xd0\x98\xceK\x9b\xcb\x82\xac3Q\xaa\xd9\xf4C\x17\xfc\x9e\xbd;\x91\xa9(a\x00'\xf7\x06P\x85o\x9e\xd4\xc4v\xca\x9ec\xdc\xf4 \x05\x99\xef\x98\xb9\xba \x18c\x97\\\x1b\xac\xce\x0d_\x9e\xb5 Y\xed\x9b\x15{\x1f\xb2kHo\x1f\x08\xdf\xb3(\x0d\x95\x9eE`\x1d\xd0\x1c\xc0]\xf9}l7\xec}J?_P%\x15\xe9\x9d\x9c\xcc\x1d\xcdI\x80;4{x\x1f\xf6!\xa5\xb7\x1f\xd0R\xa4\x94\x92\xee]r\xc4\xdbM_\x10\x86!t\xec\x03\xfeb}\x14\xb9)\xc8\xae\xf4\xdc\x1ax\x7f\x9e\xee\xfb\xe4\xabn\xe5\xdb\xbag\x1f\x17\xea\x1d\x95\xa8-f,(\xc9\xf3\xa2\xa0xhId\xd8\xc5\xae\x0d(\x93\xc8\xf7\x1f\x08\xa7\xaf$x\x95\xf3\x12\xddz\xe6\x16\xbd\x0d\xc30\xf5\xab\xb1\xdb\xb2\xab\x13\xf9\xe5\x13~\xb2\x10\xf3\xab\xe9\\\xd3\x82\x12e:\xdfn\x8eUx\xc6}>\xa2>\xbfgt\xb2\x9d\n)\x85\xca\x802\x00\x91]\xb5\xdde\x87X\xb5\x03\xfb\x04\xe4=\x92\xb7_hL\xb4)\x0c\x84\x93s\xf86\x92i\x9a~\xe3\x9b\xc0>-\xd4\xcd\x05\xa1\x14\xe9Y\x82\xe3\x1e~\xaa\xb9\xdd\xd8\xc5\x96}\xc2\xdf\xeb\x07\x1a\x1b\xd5l&f(j\x13\xf5\x90\x1f\xe1\xe9)t}l\xb3uh\xa12~^.\xbc\xa3\x0b\xf7\xa9\xd6\xe78\x13\xa5\x83\xf9\x13O\xdf\xf3#\xee\xda>\xb6\xec\xe7\x94\xe2\xe7\x17\xb9\x96d\x87|.`]%\x96F\xf2#\xb6\xa1_\xc5.\xb2\x9f\x17\xea\xe2+3\xb6\x80~lr\xabQ]P\xb4\xc3\x14\xfaq\xecaP\\\xf9\xa6\x81q\x11\xd3\xbb\x0f\x88\x94\x03SZ5\xc3\x87\x14\x85\x9c\x0b\xb9F\x90\x0b\x98\x7fC\x92\x8aJ[\xda\x08\x80\xc2\xb5f)\xdc\xc6g\x87\xb1\xdf\xa1\xe3\x12\xa4\xdf/ptr \xae\xf2\x1c\xe1\x1c\xd5R\xe5\x8d\xdf\xa7\xd8\xd8\x94^|%\xd4\x7f\x9e\x9c\xbe\x9c\xca%/\xe6\xd2\xdd\xc7~\x1d\x9f\xd9-%\x89W\xa4y\xc5\xb9C\x07\xb1\xb9$1D\x87\xafZ\xd2\x1f\xb7s\x0e5\xc8\xc5\x0d\x99\x17\x17I\xf1\xd8\"/\xcb\xa5\x0e\xdb\xf0\x9c\xad|3T{\xf4\xf4\x0b\xcf\xd9\xe5\x92K\xbag\x8e\x11\x00z\xa4L\x8e\xc8$\x89\xcd\xfc.\xa5\xdf\xbf\x91\x02\xb7\xb3\xa6r\xb9\xa5\x86\x0c\xdc\xd1\xd7\xcf~b_(yxG\x10\xc7\x9c6iA\x8b\xf2\x14\xb1`\xec\xd9\xc17U\xef7\xa1\xcf\",\xd9\xc2\x1cW\xf8\xfe\x7f_O\xc1\x85\x93\xbe\xe3z\xee\x9cEnJr\xe7\xc2\xbbuu\x86\xdd\x0b\x8a#\xfb\x86\x1d\xed\x02\xdb@\x8a\xc7\x82-\xc6\xe8\xa5\xa1\x1dBl<\xbb\xc7_\xac\xd2\x02\xf4g\xdaQ\x15\xe5\x1c\x8e\x14X\x7f\x1d\xabv=e\xbb\xd84\xf1\x99\xfd\x89r\x9f(\x97\x1a\xbf0i\x7f\xb7\xd4\xb9=\xb53\x0c\xc5\x9d=u\xbe]W=\x0dJ\x18\x87;\xfb\xf8\xf6\xd2\xe7\x1bB\xf1tsA\x95y\x91\xce\xd6\xe1\x1e\xeb\x98$g\"i\x10mS\xccJ\xd0 \xee\xa4t\x86]\xdc{\x980\xb0\xc7\x85\xba\xfb\x85\xfa\xa8\x9d\x87O\x95\x97b\xe9=C\xe7\x8fU\x9f%\xa4\xe4G\xca]R\x0e[\x96\xc8\xb5I\xa5\x08-\x8b\xdc\xe2@\xf2\x18\xba\xd8z\xf6\x8d\x92\xa4u\x97\xa7\xc0\\\xb7\x10\x8bj{\xf6\x18C\xf0)v\x1b\xf6\xfd\x0d\x0de\xa8\\n\x1dE\x9f\x00\xbd\xab\x92?-Ju\x9b\xc6?\xb3\xef)\xc5y\x87\xc8\xcd\xd2IJ\xb4\x12Iu\xf5\xec\x87\xd0=\xfb)\x0b\xfd\xe0\x87\xd0\xb3\xef\xf3\x85\x0f\xe9\xc2\xc7\x1b\x02\x1d\xd1\xe9iV,\x912A~W\x0dm\x98\xb2]\xa8\xb6\xbb\xa1g\xdfS\xfeS\xca\xa7F\xed\xa8\xe9\x914\x8d\xf6g\xc7\xe4!:\xc4l\x13\xb2ch\xfd&y\x8a\xd2\x95ot\x05f\xed6\xb76\xe9\xb1\xc2\xe5<\xa9\xbfc`\xa1\xc9\xb6\xa3\xef\xaa5b\xeb\\%\x12\xe6\xd9./\xd3v\xeey\xa1\xa0\xba\xb1#\x91\xc8\xc1w\x0b\\\xd1\xfdL\x0b\x0c\xad<{\xe3\x83\xd0\x1c\x00\xf9\x18\xd8\x8f\xb0\x1a\xd7~\xe5\xdb\xb8\x8e\xec\xe7\xb7\x19\x8e\xee\xeeR\xa5\xb6X\xf0\x9c\xc6\x1c\x0db\x8d\xcf\x9e\x9a\xd8U\xe4\xc7\xf91\x91\xb0\xba(\xdf\xb4-\xf4\xaf\x9b\x9f\xd4\xf8>\x1b\xba\xaa]\xefB\xe7{v\xe3\xfb\xec\xf1\x94\xc5\x03s\x04\x19*t\xee\x80?>U\xeb\xaa\xf5P\x0c{\xdf\xb0/){EYA\xce\xec\x18\x9f\xa8X\xd6<\xc7\xc00\xd2g\xe7\xd9}J\x13#\x05.\x14\xc8-\xcb\xd2\x00kBuA\xb0\x87\x19\xd4\x05\x11\x1fR9K\x97\x06\xb2\x02qz\x11\xb9\xf6\xd82\x7f\xc8\xb61\xdb\xf8\x81]\x1c\xb2\xab\x98\xbd\xf7\x03\x1e\x9b..\xd0\x08\n\x958q\xd3i\x17}6\xf8 \xf8?A\xfeS\xf4\xd9\xa3\x9fh\x87\x8e\xcff\xd9\x82\x173\x1e\xe8\xb1\xa5\x0d\xb3\x916s\x1fR\xfc0!s\x8au8\x97j\xcb6~\x9f\xb5\xdb\xb8\xceV\xbeb\xef\xfd>\xbb\x83\xcc\xa5\xaf\x98\x9a\xfd\xf7\xe7\x9b\x03{\x13G\xdc\xf1C\x9f\xc8\x11w\xfa<\x85\x02\x96j9\x9f\xe4&7\xc8<\x12` \xbb\x19 \xa9\x04\xcd\x9a\x84X@\xcay1\x1b\x12\x1d[\xb6\xf7\xf1\xbc\xf7\xf1\xfc\x80\xbb\x96\xb7>\x9e?\xf8x~\x8f9XQH9G_\x821X\xe2\x13\xf6x\xe3!\xb0[$\x1e\xc3\xcc\xa9\xe7'\xc8\xb4b\x01\xee\xc3n\xcc6Uh\xd9\xfdn\xcc\xdeW\xe9PR\xa8\xd9\xcb]p\x97\xee{hB\x86\x81\xdd\xb2\xe1\x10\xd8}\x13\xb2K\xcc=\x1e\xc2|\xf0URXJ\x0d\xec}\xdcg\xbf\x8ek\xf6\x10\xf7\xd9\x9f\xc65!\x1a\xcd\xb8S\xb8\xf1\xe9\x80m\xa80\x08\xdc:\xeb\xfdz\xc7\x1e+\x8c\x03\xb7\xce\x1e ' \xdbM\x9f\x8aP\xe0\x9b\x1c}\x9b\x0d\x1d|\x1d\xfb\xe6\xdb\xec\x91Hh\x94R,@1|\xde\xa3\x07\x89\xff\x13\xf7\xd9\xd6W\xec/\xaf\xff\xdegW\xbeB\x0f\n)r\xa9\x0bk%5..\x85\xb2gS`\x7f\x1b\xbb\xf1W\xf6\xb7?\xc3o\xda\x9b\xd6\xc9\x9aW\xa3C,6\xda)0\xdfd?\xaa\xb6\x85F\xdbd?\x13\x95\x04\x8a4\x97\xd4*w4\xa8\x92\xc0\xf9j\xfc\xd5W\x9d\xaf\xf0l\xfer\xc9\xe0i\x0c\xfa\xc2Q\xf8\x0dIJ\xebe\xcf\xf6\xcf~\xbf\x1a\x1b\xdc@ bF7\x99\xc3\xc9/\x01\xbe\xd4\xd9\x0b\x86\xe3\xd9\x86\x16>\xf6r\xa1R(\xfb\xc4/\xcbd K\xfc\x080tI0C\x8e1\xbeDw\x96nQj/\x1d\xab\x9a\x10\xd9g\xf8\xe1\x9c\xb1s\x9e\xc3\xea\x9e/\xe7\xf7/\x1dL\x13\x9f\xc9\x88t&\xd0\xd5\xfb\x14\xcb\x19\x06\x86\xd3\x83\x9b*\xd4\x81\xdd\xe0/<\xf8|\xf9\x0c\xe87\xd6\x00O[\xfb}`w\xf8\x8b& :/\x1d\x95\xac09O\xb3\x88\x97\x8e\x0d\xfd\xae\xda\x1f\xc6\xbab\x8f\x0b\x85p\x95&w\xe5\x82\xffhdi\xce^\x9e\xd9~\x88]\xecw\xf8\x8e\xb7oh\xae\x17\xefw\xc5\x17\x9c\xd8\xff \x00\x00\xff\xffPK\x07\x08\x15\x0b'\xdatY\x00\x00>\xba\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00w\x8fqM\x15\x0b'\xdatY\x00\x00>\xba\x00\x00\x14\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00worldcitiespop1k.csvUT\x05\x00\x01\x13W\xf0[PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00K\x00\x00\x00\xbfY\x00\x00\x00\x00"
fs.Register(data)
}
gowid-1.4.0/examples/gowid-table/table.go 0000664 0000000 0000000 00000006606 14262344540 0020273 0 ustar 00root root 0000000 0000000 //go:generate statik -src=data
// Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
// A demonstration of gowid's table widget.
package main
import (
"fmt"
"io"
"os"
"strconv"
"strings"
"github.com/gcla/gowid/examples"
_ "github.com/gcla/gowid/examples/gowid-table/statik"
kingpin "gopkg.in/alecthomas/kingpin.v2"
tcell "github.com/gdamore/tcell/v2"
"github.com/rakyll/statik/fs"
log "github.com/sirupsen/logrus"
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/fill"
"github.com/gcla/gowid/widgets/table"
)
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
if evk, ok := ev.(*tcell.EventKey); ok {
if evk.Key() == tcell.KeyCtrlC || evk.Rune() == 'q' || evk.Rune() == 'Q' {
app.Quit()
return true
}
}
return false
}
//======================================================================
var (
file = kingpin.Arg("file", "CSV file to read.").String()
colTypes = kingpin.Flag("column-types", "Column data types (for sorting e.g. 0:int,2:string,5:float).").Short('t').String()
)
//======================================================================
func main() {
//f := examples.RedirectLogger("table.log")
//defer f.Close()
kingpin.Parse()
palette := gowid.Palette{
"green": gowid.MakePaletteEntry(gowid.ColorDarkGreen, gowid.ColorDefault),
"red": gowid.MakePaletteEntry(gowid.ColorRed, gowid.ColorDefault),
}
var csvFile io.Reader
if *file == "" {
statikFS, err := fs.New()
if err != nil {
log.Fatal(err)
}
stFile, err := statikFS.Open("/worldcitiespop1k.csv")
if err != nil {
log.Fatal(err)
}
defer stFile.Close()
csvFile = stFile
} else {
fsFile, err := os.Open(*file)
if err != nil {
log.Fatal(err)
}
defer fsFile.Close()
csvFile = fsFile
}
model := table.NewCsvModel(csvFile, true, table.SimpleOptions{
Style: table.StyleOptions{
HorizontalSeparator: divider.NewAscii(),
TableSeparator: divider.NewUnicode(),
VerticalSeparator: fill.New('|'),
},
})
if *file == "" {
// Requires knowledge of the CSV file loaded, of course...
model.Comparators[3] = table.IntCompare{}
model.Comparators[4] = table.IntCompare{}
model.Comparators[5] = table.FloatCompare{}
model.Comparators[6] = table.FloatCompare{}
} else {
if *colTypes != "" {
types := strings.Split(*colTypes, ",")
for _, typ := range types {
colPlusType := strings.Split(typ, ":")
if len(colPlusType) == 2 {
if colNum, err := strconv.Atoi(colPlusType[0]); err == nil {
switch colPlusType[1] {
case "int":
model.Comparators[colNum] = table.IntCompare{}
case "string":
model.Comparators[colNum] = table.StringCompare{}
case "float":
model.Comparators[colNum] = table.FloatCompare{}
default:
panic(fmt.Errorf("Did not recognize column type %v", typ))
}
}
}
}
}
}
table := table.New(model)
app, err := gowid.NewApp(gowid.AppArgs{
View: table,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.MainLoop(handler{})
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-terminal/ 0000775 0000000 0000000 00000000000 14262344540 0017371 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-terminal/terminal.go 0000664 0000000 0000000 00000022113 14262344540 0021532 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A very poor-man's tmux written using gowid's terminal widget.
package main
import (
"os"
"strings"
"syscall"
"time"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/fill"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/holder"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/terminal"
"github.com/gcla/gowid/widgets/text"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
//======================================================================
type ResizeableColumnsWidget struct {
*columns.Widget
offset int
}
func NewResizeableColumns(widgets []gowid.IContainerWidget) *ResizeableColumnsWidget {
res := &ResizeableColumnsWidget{}
res.Widget = columns.New(widgets)
return res
}
func (w *ResizeableColumnsWidget) WidgetWidths(size gowid.IRenderSize, focus gowid.Selector, focusIdx int, app gowid.IApp) []int {
widths := w.Widget.WidgetWidths(size, focus, focusIdx, app)
addme := w.offset
if widths[0]+addme < 0 {
addme = -widths[0]
} else if widths[2]-addme < 0 {
addme = widths[2]
}
widths[0] += addme
widths[2] -= addme
return widths
}
func (w *ResizeableColumnsWidget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas {
return columns.Render(w, size, focus, app)
}
func (w *ResizeableColumnsWidget) RenderSubWidgets(size gowid.IRenderSize, focus gowid.Selector, focusIdx int, app gowid.IApp) []gowid.ICanvas {
return columns.RenderSubWidgets(w, size, focus, focusIdx, app)
}
func (w *ResizeableColumnsWidget) RenderedSubWidgetsSizes(size gowid.IRenderSize, focus gowid.Selector, focusIdx int, app gowid.IApp) []gowid.IRenderBox {
return columns.RenderedSubWidgetsSizes(w, size, focus, focusIdx, app)
}
func (w *ResizeableColumnsWidget) SubWidgetSize(size gowid.IRenderSize, newX int, sub gowid.IWidget, dim gowid.IWidgetDimension) gowid.IRenderSize {
return w.Widget.SubWidgetSize(size, newX, sub, dim)
}
//======================================================================
type ResizeablePileWidget struct {
*pile.Widget
offset int
}
func NewResizeablePile(widgets []gowid.IContainerWidget) *ResizeablePileWidget {
res := &ResizeablePileWidget{}
res.Widget = pile.New(widgets)
return res
}
type PileAdjuster struct {
widget *ResizeablePileWidget
origSizer pile.IPileBoxMaker
}
func (f PileAdjuster) MakeBox(w gowid.IWidget, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.IRenderBox {
adjustedSize := size
var box gowid.RenderBox
isbox := false
switch s2 := size.(type) {
case gowid.IRenderBox:
box.C = s2.BoxColumns()
box.R = s2.BoxRows()
isbox = true
}
i := 0
for ; i < len(f.widget.SubWidgets()); i++ {
if w == f.widget.SubWidgets()[i] {
break
}
}
if i == len(f.widget.SubWidgets()) {
panic("Unexpected pile state!")
}
if isbox {
switch i {
case 0:
if box.R+f.widget.offset < 0 {
f.widget.offset = -box.R
}
box.R += f.widget.offset
case 2:
if box.R-f.widget.offset < 0 {
f.widget.offset = box.R
}
box.R -= f.widget.offset
}
adjustedSize = box
}
return f.origSizer.MakeBox(w, adjustedSize, focus, app)
}
func (w *ResizeablePileWidget) FindNextSelectable(dir gowid.Direction, wrap bool) (int, bool) {
return gowid.FindNextSelectableFrom(w, w.Focus(), dir, wrap)
}
func (w *ResizeablePileWidget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool {
return pile.UserInput(w, ev, size, focus, app)
}
func (w *ResizeablePileWidget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas {
return pile.Render(w, size, focus, app)
}
func (w *ResizeablePileWidget) RenderedSubWidgetsSizes(size gowid.IRenderSize, focus gowid.Selector, focusIdx int, app gowid.IApp) []gowid.IRenderBox {
res, _ := pile.RenderedChildrenSizes(w, size, focus, focusIdx, app)
return res
}
func (w *ResizeablePileWidget) RenderSubWidgets(size gowid.IRenderSize, focus gowid.Selector, focusIdx int, app gowid.IApp) []gowid.ICanvas {
return pile.RenderSubwidgets(w, size, focus, focusIdx, app)
}
func (w *ResizeablePileWidget) RenderBoxMaker(size gowid.IRenderSize, focus gowid.Selector, focusIdx int, app gowid.IApp, sizer pile.IPileBoxMaker) ([]gowid.IRenderBox, []gowid.IRenderSize) {
x := &PileAdjuster{
widget: w,
origSizer: sizer,
}
return pile.RenderBoxMaker(w, size, focus, focusIdx, app, x)
}
//======================================================================
var app *gowid.App
var cols *ResizeableColumnsWidget
var pilew *ResizeablePileWidget
var twidgets []*terminal.Widget
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
handled := false
if evk, ok := ev.(*tcell.EventKey); ok {
switch evk.Key() {
case tcell.KeyCtrlC, tcell.KeyEsc:
handled = true
for _, t := range twidgets {
t.Signal(syscall.SIGINT)
}
case tcell.KeyCtrlBackslash:
handled = true
for _, t := range twidgets {
t.Signal(syscall.SIGQUIT)
}
case tcell.KeyRune:
handled = true
switch evk.Rune() {
case '>':
cols.offset += 1
case '<':
cols.offset -= 1
case '+':
pilew.offset += 1
case '-':
pilew.offset -= 1
default:
handled = false
}
}
}
return handled
}
//======================================================================
func main() {
var err error
f := examples.RedirectLogger("terminal.log")
defer f.Close()
palette := gowid.Palette{
"invred": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed),
"invblue": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorCyan),
"line": gowid.MakeStyledPaletteEntry(gowid.NewUrwidColor("black"), gowid.NewUrwidColor("light gray"), gowid.StyleBold),
}
hkDuration := terminal.HotKeyDuration{time.Second * 3}
twidgets = make([]*terminal.Widget, 0)
//foo := os.Env()
os.Open("foo")
tcommands := []string{
os.Getenv("SHELL"),
os.Getenv("SHELL"),
os.Getenv("SHELL"),
//"less cell.go",
//"vttest",
//"emacs -nw -q ./cell.go",
}
for _, cmd := range tcommands {
tapp, err := terminal.NewExt(terminal.Options{
Command: strings.Split(cmd, " "),
HotKeyPersistence: &hkDuration,
Scrollback: 100,
Scrollbar: true,
EnableBracketedPaste: true,
HotKeyFns: []terminal.HotKeyInputFn{
func(ev *tcell.EventKey, w terminal.IWidget, app gowid.IApp) bool {
if w2, ok := w.(terminal.IScrollbar); ok {
if ev.Key() == tcell.KeyRune && ev.Rune() == 's' {
if w2.ScrollbarEnabled() {
w2.DisableScrollbar(app)
} else {
w2.EnableScrollbar(app)
}
return true
}
}
return false
},
},
})
if err != nil {
panic(err)
}
twidgets = append(twidgets, tapp)
}
tw := text.New(" Terminal Demo ")
twir := styled.New(tw, gowid.MakePaletteRef("invred"))
twib := styled.New(tw, gowid.MakePaletteRef("invblue"))
twp := holder.New(tw)
vline := styled.New(fill.New('│'), gowid.MakePaletteRef("line"))
hline := styled.New(fill.New('⎯'), gowid.MakePaletteRef("line"))
pilew = NewResizeablePile([]gowid.IContainerWidget{
&gowid.ContainerWidget{twidgets[1], gowid.RenderWithWeight{1}},
&gowid.ContainerWidget{hline, gowid.RenderWithUnits{U: 1}},
&gowid.ContainerWidget{twidgets[2], gowid.RenderWithWeight{1}},
})
cols = NewResizeableColumns([]gowid.IContainerWidget{
&gowid.ContainerWidget{twidgets[0], gowid.RenderWithWeight{3}},
&gowid.ContainerWidget{vline, gowid.RenderWithUnits{U: 1}},
&gowid.ContainerWidget{pilew, gowid.RenderWithWeight{1}},
})
view := framed.New(cols, framed.Options{
Frame: framed.UnicodeFrame,
TitleWidget: twp,
})
for _, t := range twidgets {
t.OnProcessExited(gowid.WidgetCallback{"cb",
func(app gowid.IApp, w gowid.IWidget) {
app.Quit()
},
})
t.OnBell(gowid.WidgetCallback{"cb",
func(app gowid.IApp, w gowid.IWidget) {
twp.SetSubWidget(twir, app)
timer := time.NewTimer(time.Millisecond * 800)
go func() {
<-timer.C
app.Run(gowid.RunFunction(func(app gowid.IApp) {
twp.SetSubWidget(tw, app)
}))
}()
},
})
t.OnSetTitle(gowid.WidgetCallback{"cb",
func(app gowid.IApp, w gowid.IWidget) {
w2 := w.(*terminal.Widget)
tw.SetText(" "+w2.GetTitle()+" ", app)
},
})
t.OnHotKey(gowid.WidgetCallback{"cb",
func(app gowid.IApp, w gowid.IWidget) {
w2 := w.(*terminal.Widget)
if w2.HotKeyActive() {
twp.SetSubWidget(twib, app)
} else {
twp.SetSubWidget(tw, app)
}
},
})
}
app, err = gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
Log: log.StandardLogger(),
EnableBracketedPaste: true,
})
examples.ExitOnErr(err)
app.MainLoop(handler{})
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-tree/ 0000775 0000000 0000000 00000000000 14262344540 0016515 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-tree/tree.go 0000664 0000000 0000000 00000014572 14262344540 0020014 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
// A demonstration of gowid's tree widget.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/gwutil"
"github.com/gcla/gowid/widgets/button"
"github.com/gcla/gowid/widgets/checkbox"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/list"
"github.com/gcla/gowid/widgets/palettemap"
"github.com/gcla/gowid/widgets/selectable"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/tree"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
var pos *tree.TreePos
var tb *list.Widget
var parent1 *tree.Collapsible
var walker *tree.TreeWalker
//======================================================================
func MakeDemoDecoration(pos tree.IPos, tr tree.IModel, wmaker tree.IWidgetMaker) gowid.IWidget {
var res gowid.IWidget
level := -1
for cur := pos; cur != nil; cur = tree.ParentPosition(cur) {
level += 1
}
pad := gwutil.StringOfLength(' ', level*10)
cwidgets := make([]gowid.IContainerWidget, 0)
cwidgets = append(cwidgets, &gowid.ContainerWidget{text.New(pad), gowid.RenderWithUnits{U: len(pad)}})
if ct, ok := tr.(tree.ICollapsible); ok {
var bn *button.Widget
if ct.IsCollapsed() {
bn = button.New(text.New("+"))
} else {
bn = button.New(text.New("-"))
}
// If I use one button with conditional logic in the callback, rather than make
// a separate button depending on whether or not the tree is collapsed, it will
// correctly work when the DecoratorMaker is caching the widgets i.e. it will
// collapse or expand even when the widget is rendered from the cache
bn.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
// Run this outside current event loop because we are implicitly
// adjusting the data structure behind the list walker, and it's
// not prepared to handle that in the same pass of processing
// UserInput. TODO.
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ct.SetCollapsed(app, !ct.IsCollapsed())
}))
}})
cwidgets = append(cwidgets, &gowid.ContainerWidget{
framed.NewUnicode(
styled.NewExt(
bn,
gowid.MakePaletteRef("body"),
gowid.MakePaletteRef("fbody"),
),
),
gowid.RenderFixed{},
})
}
inner := wmaker.MakeWidget(pos, tr)
cwidgets = append(cwidgets, &gowid.ContainerWidget{inner, gowid.RenderFixed{}})
res = palettemap.New(
columns.New(cwidgets),
palettemap.Map{"body": "fbody"},
palettemap.Map{},
)
return res
}
func MakeDemoWidget(pos tree.IPos, tr tree.IModel) gowid.IWidget {
var res gowid.IWidget
cbox := checkbox.New(false)
cbox.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
log.Info("Clicked checkbox in tree")
}})
res = columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{
framed.NewUnicode(cbox),
gowid.RenderFixed{},
},
&gowid.ContainerWidget{
styled.NewExt(
framed.NewUnicode(
selectable.New(
text.NewFromContent(
text.NewContent(
[]text.ContentSegment{
text.StyledContent(
fmt.Sprintf("tr %s:%v", tr.Leaf(), pos.String()),
gowid.MakePaletteRef("body"),
),
},
),
),
),
),
gowid.MakePaletteRef("body"),
gowid.MakePaletteRef("fbody"),
),
gowid.RenderFixed{},
},
})
return res
}
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
handled := false
if evk, ok := ev.(*tcell.EventKey); ok {
handled = true
if evk.Key() == tcell.KeyCtrlC || evk.Rune() == 'q' || evk.Rune() == 'Q' {
app.Quit()
} else if evk.Rune() == 'x' {
f := walker.Focus()
f2 := f.(tree.IPos)
s := f2.GetSubStructure(parent1)
if t2, ok := s.(tree.ICollapsible); ok {
t2.SetCollapsed(app, true)
}
} else if evk.Rune() == 'z' {
f := walker.Focus()
f2 := f.(tree.IPos)
s := f2.GetSubStructure(parent1)
if t2, ok := s.(tree.ICollapsible); ok {
t2.SetCollapsed(app, false)
}
} else {
handled = false
}
}
return handled
}
//======================================================================
func main() {
f := examples.RedirectLogger("tree1.log")
defer f.Close()
palette := gowid.Palette{
"title": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack),
"key": gowid.MakePaletteEntry(gowid.ColorCyan, gowid.ColorBlack),
"foot": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack),
"body": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorCyan),
"fbody": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack),
}
body := gowid.MakePaletteRef("body")
leaf1 := tree.NewTree("leaf1", []tree.IModel{})
leaf2 := tree.NewTree("leaf2", []tree.IModel{})
leaf3 := tree.NewTree("leaf3", []tree.IModel{})
leaf4 := tree.NewTree("leaf4", []tree.IModel{})
leaf5 := tree.NewTree("leaf5", []tree.IModel{})
leaf21 := tree.NewTree("leaf21", []tree.IModel{})
leaf22 := tree.NewTree("leaf22", []tree.IModel{})
leaf23 := tree.NewTree("leaf23", []tree.IModel{})
stree1 := tree.NewCollapsible("stree1", []tree.IModel{leaf4, leaf5})
stree2 := tree.NewCollapsible("stree2", []tree.IModel{leaf21, leaf22, leaf23})
parent1 = tree.NewCollapsible("parent1", []tree.IModel{leaf1, stree1, leaf2, stree2, leaf3})
parent1.AddOnExpanded("exp", tree.ExpandedFunction(func(app gowid.IApp) {
ch := parent1.GetChildren()
newLeaf := tree.NewTree("foo", []tree.IModel{})
parent1.SetChildren(append([]tree.IModel{newLeaf}, ch...))
}))
pos = tree.NewPos()
walker = tree.NewWalker(parent1, pos,
tree.NewCachingMaker(tree.WidgetMakerFunction(MakeDemoWidget)),
tree.NewCachingDecorator(tree.DecoratorFunction(MakeDemoDecoration)))
tb = tree.New(walker)
tb.OnFocusChanged(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
log.Infof("Focus changed - widget is now %v", w)
}})
view := styled.New(tb, body)
app, err := gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.MainLoop(handler{})
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-tutorial1/ 0000775 0000000 0000000 00000000000 14262344540 0017502 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-tutorial1/tutorial1.go 0000664 0000000 0000000 00000001251 14262344540 0021754 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// The first example from the gowid tutorial.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/text"
)
//======================================================================
func main() {
txt := text.New("hello world")
app, err := gowid.NewApp(gowid.AppArgs{View: txt})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-tutorial2/ 0000775 0000000 0000000 00000000000 14262344540 0017503 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-tutorial2/tutorial2.go 0000664 0000000 0000000 00000002001 14262344540 0021750 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// The second example from the gowid tutorial.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/text"
tcell "github.com/gdamore/tcell/v2"
)
//======================================================================
var txt *text.Widget
func unhandled(app gowid.IApp, ev interface{}) bool {
if evk, ok := ev.(*tcell.EventKey); ok {
switch evk.Rune() {
case 'q', 'Q':
app.Quit()
default:
txt.SetText(fmt.Sprintf("hello world - %c", evk.Rune()), app)
}
}
return true
}
func main() {
txt = text.New("hello world")
app, err := gowid.NewApp(gowid.AppArgs{View: txt})
examples.ExitOnErr(err)
app.MainLoop(gowid.UnhandledInputFunc(unhandled))
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-tutorial3/ 0000775 0000000 0000000 00000000000 14262344540 0017504 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-tutorial3/tutorial3.go 0000664 0000000 0000000 00000002600 14262344540 0021757 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// The third example from the gowid tutorial.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
)
//======================================================================
func main() {
palette := gowid.Palette{
"banner": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.NewUrwidColor("light gray")),
"streak": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed),
"bg": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorDarkBlue),
}
txt := text.NewFromContentExt(
text.NewContent([]text.ContentSegment{
text.StyledContent("hello world", gowid.MakePaletteRef("banner")),
}), text.Options{
Align: gowid.HAlignMiddle{},
})
map1 := styled.New(txt, gowid.MakePaletteRef("streak"))
vert := vpadding.New(map1, gowid.VAlignMiddle{}, gowid.RenderFlow{})
map2 := styled.New(vert, gowid.MakePaletteRef("bg"))
app, err := gowid.NewApp(gowid.AppArgs{
View: map2,
Palette: palette,
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-tutorial4/ 0000775 0000000 0000000 00000000000 14262344540 0017505 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-tutorial4/tutorial4.go 0000664 0000000 0000000 00000002452 14262344540 0021766 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// The fourth example from the gowid tutorial.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/text"
tcell "github.com/gdamore/tcell/v2"
)
//======================================================================
type QuestionBox struct {
gowid.IWidget
}
func (w *QuestionBox) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool {
res := true
if evk, ok := ev.(*tcell.EventKey); ok {
switch evk.Key() {
case tcell.KeyEnter:
w.IWidget = text.New(fmt.Sprintf("Nice to meet you, %s.\n\nPress Q to exit.", w.IWidget.(*edit.Widget).Text()))
default:
res = w.IWidget.UserInput(ev, size, focus, app)
}
}
return res
}
func main() {
edit := edit.New(edit.Options{Caption: "What is your name?\n"})
qb := &QuestionBox{edit}
app, err := gowid.NewApp(gowid.AppArgs{View: qb})
examples.ExitOnErr(err)
app.MainLoop(gowid.UnhandledInputFunc(gowid.HandleQuitKeys))
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-tutorial5/ 0000775 0000000 0000000 00000000000 14262344540 0017506 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-tutorial5/tutorial5.go 0000664 0000000 0000000 00000003251 14262344540 0021766 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// The fifth example from the gowid tutorial.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/button"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
)
//======================================================================
func main() {
ask := edit.New(edit.Options{Caption: "What is your name?\n"})
reply := text.New("")
btn := button.New(text.New("Exit"))
sbtn := styled.New(btn, gowid.MakeStyledAs(gowid.StyleReverse))
div := divider.NewBlank()
btn.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
app.Quit()
}})
ask.OnTextSet(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
if ask.Text() == "" {
reply.SetText("", app)
} else {
reply.SetText(fmt.Sprintf("Nice to meet you, %s", ask.Text()), app)
}
}})
f := gowid.RenderFlow{}
view := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{IWidget: ask, D: f},
&gowid.ContainerWidget{IWidget: div, D: f},
&gowid.ContainerWidget{IWidget: reply, D: f},
&gowid.ContainerWidget{IWidget: div, D: f},
&gowid.ContainerWidget{IWidget: sbtn, D: f},
})
app, err := gowid.NewApp(gowid.AppArgs{View: view})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-tutorial6/ 0000775 0000000 0000000 00000000000 14262344540 0017507 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-tutorial6/tutorial6.go 0000664 0000000 0000000 00000004336 14262344540 0021775 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// The sixth example from the gowid tutorial.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/list"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/text"
tcell "github.com/gdamore/tcell/v2"
)
//======================================================================
func question() *pile.Widget {
return pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{
IWidget: edit.New(edit.Options{Caption: "What is your name?\n"}),
D: gowid.RenderFlow{},
},
})
}
func answer(name string) *gowid.ContainerWidget {
return &gowid.ContainerWidget{
IWidget: text.New(fmt.Sprintf("Nice to meet you, %s", name)),
D: gowid.RenderFlow{},
}
}
type ConversationWidget struct {
*list.Widget
}
func NewConversationWidget() *ConversationWidget {
widgets := make([]gowid.IWidget, 1)
widgets[0] = question()
lb := list.New(list.NewSimpleListWalker(widgets))
return &ConversationWidget{lb}
}
func (w *ConversationWidget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool {
res := false
if evk, ok := ev.(*tcell.EventKey); ok && evk.Key() == tcell.KeyEnter {
res = true
focus := w.Walker().Focus()
curw := w.Walker().At(focus)
focusPile := curw.(*pile.Widget)
pileSubWidgets := focusPile.SubWidgets()
ed := pileSubWidgets[0].(*gowid.ContainerWidget).SubWidget().(*edit.Widget)
focusPile.SetSubWidgets(append(pileSubWidgets[0:1], answer(ed.Text())), app)
walker := w.Widget.Walker().(*list.SimpleListWalker)
walker.Widgets = append(walker.Widgets, question())
nextPos := walker.Next(focus)
walker.SetFocus(nextPos, app)
w.Widget.GoToBottom(app)
} else {
res = w.Widget.UserInput(ev, size, focus, app)
}
return res
}
func main() {
app, err := gowid.NewApp(gowid.AppArgs{View: NewConversationWidget()})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-widgets1/ 0000775 0000000 0000000 00000000000 14262344540 0017305 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-widgets1/widgets1.go 0000664 0000000 0000000 00000013061 14262344540 0021364 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A gowid test app which exercises the pile, button, edit and progress widgets.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/gwutil"
"github.com/gcla/gowid/widgets/button"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/holder"
"github.com/gcla/gowid/widgets/palettemap"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/progress"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
)
//======================================================================
// An example of how to override
type PBWidget struct {
*progress.Widget
}
func NewPB() *PBWidget {
return &PBWidget{progress.New(progress.Options{
Normal: gowid.MakeEmptyPalette(),
Complete: gowid.MakePaletteRef("invred"),
})}
}
func (w *PBWidget) Text() string {
cur, done := w.Progress(), w.Target()
percent := gwutil.Min(100, gwutil.Max(0, cur*100/done))
return fmt.Sprintf("At %d %% (%d/%d)", percent, cur, done)
}
func (w *PBWidget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas {
return progress.Render(w, size, focus, app)
}
//======================================================================
type handler struct{}
func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
if evk, ok := ev.(*tcell.EventKey); ok {
if evk.Key() == tcell.KeyCtrlC || evk.Rune() == 'q' || evk.Rune() == 'Q' {
app.Quit()
return true
}
}
return false
}
//======================================================================
func main() {
f := examples.RedirectLogger("widgets1.log")
defer f.Close()
styles := gowid.Palette{
"banner": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorWhite),
"streak": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed),
"bg": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorBlue),
"test1focus": gowid.MakePaletteEntry(gowid.ColorBlue, gowid.ColorBlack),
"test1notfocus": gowid.MakePaletteEntry(gowid.ColorGreen, gowid.ColorBlack),
"red": gowid.MakePaletteEntry(gowid.ColorRed, gowid.ColorBlack),
"invred": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed),
"magenta": gowid.MakePaletteEntry(gowid.ColorMagenta, gowid.ColorBlack),
"cyan": gowid.MakePaletteEntry(gowid.ColorCyan, gowid.ColorBlack),
}
flowme := gowid.RenderFlow{}
pb1 := NewPB()
nl := gowid.MakePaletteRef
mh := text.NewContent([]text.ContentSegment{
text.StyledContent("abc", nl("invred")),
text.StringContent("def"),
text.StyledContent("ghijk", nl("cyan")),
text.StyledContent("lmnopq", nl("magenta")),
})
mti := text.NewFromContent(mh)
mtj := palettemap.New(mti, palettemap.Map{}, palettemap.Map{"invred": "red"})
mt := holder.New(mti)
xt := text.New("something else")
xt2 := styled.New(xt, gowid.MakePaletteEntry(gowid.NewUrwidColor("dark red"), gowid.NewUrwidColor("light red")))
tw1 := text.New("click me█ █xx")
tw := styled.NewWithRanges(tw1,
[]styled.AttributeRange{styled.AttributeRange{0, 2, nl("test1notfocus")}}, []styled.AttributeRange{styled.AttributeRange{0, -1, nl("test1focus")}})
bw1i := button.New(tw)
bw1 := holder.New(bw1i)
dv1 := divider.NewAscii()
e1e := edit.New(edit.Options{Caption: "Name:", Text: "(1)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab(2)CDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCD(3)efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef(4)&^&^&^&^&GHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"})
e1 := vpadding.New(e1e, gowid.VAlignTop{}, gowid.RenderWithUnits{U: 2})
e2 := edit.New(edit.Options{Caption: "Password:", Text: "foobar", Mask: edit.MakeMask('*')})
e2e := edit.New(edit.Options{Caption: "Domain:", Text: "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"})
bw1i.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
pb1.SetProgress(app, pb1.Progress()+1)
if mt.SubWidget() == mti {
mt.SetSubWidget(mtj, app)
} else {
mt.SetSubWidget(mti, app)
}
}})
pw := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{pb1, flowme},
&gowid.ContainerWidget{dv1, flowme},
&gowid.ContainerWidget{bw1, flowme},
&gowid.ContainerWidget{dv1, flowme},
&gowid.ContainerWidget{e1, flowme},
&gowid.ContainerWidget{dv1, flowme},
&gowid.ContainerWidget{e2, flowme},
&gowid.ContainerWidget{dv1, flowme},
&gowid.ContainerWidget{e2e, flowme},
&gowid.ContainerWidget{dv1, flowme},
&gowid.ContainerWidget{mt, flowme},
&gowid.ContainerWidget{dv1, flowme},
&gowid.ContainerWidget{xt2, flowme},
})
twi := styled.New(text.New(" widgets1 "), gowid.MakePaletteRef("magenta"))
params := framed.Options{
TitleWidget: twi,
}
fw1 := framed.New(pw, params)
fw := framed.NewUnicode(fw1)
pw2 := vpadding.New(fw, gowid.VAlignMiddle{}, gowid.RenderFlow{})
app, err := gowid.NewApp(gowid.AppArgs{
View: pw2,
Palette: &styles,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.MainLoop(handler{})
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-widgets2/ 0000775 0000000 0000000 00000000000 14262344540 0017306 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-widgets2/widgets2.go 0000664 0000000 0000000 00000004651 14262344540 0021373 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A gowid test app which exercises the columns, checkbox, edit and styled widgets.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/checkbox"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/fill"
"github.com/gcla/gowid/widgets/selectable"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
log "github.com/sirupsen/logrus"
)
//======================================================================
func main() {
f := examples.RedirectLogger("widgets2.log")
defer f.Close()
palette := gowid.Palette{
"regular": gowid.MakePaletteEntry(gowid.ColorDefault, gowid.ColorDefault),
"focus": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorWhite),
}
text1 := text.New("hello1")
text2 := text.New("hello2 yahoo foo another one bar will it wrap")
text3 := text.New("cols==7")
text4 := styled.NewExt(text.New("len4"), gowid.MakePaletteRef("regular"), gowid.MakePaletteRef("focus"))
text5 := selectable.New(text4)
edit2 := edit.New(edit.Options{Caption: "Pass:", Text: "foobar", Mask: edit.MakeMask('*')})
edit3 := edit.New(edit.Options{Caption: "E3:", Text: "something"})
cb1 := checkbox.New(true)
div1 := fill.New('|')
fixed := gowid.RenderFixed{}
units7 := gowid.RenderWithUnits{U: 7}
weight1 := gowid.RenderWithWeight{1}
units1 := gowid.RenderWithUnits{U: 1}
c1 := columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{text1, weight1},
&gowid.ContainerWidget{div1, units1},
&gowid.ContainerWidget{cb1, fixed},
&gowid.ContainerWidget{div1, units1},
&gowid.ContainerWidget{edit2, units7},
&gowid.ContainerWidget{div1, units1},
&gowid.ContainerWidget{text2, weight1},
&gowid.ContainerWidget{div1, units1},
&gowid.ContainerWidget{edit3, weight1},
&gowid.ContainerWidget{div1, units1},
&gowid.ContainerWidget{text5, fixed},
&gowid.ContainerWidget{div1, units1},
&gowid.ContainerWidget{text3, units7},
})
app, err := gowid.NewApp(gowid.AppArgs{
View: c1,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-widgets3/ 0000775 0000000 0000000 00000000000 14262344540 0017307 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-widgets3/widgets3.go 0000664 0000000 0000000 00000012267 14262344540 0021377 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A gowid test app which exercises the button, grid, progress and radio widgets.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/button"
"github.com/gcla/gowid/widgets/checkbox"
"github.com/gcla/gowid/widgets/clicktracker"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/divider"
"github.com/gcla/gowid/widgets/grid"
"github.com/gcla/gowid/widgets/holder"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/radio"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
log "github.com/sirupsen/logrus"
)
//======================================================================
func main() {
f := examples.RedirectLogger("widgets3.log")
defer f.Close()
styles := gowid.Palette{
"streak": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed),
"test1focus": gowid.MakePaletteEntry(gowid.ColorBlue, gowid.ColorBlack),
"test1notfocus": gowid.MakePaletteEntry(gowid.ColorGreen, gowid.ColorBlack),
"test2focus": gowid.MakePaletteEntry(gowid.ColorMagenta, gowid.ColorBlack),
"test2notfocus": gowid.MakePaletteEntry(gowid.ColorCyan, gowid.ColorBlack),
}
text1 := text.New("something")
text2 := styled.NewWithRanges(text1,
[]styled.AttributeRange{styled.AttributeRange{0, 2, gowid.MakePaletteRef("test1notfocus")}}, []styled.AttributeRange{styled.AttributeRange{0, -1, gowid.MakePaletteRef("test1focus")}})
text3 := styled.NewWithRanges(text2,
[]styled.AttributeRange{styled.AttributeRange{0, 4, gowid.MakePaletteRef("test2notfocus")}}, []styled.AttributeRange{styled.AttributeRange{0, -1, gowid.MakePaletteRef("test2focus")}})
dv1 := divider.NewAscii()
bw1 := clicktracker.New(
button.NewDecorated(
text3,
button.Decoration{"[==", "==]"},
),
)
bw2 := vpadding.New(bw1, gowid.VAlignMiddle{}, gowid.RenderWithUnits{U: 10})
fixed := gowid.RenderFixed{}
flow := gowid.RenderFlow{}
cb1 := checkbox.NewDecorated(false,
checkbox.Decoration{button.Decoration{"[[", "]]"}, " X "})
cbt1 := text.New(" Are you sure?")
cols1 := columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{cb1, fixed},
&gowid.ContainerWidget{cbt1, fixed},
})
cb1.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
if _, ok := w.(*checkbox.Widget); !ok {
panic("Widget was unexpected type!")
}
log.Infof("Checkbox clicked!")
}})
rbgroup := make([]radio.IWidget, 0)
rb1 := radio.New(&rbgroup)
rbt1 := text.New(" option1 ")
rb2 := radio.New(&rbgroup)
rbt2 := text.New(" option2 ")
rb3 := radio.New(&rbgroup)
rbt3 := text.New(" option3 ")
c2cols := []gowid.IContainerWidget{
&gowid.ContainerWidget{rb1, fixed},
&gowid.ContainerWidget{rbt1, fixed},
&gowid.ContainerWidget{rb2, fixed},
&gowid.ContainerWidget{rbt2, fixed},
&gowid.ContainerWidget{rb3, fixed},
&gowid.ContainerWidget{rbt3, fixed},
}
cols2 := columns.New(c2cols)
text4 := text.New("abcde")
text4h := holder.New(text4)
text4s := styled.NewWithRanges(text4h,
[]styled.AttributeRange{styled.AttributeRange{0, -1, gowid.MakePaletteRef("test1notfocus")}}, []styled.AttributeRange{styled.AttributeRange{0, -1, gowid.MakePaletteRef("streak")}})
text4btn := button.New(text4s)
gfwids := []gowid.IWidget{text4btn, text4btn, text4btn, text4btn, text4btn, text4btn, text4btn, text4btn}
grid1 := grid.New(gfwids, 20, 3, 1, gowid.HAlignMiddle{})
bw1.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
m := text1.Content()
m.AddAt(m.Length(), text.StringContent("x"))
rb := radio.New(&rbgroup)
cols2.SetSubWidgets(append(cols2.SubWidgets(),
&gowid.ContainerWidget{
IWidget: rb,
D: fixed,
}), app)
}})
text4btn.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
text4h.IWidget = text.New("edcba")
}})
rb1.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
if _, ok := w.(*radio.Widget); !ok {
panic("Widget was unexpected type!")
}
log.Infof("Radio button 1 checked/unchecked!")
}})
rb3.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) {
if _, ok := w.(*radio.Widget); !ok {
panic("Widget was unexpected type!")
}
log.Infof("Radio button 3 checked/unchecked!")
}})
pw := pile.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{text3, fixed},
&gowid.ContainerWidget{dv1, flow},
&gowid.ContainerWidget{bw2, fixed},
&gowid.ContainerWidget{dv1, flow},
&gowid.ContainerWidget{cols1, fixed},
&gowid.ContainerWidget{dv1, flow},
&gowid.ContainerWidget{cols2, fixed},
&gowid.ContainerWidget{dv1, flow},
&gowid.ContainerWidget{grid1, flow},
&gowid.ContainerWidget{dv1, flow},
})
pw2 := vpadding.New(pw, gowid.VAlignMiddle{}, gowid.RenderFlow{})
app, err := gowid.NewApp(gowid.AppArgs{
View: pw2,
Palette: &styles,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-widgets4/ 0000775 0000000 0000000 00000000000 14262344540 0017310 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-widgets4/widgets4.go 0000664 0000000 0000000 00000007001 14262344540 0021367 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A gowid test app which exercises the columns, list and framed widgets.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/list"
"github.com/gcla/gowid/widgets/palettemap"
"github.com/gcla/gowid/widgets/selectable"
"github.com/gcla/gowid/widgets/text"
"github.com/gcla/gowid/widgets/vpadding"
log "github.com/sirupsen/logrus"
)
//======================================================================
func main() {
f := examples.RedirectLogger("widgets4.log")
defer f.Close()
styles := gowid.Palette{
"red": gowid.MakePaletteEntry(gowid.ColorRed, gowid.ColorBlack),
"invred": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed),
}
widgets := make([]gowid.IWidget, 0)
widgets2 := make([]gowid.IWidget, 0)
widgets3 := make([]gowid.IWidget, 0)
widgets4 := make([]gowid.IWidget, 0)
wid8 := gowid.RenderWithUnits{U: 8}
wid10 := gowid.RenderWithUnits{U: 10}
wid12 := gowid.RenderWithUnits{U: 12}
wid14 := gowid.RenderWithUnits{U: 14}
nl := gowid.MakePaletteRef
for i := 0; i < 23; i++ {
t := text.NewContent([]text.ContentSegment{
text.StyledContent(fmt.Sprintf("abc%dd", i), nl("invred")),
})
mt := text.NewFromContent(t)
mta := selectable.New(palettemap.New(mt, palettemap.Map{}, palettemap.Map{"invred": "red"}))
widgets = append(widgets, mta)
t2 := text.NewContent([]text.ContentSegment{
text.StyledContent(fmt.Sprintf("abc%ddefghi", i), nl("invred")),
})
mt2 := text.NewFromContent(t2)
mta2 := selectable.New(palettemap.New(mt2, palettemap.Map{}, palettemap.Map{"invred": "red"}))
widgets2 = append(widgets2, mta2)
t3 := text.NewContent([]text.ContentSegment{
text.StyledContent(fmt.Sprintf("%d%d%d-1-2-3-4-5-6-7-8-9-10-11-12-13-14-abcdefghijklmn", i, i, i), nl("invred")),
})
mt3 := text.NewFromContent(t3)
mta3 := selectable.New(palettemap.New(mt3, palettemap.Map{}, palettemap.Map{"invred": "red"}))
widgets3 = append(widgets3, mta3)
t4 := text.NewContent([]text.ContentSegment{
text.StyledContent(fmt.Sprintf("%d%d%d-1-2-3-4-5-6-7-8-9-10-11-12-13-14-abcdefghijklmn", i, i, i), nl("invred")),
})
mt4 := text.NewFromContent(t4)
mta4 := selectable.New(palettemap.New(mt4, palettemap.Map{}, palettemap.Map{"invred": "red"}))
widgets4 = append(widgets4, mta4)
}
walker := list.NewSimpleListWalker(widgets)
lb := list.New(walker)
lbb := vpadding.NewBox(lb, 7)
fr := framed.New(lbb)
walker2 := list.NewSimpleListWalker(widgets2)
lb2 := list.New(walker2)
lbb2 := vpadding.NewBox(lb2, 7)
fr2 := framed.New(lbb2)
walker3 := list.NewSimpleListWalker(widgets3)
lb3 := list.New(walker3)
lbb3 := vpadding.NewBox(lb3, 7)
fr3 := framed.New(lbb3)
walker4 := list.NewSimpleListWalker(widgets4)
lb4 := list.New(walker4)
lbb4 := vpadding.NewBox(lb4, 7)
fr4 := framed.New(lbb4)
c1 := columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{fr, wid10},
&gowid.ContainerWidget{fr2, wid12},
&gowid.ContainerWidget{fr3, wid14},
&gowid.ContainerWidget{fr4, wid8},
})
app, err := gowid.NewApp(gowid.AppArgs{
View: c1,
Palette: &styles,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-widgets5/ 0000775 0000000 0000000 00000000000 14262344540 0017311 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-widgets5/widgets5.go 0000664 0000000 0000000 00000003015 14262344540 0021372 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A gowid test app which exercises the edit and vpadding widgets.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/vpadding"
log "github.com/sirupsen/logrus"
)
//======================================================================
func main() {
f := examples.RedirectLogger("widgets5.log")
defer f.Close()
palette := gowid.Palette{}
e1e := edit.New(edit.Options{Caption: "Name:", Text: "(1)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab(2)CDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCD(3)efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef(4)&^&^&^&^&GHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ(5)sskdjfhskajfhskajfhksjadfhksjdfhksahdfksahdfkjsdhfkjsdhfkjshadfkshdf(6)87267823687268276382638263826382638263(7)xyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyx(8)ewewewewewewewewewewewewewewewewewewewew"})
e1 := vpadding.New(e1e, gowid.VAlignTop{}, gowid.RenderWithUnits{U: 4})
app, err := gowid.NewApp(gowid.AppArgs{
View: e1,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-widgets6/ 0000775 0000000 0000000 00000000000 14262344540 0017312 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-widgets6/widgets6.go 0000664 0000000 0000000 00000003750 14262344540 0021402 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A gowid test app which exercises the list, edit, columns and styled widgets.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/checkbox"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/list"
"github.com/gcla/gowid/widgets/styled"
"github.com/gcla/gowid/widgets/vpadding"
log "github.com/sirupsen/logrus"
)
//======================================================================
func main() {
f := examples.RedirectLogger("widgets6.log")
defer f.Close()
palette := gowid.Palette{
"body": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorCyan),
"fbody": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack),
}
edits := make([]gowid.IWidget, 0)
for i := 0; i < 5; i++ {
w11 := edit.New(edit.Options{Caption: fmt.Sprintf("Cap%d:", i+1), Text: "abcde"})
w1 := styled.NewExt(w11, gowid.MakePaletteRef("body"), gowid.MakePaletteRef("fbody"))
w22 := checkbox.New(false)
w2 := styled.NewExt(w22, gowid.MakePaletteRef("body"), gowid.MakePaletteRef("fbody"))
colwids := make([]gowid.IContainerWidget, 0)
colwids = append(colwids, &gowid.ContainerWidget{w1, gowid.RenderWithWeight{50}})
colwids = append(colwids, &gowid.ContainerWidget{w2, gowid.RenderFixed{}})
cols1 := columns.New(colwids)
edits = append(edits, cols1)
}
walker := list.NewSimpleListWalker(edits)
lbox := list.New(walker)
lbox2 := vpadding.NewBox(lbox, 5)
fr := framed.New(lbox2)
app, err := gowid.NewApp(gowid.AppArgs{
View: fr,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-widgets7/ 0000775 0000000 0000000 00000000000 14262344540 0017313 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-widgets7/widgets7.go 0000664 0000000 0000000 00000002343 14262344540 0021401 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A gowid test app which exercises the list, edit and framed widgets.
package main
import (
"fmt"
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/framed"
"github.com/gcla/gowid/widgets/list"
log "github.com/sirupsen/logrus"
)
//======================================================================
func main() {
f := examples.RedirectLogger("widgets7.log")
defer f.Close()
palette := gowid.Palette{}
edits := make([]gowid.IWidget, 0)
for i := 0; i < 40; i++ {
edits = append(edits, edit.New(edit.Options{Caption: fmt.Sprintf("Cap%d:", i+1), Text: "abcde1111111222222222222222223333333333444444444"}))
}
walker := list.NewSimpleListWalker(edits)
lb := list.New(walker)
fr := framed.New(lb)
app, err := gowid.NewApp(gowid.AppArgs{
View: fr,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/gowid-widgets8/ 0000775 0000000 0000000 00000000000 14262344540 0017314 5 ustar 00root root 0000000 0000000 gowid-1.4.0/examples/gowid-widgets8/widgets8.go 0000664 0000000 0000000 00000002457 14262344540 0021411 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// A gowid test app which exercises the checkbox, columns and hpadding widgets.
package main
import (
"github.com/gcla/gowid"
"github.com/gcla/gowid/examples"
"github.com/gcla/gowid/widgets/checkbox"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/hpadding"
log "github.com/sirupsen/logrus"
)
//======================================================================
func main() {
f := examples.RedirectLogger("widgets8.log")
defer f.Close()
palette := gowid.Palette{}
fixed := gowid.RenderFixed{}
cb1 := checkbox.New(false)
cp1 := hpadding.New(cb1, gowid.HAlignLeft{}, fixed)
cb2 := checkbox.New(false)
cp2 := hpadding.New(cb2, gowid.HAlignLeft{}, fixed)
view := columns.New([]gowid.IContainerWidget{
&gowid.ContainerWidget{cp1, gowid.RenderWithWeight{10}},
&gowid.ContainerWidget{cp2, gowid.RenderWithWeight{10}},
})
app, err := gowid.NewApp(gowid.AppArgs{
View: view,
Palette: &palette,
Log: log.StandardLogger(),
})
examples.ExitOnErr(err)
app.SimpleMainLoop()
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/examples/utils.go 0000664 0000000 0000000 00000001657 14262344540 0016147 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
package examples
import (
"fmt"
"os"
log "github.com/sirupsen/logrus"
)
// RedirectLogger sets the global logger to write to a file in append mode, the filename
// specified by the argument to the function. This is a convenience method used in a few
// of the example programs to avoid polluting the tty used to display the application.
func RedirectLogger(path string) *os.File {
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Error opening log file: %v", err)
}
log.SetOutput(f)
return f
}
func ExitOnErr(err error) {
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/go.mod 0000664 0000000 0000000 00000001443 14262344540 0013741 0 ustar 00root root 0000000 0000000 module github.com/gcla/gowid
go 1.13
require (
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e
github.com/creack/pty v1.1.15
github.com/gdamore/tcell/v2 v2.5.0
github.com/go-test/deep v1.0.1
github.com/guptarohit/asciigraph v0.4.1
github.com/hashicorp/golang-lru v0.5.1
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.13
github.com/pkg/errors v0.8.1
github.com/rakyll/statik v0.1.6
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.7.0
golang.org/x/text v0.3.7
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)
gowid-1.4.0/go.sum 0000664 0000000 0000000 00000013311 14262344540 0013763 0 ustar 00root root 0000000 0000000 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e h1:OjdSMCht0ZVX7IH0nTdf00xEustvbtUGRgMh3gbdmOg=
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.5.0 h1:/LA5f/wqTP5mWT79czngibKVVx5wOgdFTIXPQ68fMO8=
github.com/gdamore/tcell/v2 v2.5.0/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/guptarohit/asciigraph v0.4.1 h1:YHmCMN8VH81BIUIgTg2Fs3B52QDxNZw2RQ6j5pGoSxo=
github.com/guptarohit/asciigraph v0.4.1/go.mod h1:9fYEfE5IGJGxlP1B+w8wHFy7sNZMhPtn59f0RLtpRFM=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs=
github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5 h1:saXMvIOKvRFwbOMicHXr0B1uwoxq9dGmLe5ExMES6c4=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gowid-1.4.0/gwtest/ 0000775 0000000 0000000 00000000000 14262344540 0014146 5 ustar 00root root 0000000 0000000 gowid-1.4.0/gwtest/support_test.go 0000664 0000000 0000000 00000004450 14262344540 0017253 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
package gwtest
import (
"testing"
"github.com/gcla/gowid"
"github.com/gcla/gowid/widgets/columns"
"github.com/gcla/gowid/widgets/edit"
"github.com/gcla/gowid/widgets/pile"
"github.com/gcla/gowid/widgets/styled"
"github.com/stretchr/testify/assert"
)
func Test1(t *testing.T) {
fx := gowid.RenderFixed{}
t1 := edit.New(edit.Options{Text: "foo"})
ct1 := &gowid.ContainerWidget{IWidget: t1, D: fx}
c1 := columns.New([]gowid.IContainerWidget{ct1, ct1, ct1})
cc1 := &gowid.ContainerWidget{IWidget: c1, D: fx}
c1.SetFocus(D, 2)
assert.Equal(t, 2, c1.Focus())
c2 := columns.New([]gowid.IContainerWidget{ct1, ct1})
c2.SetFocus(D, 1)
assert.Equal(t, 1, c2.Focus())
w3 := styled.New(c2, gowid.MakeForeground(gowid.ColorBlack))
cw3 := &gowid.ContainerWidget{IWidget: w3, D: fx}
p1 := pile.New([]gowid.IContainerWidget{ct1, cc1, cw3})
assert.Equal(t, 0, p1.Focus())
assert.Equal(t, gowid.FocusPath(p1), []interface{}{0})
p1.SetFocus(D, 1)
assert.Equal(t, 1, p1.Focus())
assert.Equal(t, gowid.FocusPath(p1), []interface{}{1, 2})
p1.SetFocus(D, 2)
assert.Equal(t, 2, p1.Focus())
assert.Equal(t, gowid.FocusPath(p1), []interface{}{2, 1})
r := gowid.SetFocusPath(p1, []interface{}{0, 4, 5}, D)
assert.Equal(t, false, r.Succeeded)
assert.Equal(t, 1, r.FailedLevel)
assert.Equal(t, 0, p1.Focus())
p1.SetFocus(D, 2)
assert.Equal(t, 2, p1.Focus())
r = gowid.SetFocusPath(p1, []interface{}{0}, D)
assert.Equal(t, true, r.Succeeded)
assert.Equal(t, 0, p1.Focus())
c1.SetFocus(D, 2)
assert.Equal(t, 2, c1.Focus())
r = gowid.SetFocusPath(p1, []interface{}{1}, D)
assert.Equal(t, true, r.Succeeded)
assert.Equal(t, 1, p1.Focus())
assert.Equal(t, 2, c1.Focus())
r = gowid.SetFocusPath(p1, []interface{}{1, 0}, D)
assert.Equal(t, true, r.Succeeded)
assert.Equal(t, 1, p1.Focus())
assert.Equal(t, 0, c1.Focus())
c2.SetFocus(D, 1)
assert.Equal(t, 1, c2.Focus())
r = gowid.SetFocusPath(p1, []interface{}{2, 0}, D)
assert.Equal(t, true, r.Succeeded)
assert.Equal(t, 2, p1.Focus())
assert.Equal(t, 0, c2.Focus())
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/gwtest/testutils.go 0000664 0000000 0000000 00000014221 14262344540 0016535 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
// Package gwtest provides utilities for testing gowid widgets.
package gwtest
import (
"errors"
"testing"
"github.com/gcla/gowid"
tcell "github.com/gdamore/tcell/v2"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
var testAppData gowid.Palette
func init() {
testAppData = make(gowid.Palette)
testAppData["test1focus"] = gowid.MakePaletteEntry(gowid.ColorRed, gowid.ColorBlack)
testAppData["test1notfocus"] = gowid.MakePaletteEntry(gowid.ColorGreen, gowid.ColorBlack)
}
type testApp struct {
doQuit bool
gowid.ClickTargets
lastMouse gowid.MouseState
}
func NewTestApp() *testApp {
a := &testApp{
ClickTargets: gowid.MakeClickTargets(),
}
return a
}
var D *testApp = NewTestApp()
func ClearTestApp() {
D.DeleteClickTargets(tcell.Button1)
D.DeleteClickTargets(tcell.Button2)
D.DeleteClickTargets(tcell.Button3)
D.DeleteClickTargets(tcell.ButtonNone)
}
func (d testApp) CellStyler(name string) (gowid.ICellStyler, bool) {
x, y := testAppData[name]
return x, y
}
func (d testApp) RangeOverPalette(f func(name string, entry gowid.ICellStyler) bool) {
for k, v := range testAppData {
if !f(k, v) {
break
}
}
}
func (d testApp) Quit() {
d.doQuit = true
}
func (d testApp) Run(f gowid.IAfterRenderEvent) error {
f.RunThenRenderEvent(&d)
return nil
}
func (d testApp) GetColorMode() gowid.ColorMode {
return gowid.Mode256Colors
}
func (d testApp) GetMouseState() gowid.MouseState {
return gowid.MouseState{
MouseLeftClicked: true,
MouseMiddleClicked: false,
MouseRightClicked: false,
}
}
func (d *testApp) SetLastMouseState(m gowid.MouseState) {
d.lastMouse = m
}
func (d testApp) GetLastMouseState() gowid.MouseState {
return d.lastMouse
}
func (d testApp) InCopyMode(...bool) bool {
return false
}
func (d testApp) Log(lvl log.Level, msg string, fields ...gowid.LogField) {
panic(errors.New("Must not call!"))
}
func (d testApp) CopyModeClaimedBy(...gowid.IIdentity) gowid.IIdentity {
panic(errors.New("Must not call!"))
}
func (d testApp) RefreshCopyMode() { panic(errors.New("Must not call!")) }
func (d testApp) CopyLevel(...int) int { panic(errors.New("Must not call!")) }
func (d testApp) Clips() []gowid.ICopyResult { panic(errors.New("Must not call!")) }
func (d testApp) CopyModeClaimedAt(...int) int { panic(errors.New("Must not call!")) }
func (d testApp) RegisterMenu(m gowid.IMenuCompatible) { panic(errors.New("Must not call!")) }
func (d testApp) UnregisterMenu(m gowid.IMenuCompatible) bool { panic(errors.New("Must not call!")) }
func (d testApp) GetLog() log.StdLogger { panic(errors.New("Must not call!")) }
func (d testApp) SetLog(log.StdLogger) { panic(errors.New("Must not call!")) }
func (d testApp) ID() interface{} { panic(errors.New("Must not call!")) }
func (d testApp) GetScreen() tcell.Screen { panic(errors.New("Must not call!")) }
func (d testApp) Redraw() { panic(errors.New("Must not call!")) }
func (d testApp) Sync() { panic(errors.New("Must not call!")) }
func (d testApp) SetColorMode(gowid.ColorMode) { panic(errors.New("Must not call!")) }
func (d testApp) SetSubWidget(gowid.IWidget, gowid.IApp) { panic(errors.New("Must not call!")) }
func (d testApp) SubWidget() gowid.IWidget { panic(errors.New("Must not call!")) }
//======================================================================
type CheckBoxTester struct {
Gotit bool
}
func (f *CheckBoxTester) Changed(t gowid.IApp, w gowid.IWidget, data ...interface{}) {
f.Gotit = true
}
func (f *CheckBoxTester) ID() interface{} { return "foo" }
//======================================================================
type ButtonTester struct {
Gotit bool
}
func (f *ButtonTester) Changed(gowid.IApp, gowid.IWidget, ...interface{}) {
f.Gotit = true
}
func (f *ButtonTester) ID() interface{} { return "foo" }
//======================================================================
func RenderBoxManyTimes(t *testing.T, w gowid.IWidget, minX, maxX, minY, maxY int) {
for x := minX; x <= maxX; x++ {
for y := minY; y <= maxY; y++ {
assert.NotPanics(t, func() {
w.Render(gowid.RenderBox{C: x, R: y}, gowid.Focused, D)
})
c := w.Render(gowid.RenderBox{C: x, R: y}, gowid.Focused, D)
if c.BoxRows() > 0 {
assert.Equal(t, c.BoxColumns(), x, "foo boxcol=%v boxrows=%v x=%v y=%v", c.BoxColumns(), c.BoxRows(), x, y)
}
assert.Equal(t, c.BoxRows(), y)
}
}
}
func RenderFlowManyTimes(t *testing.T, w gowid.IWidget, minX, maxX int) {
for x := minX; x <= maxX; x++ {
assert.NotPanics(t, func() {
w.Render(gowid.RenderFlowWith{C: x}, gowid.Focused, D)
})
c := w.Render(gowid.RenderFlowWith{C: x}, gowid.Focused, D)
if c.BoxRows() > 0 {
assert.Equal(t, c.BoxColumns(), x)
}
}
}
func RenderFixedDoesNotPanic(t *testing.T, w gowid.IWidget) {
assert.NotPanics(t, func() {
w.Render(gowid.RenderFixed{}, gowid.Focused, D)
})
}
//======================================================================
//======================================================================
func ClickAt(x, y int) *tcell.EventMouse {
return tcell.NewEventMouse(x, y, tcell.Button1, 0)
}
func ClickUpAt(x, y int) *tcell.EventMouse {
return tcell.NewEventMouse(x, y, tcell.ButtonNone, 0)
}
func KeyEvent(ch rune) *tcell.EventKey {
return tcell.NewEventKey(tcell.KeyRune, ch, tcell.ModNone)
}
func CursorDown() *tcell.EventKey {
return tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone)
}
func CursorUp() *tcell.EventKey {
return tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone)
}
func CursorLeft() *tcell.EventKey {
return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone)
}
func CursorRight() *tcell.EventKey {
return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone)
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
gowid-1.4.0/gwutil/ 0000775 0000000 0000000 00000000000 14262344540 0014144 5 ustar 00root root 0000000 0000000 gowid-1.4.0/gwutil/utils.go 0000664 0000000 0000000 00000017327 14262344540 0015645 0 ustar 00root root 0000000 0000000 // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
// Package gwutil provides general-purpose utilities that are not used by
// the core of gowid but that have proved useful for several pre-canned
// widgets.
package gwutil
import (
"errors"
"fmt"
"math"
"os"
"runtime/pprof"
"sort"
log "github.com/sirupsen/logrus"
)
//======================================================================
// Min returns the smaller of >1 integer arguments.
func Min(i int, js ...int) int {
res := i
for _, j := range js {
if j < res {
res = j
}
}
return res
}
// Min returns the larger of >1 integer arguments.
func Max(i int, js ...int) int {
res := i
for _, j := range js {
if j > res {
res = j
}
}
return res
}
// LimitTo is a one-liner that uses Min and Max to bound a value. Assumes
// a <= b.
func LimitTo(a, v, b int) int {
if v < a {
return a
}
if v > b {
return b
}
return v
}
// StringOfLength returns a string consisting of n runes.
func StringOfLength(r rune, n int) string {
res := make([]rune, n)
for i := 0; i < n; i++ {
res[i] = r
}
return string(res)
}
// Map is the traditional functional map function for strings.
func Map(vs []string, f func(string) string) []string {
vsm := make([]string, len(vs))
for i, v := range vs {
vsm[i] = f(v)
}
return vsm
}
// IPow returns a raised to the bth power.
func IPow(a, b int) int {
var result int = 1
for 0 != b {
if 0 != (b & 1) {
result *= a
}
b >>= 1
a *= a
}
return result
}
// Sum is a variadic function that returns the sum of its integer arguments.
func Sum(input ...int) int {
sum := 0
for i := range input {
sum += input[i]
}
return sum
}
//======================================================================
type fract struct {
fp float64
idx int
}
type fractlist []fract
func (slice fractlist) Len() int {
return len(slice)
}
// Note > to skip the reverse
func (slice fractlist) Less(i, j int) bool {
return slice[i].fp > slice[j].fp
}
func (slice fractlist) Swap(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}
// HamiltonAllocation implements the Hamilton Method (Largest remainder method) to calculate
// integral ratios. (Like it is used in some elections.)
//
// This is shamelessly cribbed from https://excess.org/svn/urwid/contrib/trunk/rbreu_scrollbar.py
//
// counts -- list of integers ('votes per party')
// alloc -- total amount to be allocated ('total amount of seats')
//
func HamiltonAllocation(counts []int, alloc int) []int {
totalCounts := Sum(counts...)
if totalCounts == 0 {
return counts
}
res := make([]int, len(counts))
quotas := make([]float64, len(counts))
fracts := fractlist(make([]fract, len(counts)))
for i, c := range counts {
quotas[i] = (float64(c) * float64(alloc)) / float64(totalCounts)
}
for i, fp := range quotas {
_, f := math.Modf(fp)
fracts[i] = fract{fp: f, idx: i}
}
sort.Sort(fracts)
for i, fp := range quotas {
n, _ := math.Modf(fp)
res[i] = int(n)
}
remainder := alloc - Sum(res...)
for i := 0; i < remainder; i++ {
res[fracts[i].idx] += 1
}
return res
}
//======================================================================
// LStripByte returns a slice of its first argument which contains all
// bytes up to but not including its second argument.
func LStripByte(data []byte, s byte) []byte {
var i int
for i = 0; i < len(data); i++ {
if data[i] != s {
break
}
}
return data[i:]
}
//======================================================================
type IOption interface {
IsNone() bool
Value() interface{}
}
// For fmt.Stringer
func OptionString(opt IOption) string {
if opt.IsNone() {
return "None"
} else {
return fmt.Sprintf("%v", opt.Value())
}
}
//======================================================================
// IntOption is intended to represent an Option[int]
type IntOption struct {
some bool
val int
}
var _ fmt.Stringer = IntOption{}
var _ IOption = IntOption{}
func SomeInt(x int) IntOption {
return IntOption{true, x}
}
func NoneInt() IntOption {
return IntOption{}
}
func (i IntOption) IsNone() bool {
return !i.some
}
func (i IntOption) Value() interface{} {
return i.Val()
}
func (i IntOption) Val() int {
if i.IsNone() {
panic(errors.New("Called Val on empty IntOption"))
}
return i.val
}
// For fmt.Stringer
func (i IntOption) String() string {
return OptionString(i)
}
//======================================================================
// Int64Option is intended to represent an Option[int]
type Int64Option struct {
some bool
val int64
}
var _ fmt.Stringer = Int64Option{}
var _ IOption = Int64Option{}
func SomeInt64(x int64) Int64Option {
return Int64Option{true, x}
}
func NoneInt64() Int64Option {
return Int64Option{}
}
func (i Int64Option) IsNone() bool {
return !i.some
}
func (i Int64Option) Value() interface{} {
return i.Val()
}
func (i Int64Option) Val() int64 {
if i.IsNone() {
panic(errors.New("Called Val on empty Int64Option"))
}
return i.val
}
// For fmt.Stringer
func (i Int64Option) String() string {
return OptionString(i)
}
//======================================================================
// RuneOption is intended to represent an Option[rune]
type RuneOption struct {
some bool
val rune
}
var _ fmt.Stringer = RuneOption{}
var _ IOption = RuneOption{}
func SomeRune(x rune) RuneOption {
return RuneOption{true, x}
}
func NoneRune() RuneOption {
return RuneOption{}
}
func (i RuneOption) IsNone() bool {
return !i.some
}
func (i RuneOption) Value() interface{} {
return i.Val()
}
func (i RuneOption) Val() rune {
if i.IsNone() {
panic(errors.New("Called Val on empty ByteOption"))
}
return i.val
}
func (i RuneOption) String() string {
return OptionString(i)
}
//======================================================================
const float64EqualityThreshold = 1e-5
// AlmostEqual returns true if its two arguments are within 1e-5 of each other.
func AlmostEqual(a, b float64) bool {
return math.Abs(a-b) <= float64EqualityThreshold
}
// Round returns a float64 representing the closest whole number
// to the supplied float64 argument.
func Round(f float64) float64 {
if f < 0 {
return math.Ceil(f - 0.5)
} else {
return math.Floor(f + 0.5)
}
}
// RoundFloatToInt returns an int representing the closest int to the
// supplied float, rounding up or down.
func RoundFloatToInt(val float32) int {
if val < 0 {
return int(val - 0.5)
}
return int(val + 0.5)
}
//======================================================================
// If is a convenience function for mimicking a ternary operator e.g. If(x