From bc42865277204ef15960d74d742b54817e9925ea Mon Sep 17 00:00:00 2001 From: Jakub Sobon Date: Tue, 27 Mar 2018 19:01:35 +0100 Subject: [PATCH] Defining the APIs. --- README.md | 2 +- canvas/canvas.go | 28 ++++++ cell/cell.go | 40 +++++++++ cell/color.go | 39 +++++++++ container/container.go | 113 ++++++++++++++++++++++++ container/container_test.go | 19 ++++ container/options.go | 169 ++++++++++++++++++++++++++++++++++++ doc/design.md | 151 +++++--------------------------- keyboard/keyboard.go | 118 +++++++++++++++++++++++++ mouse/mouse.go | 33 +++++++ terminalapi/event.go | 48 ++++++++++ terminalapi/terminalapi.go | 36 ++++++++ widget/widget.go | 43 +++++++++ 13 files changed, 709 insertions(+), 130 deletions(-) create mode 100644 canvas/canvas.go create mode 100644 cell/cell.go create mode 100644 cell/color.go create mode 100644 container/container.go create mode 100644 container/container_test.go create mode 100644 container/options.go create mode 100644 keyboard/keyboard.go create mode 100644 mouse/mouse.go create mode 100644 terminalapi/event.go create mode 100644 terminalapi/terminalapi.go create mode 100644 widget/widget.go diff --git a/README.md b/README.md index fef35ee..79ad505 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ See the [design document](doc/design.md). # Project status - [x] High-Level Design. -- [ ] Submit the APIs. +- [x] Submit the APIs. - [ ] Implement the terminal layer. - [ ] Implement the container. - [ ] Implement the input event pre-processing. diff --git a/canvas/canvas.go b/canvas/canvas.go new file mode 100644 index 0000000..57cda0b --- /dev/null +++ b/canvas/canvas.go @@ -0,0 +1,28 @@ +// Package canvas defines the canvas that the widgets draw on. +package canvas + +import ( + "image" + + "github.com/mum4k/termdash/cell" +) + +// Canvas is where a widget draws its output for display on the terminal. +type Canvas struct{} + +// Size returns the size of the 2-D canvas given to the widget. +func (c *Canvas) Size() image.Point { + return image.Point{0, 0} +} + +// Clear clears all the content on the canvas. +func (c *Canvas) Clear() {} + +// FlushDesired provides a hint to the infrastructure that the canvas was +// changed and should be flushed to the terminal. +func (c *Canvas) FlushDesired() {} + +// SetCell sets the value of the specified cell on the canvas. +// Use the options to specify which attributes to modify, if an attribute +// option isn't specified, the attribute retains its previous value. +func (c *Canvas) SetCell(p image.Point, r rune, opts ...cell.Option) {} diff --git a/cell/cell.go b/cell/cell.go new file mode 100644 index 0000000..c4304b7 --- /dev/null +++ b/cell/cell.go @@ -0,0 +1,40 @@ +/* +Package cell implements cell options and attributes. + +A cell is the smallest point on the terminal. +*/ +package cell + +// Option is used to provide options for cells on a 2-D terminal. +type Option interface { + // set sets the provided option. + set(*Options) +} + +// Options stores the provided options. +type Options struct { + FgColor Color + BgColor Color +} + +// option implements Option. +type option func(*Options) + +// set implements Option.set. +func (co option) set(opts *Options) { + co(opts) +} + +// FgColor sets the foreground color of the cell. +func FgColor(color Color) Option { + return option(func(co *Options) { + co.FgColor = color + }) +} + +// BgColor sets the background color of the cell. +func BgColor(color Color) Option { + return option(func(co *Options) { + co.BgColor = color + }) +} diff --git a/cell/color.go b/cell/color.go new file mode 100644 index 0000000..a999de2 --- /dev/null +++ b/cell/color.go @@ -0,0 +1,39 @@ +package cell + +// color.go defines constants for cell colors. + +// Color is the color of a cell. +type Color int + +// String implements fmt.Stringer() +func (cc Color) String() string { + if n, ok := colorNames[cc]; ok { + return n + } + return "ColorUnknown" +} + +// colorNames maps Color values to human readable names. +var colorNames = map[Color]string{ + ColorDefault: "ColorDefault", + ColorBlack: "ColorBlack", + ColorRed: "ColorRed", + ColorGreen: "ColorGreen", + ColorYellow: "ColorYellow", + ColorBlue: "ColorBlue", + ColorMagenta: "ColorMagenta", + ColorCyan: "ColorCyan", + ColorWhite: "ColorWhite", +} + +const ( + ColorDefault Color = iota + ColorBlack + ColorRed + ColorGreen + ColorYellow + ColorBlue + ColorMagenta + ColorCyan + ColorWhite +) diff --git a/container/container.go b/container/container.go new file mode 100644 index 0000000..08d751b --- /dev/null +++ b/container/container.go @@ -0,0 +1,113 @@ +/* +Package container defines a type that wraps other containers or widgets. + +The container supports splitting container into sub containers, defining +container styles and placing widgets. The container also creates and manages +canvases assigned to the placed widgets. +*/ +package container + +import ( + "github.com/mum4k/termdash/terminalapi" + "github.com/mum4k/termdash/widget" +) + +// Container wraps either sub containers or widgets and positions them on the +// terminal. +type Container struct { + // parent is the parent container, nil if this is the root container. + parent *Container + // The sub containers, if these aren't nil, the widget must be. + first *Container + second *Container + + // term is the terminal this container is placed on. + term terminalapi.Terminal + + // split identifies how is this container split. + split splitType + + // widget is the widget in the container. + // A container can have either two sub containers (left and right) or a + // widget. But not both. + widget widget.Widget + + // Alignment of the widget if present. + hAlign hAlignType + vAlign vAlignType +} + +// New returns a new root container that will use the provided terminal and +// applies the provided options. +func New(t terminalapi.Terminal, opts ...Option) *Container { + c := &Container{ + term: t, + } + + for _, opt := range opts { + opt.set(c) + } + return c +} + +// Returns the parent container of this container. +// Returns nil if this container is the root of the container tree. +func (c *Container) Parent(opts ...Option) *Container { + if c == nil || c.parent == nil { + return nil + } + + p := c.parent + for _, opt := range opts { + opt.set(p) + } + return p +} + +// First returns the first sub container of this container. +// This is the left sub container when using SplitVertical() or the top sub +// container when using SplitHorizontal(). +// If this container doesn't have the first sub container yet, it will be +// created. Applies the provided options to the first sub container. +// Returns nil if this container contains a widget, containers with widgets +// cannot have sub containers. +func (c *Container) First(opts ...Option) *Container { + if c == nil || c.widget != nil { + return nil + } + + if child := c.first; child != nil { + for _, opt := range opts { + opt.set(child) + } + return child + } + + c.first = New(c.term, opts...) + c.first.parent = c + return c.first +} + +// Second returns the second sub container of this container. +// This is the left sub container when using SplitVertical() or the top sub +// container when using SplitHorizontal(). +// If this container doesn't have the second sub container yet, it will be +// created. Applies the provided options to the second sub container. +// Returns nil if this container contains a widget, containers with widgets +// cannot have sub containers. +func (c *Container) Second(opts ...Option) *Container { + if c == nil || c.widget != nil { + return nil + } + + if child := c.second; child != nil { + for _, opt := range opts { + opt.set(child) + } + return child + } + + c.second = New(c.term, opts...) + c.second.parent = c + return c.second +} diff --git a/container/container_test.go b/container/container_test.go new file mode 100644 index 0000000..4293510 --- /dev/null +++ b/container/container_test.go @@ -0,0 +1,19 @@ +package container + +// Example demonstrates how to use the Container API. +func Example() { + New( // Create the root container. + /* terminal = */ nil, + SplitHorizontal(), + ).First( // This is the top half part of the terminal. + SplitVertical(), + ).First( // Left side on the top. + VerticalAlignTop(), + PlaceWidget( /* widget = */ nil), + ).Parent().Second( // Right side on the top. + HorizontalAlignRight(), + PlaceWidget( /* widget = */ nil), + ).Parent().Parent().Second( // Bottom half of the terminal. + PlaceWidget( /* widget = */ nil), + ) +} diff --git a/container/options.go b/container/options.go new file mode 100644 index 0000000..a2c0980 --- /dev/null +++ b/container/options.go @@ -0,0 +1,169 @@ +package container + +// options.go defines container options. + +import "github.com/mum4k/termdash/widget" + +// Option is used to provide options. +type Option interface { + // set sets the provided option. + set(*Container) +} + +// options stores the provided options. +type options struct{} + +// option implements Option. +type option func(*Container) + +// set implements Option.set. +func (o option) set(c *Container) { + o(c) +} + +// PlaceWidget places the provided widget into the container. +func PlaceWidget(w widget.Widget) Option { + return option(func(c *Container) { + c.widget = w + }) +} + +// SplitHorizontal configures the container for a horizontal split. +func SplitHorizontal() Option { + return option(func(c *Container) { + c.split = splitTypeHorizontal + }) +} + +// SplitVertical configures the container for a vertical split. +// This is the default split type if neither if SplitHorizontal() or +// SplitVertical() is specified. +func SplitVertical() Option { + return option(func(c *Container) { + c.split = splitTypeVertical + }) +} + +// HorizontalAlignLeft aligns the placed widget on the left of the +// container along the horizontal axis. Has no effect if the container contains +// no widget. This is the default horizontal alignment if no other is specified. +func HorizontalAlignLeft() Option { + return option(func(c *Container) { + c.hAlign = hAlignTypeLeft + }) +} + +// HorizontalAlignCenter aligns the placed widget in the center of the +// container along the horizontal axis. Has no effect if the container contains +// no widget. +func HorizontalAlignCenter() Option { + return option(func(c *Container) { + c.hAlign = hAlignTypeCenter + }) +} + +// HorizontalAlignRight aligns the placed widget on the right of the +// container along the horizontal axis. Has no effect if the container contains +// no widget. +func HorizontalAlignRight() Option { + return option(func(c *Container) { + c.hAlign = hAlignTypeRight + }) +} + +// VerticalAlignTop aligns the placed widget on the top of the +// container along the vertical axis. Has no effect if the container contains +// no widget. This is the default vertical alignment if no other is specified. +func VerticalAlignTop() Option { + return option(func(c *Container) { + c.vAlign = vAlignTypeTop + }) +} + +// VerticalAlignMiddle aligns the placed widget in the middle of the +// container along the vertical axis. Has no effect if the container contains +// no widget. +func VerticalAlignMiddle() Option { + return option(func(c *Container) { + c.vAlign = vAlignTypeMiddle + }) +} + +// VerticalAlignBottom aligns the placed widget at the bottom of the +// container along the vertical axis. Has no effect if the container contains +// no widget. +func VerticalAlignBottom() Option { + return option(func(c *Container) { + c.vAlign = vAlignTypeBottom + }) +} + +// splitType identifies how a container is split. +type splitType int + +// String implements fmt.Stringer() +func (st splitType) String() string { + if n, ok := splitTypeNames[st]; ok { + return n + } + return "splitTypeUnknown" +} + +// splitTypeNames maps splitType values to human readable names. +var splitTypeNames = map[splitType]string{ + splitTypeVertical: "splitTypeVertical", + splitTypeHorizontal: "splitTypeHorizontal", +} + +const ( + splitTypeVertical splitType = iota + splitTypeHorizontal +) + +// hAlignType indicates the horizontal alignment of the widget in the container. +type hAlignType int + +// String implements fmt.Stringer() +func (hat hAlignType) String() string { + if n, ok := hAlignTypeNames[hat]; ok { + return n + } + return "hAlignTypeUnknown" +} + +// hAlignTypeNames maps hAlignType values to human readable names. +var hAlignTypeNames = map[hAlignType]string{ + hAlignTypeLeft: "hAlignTypeLeft", + hAlignTypeCenter: "hAlignTypeCenter", + hAlignTypeRight: "hAlignTypeRight", +} + +const ( + hAlignTypeLeft hAlignType = iota + hAlignTypeCenter + hAlignTypeRight +) + +// vAlignType represents +type vAlignType int + +// String implements fmt.Stringer() +func (vat vAlignType) String() string { + if n, ok := vAlignTypeNames[vat]; ok { + return n + } + return "vAlignTypeUnknown" +} + +// vAlignTypeNames maps vAlignType values to human readable names. +var vAlignTypeNames = map[vAlignType]string{ + vAlignTypeTop: "vAlignTypeTop", + vAlignTypeMiddle: "vAlignTypeMiddle", + vAlignTypeBottom: "vAlignTypeBottom", +} + +const ( + vAlignTypeTop vAlignType = iota + vAlignTypeMiddle + vAlignTypeBottom +) diff --git a/doc/design.md b/doc/design.md index f227744..03ee033 100644 --- a/doc/design.md +++ b/doc/design.md @@ -146,152 +146,45 @@ The Terminal API is an interface private to the terminal dashboard library. Its primary purpose is to act as a shim layer over different terminal implementations. -The following outlines the terminal API: - -```go -// Terminal abstracts an implementation of a 2-D terminal. -// A terminal consists of a number of cells. -type Terminal interface { - // Size returns the terminal width and height in cells. - Size() image.Point - - // Clear clears the content of the internal back buffer, resetting all cells - // to their default content and attributes. - Clear() error - // Flush flushes the internal back buffer to the terminal. - Flush() error - - // SetCursor sets the position of the cursor. - SetCursor(p image.Point) - // HideCursos hides the cursor. - HideCursor() - - // SetCell sets the value of the specified cell to the provided rune. - // Use the options to specify which attributes to modify, if an attribute - // option isn't specified, the attribute retains its previous value. - SetCell(p image.Point, r rune, opts ...CellOption) - - // Event waits for the next event and returns it. - // This call blocks until the next event or cancellation of the context. - Event(ctx context.Context) Event -} -``` +The Terminal API is defined in the +[terminalapi](http://github.com/mum4k/termdash/terminalapi/terminalapi.go) +package. The **Event()** method returns the next input event. Different input event -types are defined as follows. - -```go -// Event represents an input event. -type Event interface { - isEvent() -} - -// Keyboard is the event used when a key is pressed. -// Implements Event. -type Keyboard struct { - // Key identifies the pressed key. - Key rune -} - -func (*Keyboard) isEvent() {} - -// Resize is the event used when the terminal was resized. -// Implements Event. -type Resize struct { - // Size is the new size of the terminal. - Size image.Point -} - -func (*Resize) isEvent() {} - -// Mouse is the event used when the mouse is moved or a mouse button is -// pressed. -// Implements Event. -type Mouse struct { - // Position of the mouse on the terminal. - Position() image.Point - // Button identifies the pressed button if any. - Button MouseButton -} - -func (*Mouse) isEvent() {} -``` +types are defined in the +[event.go](http://github.com/mum4k/termdash/terminalapi/event.go) +file. ### Container API -The container API is used to split the terminal and place the widgets. Each +The Container API is used to split the terminal and place the widgets. Each container can be split to two sub containers or have a widget placed into it. A container can be split either horizontally or vertically. The containers further accept styling options and alignment options. The -following indicates how the container API will be used. +following indicates how the Container API will be used. -```go -func main() { - w := mywidget.New(widgetOptions) - t := terminal.New(terminalOptions) - - t.VerticalSplit() - .Left(AlignCenter(), WithWidget(w)) - .Right(SolidBorder()) -} -``` +The Container API is defined in the +[container](http://github.com/mum4k/termdash/container/container.go) +package. + +A demonstration how this is used from the client perspective is in the +[container_test.go](http://github.com/mum4k/termdash/container/container_test.go) +file. ### Widget API -Each widget must implement the following API. All widget implementations must +Each widget must implement the Widget API. All widget implementations must be thread-safe since the calls that update the displayed values come in concurrently with requests and events from the infrastructure. -```go -// Canvas is where a widget draws its output for display on the terminal. -type Canvas struct {} +The Widget API is defined in the +[widget](http://github.com/mum4k/termdash/widget/widget.go) +package. -// Size returns the size of the 2-D canvas given to the widget. -func (c *Canvas) Size() image.Point {} - -// Clear clears all the content on the canvas. -func (c *Canvas) Clear() {} - -// FlushDesired provides a hint to the infrastructure that the canvas was -// changed and should be flushed to the terminal. -func (c *Canvas) FlushDesired() {} - -// SetCell sets the value of the specified cell on the canvas. -// Use the options to specify which attributes to modify, if an attribute -// option isn't specified, the attribute retains its previous value. -func (c *Canvas) SetCell(p image.Point, r rune, opts ...CellOption) {} - -// Widget is a single widget on the dashboard. -type Widget interface { - // Draw executes the widget, when called the widget should draw on the - // canvas. The widget can assume that the canvas content wasn't modified - // since the last call, i.e. if the widget doesn't need to change anything in - // the output, this can be a no-op. - Draw(canvas *Canvas) error - - // Redraw is called when the widget must redraw all of its content because - // the previous canvas was invalidated. The widget must not assume that - // anything on the canvas remained the same, including its size. - Redraw(canvas *Canvas) error - - // Keyboard is called when the widget is focused on the dashboard and a key - // shortcut the widget registered for was pressed. Only called if the widget - // registered for keyboard events. - Keyboard(s Shortcut) error - - // Mouse is called when the widget is focused on the dashboard and a mouse - // event happens on its canvas. Only called if the widget registered for mouse - // events. - Mouse(m *Mouse) error - - // Options returns registration options for the widget. - // This is how the widget indicates to the infrastructure whether it is - // interested in keyboard or mouse shortcuts, what is its minimum canvas - // size, etc. - Options() *WidgetOptions -} -``` +Each widget gets a Canvas to draw on. The Canvas API is defined in the +[canvas](http://github.com/mum4k/termdash/canvas/canvas.go) +package. ## Testing plan diff --git a/keyboard/keyboard.go b/keyboard/keyboard.go new file mode 100644 index 0000000..c1b7991 --- /dev/null +++ b/keyboard/keyboard.go @@ -0,0 +1,118 @@ +// Package keyboard defines well known keyboard keys and shortcuts. +package keyboard + +// Button represents a single button on the keyboard. +type Button rune + +// String implements fmt.Stringer() +func (b Button) String() string { + if n, ok := buttonNames[b]; ok { + return n + } + return "ButtonUnknown" +} + +// buttonNames maps Button values to human readable names. +var buttonNames = map[Button]string{ + ButtonArrowDown: "ButtonArrowDown", + ButtonArrowLeft: "ButtonArrowLeft", + ButtonArrowRight: "ButtonArrowRight", + ButtonArrowUp: "ButtonArrowUp", + ButtonBackspace: "ButtonBackspace", + ButtonDelete: "ButtonDelete", + ButtonEnd: "ButtonEnd", + ButtonEnter: "ButtonEnter", + ButtonEsc: "ButtonEsc", + ButtonF1: "ButtonF1", + ButtonF10: "ButtonF10", + ButtonF11: "ButtonF11", + ButtonF12: "ButtonF12", + ButtonF2: "ButtonF2", + ButtonF3: "ButtonF3", + ButtonF4: "ButtonF4", + ButtonF5: "ButtonF5", + ButtonF6: "ButtonF6", + ButtonF7: "ButtonF7", + ButtonF8: "ButtonF8", + ButtonF9: "ButtonF9", + ButtonHome: "ButtonHome", + ButtonInsert: "ButtonInsert", + ButtonPgdn: "ButtonPgdn", + ButtonPgup: "ButtonPgup", + ButtonSpace: "ButtonSpace", + ButtonTab: "ButtonTab", + ButtonTilde: "ButtonTilde", +} + +const ( + ButtonArrowDown Button = -(iota + 1) + ButtonArrowLeft + ButtonArrowRight + ButtonArrowUp + ButtonBackspace + ButtonDelete + ButtonEnd + ButtonEnter + ButtonEsc + ButtonF1 + ButtonF10 + ButtonF11 + ButtonF12 + ButtonF2 + ButtonF3 + ButtonF4 + ButtonF5 + ButtonF6 + ButtonF7 + ButtonF8 + ButtonF9 + ButtonHome + ButtonInsert + ButtonPgdn + ButtonPgup + ButtonSpace + ButtonTab + ButtonTilde +) + +// Modifier represents a modified key on the keyboard, i.e. a keys that +// together with buttons can form shortcuts. +type Modifier int + +// String implements fmt.Stringer() +func (m Modifier) String() string { + if n, ok := modifierNames[m]; ok { + return n + } + return "ModifierUnknown" +} + +// modifierNames maps Modifier values to human readable names. +var modifierNames = map[Modifier]string{ + ModifierShift: "ModifierShift", + ModifierCtrl: "ModifierCtrl", + ModifierAlt: "ModifierAlt", + ModifierMeta: "ModifierMeta", +} + +const ( + modifierUnknown Modifier = iota + ModifierShift + ModifierCtrl + ModifierAlt + + // ModifierMeta is the platform specific key, i.e. Windows key on windows + // or Apple (command) key on MacOS keyboard. + ModifierMeta +) + +// Shortcut is a key combination pressed on the keyboard. +type Shortcut struct { + // Modifiers contains zero or more unique modifier keys. + Modifiers []Modifier + + // Key is the key pressed on the keyboard. + // Either equals to one of the defined Button values or contains the raw + // Unicode byte sequence. + Key Button +} diff --git a/mouse/mouse.go b/mouse/mouse.go new file mode 100644 index 0000000..90829df --- /dev/null +++ b/mouse/mouse.go @@ -0,0 +1,33 @@ +// Package mouse defines known mouse buttons. +package mouse + +// Button represents +type Button int + +// String implements fmt.Stringer() +func (b Button) String() string { + if n, ok := buttonNames[b]; ok { + return n + } + return "ButtonUnknown" +} + +// buttonNames maps Button values to human readable names. +var buttonNames = map[Button]string{ + ButtonLeft: "ButtonLeft", + ButtonRight: "ButtonRight", + ButtonMiddle: "ButtonMiddle", + ButtonRelease: "ButtonRelease", + ButtonWheelUp: "ButtonWheelUp", + ButtonWheelDown: "ButtonWheelDown", +} + +const ( + buttonUnknown Button = iota + ButtonLeft + ButtonRight + ButtonMiddle + ButtonRelease + ButtonWheelUp + ButtonWheelDown +) diff --git a/terminalapi/event.go b/terminalapi/event.go new file mode 100644 index 0000000..5017f9f --- /dev/null +++ b/terminalapi/event.go @@ -0,0 +1,48 @@ +package terminalapi + +import ( + "image" + + "github.com/mum4k/termdash/keyboard" + "github.com/mum4k/termdash/mouse" +) + +// event.go defines events that can be received through the terminal API. + +// Event represents an input event. +type Event interface { + isEvent() +} + +// Keyboard is the event used when a key is pressed. +// Implements terminalapi.Event. +type Keyboard struct { + // Key identifies the pressed key. + // The rune either has a negative int32 value equal to one of the + // keyboard.Button values or a positive int32 value for all other Unicode + // byte sequences. + Key keyboard.Button +} + +func (*Keyboard) isEvent() {} + +// Resize is the event used when the terminal was resized. +// Implements terminalapi.Event. +type Resize struct { + // Size is the new size of the terminal. + Size image.Point +} + +func (*Resize) isEvent() {} + +// Mouse is the event used when the mouse is moved or a mouse button is +// pressed. +// Implements terminalapi.Event. +type Mouse struct { + // Position of the mouse on the terminal. + Position image.Point + // Button identifies the pressed button if any. + Button mouse.Button +} + +func (*Mouse) isEvent() {} diff --git a/terminalapi/terminalapi.go b/terminalapi/terminalapi.go new file mode 100644 index 0000000..107da3a --- /dev/null +++ b/terminalapi/terminalapi.go @@ -0,0 +1,36 @@ +// Package terminalapi defines the API of all terminal implementations. +package terminalapi + +import ( + "context" + "image" + + "github.com/mum4k/termdash/cell" +) + +// Terminal abstracts an implementation of a 2-D terminal. +// A terminal consists of a number of cells. +type Terminal interface { + // Size returns the terminal width and height in cells. + Size() image.Point + + // Clear clears the content of the internal back buffer, resetting all cells + // to their default content and attributes. + Clear() error + // Flush flushes the internal back buffer to the terminal. + Flush() error + + // SetCursor sets the position of the cursor. + SetCursor(p image.Point) + // HideCursos hides the cursor. + HideCursor() + + // SetCell sets the value of the specified cell to the provided rune. + // Use the options to specify which attributes to modify, if an attribute + // option isn't specified, the attribute retains its previous value. + SetCell(p image.Point, r rune, opts ...cell.Option) error + + // Event waits for the next event and returns it. + // This call blocks until the next event or cancellation of the context. + Event(ctx context.Context) Event +} diff --git a/widget/widget.go b/widget/widget.go new file mode 100644 index 0000000..2b12997 --- /dev/null +++ b/widget/widget.go @@ -0,0 +1,43 @@ +// Package widget defines the API of a widget on the dashboard. +package widget + +import ( + "github.com/mum4k/termdash/canvas" + "github.com/mum4k/termdash/keyboard" + "github.com/mum4k/termdash/mouse" +) + +// Options contains registration options for a widget. +// This is how the widget indicates its needs to the infrastructure. +type Options struct { +} + +// Widget is a single widget on the dashboard. +type Widget interface { + // Draw executes the widget, when called the widget should draw on the + // canvas. The widget can assume that the canvas content wasn't modified + // since the last call, i.e. if the widget doesn't need to change anything in + // the output, this can be a no-op. + Draw(canvas *canvas.Canvas) error + + // Redraw is called when the widget must redraw all of its content because + // the previous canvas was invalidated. The widget must not assume that + // anything on the canvas remained the same, including its size. + Redraw(canvas *canvas.Canvas) error + + // Keyboard is called when the widget is focused on the dashboard and a key + // shortcut the widget registered for was pressed. Only called if the widget + // registered for keyboard events. + Keyboard(s *keyboard.Shortcut) error + + // Mouse is called when the widget is focused on the dashboard and a mouse + // event happens on its canvas. Only called if the widget registered for mouse + // events. + Mouse(m *mouse.Button) error + + // Options returns registration options for the widget. + // This is how the widget indicates to the infrastructure whether it is + // interested in keyboard or mouse shortcuts, what is its minimum canvas + // size, etc. + Options() *Options +}