1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-28 13:48:51 +08:00

Defining API for the Table widget.

This commit is contained in:
Jakub Sobon 2019-03-09 00:44:48 -05:00
parent e6ef3f7ed1
commit 98cd828606
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
9 changed files with 1016 additions and 0 deletions

201
widgets/table/content.go Normal file
View File

@ -0,0 +1,201 @@
package table
// content.go defines a type that allow callers to populate the table with
// content.
import (
"errors"
"github.com/mum4k/termdash/align"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/linestyle"
)
// ContentOption is used to provide options to NewContent.
type ContentOption interface {
// set sets the provided option.
set(*contentOptions)
}
// contentOptions stores options that apply to the content level.
type contentOptions struct {
border linestyle.LineStyle
borderCellOpts []cell.Option
columnWidthsPercent []int
horizontalCellSpacing int
verticalCellSpacing int
hierarchical *hierarchicalOptions
}
// hierarchicalOptions stores options that can be applied at multiple levels or
// hierarchy, i.e. the Content (top level), the Row or the Cell.
type hierarchicalOptions struct {
cellOpts []cell.Option
horizontalCellPadding *int
verticalCellPadding *int
alignHorizontal *align.Horizontal
alignVertical *align.Vertical
height int
}
// contentOption implements ContentOption.
type contentOption func(*contentOptions)
// set implements Option.set.
func (co contentOption) set(cOpts *contentOptions) {
co(cOpts)
}
// Border configures the table to have a border of the specified line style.
// Defaults to linestyle.None which means no border.
func Border(ls linestyle.LineStyle) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.border = ls
})
}
// BorderCellOpts sets cell options for the cells that contain the border.
func BorderCellOpts(cellOpts ...cell.Option) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.borderCellOpts = cellOpts
})
}
// ColumnWidthsPercent sets the widths of columns to the provided percentage.
// The number of values must match the number of Columns specified on the call
// to NewContent. All the values must be in the range 0 < v <= 100 and the sum
// of the values must be 100.
// Defaults to column width automatically adjusted to the content.
func ColumnWidthsPercent(widths ...int) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.columnWidthsPercent = widths
})
}
// HorizontalCellSpacing sets the horizontal space between cells as the number
// of cells on the terminal that are left empty.
// The value must be a non-zero positive integer.
// Defaults to zero cells.
func HorizontalCellSpacing(cells int) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.horizontalCellSpacing = cells
})
}
// VerticalCellSpacing sets the vertical space between cells as the number
// of cells on the terminal that are left empty.
// The value must be a non-zero positive integer.
// Defaults to zero cells.
func VerticalCellSpacing(cells int) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.verticalCellSpacing = cells
})
}
// ContentCellOpts sets cell options for the cells that contain the table rows
// and cells.
// This is a hierarchical option and can be overridden when provided at Row,
// Cell or Data level.
func ContentCellOpts(cellOpts ...cell.Option) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.hierarchical.cellOpts = cellOpts
})
}
// ContentRowHeight sets the height of rows to the provided number of cells.
// The number must be a non-zero positive integer.
// Defaults to row height automatically adjusted to the content.
// This is a hierarchical option and can be overridden when provided at Row
// level.
func ContentRowHeight(height int) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.hierarchical.height = height
})
}
// HorizontalCellPadding sets the horizontal space between cell wall and its
// content as the number of cells on the terminal that are left empty.
// The value must be a non-zero positive integer.
// Defaults to zero cells.
// This is a hierarchical option and can be overridden when provided at Row
// or Cell level.
func HorizontalCellPadding(cells int) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.hierarchical.horizontalCellPadding = &cells
})
}
// VerticalCellPadding sets the vertical space between cell wall and its
// content as the number of cells on the terminal that are left empty.
// The value must be a non-zero positive integer.
// Defaults to zero cells.
// This is a hierarchical option and can be overridden when provided at Row
// or Cell level.
func VerticalCellPadding(cells int) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.hierarchical.verticalCellPadding = &cells
})
}
// AlignHorizontal sets the horizontal alignment for the content.
// Defaults for left horizontal alignment.
// This is a hierarchical option and can be overridden when provided at Row
// or Cell level.
func AlignHorizontal(h align.Horizontal) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.hierarchical.alignHorizontal = &h
})
}
// AlignVertical sets the vertical alignment for the content.
// Defaults for top vertical alignment.
// This is a hierarchical option and can be overridden when provided at Row
// or Cell level.
func AlignVertical(v align.Vertical) ContentOption {
return contentOption(func(cOpts *contentOptions) {
cOpts.hierarchical.alignVertical = &v
})
}
// Columns specifies the number of columns in the table.
type Columns int
// Content is the content displayed in the table.
//
// Content is organized into rows of cells. Each cell can zero, one or multiple
// instances of text data with their own cell options.
//
// Certain options are applied hierarchically, the values provided at the
// Content level apply to all child rows and cells. Specifying a different
// value at a lower level overrides the values provided above.
//
// This object is thread-safe.
type Content struct {
colNum Columns
header *Row
rows []*Row
opts *contentOptions
}
// NewContent returns a new Content instance.
//
// The number of columns must be a non-zero positive integer.
// All rows must contain the same number of columns (the same number of cells)
// allowing for the CellColSpan option.
func NewContent(cols Columns, rows []*Row, opts ...ContentOption) (*Content, error) {
return nil, errors.New("unimplemented")
}
// AddRow adds a row to the content.
// If you need to apply options at the Row level, use AddRowWithOpts.
func (c *Content) AddRow(cells ...*Cell) error {
return errors.New("unimplemented")
}
// AddRowWithOpts adds a row to the content and applies the options.
func (c *Content) AddRowWithOpts(cells []*Cell, opts ...RowOption) error {
return errors.New("unimplemented")
}

View File

@ -0,0 +1,125 @@
package table
// content_cell.go defines a type that represents a single cell in the table.
import (
"github.com/mum4k/termdash/align"
"github.com/mum4k/termdash/cell"
)
// CellOption is used to provide options to NewCellWithOpts.
type CellOption interface {
// set sets the provided option.
set(*Cell)
}
// cellOption implements CellOption.
type cellOption func(*Cell)
// set implements Option.set.
func (co cellOption) set(c *Cell) {
co(c)
}
// CellColSpan configures the number of columns this cell spans.
// The number must be a non-zero positive integer.
// Defaults to a cell spanning just one column.
func CellColSpan(cols int) CellOption {
return cellOption(func(c *Cell) {
c.colSpan = cols
})
}
// CellRowSpan configures the number of rows this cell spans.
// The number must be a non-zero positive integer.
// Defaults to a cell spanning just one row.
func CellRowSpan(rows int) CellOption {
return cellOption(func(c *Cell) {
c.rowSpan = rows
})
}
// CellOpts sets cell options for the cells that contain the table cells and
// cells.
// This is a hierarchical option, it overrides the one provided at Content or
// Row level and can be overridden at the Data level.
func CellOpts(cellOpts ...cell.Option) CellOption {
return cellOption(func(c *Cell) {
c.hierarchical.cellOpts = cellOpts
})
}
// CellHeight sets the height of cells to the provided number of cells.
// The number must be a non-zero positive integer.
// Defaults to cell height automatically adjusted to the content.
// This is a hierarchical option, it overrides the one provided at Content or
// Row level.
func CellHeight(height int) CellOption {
return cellOption(func(c *Cell) {
c.hierarchical.height = height
})
}
// CellHorizontalCellPadding sets the horizontal space between cell wall and its
// content as the number of cells on the terminal that are left empty.
// The value must be a non-zero positive integer.
// Defaults to zero cells.
// This is a hierarchical option, it overrides the one provided at Content or
// Row level.
func CellHorizontalCellPadding(cells int) CellOption {
return cellOption(func(c *Cell) {
c.hierarchical.horizontalCellPadding = &cells
})
}
// CellVerticalCellPadding sets the vertical space between cell wall and its
// content as the number of cells on the terminal that are left empty.
// The value must be a non-zero positive integer.
// Defaults to zero cells.
// This is a hierarchical option, it overrides the one provided at Content or
// Row level.
func CellVerticalCellPadding(cells int) CellOption {
return cellOption(func(c *Cell) {
c.hierarchical.verticalCellPadding = &cells
})
}
// CellAlignHorizontal sets the horizontal alignment for the content.
// Defaults for left horizontal alignment.
// This is a hierarchical option, it overrides the one provided at Content or
// Row level.
func CellAlignHorizontal(h align.Horizontal) CellOption {
return cellOption(func(c *Cell) {
c.hierarchical.alignHorizontal = &h
})
}
// CellAlignVertical sets the vertical alignment for the content.
// Defaults for top vertical alignment.
// This is a hierarchical option, it overrides the one provided at Content or
// Row level.
func CellAlignVertical(v align.Vertical) CellOption {
return cellOption(func(c *Cell) {
c.hierarchical.alignVertical = &v
})
}
// Cell is one cell in a Row.
type Cell struct {
data []*Data
colSpan int
rowSpan int
hierarchical *hierarchicalOptions
}
// NewCell returns a new Cell with the provided text.
// If you need to apply options at the Cell or Data level use NewCellWithOpts.
func NewCell(text string) *Cell {
return nil
}
// NewCellWithOpts returns a new Cell with the provided data and options.
func NewCellWithOpts(data []*Data, opts ...CellOption) *Cell {
return nil
}

View File

@ -0,0 +1,55 @@
package table
// content_data.go defines a type that represents data within a table cell.
import (
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/internal/canvas/buffer"
)
// DataOption is used to provide options to NewDataWithOpts.
type DataOption interface {
// set sets the provided option.
set(*dataOptions)
}
// dataOptions stores the provided data options.
type dataOptions struct {
cellOpts []cell.Option
}
// dataOption implements DataOption.
type dataOption func(*dataOptions)
// set implements Option.set.
func (do dataOption) set(dOpts *dataOptions) {
do(dOpts)
}
// DataCellOpts sets options on the cells that contain the data.
func DataCellOpts(cellOpts ...cell.Option) DataOption {
return dataOption(func(dOpts *dataOptions) {
dOpts.cellOpts = cellOpts
})
}
// Data is part of (or the full) the data that is displayed inside one Cell.
type Data struct {
cells []*buffer.Cell
}
// NewData creates new Data with the provided text and applies the options.
func NewData(text string, opts ...DataOption) *Data {
dOpts := &dataOptions{}
for _, opt := range opts {
opt.set(dOpts)
}
var cells []*buffer.Cell
for _, r := range text {
cells = append(cells, buffer.NewCell(r, dOpts.cellOpts...))
}
return &Data{
cells: cells,
}
}

View File

@ -0,0 +1,154 @@
package table
// content_row.go defines a type that represents a single row in the table.
import (
"github.com/mum4k/termdash/align"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/terminal/terminalapi"
)
// RowCallbackFn is a function called when the user activates a Row.
//
// Only Rows that have the RowCallback option can be activated. A Row can be
// activated by a mouse click or by pressing any non-navigation key (see the
// NavigationKeys option) while the row is highlighted.
//
// The called function receives the event that was used to activate the Row.
// The event is either *terminalapi.Keyboard or *terminalapi.Mouse.
//
// The callback function must be light-weight, ideally just storing a value and
// returning, since more button presses might occur.
//
// The callback function must be thread-safe as the mouse or keyboard events
// that activate the row are processed in a separate goroutine.
//
// If the function returns an error, the widget will forward it back to the
// termdash infrastructure which causes a panic, unless the user provided a
// termdash.ErrorHandler.
type RowCallbackFn func(event terminalapi.Event) error
// RowOption is used to provide options to NewRowWithOpts.
type RowOption interface {
// set sets the provided option.
set(*Row)
}
// rowOption implements RowOption.
type rowOption func(*Row)
// set implements Option.set.
func (ro rowOption) set(r *Row) {
ro(r)
}
// RowHighlighted sets the row as highlighted, the user can then change which
// row is highlighted using keyboard or mouse input.
func RowHighlighted() RowOption {
return rowOption(func(r *Row) {
r.highlighted = true
})
}
// RowCallback allows this row to be activated and provides a function that
// should be called upon each row activation by the user.
func RowCallback(fn RowCallbackFn) RowOption {
return rowOption(func(r *Row) {
r.rowCallback = fn
})
}
// RowCellOpts sets cell options for the cells that contain the table rows and
// cells.
// This is a hierarchical option, it overrides the one provided at Content
// level and can be overridden when provided at the Cell or Data level.
func RowCellOpts(cellOpts ...cell.Option) RowOption {
return rowOption(func(r *Row) {
r.hierarchical.cellOpts = cellOpts
})
}
// RowHeight sets the height of rows to the provided number of cells.
// The number must be a non-zero positive integer.
// Defaults to row height automatically adjusted to the content.
// This is a hierarchical option, it overrides the one provided at Content
// level and can be overridden when provided at the Cell level.
func RowHeight(height int) RowOption {
return rowOption(func(r *Row) {
r.hierarchical.height = height
})
}
// RowHorizontalCellPadding sets the horizontal space between cell wall and its
// content as the number of cells on the terminal that are left empty.
// The value must be a non-zero positive integer.
// Defaults to zero cells.
// This is a hierarchical option, it overrides the one provided at Content
// level and can be overridden when provided at the Cell level.
func RowHorizontalCellPadding(cells int) RowOption {
return rowOption(func(r *Row) {
r.hierarchical.horizontalCellPadding = &cells
})
}
// RowVerticalCellPadding sets the vertical space between cell wall and its
// content as the number of cells on the terminal that are left empty.
// The value must be a non-zero positive integer.
// Defaults to zero cells.
// This is a hierarchical option, it overrides the one provided at Content
// level and can be overridden when provided at the Cell level.
func RowVerticalCellPadding(cells int) RowOption {
return rowOption(func(r *Row) {
r.hierarchical.verticalCellPadding = &cells
})
}
// RowAlignHorizontal sets the horizontal alignment for the content.
// Defaults for left horizontal alignment.
// This is a hierarchical option, it overrides the one provided at Content
// level and can be overridden when provided at the Cell level.
func RowAlignHorizontal(h align.Horizontal) RowOption {
return rowOption(func(r *Row) {
r.hierarchical.alignHorizontal = &h
})
}
// RowAlignVertical sets the vertical alignment for the content.
// Defaults for top vertical alignment.
// This is a hierarchical option, it overrides the one provided at Content
// level and can be overridden when provided at the Cell level.
func RowAlignVertical(v align.Vertical) RowOption {
return rowOption(func(r *Row) {
r.hierarchical.alignVertical = &v
})
}
// Row is one row in the table.
type Row struct {
cells []*Cell
rowCallback RowCallbackFn
highlighted bool
hierarchical *hierarchicalOptions
}
// NewHeader returns a new Row that will be the header of the table.
// The header remains visible while scrolling and allows for sorting of content
// based on its values. Header row cannot be highlighted.
// Content can only have one header Row.
func NewHeader(cells []*Cell, opts ...RowOption) *Row {
return nil
}
// NewRow returns a new Row instance with the provided cells.
// If you need to apply options at the Row level, use NewRowWithOpts.
// If you need to add a table header Row, use NewHeader.
func NewRow(cells ...*Cell) *Row {
return nil
}
// NewRowWithOpts returns a new Row instance with the provided cells and applies
// the row options.
func NewRowWithOpts(cells []*Cell, opts ...RowOption) *Row {
return nil
}

View File

@ -0,0 +1,53 @@
package table
func Example_DisplayDataOnly() {
rows := []*Row{
NewRow(
NewCell("hello"),
NewCell("world"),
),
NewRow(
NewCell("hello"),
NewCell("world"),
),
}
_, err := NewContent(Columns(3), rows)
if err != nil {
panic(err)
}
data := [][]string{}
myRows := []*Row{}
for _, dataRow := range data {
cells := []*Cell{}
for _, dataCol := range dataRow {
cells = append(cells, NewCell(dataCol))
}
myRows = append(myRows, NewRow(cells...))
}
}
func Example_ColorInheritance() {
}
func Example_ColAndRowSpan() {
}
func Example_TextTrimmingAndWrapping() {
}
func Example_AddRow() {
}
func Example_DeleteRow() {
}
func Example_Callback() {
}

126
widgets/table/options.go Normal file
View File

@ -0,0 +1,126 @@
// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package table
import (
"time"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/keyboard"
)
// options.go contains configurable options for Table.
// Option is used to provide options.
type Option interface {
// set sets the provided option.
set(*options)
}
// option implements Option.
type option func(*options)
// set implements Option.set.
func (o option) set(opts *options) {
o(opts)
}
// options holds the provided options.
type options struct {
keyUp keyboard.Key
keyDown keyboard.Key
keyPgUp keyboard.Key
keyPgDown keyboard.Key
disableHighlight bool
disableSorting bool
highlightDelay time.Duration
highlightCellOpts []cell.Option
}
// validate validates the provided options.
func (o *options) validate() error {
return nil
}
// newOptions returns options with the default values set.
func newOptions() *options {
return &options{
keyUp: DefaultKeyUp,
keyDown: DefaultKeyDown,
keyPgUp: DefaultKeyPageUp,
keyPgDown: DefaultKeyPageDown,
}
}
// The default keys for navigating the content.
const (
DefaultKeyUp = keyboard.KeyArrowUp
DefaultKeyDown = keyboard.KeyArrowDown
DefaultKeyPageUp = keyboard.KeyPgUp
DefaultKeyPageDown = keyboard.KeyPgDn
)
// NavigationKeys configures the keyboard keys that allow the user to navigate
// the table content.
// The provided keys must be unique, e.g. the same key cannot be both up and
// down.
func NavigationKeys(up, down, pageUp, pageDown keyboard.Key) Option {
return option(func(opts *options) {
opts.keyUp = up
opts.keyDown = down
opts.keyPgUp = pageUp
opts.keyPgDown = pageDown
})
}
// DisableHighlight disables the ability to highlight a row using keyboard or
// mouse.
// Navigation and scrolling still works, but it no longer moves the highlighted
// row, it just moves up or down a row at a time.
// The highlight functionality is enabled by default.
func DisableHighlight() Option {
return option(func(opts *options) {
opts.disableHighlight = true
})
}
// DisableSorting disables the ability to sort the content by column values.
// The sorting functionality is enabled by default.
func DisableSorting() Option {
return option(func(opts *options) {
opts.disableSorting = true
})
}
// HighlightDelay configures the time after which a highlighted row loses the
// highlight.
// This only works if the manual termdash redraw or the periodic redraw
// interval are reasonably close to this delay.
// The duration cannot be negative.
// Defaults to zero which means a highlighted row remains highlighted forever.
func HighlightDelay(d time.Duration) Option {
return option(func(opts *options) {
opts.highlightDelay = d
})
}
// HighlightCellOpts sets the cell options on cells that are part of a
// highlighted row.
// Defaults to DefaultHighlightColor.
func HighlightColor(cellOpts ...cell.Option) Option {
return option(func(opts *options) {
opts.highlightCellOpts = cellOpts
})
}

109
widgets/table/table.go Normal file
View File

@ -0,0 +1,109 @@
// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package table is a widget that displays text a table consisting of rows and
// columns.
package table
import (
"errors"
"image"
"sync"
"github.com/mum4k/termdash/internal/canvas"
"github.com/mum4k/termdash/terminal/terminalapi"
"github.com/mum4k/termdash/widgetapi"
)
// Table displays text content aligned to rows and columns.
//
// Sizes of columns and rows are automatically sized based on the content or
// specified by the caller. Each table cell supports text alignment, trimming
// or wrapping. Table data can be sorted according to values in individual
// columns.
//
// The entire table can be scrolled if it has more content that fits into the
// container. The table content can be interacted with using keyboard or mouse.
//
// Implements widgetapi.Widget. This object is thread-safe.
type Table struct {
// mu protects the widget.
mu sync.Mutex
// opts are the provided options.
opts *options
}
// New returns a new Table.
func New(opts ...Option) (*Table, error) {
opt := newOptions()
for _, o := range opts {
o.set(opt)
}
if err := opt.validate(); err != nil {
return nil, err
}
return &Table{}, nil
}
// Reset resets the table to an empty content.
func (t *Table) Reset() {
t.mu.Lock()
defer t.mu.Unlock()
}
// Write writes the provided content and replaces any content written
// previously.
// The caller is allowed to modify the content after Write, changes to content
// will become visible.
func (t *Table) Write(c *Content) error {
t.mu.Lock()
defer t.mu.Unlock()
return errors.New("unimplemented")
}
// Draw draws the Table widget onto the canvas.
// Implements widgetapi.Widget.Draw.
func (t *Table) Draw(cvs *canvas.Canvas) error {
t.mu.Lock()
defer t.mu.Unlock()
return nil
}
// Keyboard processes keyboard events.
// The Table widget uses keyboard events to highlight selected rows or columns.
// Implements widgetapi.Widget.Keyboard.
func (t *Table) Keyboard(k *terminalapi.Keyboard) error {
t.mu.Lock()
defer t.mu.Unlock()
return errors.New("unimplemented")
}
// Mouse processes mouse events.
// Mouse events are used to scroll the content and highlight rows or columns.
// Implements widgetapi.Widget.Mouse.
func (t *Table) Mouse(m *terminalapi.Mouse) error {
t.mu.Lock()
defer t.mu.Unlock()
return errors.New("unimplemented")
}
// Options implements widgetapi.Widget.Options.
func (t *Table) Options() widgetapi.Options {
return widgetapi.Options{
MinimumSize: image.Point{1, 1},
WantKeyboard: widgetapi.KeyScopeNone,
WantMouse: widgetapi.MouseScopeNone,
}
}

131
widgets/table/table_test.go Normal file
View File

@ -0,0 +1,131 @@
// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package table
import (
"image"
"testing"
"github.com/kylelemons/godebug/pretty"
"github.com/mum4k/termdash/internal/canvas"
"github.com/mum4k/termdash/internal/faketerm"
"github.com/mum4k/termdash/terminal/terminalapi"
"github.com/mum4k/termdash/widgetapi"
)
func TestTable(t *testing.T) {
tests := []struct {
desc string
opts []Option
update func(*Table) error // update gets called before drawing of the widget.
canvas image.Rectangle
want func(size image.Point) *faketerm.Terminal
wantNewErr bool
wantUpdateErr bool // whether to expect an error on a call to the update function
wantDrawErr bool
}{}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
tbl, err := New(tc.opts...)
if (err != nil) != tc.wantNewErr {
t.Errorf("New => unexpected error: %v, wantNewErr: %v", err, tc.wantNewErr)
}
if err != nil {
return
}
c, err := canvas.New(tc.canvas)
if err != nil {
t.Fatalf("canvas.New => unexpected error: %v", err)
}
if tc.update != nil {
err = tc.update(tbl)
if (err != nil) != tc.wantUpdateErr {
t.Errorf("update => unexpected error: %v, wantUpdateErr: %v", err, tc.wantUpdateErr)
}
if err != nil {
return
}
}
err = tbl.Draw(c)
if (err != nil) != tc.wantDrawErr {
t.Errorf("Draw => unexpected error: %v, wantDrawErr: %v", err, tc.wantDrawErr)
}
if err != nil {
return
}
got, err := faketerm.New(c.Size())
if err != nil {
t.Fatalf("faketerm.New => unexpected error: %v", err)
}
if err := c.Apply(got); err != nil {
t.Fatalf("Apply => unexpected error: %v", err)
}
var want *faketerm.Terminal
if tc.want != nil {
want = tc.want(c.Size())
} else {
want = faketerm.MustNew(c.Size())
}
if diff := faketerm.Diff(want, got); diff != "" {
t.Errorf("Draw => %v", diff)
}
})
}
}
func TestKeyboard(t *testing.T) {
tbl, err := New()
if err != nil {
t.Fatalf("New => unexpected error: %v", err)
}
if err := tbl.Keyboard(&terminalapi.Keyboard{}); err == nil {
t.Errorf("Keyboard => got nil err, wanted one")
}
}
func TestMouse(t *testing.T) {
tbl, err := New()
if err != nil {
t.Fatalf("New => unexpected error: %v", err)
}
if err := tbl.Mouse(&terminalapi.Mouse{}); err == nil {
t.Errorf("Mouse => got nil err, wanted one")
}
}
func TestOptions(t *testing.T) {
tbl, err := New()
if err != nil {
t.Fatalf("New => unexpected error: %v", err)
}
got := tbl.Options()
want := widgetapi.Options{
MinimumSize: image.Point{1, 1},
WantKeyboard: widgetapi.KeyScopeNone,
WantMouse: widgetapi.MouseScopeNone,
}
if diff := pretty.Compare(want, got); diff != "" {
t.Errorf("Options => unexpected diff (-want, +got):\n%s", diff)
}
}

View File

@ -0,0 +1,62 @@
// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Binary tabledemo shows the functionality of the table widget.
package main
import (
"context"
"time"
"github.com/mum4k/termdash"
"github.com/mum4k/termdash/container"
"github.com/mum4k/termdash/linestyle"
"github.com/mum4k/termdash/terminal/termbox"
"github.com/mum4k/termdash/terminal/terminalapi"
"github.com/mum4k/termdash/widgets/table"
)
func main() {
t, err := termbox.New()
if err != nil {
panic(err)
}
defer t.Close()
ctx, cancel := context.WithCancel(context.Background())
tbl, err := table.New()
if err != nil {
panic(err)
}
c, err := container.New(
t,
container.Border(linestyle.Light),
container.BorderTitle("PRESS Q TO QUIT"),
container.PlaceWidget(tbl),
)
if err != nil {
panic(err)
}
quitter := func(k *terminalapi.Keyboard) {
if k.Key == 'q' || k.Key == 'Q' {
cancel()
}
}
if err := termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(quitter), termdash.RedrawInterval(1*time.Second)); err != nil {
panic(err)
}
}